diff --git a/EntityNavigation.md b/EntityNavigation.md new file mode 100644 index 0000000..506982a --- /dev/null +++ b/EntityNavigation.md @@ -0,0 +1,130 @@ +# Navigation between entities + +The class `AnnotationHelper.AnnotatedNavInfo` is constructed by the helper method +`Annotation#getCommonNavigationInfo` to describe the annotated navigation between +two entities. + +## Example code fragments + +```java +@EdmEntityType(name = "Building") +@EdmEntitySet(name = "Buildings") +public class Building { + @EdmKey @EdmProperty(name = "ID") + private String id; + @EdmProperty(name = "Name") + private String name; + @EdmNavigationProperty(toMultiplicity = Multiplicity.MANY) + private List rooms = new ArrayList<>(); + // etc. +} +``` + +```java +@EdmEntityType(name = "Room") +@EdmEntitySet(name = "Rooms") +public class Room { + @EdmKey @EdmProperty(name = "ID") + private String id; + @EdmProperty(name = "Name") + private String name; + @EdmNavigationProperty + private Building building; + // etc. +} +``` + +```java +@EdmEntityType(name = "Closet") +@EdmEntitySet(name = "Closets") +public class Closet { + @EdmKey @EdmProperty(name = "ID") + private String id; + @EdmProperty(name = "Name") + private String name; + @EdmNavigationProperty() + private Building building; + // etc. +} +``` + +```java +@EdmEntityType(name = "Task") +@EdmEntitySet(name = "Tasks") +public class Task { + @EdmKey @EdmProperty(name = "ID") + private String id; + @EdmProperty(name = "Name") + private String name; + @EdmNavigationProperty(multiplicity = Multiplicity.MANY) + private List subTasks = new ArrayList<>(); + // etc. +} +``` + +## Bi-directional navigation + +A class has a navigation property to another class and the second class +has a navigation property back to the first. + +In the above code fragments, the relationship between `Building` and `Room` is +bi-directional. A room has a link to the building where it is found and a +building maintains a list of its rooms. + +A call to `AnnotationHelper.getCommonNavigation(Room.class, Building.class)` +returns an `AnnotatedNavInfo` instance with: + +* `isBiDirectional()` = `true` +* `getFromField()` = `building` +* `getToMultiplicity()` = `ONE` +* `getToField()` = `rooms` +* `getFromMultiplicity()` = `MANY` + +A call to `AnnotationHelper.getCommonNavigation(Building.class, Room.class)` +returns an `AnnotatedNavInfo` instance with: + +* `isBiDirectional()` = `true` +* `getFromField()` = `rooms` +* `getToMultiplicity()` = `MANY` +* `getToField()` = `building` +* `getFromMultiplicity()` = `ONE` + +## Uni-directional navigation + +A class has a navigation property to another class but the second class +does not have a navigation property back to the first. + +In the above code fragments, the relationship between `Building` and `Closet` is +uni-directional. A closet has a link to the building where it is found but a +building does not have a list of any closets. + +A call to `AnnotationHelper.getCommonNavigation(Closet.class, Building.class)` +returns an `AnnotatedNavInfo` instance with: + +* `isBiDirectional()` = `false` +* `getFromField()` = `building` +* `getFromMultiplicity()` = `ONE` +* `getToField()` = `null` +* `getToMultiplicity()` = `ONE` + +A call to `AnnotationHelper.getCommonNavigation(Building.class, Closet.class)` +returns an `AnnotatedNavInfo` instance with: + +* `isBiDirectional()` = `false` +* `getFromField()` = `null` +* `getFromMultiplicity()` = `ONE` +* `getToField()` = `closet` +* `getToMultiplicity()` = `ONE` + +## Self-navigation + +Self-navigation is a special case of uni-directional navigation. + +A call to `AnnotationHelper.getCommonNavigation(Task.class, Task.class)` +returns an `AnnotatedNavInfo` instance with: + +* `isBiDirectional()` = `false` +* `getFromField()` = `subTasks` +* `getToMultiplicity()` = `ONE` +* `getToField()` = `null` +* `getFromMultiplicity()` = `ONE` diff --git a/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/data/source/AnnotationDataSource.java b/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/data/source/AnnotationDataSource.java index 9c3be3b..285907f 100644 --- a/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/data/source/AnnotationDataSource.java +++ b/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/data/source/AnnotationDataSource.java @@ -125,7 +125,7 @@ public Object readRelatedData(final EdmEntitySet sourceEntitySet, final Object s AnnotationHelper.AnnotatedNavInfo navInfo = ANNOTATION_HELPER.getCommonNavigationInfo( sourceStore.getDataTypeClass(), targetStore.getDataTypeClass()); final Field sourceField; - if(navInfo.isBiDirectional()) { + if (navInfo.getFromField() == null) { sourceField = navInfo.getToField(); } else { sourceField = navInfo.getFromField(); @@ -162,10 +162,14 @@ private List readResultData(final DataStore targetStore, final Object Map keyName2Value = ANNOTATION_HELPER.getValueForAnnotatedFields(sourceData, EdmKey.class); Field toField = navInfo.getToField(); - Object backInstance = ClassHelper.getFieldValue(targetInstance, toField); - boolean keyMatch = ANNOTATION_HELPER.keyMatch(backInstance, keyName2Value); - if(keyMatch) { + if (toField == null) { resultData.add(targetInstance); + } else { + Object backInstance = ClassHelper.getFieldValue(targetInstance, toField); + boolean keyMatch = ANNOTATION_HELPER.keyMatch(backInstance, keyName2Value); + if (keyMatch) { + resultData.add(targetInstance); + } } } else if (targetStore.isKeyEqualChecked(targetInstance, navigationInstance)) { resultData.add(targetInstance); diff --git a/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelper.java b/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelper.java index b47e989..6695168 100644 --- a/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelper.java +++ b/janos-core/src/main/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelper.java @@ -81,7 +81,7 @@ public boolean keyMatch(final Object firstInstance, final Object secondInstance) * @return */ public boolean keyMatch(final Object instance, final Map keyName2Value) { - if(instance == null) { + if (instance == null) { return false; } Map instanceKeyFields = getValueForAnnotatedFields(instance, EdmKey.class); @@ -278,7 +278,7 @@ public String extractFromRoleEntityName(final Field field) { public String extractToRoleEntityName(final EdmNavigationProperty enp, final Field field) { Class clazz = enp.toType(); if (clazz == Object.class) { - if(field.getType().isArray() || Collection.class.isAssignableFrom(field.getType())) { + if (field.getType().isArray() || Collection.class.isAssignableFrom(field.getType())) { clazz = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; } else { clazz = field.getType(); @@ -352,18 +352,31 @@ public ODataAnnotationException(final String message, final Exception cause) { } } + /** + * Description of annotated navigation between two entity classes. + * + * Navigation may be unidirectional or bidirectional: + * + *
+ *
Unidirectional
+ *
A class has a navigation property to another class but the second class + * does not have a navigation property back to the first.
+ *
Bidirectional
+ *
A class has a navigation property to another class and the second class + * has a navigation property back to the first.
+ *
+ */ public class AnnotatedNavInfo { private final Field fromField; private final Field toField; private final EdmNavigationProperty fromNavigation; private final EdmNavigationProperty toNavigation; - public AnnotatedNavInfo(final Field fromField, final Field toField, final EdmNavigationProperty fromNavigation, - final EdmNavigationProperty toNavigation) { + public AnnotatedNavInfo(final Field fromField, final Field toField) { this.fromField = fromField; this.toField = toField; - this.fromNavigation = fromNavigation; - this.toNavigation = toNavigation; + this.fromNavigation = fromField == null ? null : fromField.getAnnotation(EdmNavigationProperty.class); + this.toNavigation = toField == null ? null : toField.getAnnotation(EdmNavigationProperty.class); } public Field getFromField() { @@ -371,10 +384,11 @@ public Field getFromField() { } public String getFromRoleName() { - if(isBiDirectional()) { - return extractFromRoleEntityName(toField); + if (toField != null) { + return extractToRoleName(toNavigation, toField); + } else { + return extractFromRoleEntityName(fromField); } - return extractToRoleName(toNavigation, toField); } public Field getToField() { @@ -382,42 +396,43 @@ public Field getToField() { } public String getToRoleName() { - if(isBiDirectional()) { - return extractToRoleName(toNavigation, toField); + if (fromField != null) { + return extractToRoleName(fromNavigation, fromField); + } else { + return extractFromRoleEntityName(toField); } - return extractToRoleName(fromNavigation, fromField); } public EdmMultiplicity getFromMultiplicity() { - if(isBiDirectional()) { + if (toField != null) { + return extractMultiplicity(toNavigation, toField); + } else { return EdmMultiplicity.ONE; } - return extractMultiplicity(toNavigation, toField); } public EdmMultiplicity getToMultiplicity() { - if(isBiDirectional()) { - return extractMultiplicity(toNavigation, toField); + if (fromField != null) { + return extractMultiplicity(fromNavigation, fromField); + } else { + return EdmMultiplicity.ONE; } - return extractMultiplicity(fromNavigation, fromField); } public boolean isBiDirectional() { - return fromNavigation == null; + return fromNavigation != null && toNavigation != null; } public String getRelationshipName() { - String toAssociation = toNavigation.association(); - String fromAssociation = ""; - if(!isBiDirectional()) { - fromAssociation = fromNavigation.association(); - } - if(fromAssociation.isEmpty() && fromAssociation.equals(toAssociation)) { + final String fromAssociation = fromNavigation == null ? "" : fromNavigation.association(); + final String toAssociation = toNavigation == null ? "" : toNavigation.association(); + + if (fromAssociation.isEmpty() && fromAssociation.equals(toAssociation)) { return createCanonicalRelationshipName(getFromRoleName(), getToRoleName()); - } else if(toAssociation.isEmpty()) { + } else if (toAssociation.isEmpty()) { return fromAssociation; - } else if(!toAssociation.equals(fromAssociation)) { + } else if (!toAssociation.equals(fromAssociation)) { throw new AnnotationRuntimeException("Invalid associations for navigation properties '" + this.toString() + "'"); } @@ -425,80 +440,93 @@ public String getRelationshipName() { } public String getFromTypeName() { - if(isBiDirectional()) { - return extractEntityTypeName(toField.getDeclaringClass()); + if (toField != null) { + return extractEntityTypeName(ClassHelper.getFieldType(toField)); + } else { + return extractEntityTypeName(fromField.getDeclaringClass()); } - return extractEntityTypeName(fromField.getDeclaringClass()); } public String getToTypeName() { - if(isBiDirectional()) { - return extractEntityTypeName(ClassHelper.getFieldType(toField)); + if (fromField != null) { + return extractEntityTypeName(ClassHelper.getFieldType(fromField)); + } else { + return extractEntityTypeName(toField.getDeclaringClass()); } - return extractEntityTypeName(toField.getDeclaringClass()); } @Override public String toString() { - if(isBiDirectional()) { + if (isBiDirectional()) { return "AnnotatedNavInfo{biDirectional = true" + + ", fromField=" + fromField.getName() + ", toField=" + toField.getName() + + ", toNavigation=" + toNavigation.name() + + ", fromNavigation=" + fromNavigation.name() + ", toNavigation=" + toNavigation.name() + '}'; + } else if (fromField != null) { + return "AnnotatedNavInfo{" + + "fromField=" + fromField.getName() + + ", fromNavigation=" + fromNavigation.name() + '}'; + } else if (toField != null) { + return "AnnotatedNavInfo{" + + "toField=" + toField.getName() + + ", toNavigation=" + toNavigation.name() + '}'; + } else { + return "AnnotatedNavInfo{}"; } - return "AnnotatedNavInfo{" + - "fromField=" + fromField.getName() + - ", toField=" + toField.getName() + - ", fromNavigation=" + fromNavigation.name() + - ", toNavigation=" + toNavigation.name() + '}'; } } - + /** + * Gets navigation information between two entity classes. + * + * @param sourceClass one entity class + * @param targetClass another entity class + * @return navigation information between the two classes, if any + */ public AnnotatedNavInfo getCommonNavigationInfo(final Class sourceClass, final Class targetClass) { - List sourceFields = getAnnotatedFields(sourceClass, EdmNavigationProperty.class); - List targetFields = getAnnotatedFields(targetClass, EdmNavigationProperty.class); - if(sourceClass == targetClass) { - // special case, actual handled as bi-directional - return getCommonNavigationInfoBiDirectional(sourceClass, targetClass); + // Lists of fields of the type of the other class. + List sourceFields = getAnnotatedFieldsOfClass(sourceClass, EdmNavigationProperty.class, targetClass); + List targetFields = getAnnotatedFieldsOfClass(targetClass, EdmNavigationProperty.class, sourceClass); + + // Self-navigation: return the first field in the list. + if (sourceClass == targetClass && !sourceFields.isEmpty()) { + return new AnnotatedNavInfo(sourceFields.get(0), null); } - // first try via association name to get full navigation information + // Look for bi-directional navigation, returning the first with matching association name. for (Field sourceField : sourceFields) { - if(ClassHelper.getFieldType(sourceField) == targetClass) { - final EdmNavigationProperty sourceNav = sourceField.getAnnotation(EdmNavigationProperty.class); - final String sourceAssociation = extractRelationshipName(sourceNav, sourceField); - for (Field targetField : targetFields) { - if(ClassHelper.getFieldType(targetField) == sourceClass) { - final EdmNavigationProperty targetNav = targetField.getAnnotation(EdmNavigationProperty.class); - final String targetAssociation = extractRelationshipName(targetNav, targetField); - if (sourceAssociation.equals(targetAssociation)) { - return new AnnotatedNavInfo(sourceField, targetField, sourceNav, targetNav); - } - } + final EdmNavigationProperty sourceNav = sourceField.getAnnotation(EdmNavigationProperty.class); + final String sourceAssociation = extractRelationshipName(sourceNav, sourceField); + for (Field targetField : targetFields) { + final EdmNavigationProperty targetNav = targetField.getAnnotation(EdmNavigationProperty.class); + final String targetAssociation = extractRelationshipName(targetNav, targetField); + if (sourceAssociation.equals(targetAssociation)) { + return new AnnotatedNavInfo(sourceField, targetField); } } } - // if nothing was found assume/guess none bi-directional navigation - return getCommonNavigationInfoBiDirectional(sourceClass, targetClass); - } - - private AnnotatedNavInfo getCommonNavigationInfoBiDirectional(final Class sourceClass, - final Class targetClass) { - List sourceFields = getAnnotatedFields(sourceClass, EdmNavigationProperty.class); - - String targetEntityTypeName = extractEntityTypeName(targetClass); - for (Field sourceField : sourceFields) { - final EdmNavigationProperty sourceNav = sourceField.getAnnotation(EdmNavigationProperty.class); - final String navTargetEntityName = extractEntityTypeName(sourceNav, sourceField); - - if (navTargetEntityName.equals(targetEntityTypeName)) { - return new AnnotatedNavInfo(null, sourceField, null, sourceNav); + // Look for uni-directional navigation. + if (sourceFields.isEmpty()) { + if (targetFields.isEmpty()) { + return null; + } else { + return new AnnotatedNavInfo(null, targetFields.get(0)); } + } else { + return new AnnotatedNavInfo(sourceFields.get(0), null); } + } - return null; + private List getAnnotatedFieldsOfClass(Class sourceClass, + Class annotationClass, + Class targetClass) { + final List fieldList = getAnnotatedFields(sourceClass, annotationClass); + fieldList.removeIf(field -> ClassHelper.getFieldType(field) != targetClass); + return fieldList; } public Class getFieldTypeForProperty(final Class clazz, final String propertyName) @@ -547,13 +575,13 @@ public void setValueForProperty(final Object instance, final String propertyName } private Field getFieldForPropertyName(final String propertyName, - final Class resultClass, final boolean inherited) { + final Class resultClass, final boolean inherited) { Field[] fields = resultClass.getDeclaredFields(); for (Field field : fields) { EdmProperty property = field.getAnnotation(EdmProperty.class); if (property == null) { - if(getCanonicalName(field).equals(propertyName)) { + if (getCanonicalName(field).equals(propertyName)) { return field; } } else { @@ -574,7 +602,7 @@ private Field getFieldForPropertyName(final String propertyName, } public Object getValueForField(final Object instance, final String fieldName, - final Class annotation) { + final Class annotation) { if (instance == null) { return null; } @@ -589,12 +617,12 @@ public Object getValueForField(final Object instance, final Class resultClass, - final Class annotation, final boolean inherited) { + final Class annotation, final boolean inherited) { return getValueForField(instance, null, resultClass, annotation, inherited); } public Map getValueForAnnotatedFields(final Object instance, - final Class annotation) { + final Class annotation) { if (instance == null) { return null; } @@ -602,7 +630,7 @@ public Map getValueForAnnotatedFields(final Object instance, } private Map getValueForAnnotatedFields(final Object instance, final Class resultClass, - final Class annotation, final boolean inherited) { + final Class annotation, final boolean inherited) { Field[] fields = resultClass.getDeclaredFields(); Map fieldName2Value = new HashMap<>(); @@ -663,7 +691,7 @@ public ArrayList extractFunctionImportParameters(final for (Annotation annotation : annotations) { if (annotation.annotationType().equals(EdmFunctionImportParameter.class)) { FunctionImportParameter fip = new FunctionImportParameter(); - EdmFunctionImportParameter fipAnnotation = (EdmFunctionImportParameter)annotation; + EdmFunctionImportParameter fipAnnotation = (EdmFunctionImportParameter) annotation; // Set facets if (fipAnnotation.facets() != null) { EdmFacets edmFacets = fipAnnotation.facets(); @@ -720,11 +748,11 @@ public ReturnType extractReturnType(final Method annotatedMethod) { break; case ENTITY: returnType.setTypeName(extractEntityTypeFqn( - determineAnnotatedClass(functionImport, annotatedMethod))); + determineAnnotatedClass(functionImport, annotatedMethod))); break; case COMPLEX: returnType.setTypeName(extractComplexTypeFqn( - determineAnnotatedClass(functionImport, annotatedMethod))); + determineAnnotatedClass(functionImport, annotatedMethod))); break; default: throw new UnsupportedOperationException("Not yet supported return type type '" + functionImport.returnType().type() + "'."); @@ -760,7 +788,7 @@ public String extractHttpMethod(final Method annotatedMethod) { public void setValueForAnnotatedField(final Object instance, final Class annotation, - final Object value) + final Object value) throws ODataAnnotationException { List fields = getAnnotatedFields(instance, annotation); @@ -776,7 +804,7 @@ public void setValueForAnnotatedField(final Object instance, final Class annotation, final Map fieldName2Value) { + final Class annotation, final Map fieldName2Value) { List fields = getAnnotatedFields(instance, annotation); // XXX: refactore @@ -800,15 +828,8 @@ public List getAnnotatedFields(final Class fieldClass, final Class getAnnotatedFields(final Class resultClass, - final Class annotation, final boolean inherited) { + final Class annotation, final boolean inherited) { if (resultClass == null) { return null; } @@ -832,7 +853,7 @@ private List getAnnotatedFields(final Class resultClass, } private Object getValueForField(final Object instance, final String fieldName, final Class resultClass, - final Class annotation, final boolean inherited) { + final Class annotation, final boolean inherited) { if (instance == null) { return null; } @@ -884,8 +905,8 @@ private void setFieldValue(final Object instance, final Field field, final Objec public List getAnnotatedMethods(final Class resultClass, - final Class annotation, - final boolean inherited) { + final Class annotation, + final boolean inherited) { if (resultClass == null) { return null; } @@ -893,7 +914,7 @@ public List getAnnotatedMethods(final Class resultClass, Method[] methods = resultClass.getDeclaredMethods(); List annotatedMethods = new ArrayList<>(); - for (Method method: methods) { + for (Method method : methods) { if (method.getAnnotation(annotation) != null) { annotatedMethods.add(method); } @@ -977,49 +998,49 @@ private String firstCharToUpperCase(final String content) { public EdmSimpleTypeKind mapTypeKind(final org.apache.olingo.odata2.api.annotation.edm.EdmType type) { switch (type) { - case BINARY: - return EdmSimpleTypeKind.Binary; - case BOOLEAN: - return EdmSimpleTypeKind.Boolean; - case BYTE: - return EdmSimpleTypeKind.Byte; - case COMPLEX: - return EdmSimpleTypeKind.Null; - case DATE_TIME: - return EdmSimpleTypeKind.DateTime; - case DATE_TIME_OFFSET: - return EdmSimpleTypeKind.DateTimeOffset; - case DECIMAL: - return EdmSimpleTypeKind.Decimal; - case DOUBLE: - return EdmSimpleTypeKind.Double; - case GUID: - return EdmSimpleTypeKind.Guid; - case INT16: - return EdmSimpleTypeKind.Int16; - case INT32: - return EdmSimpleTypeKind.Int32; - case INT64: - return EdmSimpleTypeKind.Int64; - case NULL: - return EdmSimpleTypeKind.Null; - case SBYTE: - return EdmSimpleTypeKind.SByte; - case SINGLE: - return EdmSimpleTypeKind.Single; - case STRING: - return EdmSimpleTypeKind.String; - case TIME: - return EdmSimpleTypeKind.Time; - default: - throw new AnnotationRuntimeException("Unknown type '" + type - + "' for mapping to EdmSimpleTypeKind."); + case BINARY: + return EdmSimpleTypeKind.Binary; + case BOOLEAN: + return EdmSimpleTypeKind.Boolean; + case BYTE: + return EdmSimpleTypeKind.Byte; + case COMPLEX: + return EdmSimpleTypeKind.Null; + case DATE_TIME: + return EdmSimpleTypeKind.DateTime; + case DATE_TIME_OFFSET: + return EdmSimpleTypeKind.DateTimeOffset; + case DECIMAL: + return EdmSimpleTypeKind.Decimal; + case DOUBLE: + return EdmSimpleTypeKind.Double; + case GUID: + return EdmSimpleTypeKind.Guid; + case INT16: + return EdmSimpleTypeKind.Int16; + case INT32: + return EdmSimpleTypeKind.Int32; + case INT64: + return EdmSimpleTypeKind.Int64; + case NULL: + return EdmSimpleTypeKind.Null; + case SBYTE: + return EdmSimpleTypeKind.SByte; + case SINGLE: + return EdmSimpleTypeKind.Single; + case STRING: + return EdmSimpleTypeKind.String; + case TIME: + return EdmSimpleTypeKind.Time; + default: + throw new AnnotationRuntimeException("Unknown type '" + type + + "' for mapping to EdmSimpleTypeKind."); } } public EdmType mapType(final Class type) { if (type == String.class || type == URI.class || type == URL.class - || type.isEnum() || type == TimeZone.class || type == Locale.class ) { + || type.isEnum() || type == TimeZone.class || type == Locale.class) { return EdmType.STRING; } else if (type == boolean.class || type == Boolean.class) { return EdmType.BOOLEAN; @@ -1052,14 +1073,14 @@ public EdmType mapType(final Class type) { public EdmMultiplicity mapMultiplicity(final Multiplicity multiplicity) { switch (multiplicity) { - case ZERO_OR_ONE: - return EdmMultiplicity.ZERO_TO_ONE; - case ONE: - return EdmMultiplicity.ONE; - case MANY: - return EdmMultiplicity.MANY; - default: - throw new AnnotationRuntimeException("Unknown type '" + multiplicity + "' for mapping to EdmMultiplicity."); + case ZERO_OR_ONE: + return EdmMultiplicity.ZERO_TO_ONE; + case ONE: + return EdmMultiplicity.ONE; + case MANY: + return EdmMultiplicity.MANY; + default: + throw new AnnotationRuntimeException("Unknown type '" + multiplicity + "' for mapping to EdmMultiplicity."); } } diff --git a/janos-core/src/test/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelperTest.java b/janos-core/src/test/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelperTest.java index 4b955c1..94767f0 100644 --- a/janos-core/src/test/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelperTest.java +++ b/janos-core/src/test/java/org/apache/olingo/odata2/janos/processor/core/util/AnnotationHelperTest.java @@ -17,14 +17,16 @@ import junit.framework.Assert; import org.apache.olingo.odata2.api.annotation.edm.*; +import org.apache.olingo.odata2.api.edm.EdmMultiplicity; import org.apache.olingo.odata2.api.edm.FullQualifiedName; -import org.apache.olingo.odata2.api.edm.provider.*; +import org.apache.olingo.odata2.api.edm.provider.ReturnType; import org.apache.olingo.odata2.api.exception.ODataException; import org.apache.olingo.odata2.janos.processor.core.model.Location; import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -253,6 +255,95 @@ public void extractEntityReturnType() throws Exception { Assert.assertEquals("SimpleEntity", returnType.getTypeName().getName()); } + @Test + public void navInfoBiDirectionalContainedToContainer() throws Exception { + + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + BiDirectionalContainedEntity.class, BiDirectionalContainerEntity.class); + Assert.assertTrue("Navigation should be bi-directional", navInfo.isBiDirectional()); + + Assert.assertEquals("containerEntity", navInfo.getFromField().getName()); + Assert.assertEquals("BiDirectionalContainedEntity", navInfo.getFromTypeName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); + Assert.assertEquals("r_ContainerEntity", navInfo.getToRoleName()); + + Assert.assertEquals("containedEntities", navInfo.getToField().getName()); + Assert.assertEquals("BiDirectionalContainerEntity", navInfo.getToTypeName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getFromMultiplicity()); + Assert.assertEquals("r_ContainedEntities", navInfo.getFromRoleName()); + } + + @Test + public void navInfoBiDirectionalContainerToContained() throws Exception { + + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + BiDirectionalContainerEntity.class, BiDirectionalContainedEntity.class); + Assert.assertTrue("Navigation should be bi-directional", navInfo.isBiDirectional()); + + Assert.assertEquals("containedEntities", navInfo.getFromField().getName()); + Assert.assertEquals("BiDirectionalContainerEntity", navInfo.getFromTypeName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); + Assert.assertEquals("r_ContainedEntities", navInfo.getToRoleName()); + + Assert.assertEquals("containerEntity", navInfo.getToField().getName()); + Assert.assertEquals("BiDirectionalContainedEntity", navInfo.getToTypeName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); + Assert.assertEquals("r_ContainerEntity", navInfo.getFromRoleName()); + } + + @Test + public void navInfoUniDirectionalSourceFirst() throws Exception { + + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + UniDirectionalSourceEntity.class, UniDirectionalTargetEntity.class); + Assert.assertFalse("Navigation should be uni-directional", navInfo.isBiDirectional()); + + Assert.assertEquals("targetEntity", navInfo.getFromField().getName()); + Assert.assertEquals("UniDirectionalSourceEntity", navInfo.getFromTypeName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); + Assert.assertEquals("r_TargetEntity", navInfo.getToRoleName()); + + Assert.assertNull("Uni-directional target entity has no return field", navInfo.getToField()); + Assert.assertEquals("UniDirectionalTargetEntity", navInfo.getToTypeName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); + Assert.assertEquals("UniDirectionalSourceEntity", navInfo.getFromRoleName()); + } + + @Test + public void navInfoUniDirectionalTargetFirst() throws Exception { + + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + UniDirectionalTargetEntity.class, UniDirectionalSourceEntity.class); + Assert.assertFalse("Navigation is uni-directional", navInfo.isBiDirectional()); + + Assert.assertNull("Uni-directional target entity has no return field", navInfo.getFromField()); + Assert.assertEquals("UniDirectionalTargetEntity", navInfo.getFromTypeName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); + Assert.assertEquals("UniDirectionalSourceEntity", navInfo.getToRoleName()); + + Assert.assertEquals("targetEntity", navInfo.getToField().getName()); + Assert.assertEquals("UniDirectionalSourceEntity", navInfo.getToTypeName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); + Assert.assertEquals("r_TargetEntity", navInfo.getFromRoleName()); + } + + @Test + public void navInfoSelfNavigation() throws Exception { + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + NavigationAnnotated.class, NavigationAnnotated.class); + Assert.assertFalse("Self-navigation should be uni-directional", navInfo.isBiDirectional()); + + Assert.assertEquals("selfReferencedNavigation", navInfo.getFromField().getName()); + Assert.assertEquals("NavigationAnnotated", navInfo.getFromTypeName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); + Assert.assertEquals("r_SelfReferencedNavigation", navInfo.getToRoleName()); + + Assert.assertNull("Self-navigation target entity has no return field", navInfo.getToField()); + Assert.assertEquals("NavigationAnnotated", navInfo.getToTypeName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); + Assert.assertEquals("NavigationAnnotated", navInfo.getFromRoleName()); + } + @EdmEntityType private class SimpleEntity { @EdmKey @@ -320,4 +411,43 @@ private class ConversionProperty { } private class NotAnnotatedBean {} + + @EdmEntityType + private class UniDirectionalSourceEntity { + @EdmKey @EdmProperty + Long id; + @EdmProperty + String name; + @EdmNavigationProperty + UniDirectionalTargetEntity targetEntity; + } + + @EdmEntityType + private class UniDirectionalTargetEntity { + @EdmKey @EdmProperty + Long id; + @EdmProperty + String name; + } + + @EdmEntityType + private class BiDirectionalContainedEntity { + @EdmKey @EdmProperty + Long id; + @EdmProperty + String name; + @EdmNavigationProperty + BiDirectionalContainerEntity containerEntity; + } + + @EdmEntityType + private class BiDirectionalContainerEntity { + @EdmKey @EdmProperty + Long id; + @EdmProperty + String name; + @EdmNavigationProperty(toMultiplicity = EdmNavigationProperty.Multiplicity.MANY) + List containedEntities = new ArrayList<>(); + } + }