-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add utility methods * feat: indexing class for ddi objects * feat: typed get methods in index * chore: bump version
- Loading branch information
Showing
11 changed files
with
498 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
# Generated sources | ||
src/main/java/* | ||
src/main/java/fr/insee/ddi/lifecycle33/* | ||
src/main/java/org/* | ||
# Generated resources | ||
src/main/resources/org/apache/xmlbeans/* | ||
src/main/resources/org/apache/xmlbeans/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
model/src/main/java/fr/insee/ddi/exception/DuplicateIdException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package fr.insee.ddi.exception; | ||
|
||
/** | ||
* Exception to be thrown if duplicate identifiers are detected in a DDI object. | ||
*/ | ||
public class DuplicateIdException extends RuntimeException { | ||
|
||
public DuplicateIdException(String message) { | ||
super(message); | ||
} | ||
|
||
} |
16 changes: 16 additions & 0 deletions
16
model/src/main/java/fr/insee/ddi/exception/IndexingException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package fr.insee.ddi.exception; | ||
|
||
/** | ||
* Exception to be thrown if an unexpected exception occurs during DDI indexing. | ||
*/ | ||
public class IndexingException extends RuntimeException { | ||
|
||
public IndexingException(String message) { | ||
super(message); | ||
} | ||
|
||
public IndexingException(String message, Exception e) { | ||
super(message, e); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
package fr.insee.ddi.index; | ||
|
||
import fr.insee.ddi.exception.DuplicateIdException; | ||
import fr.insee.ddi.exception.IndexingException; | ||
import fr.insee.ddi.lifecycle33.instance.DDIInstanceDocument; | ||
import fr.insee.ddi.lifecycle33.reusable.AbstractIdentifiableType; | ||
import fr.insee.ddi.utils.DDIUtils; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.springframework.beans.BeanWrapper; | ||
import org.springframework.beans.BeanWrapperImpl; | ||
import org.springframework.core.convert.TypeDescriptor; | ||
|
||
import java.beans.PropertyDescriptor; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.util.*; | ||
|
||
/** | ||
* Class designed to store all DDI identifiable objects within a DDI object in a flat map. | ||
* Also contains a parents map that make the link between each object and its parent. | ||
*/ | ||
public class DDIIndex { | ||
|
||
private static final Logger log = LogManager.getLogger(); | ||
|
||
/** Index of DDI identifiable objects. | ||
* Key: a DDI object identifier | ||
* Value: the DDI object with corresponding identifier. */ | ||
private Map<String, AbstractIdentifiableType> index; | ||
|
||
/** Map containing nesting relationships between objects in index. | ||
* Key: a DDI object identifier | ||
* Value: the identifier of its parent object */ | ||
private Map<String, String> parentsMap; | ||
|
||
private void setup() { | ||
index = new HashMap<>(); | ||
parentsMap = new HashMap<>(); | ||
} | ||
|
||
/** | ||
* Stores all DDI identifiable objects that are in the given DDI document in the index. | ||
* @param ddiInstanceDocument A DDIInstanceDocument. | ||
* @throws DuplicateIdException if two objects with the same identifier are found. | ||
*/ | ||
public void indexDDI(DDIInstanceDocument ddiInstanceDocument) throws DuplicateIdException { | ||
indexDDIObject(ddiInstanceDocument.getDDIInstance()); | ||
} | ||
|
||
/** | ||
* Stores all DDI identifiable objects that are in the given DDI object in the index. | ||
* @param ddiObject A DDI identifiable object. | ||
* @throws DuplicateIdException if two objects with the same identifier are found. | ||
*/ | ||
public void indexDDIObject(AbstractIdentifiableType ddiObject) throws DuplicateIdException { | ||
log.info("Indexing DDI object {}...", DDIUtils.ddiToString(ddiObject)); | ||
setup(); | ||
recursiveIndexing(ddiObject); | ||
log.info("Finished indexing of DDI object."); | ||
} | ||
|
||
/** | ||
* In DDI, the most generic object is the AbstractIdentifiableType | ||
* (i.e. each DDI object that has an id is an AbstractIdentifiableType). | ||
* This method recursively path through all AbstractIdentifiableType objects within the given object. | ||
* @param ddiObject A AbstractIdentifiableType object. | ||
*/ | ||
private void recursiveIndexing(AbstractIdentifiableType ddiObject) throws IndexingException, DuplicateIdException { | ||
|
||
// Get the DDI object id | ||
String ddiObjectId = !ddiObject.getIDList().isEmpty() ? ddiObject.getIDArray(0).getStringValue() : null; | ||
if (ddiObjectId == null) | ||
throw new IndexingException("DDI object with null identifier encountered while indexing."); | ||
|
||
// Put the object in the map under the id (there should never be duplicate ids in DDI documents) | ||
index.merge(ddiObjectId, ddiObject, (oldDDIObject, newDDIObject) -> { | ||
throw new DuplicateIdException(String.format("Duplicate ID \"%s\" found in given DDI.", ddiObjectId)); | ||
}); | ||
|
||
// Use Spring BeanWrapper to iterate on object property descriptors | ||
BeanWrapper beanWrapper = new BeanWrapperImpl(ddiObject); | ||
Iterator<PropertyDescriptor> iterator = Arrays.stream(beanWrapper.getPropertyDescriptors()) | ||
.filter(propertyDescriptor -> !propertyDescriptor.getName().equals("class")) | ||
.iterator(); | ||
while (iterator.hasNext()) { | ||
PropertyDescriptor propertyDescriptor = iterator.next(); | ||
|
||
// In DDI classes, everything is in a list | ||
if (List.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { | ||
|
||
// Use Spring TypeDescriptor to determine the content type of the list | ||
TypeDescriptor typeDescriptor = beanWrapper.getPropertyTypeDescriptor(propertyDescriptor.getName()); | ||
assert typeDescriptor != null; | ||
Class<?> listContentType = typeDescriptor.getResolvableType().getGeneric(0).getRawClass(); | ||
|
||
// In some DDI objects, there are some methods (generated by xmlbeans) that does not interest us | ||
assert listContentType != null || propertyDescriptor.getName().equals("listValue") | ||
|| propertyDescriptor.getName().equals("limitArrayIndex"); | ||
|
||
// Check that the list content applies for AbstractIdentifiableType | ||
if (listContentType != null && AbstractIdentifiableType.class.isAssignableFrom(listContentType)) { | ||
|
||
// Now that we have what we want, index list content | ||
indexListContent(ddiObject, ddiObjectId, propertyDescriptor); | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
private void indexListContent(AbstractIdentifiableType ddiObject, String ddiObjectId, PropertyDescriptor propertyDescriptor) { | ||
try { | ||
// Iteration on each object in the list. | ||
@SuppressWarnings("unchecked") // https://stackoverflow.com/a/4388173/13425151 | ||
Collection<AbstractIdentifiableType> ddiCollection = (Collection<AbstractIdentifiableType>) propertyDescriptor.getReadMethod().invoke(ddiObject); | ||
for (AbstractIdentifiableType ddiObject2 : ddiCollection) { | ||
// Keep track of link between nested objects | ||
parentsMap.put(ddiObject2.getIDArray(0).getStringValue(), ddiObjectId); | ||
// Recursive call of the function | ||
recursiveIndexing(ddiObject2); | ||
} | ||
} catch (IllegalAccessException | InvocationTargetException e) { | ||
throw new IndexingException(String.format( | ||
"Error when calling read method from property descriptor '%s' in class %s.", | ||
propertyDescriptor.getName(), ddiObject.getClass()), | ||
e); | ||
} | ||
} | ||
|
||
/** Returns the inner index. */ | ||
public Map<String, AbstractIdentifiableType> getIndex() { | ||
return index; | ||
} | ||
|
||
/** | ||
* Returns the DDI object corresponding to the given identifier. | ||
* @param ddiObjectId String identifier value. | ||
* @return The DDI object corresponding to the given identifier. | ||
* @throws NoSuchElementException if there is no object under given identifier. | ||
*/ | ||
public AbstractIdentifiableType get(String ddiObjectId) { | ||
AbstractIdentifiableType object = index.get(ddiObjectId); | ||
if (object == null) | ||
throw new NoSuchElementException(String.format("Index has no object with id '%s'.", ddiObjectId)); | ||
return object; | ||
} | ||
|
||
/** | ||
* Returns the DDI object corresponding to the given identifier, cast in the given type. | ||
* @param ddiObjectId String identifier value. | ||
* @param clazz Class into which the result should be cast. | ||
* @return The DDI object corresponding to the given identifier, cast in the given type. | ||
* @param <T> Subtype of DDI AbstractIdentifiableType. | ||
* @throws NoSuchElementException if there is no object under given identifier. | ||
* @throws ClassCastException if the given object cannot be cast to the given type. | ||
*/ | ||
public <T extends AbstractIdentifiableType> T get(String ddiObjectId, Class<T> clazz) { | ||
AbstractIdentifiableType object = this.get(ddiObjectId); | ||
if (! clazz.isInstance(object)) | ||
throw new ClassCastException(String.format( | ||
"Index object with id '%s' is of type %s that cannot be cast to %s.", | ||
ddiObjectId, ddiObjectId.getClass(), clazz)); | ||
return clazz.cast(object); | ||
} | ||
|
||
/** | ||
* Checks if the given identifier is present in the index. | ||
* @param ddiObjectId String identifier value. | ||
* @return True if the index contains the given identifier. | ||
*/ | ||
public boolean containsId(String ddiObjectId) { | ||
return index.containsKey(ddiObjectId); | ||
} | ||
|
||
/** | ||
* Returns the parent object of DDI object with given identifier. | ||
* @param ddiObjectId String identifier value. | ||
* @throws NoSuchElementException if the parent object for given identifier cannot be found. | ||
*/ | ||
public AbstractIdentifiableType getParent(String ddiObjectId) { | ||
return this.get(parentsMap.get(ddiObjectId)); | ||
} | ||
|
||
/** | ||
* Returns the parent object of DDI object with given identifier. | ||
* @param ddiObjectId String identifier value. | ||
* @throws NoSuchElementException if the parent object for given identifier cannot be found. | ||
* @throws ClassCastException if the given object cannot be cast to the given type. | ||
*/ | ||
public <T extends AbstractIdentifiableType> T getParent(String ddiObjectId, Class<T> clazz) { | ||
return this.get(parentsMap.get(ddiObjectId), clazz); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package fr.insee.ddi.utils; | ||
|
||
import fr.insee.ddi.lifecycle33.reusable.AbstractIdentifiableType; | ||
import fr.insee.ddi.lifecycle33.reusable.IDType; | ||
|
||
/** | ||
* Utility class that provide some methods for DDI objects. | ||
*/ | ||
public class DDIUtils { | ||
|
||
private DDIUtils() {} | ||
|
||
/** | ||
* Returns a better representation than the "toString" method for a DDI object. | ||
* @param ddiObject A DDI object. | ||
* @return String representation of the object. | ||
*/ | ||
public static String ddiToString(Object ddiObject) { | ||
String className = ddiObject.getClass().getSimpleName(); | ||
if (! (ddiObject instanceof AbstractIdentifiableType ddiIdentifiableObject)) | ||
return className; | ||
if (ddiIdentifiableObject.getIDList().isEmpty()) | ||
return className + "[id=null]"; | ||
return className + "[id=" + ddiIdentifiableObject.getIDArray(0).getStringValue() + "]"; | ||
} | ||
|
||
/** | ||
* Returns the identifier of the given DDI object. | ||
* @param ddiIdentifiableObject A DDI identifiable object. | ||
* @return String value of the object identifier. | ||
*/ | ||
public static String getIdValue(AbstractIdentifiableType ddiIdentifiableObject) { | ||
if (ddiIdentifiableObject.getIDList().isEmpty()) | ||
return null; | ||
return ddiIdentifiableObject.getIDArray(0).getStringValue(); | ||
} | ||
|
||
/** | ||
* Sets the identifier value of the given DDI object. | ||
* @param ddiIdentifiableObject A DDI identifiable object. | ||
* @param id String identifier value. | ||
*/ | ||
public static void setIdValue(AbstractIdentifiableType ddiIdentifiableObject, String id) { | ||
if (ddiIdentifiableObject.getIDList().isEmpty()) | ||
ddiIdentifiableObject.getIDList().add(IDType.Factory.newInstance()); | ||
ddiIdentifiableObject.getIDArray(0).setStringValue(id); | ||
} | ||
|
||
} |
Oops, something went wrong.