From fe72d7f307759ee868aab98a023b8bf2e8c8b5ed Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Sun, 2 Oct 2016 15:07:08 +1100 Subject: [PATCH 01/10] Start of new tests; added javadoc and Markdown description. --- EntityNavigation.md | 104 ++++++++++++++++++ .../processor/core/util/AnnotationHelper.java | 16 ++- .../core/util/AnnotationHelperTest.java | 41 ++++++- 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 EntityNavigation.md diff --git a/EntityNavigation.md b/EntityNavigation.md new file mode 100644 index 0000000..1260269 --- /dev/null +++ b/EntityNavigation.md @@ -0,0 +1,104 @@ +# 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. +} +``` + +## 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()` = `null` + +A call to `AnnotationHelper.getCommonNavigation(Building.class, Closet.class)` +returns an `AnnotatedNavInfo` instance with: + +* `isBiDirectional()` = `false` +* `getFromField()` = `null` +* `getFromMultiplicity()` = `null` +* `getToField()` = `closet` +* `getToMultiplicity()` = `ONE` + +## 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` +* `getFromMultiplicity()` = `MANY` +* `getToField()` = `room` +* `getToMultiplicity()` = `ONE` + +A call to `AnnotationHelper.getCommonNavigation(Building.class, Room.class)` +returns an `AnnotatedNavInfo` instance with: + +* `isBiDirectional()` = `true` +* `getFromField()` = `room` +* `getFromMultiplicity()` = `ONE` +* `getToField()` = `building` +* `getToMultiplicity()` = `MANY` + 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..8432ec7 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 @@ -352,6 +352,20 @@ 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; @@ -403,7 +417,7 @@ public EdmMultiplicity getToMultiplicity() { } public boolean isBiDirectional() { - return fromNavigation == null; + return fromNavigation != null && toNavigation != null; } public String getRelationshipName() { 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..16688d2 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,10 +17,13 @@ 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.Building; import org.apache.olingo.odata2.janos.processor.core.model.Location; +import org.apache.olingo.odata2.janos.processor.core.model.Room; import org.junit.Test; import java.lang.reflect.Field; @@ -253,6 +256,42 @@ public void extractEntityReturnType() throws Exception { Assert.assertEquals("SimpleEntity", returnType.getTypeName().getName()); } + @Test + public void getNavInfoBiDirectionalFromManyToOne() throws Exception { + + AnnotationHelper.AnnotatedNavInfo navInfo = + annotationHelper.getCommonNavigationInfo(Room.class, Building.class); + + Assert.assertTrue("Navigation between Room and Building should be bi-directional", + navInfo.isBiDirectional()); + + Assert.assertEquals("building", navInfo.getFromField().getName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); + Assert.assertEquals("Building", navInfo.getFromTypeName()); + + Assert.assertEquals("rooms", navInfo.getToField().getName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); + Assert.assertEquals("Room", navInfo.getToTypeName()); + } + + @Test + public void getNavInfoBiDirectionalFromOneToMany() throws Exception { + + AnnotationHelper.AnnotatedNavInfo navInfo = + annotationHelper.getCommonNavigationInfo(Building.class, Room.class); + + Assert.assertTrue("Navigation between Building and Room should be bi-directional", + navInfo.isBiDirectional()); + + Assert.assertEquals("rooms", navInfo.getFromField().getName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getFromMultiplicity()); + Assert.assertEquals("Room", navInfo.getFromTypeName()); + + Assert.assertEquals("building", navInfo.getToField().getName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); + Assert.assertEquals("Building", navInfo.getToTypeName()); + } + @EdmEntityType private class SimpleEntity { @EdmKey From fc1edaad968b3245d5a59bc3b98ff443177da48d Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Sun, 2 Oct 2016 22:25:33 +1100 Subject: [PATCH 02/10] Re-worked getCommonNavigationInfo method and AnnotatedNavInfo class. New unit tests. --- .../processor/core/util/AnnotationHelper.java | 246 +++++++++--------- .../core/util/AnnotationHelperTest.java | 111 ++++++-- 2 files changed, 209 insertions(+), 148 deletions(-) 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 8432ec7..8ebb84a 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(); @@ -373,7 +373,7 @@ public class AnnotatedNavInfo { private final EdmNavigationProperty toNavigation; public AnnotatedNavInfo(final Field fromField, final Field toField, final EdmNavigationProperty fromNavigation, - final EdmNavigationProperty toNavigation) { + final EdmNavigationProperty toNavigation) { this.fromField = fromField; this.toField = toField; this.fromNavigation = fromNavigation; @@ -385,7 +385,7 @@ public Field getFromField() { } public String getFromRoleName() { - if(isBiDirectional()) { + if (isBiDirectional()) { return extractFromRoleEntityName(toField); } return extractToRoleName(toNavigation, toField); @@ -396,24 +396,26 @@ public Field getToField() { } public String getToRoleName() { - if(isBiDirectional()) { + if (isBiDirectional()) { return extractToRoleName(toNavigation, toField); } return extractToRoleName(fromNavigation, fromField); } public EdmMultiplicity getFromMultiplicity() { - if(isBiDirectional()) { - return EdmMultiplicity.ONE; + if (toField != null) { + return extractMultiplicity(toNavigation, toField); + } else { + return null; } - return extractMultiplicity(toNavigation, toField); } public EdmMultiplicity getToMultiplicity() { - if(isBiDirectional()) { - return extractMultiplicity(toNavigation, toField); + if (fromField != null) { + return extractMultiplicity(fromNavigation, fromField); + } else { + return null; } - return extractMultiplicity(fromNavigation, fromField); } public boolean isBiDirectional() { @@ -423,15 +425,15 @@ public boolean isBiDirectional() { public String getRelationshipName() { String toAssociation = toNavigation.association(); String fromAssociation = ""; - if(!isBiDirectional()) { + if (!isBiDirectional()) { fromAssociation = fromNavigation.association(); } - if(fromAssociation.isEmpty() && fromAssociation.equals(toAssociation)) { + 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() + "'"); } @@ -439,14 +441,14 @@ public String getRelationshipName() { } public String getFromTypeName() { - if(isBiDirectional()) { + if (isBiDirectional()) { return extractEntityTypeName(toField.getDeclaringClass()); } return extractEntityTypeName(fromField.getDeclaringClass()); } public String getToTypeName() { - if(isBiDirectional()) { + if (isBiDirectional()) { return extractEntityTypeName(ClassHelper.getFieldType(toField)); } return extractEntityTypeName(toField.getDeclaringClass()); @@ -454,7 +456,7 @@ public String getToTypeName() { @Override public String toString() { - if(isBiDirectional()) { + if (isBiDirectional()) { return "AnnotatedNavInfo{biDirectional = true" + ", toField=" + toField.getName() + ", toNavigation=" + toNavigation.name() + '}'; @@ -467,52 +469,61 @@ public String toString() { } } - + /** + * 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()) { + final Field field = sourceFields.get(0); + final EdmNavigationProperty navProperty = field.getAnnotation(EdmNavigationProperty.class); + return new AnnotatedNavInfo(field, field, navProperty, navProperty); } - // 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, sourceNav, targetNav); } } } - // 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 { + final Field field = targetFields.get(0); + final EdmNavigationProperty navProperty = field.getAnnotation(EdmNavigationProperty.class); + return new AnnotatedNavInfo(null, field, null, navProperty); } + } else { + final Field field = sourceFields.get(0); + final EdmNavigationProperty navProperty = field.getAnnotation(EdmNavigationProperty.class); + return new AnnotatedNavInfo(field, null, navProperty, 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) @@ -561,13 +572,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 { @@ -588,7 +599,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; } @@ -603,12 +614,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; } @@ -616,7 +627,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<>(); @@ -677,7 +688,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(); @@ -734,11 +745,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() + "'."); @@ -774,7 +785,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); @@ -790,7 +801,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 @@ -814,15 +825,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; } @@ -846,7 +850,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; } @@ -898,8 +902,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; } @@ -907,7 +911,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); } @@ -991,49 +995,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; @@ -1066,14 +1070,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 16688d2..d8333a4 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 @@ -21,13 +21,12 @@ import org.apache.olingo.odata2.api.edm.FullQualifiedName; 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.Building; import org.apache.olingo.odata2.janos.processor.core.model.Location; -import org.apache.olingo.odata2.janos.processor.core.model.Room; 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; @@ -257,39 +256,58 @@ public void extractEntityReturnType() throws Exception { } @Test - public void getNavInfoBiDirectionalFromManyToOne() throws Exception { - - AnnotationHelper.AnnotatedNavInfo navInfo = - annotationHelper.getCommonNavigationInfo(Room.class, Building.class); - - Assert.assertTrue("Navigation between Room and Building should be bi-directional", - navInfo.isBiDirectional()); + 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(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); + Assert.assertNull("Uni-directional target entity has no return field", navInfo.getToField()); + Assert.assertNull("Uni-directional target entity has no return navigation", navInfo.getFromMultiplicity()); + } - Assert.assertEquals("building", navInfo.getFromField().getName()); + @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.assertNull("Uni-directional target entity has no return navigation", navInfo.getToMultiplicity()); + Assert.assertEquals("targetEntity", navInfo.getToField().getName()); Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); - Assert.assertEquals("Building", navInfo.getFromTypeName()); - - Assert.assertEquals("rooms", navInfo.getToField().getName()); - Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); - Assert.assertEquals("Room", navInfo.getToTypeName()); } @Test - public void getNavInfoBiDirectionalFromOneToMany() throws Exception { - - AnnotationHelper.AnnotatedNavInfo navInfo = - annotationHelper.getCommonNavigationInfo(Building.class, Room.class); + public void navInfoBiDirectionalContainedToContainer() throws Exception { + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + BiDirectionalContainedEntity.class, BiDirectionalContainerEntity.class); + Assert.assertTrue("Navigation is bi-directional", navInfo.isBiDirectional()); + Assert.assertEquals("containerEntity", navInfo.getFromField().getName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); + Assert.assertEquals("containedEntities", navInfo.getToField().getName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getFromMultiplicity()); + } - Assert.assertTrue("Navigation between Building and Room should be bi-directional", - navInfo.isBiDirectional()); + @Test + public void navInfoBiDirectionalContainerToContained() throws Exception { + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + BiDirectionalContainerEntity.class, BiDirectionalContainedEntity.class); + Assert.assertTrue("Navigation is bi-directional", navInfo.isBiDirectional()); + Assert.assertEquals("containedEntities", navInfo.getFromField().getName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); + Assert.assertEquals("containerEntity", navInfo.getToField().getName()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); + } - Assert.assertEquals("rooms", navInfo.getFromField().getName()); + @Test + public void navInfoSelfNavigation() throws Exception { + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( + NavigationAnnotated.class, NavigationAnnotated.class); + Assert.assertTrue("Self-navigation is bi-directional", navInfo.isBiDirectional()); + Assert.assertEquals("selfReferencedNavigation", navInfo.getFromField().getName()); + Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); + Assert.assertEquals("selfReferencedNavigation", navInfo.getToField().getName()); Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getFromMultiplicity()); - Assert.assertEquals("Room", navInfo.getFromTypeName()); - - Assert.assertEquals("building", navInfo.getToField().getName()); - Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); - Assert.assertEquals("Building", navInfo.getToTypeName()); } @EdmEntityType @@ -359,4 +377,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<>(); + } + } From 01a653096f7e218979946188c129ebd41cc17172 Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Sun, 2 Oct 2016 23:06:11 +1100 Subject: [PATCH 03/10] Fixes to AnnotatedNavInfo#getRelationshipName and toString methods. --- .../processor/core/util/AnnotationHelper.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) 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 8ebb84a..9bb9be3 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 @@ -423,11 +423,9 @@ public boolean isBiDirectional() { } public String getRelationshipName() { - String toAssociation = toNavigation.association(); - String fromAssociation = ""; - if (!isBiDirectional()) { - fromAssociation = fromNavigation.association(); - } + + final String fromAssociation = fromNavigation == null ? "" : fromNavigation.association(); + final String toAssociation = toNavigation == null ? "" : toNavigation.association(); if (fromAssociation.isEmpty() && fromAssociation.equals(toAssociation)) { return createCanonicalRelationshipName(getFromRoleName(), getToRoleName()); @@ -459,13 +457,20 @@ public String toString() { if (isBiDirectional()) { return "AnnotatedNavInfo{biDirectional = true" + ", 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() + '}'; } } From 41e5ea302433116daf3e96b7d02bc41ef7275c3b Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Sun, 2 Oct 2016 23:22:21 +1100 Subject: [PATCH 04/10] Fix to AnnotationDataSource#readRelatedData to work with updated AnnotationNavInfo. --- .../janos/processor/core/data/source/AnnotationDataSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..69974d2 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(); From 043fca2b743b6469d74c4ef83b74f1ea5f4b92ad Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Mon, 3 Oct 2016 17:52:17 +1100 Subject: [PATCH 05/10] Implemented role and type name methods of AnnotatedNavInfo. --- .../processor/core/util/AnnotationHelper.java | 29 +++++++++++-------- .../core/util/AnnotationHelperTest.java | 28 ++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) 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 9bb9be3..0399d7e 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 @@ -385,10 +385,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() { @@ -396,10 +397,11 @@ 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() { @@ -439,23 +441,26 @@ 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()) { return "AnnotatedNavInfo{biDirectional = true" + + ", fromField=" + fromField.getName() + ", toField=" + toField.getName() + ", toNavigation=" + toNavigation.name() + ", fromNavigation=" + fromNavigation.name() + 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 d8333a4..7c69a54 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 @@ -257,46 +257,74 @@ public void extractEntityReturnType() throws Exception { @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.assertNull("Uni-directional target entity has no return navigation", 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.assertNull("Uni-directional target entity has no return navigation", 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 navInfoBiDirectionalContainedToContainer() throws Exception { + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( BiDirectionalContainedEntity.class, BiDirectionalContainerEntity.class); Assert.assertTrue("Navigation is 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 is 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 From ab72306e2c895e93b00d5146593207bdca65f987 Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Wed, 5 Oct 2016 16:59:49 +1100 Subject: [PATCH 06/10] Simplified constructor for AnnotatedNavInfo. --- .../processor/core/util/AnnotationHelper.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) 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 0399d7e..48cb154 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 @@ -372,12 +372,11 @@ public class AnnotatedNavInfo { 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() { @@ -495,8 +494,7 @@ public AnnotatedNavInfo getCommonNavigationInfo(final Class sourceClass, fina // Self-navigation: return the first field in the list. if (sourceClass == targetClass && !sourceFields.isEmpty()) { final Field field = sourceFields.get(0); - final EdmNavigationProperty navProperty = field.getAnnotation(EdmNavigationProperty.class); - return new AnnotatedNavInfo(field, field, navProperty, navProperty); + return new AnnotatedNavInfo(field, field); } // Look for bi-directional navigation, returning the first with matching association name. @@ -507,7 +505,7 @@ public AnnotatedNavInfo getCommonNavigationInfo(final Class sourceClass, fina final EdmNavigationProperty targetNav = targetField.getAnnotation(EdmNavigationProperty.class); final String targetAssociation = extractRelationshipName(targetNav, targetField); if (sourceAssociation.equals(targetAssociation)) { - return new AnnotatedNavInfo(sourceField, targetField, sourceNav, targetNav); + return new AnnotatedNavInfo(sourceField, targetField); } } } @@ -517,14 +515,10 @@ public AnnotatedNavInfo getCommonNavigationInfo(final Class sourceClass, fina if (targetFields.isEmpty()) { return null; } else { - final Field field = targetFields.get(0); - final EdmNavigationProperty navProperty = field.getAnnotation(EdmNavigationProperty.class); - return new AnnotatedNavInfo(null, field, null, navProperty); + return new AnnotatedNavInfo(null, targetFields.get(0)); } } else { - final Field field = sourceFields.get(0); - final EdmNavigationProperty navProperty = field.getAnnotation(EdmNavigationProperty.class); - return new AnnotatedNavInfo(field, null, navProperty, null); + return new AnnotatedNavInfo(sourceFields.get(0), null); } } From a35facad44d3795d172e55df3205ac2c642c2669 Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Wed, 5 Oct 2016 17:32:13 +1100 Subject: [PATCH 07/10] Changed self-referential navigation to uni-directional. --- .../processor/core/util/AnnotationHelper.java | 3 +- .../core/util/AnnotationHelperTest.java | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) 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 48cb154..44be01d 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 @@ -493,8 +493,7 @@ public AnnotatedNavInfo getCommonNavigationInfo(final Class sourceClass, fina // Self-navigation: return the first field in the list. if (sourceClass == targetClass && !sourceFields.isEmpty()) { - final Field field = sourceFields.get(0); - return new AnnotatedNavInfo(field, field); + return new AnnotatedNavInfo(sourceFields.get(0), null); } // Look for bi-directional navigation, returning the first with matching association name. 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 7c69a54..404fcaf 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 @@ -255,6 +255,23 @@ public void extractEntityReturnType() throws Exception { Assert.assertEquals("SimpleEntity", returnType.getTypeName().getName()); } + @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.assertNull("Uni-directional target entity has no return navigation", navInfo.getFromMultiplicity()); + Assert.assertEquals("NavigationAnnotated", navInfo.getFromRoleName()); + } + @Test public void navInfoUniDirectionalSourceFirst() throws Exception { @@ -327,17 +344,6 @@ public void navInfoBiDirectionalContainerToContained() throws Exception { Assert.assertEquals("r_ContainerEntity", navInfo.getFromRoleName()); } - @Test - public void navInfoSelfNavigation() throws Exception { - AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( - NavigationAnnotated.class, NavigationAnnotated.class); - Assert.assertTrue("Self-navigation is bi-directional", navInfo.isBiDirectional()); - Assert.assertEquals("selfReferencedNavigation", navInfo.getFromField().getName()); - Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); - Assert.assertEquals("selfReferencedNavigation", navInfo.getToField().getName()); - Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getFromMultiplicity()); - } - @EdmEntityType private class SimpleEntity { @EdmKey From 1e12ecc5db3cc51b2364ad178c8458e486de0835 Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Wed, 5 Oct 2016 22:29:32 +1100 Subject: [PATCH 08/10] Set multiplicity of one-way navigation target to one. --- .../odata2/janos/processor/core/util/AnnotationHelper.java | 4 ++-- .../janos/processor/core/util/AnnotationHelperTest.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) 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 44be01d..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 @@ -407,7 +407,7 @@ public EdmMultiplicity getFromMultiplicity() { if (toField != null) { return extractMultiplicity(toNavigation, toField); } else { - return null; + return EdmMultiplicity.ONE; } } @@ -415,7 +415,7 @@ public EdmMultiplicity getToMultiplicity() { if (fromField != null) { return extractMultiplicity(fromNavigation, fromField); } else { - return null; + return EdmMultiplicity.ONE; } } 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 404fcaf..6019f5f 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 @@ -268,7 +268,7 @@ public void navInfoSelfNavigation() throws Exception { Assert.assertNull("Self-navigation target entity has no return field", navInfo.getToField()); Assert.assertEquals("NavigationAnnotated", navInfo.getToTypeName()); - Assert.assertNull("Uni-directional target entity has no return navigation", navInfo.getFromMultiplicity()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); Assert.assertEquals("NavigationAnnotated", navInfo.getFromRoleName()); } @@ -286,7 +286,7 @@ public void navInfoUniDirectionalSourceFirst() throws Exception { Assert.assertNull("Uni-directional target entity has no return field", navInfo.getToField()); Assert.assertEquals("UniDirectionalTargetEntity", navInfo.getToTypeName()); - Assert.assertNull("Uni-directional target entity has no return navigation", navInfo.getFromMultiplicity()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); Assert.assertEquals("UniDirectionalSourceEntity", navInfo.getFromRoleName()); } @@ -299,7 +299,7 @@ public void navInfoUniDirectionalTargetFirst() throws Exception { Assert.assertNull("Uni-directional target entity has no return field", navInfo.getFromField()); Assert.assertEquals("UniDirectionalTargetEntity", navInfo.getFromTypeName()); - Assert.assertNull("Uni-directional target entity has no return navigation", navInfo.getToMultiplicity()); + Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getToMultiplicity()); Assert.assertEquals("UniDirectionalSourceEntity", navInfo.getToRoleName()); Assert.assertEquals("targetEntity", navInfo.getToField().getName()); From 92bd304c96a7cc10a7b03b81bfa8436e3ef7dbb2 Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Wed, 5 Oct 2016 23:07:57 +1100 Subject: [PATCH 09/10] Updated entity navigation document; reordered unit tests to match. --- EntityNavigation.md | 72 ++++++++++++------ .../core/util/AnnotationHelperTest.java | 74 +++++++++---------- 2 files changed, 86 insertions(+), 60 deletions(-) diff --git a/EntityNavigation.md b/EntityNavigation.md index 1260269..506982a 100644 --- a/EntityNavigation.md +++ b/EntityNavigation.md @@ -48,6 +48,47 @@ public class Closet { } ``` +```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 @@ -64,41 +105,26 @@ returns an `AnnotatedNavInfo` instance with: * `getFromField()` = `building` * `getFromMultiplicity()` = `ONE` * `getToField()` = `null` -* `getToMultiplicity()` = `null` +* `getToMultiplicity()` = `ONE` A call to `AnnotationHelper.getCommonNavigation(Building.class, Closet.class)` returns an `AnnotatedNavInfo` instance with: * `isBiDirectional()` = `false` * `getFromField()` = `null` -* `getFromMultiplicity()` = `null` +* `getFromMultiplicity()` = `ONE` * `getToField()` = `closet` * `getToMultiplicity()` = `ONE` -## Bi-directional navigation - -A class has a navigation property to another class and the second class -has a navigation property back to the first. +## Self-navigation -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. +Self-navigation is a special case of uni-directional navigation. -A call to `AnnotationHelper.getCommonNavigation(Room.class, Building.class)` +A call to `AnnotationHelper.getCommonNavigation(Task.class, Task.class)` returns an `AnnotatedNavInfo` instance with: -* `isBiDirectional()` = `true` -* `getFromField()` = `building` -* `getFromMultiplicity()` = `MANY` -* `getToField()` = `room` +* `isBiDirectional()` = `false` +* `getFromField()` = `subTasks` * `getToMultiplicity()` = `ONE` - -A call to `AnnotationHelper.getCommonNavigation(Building.class, Room.class)` -returns an `AnnotatedNavInfo` instance with: - -* `isBiDirectional()` = `true` -* `getFromField()` = `room` +* `getToField()` = `null` * `getFromMultiplicity()` = `ONE` -* `getToField()` = `building` -* `getToMultiplicity()` = `MANY` - 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 6019f5f..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 @@ -256,20 +256,39 @@ public void extractEntityReturnType() throws Exception { } @Test - public void navInfoSelfNavigation() throws Exception { + public void navInfoBiDirectionalContainedToContainer() throws Exception { + AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( - NavigationAnnotated.class, NavigationAnnotated.class); - Assert.assertFalse("Self-navigation should be uni-directional", navInfo.isBiDirectional()); + BiDirectionalContainedEntity.class, BiDirectionalContainerEntity.class); + Assert.assertTrue("Navigation should be bi-directional", navInfo.isBiDirectional()); - Assert.assertEquals("selfReferencedNavigation", navInfo.getFromField().getName()); - Assert.assertEquals("NavigationAnnotated", navInfo.getFromTypeName()); + 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_SelfReferencedNavigation", navInfo.getToRoleName()); + Assert.assertEquals("r_ContainedEntities", navInfo.getToRoleName()); - Assert.assertNull("Self-navigation target entity has no return field", navInfo.getToField()); - Assert.assertEquals("NavigationAnnotated", navInfo.getToTypeName()); + Assert.assertEquals("containerEntity", navInfo.getToField().getName()); + Assert.assertEquals("BiDirectionalContainedEntity", navInfo.getToTypeName()); Assert.assertEquals(EdmMultiplicity.ONE, navInfo.getFromMultiplicity()); - Assert.assertEquals("NavigationAnnotated", navInfo.getFromRoleName()); + Assert.assertEquals("r_ContainerEntity", navInfo.getFromRoleName()); } @Test @@ -309,39 +328,20 @@ public void navInfoUniDirectionalTargetFirst() throws Exception { } @Test - public void navInfoBiDirectionalContainedToContainer() throws Exception { - - AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( - BiDirectionalContainedEntity.class, BiDirectionalContainerEntity.class); - Assert.assertTrue("Navigation is 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 { - + public void navInfoSelfNavigation() throws Exception { AnnotationHelper.AnnotatedNavInfo navInfo = annotationHelper.getCommonNavigationInfo( - BiDirectionalContainerEntity.class, BiDirectionalContainedEntity.class); - Assert.assertTrue("Navigation is bi-directional", navInfo.isBiDirectional()); + NavigationAnnotated.class, NavigationAnnotated.class); + Assert.assertFalse("Self-navigation should be uni-directional", navInfo.isBiDirectional()); - Assert.assertEquals("containedEntities", navInfo.getFromField().getName()); - Assert.assertEquals("BiDirectionalContainerEntity", navInfo.getFromTypeName()); + Assert.assertEquals("selfReferencedNavigation", navInfo.getFromField().getName()); + Assert.assertEquals("NavigationAnnotated", navInfo.getFromTypeName()); Assert.assertEquals(EdmMultiplicity.MANY, navInfo.getToMultiplicity()); - Assert.assertEquals("r_ContainedEntities", navInfo.getToRoleName()); + Assert.assertEquals("r_SelfReferencedNavigation", navInfo.getToRoleName()); - Assert.assertEquals("containerEntity", navInfo.getToField().getName()); - Assert.assertEquals("BiDirectionalContainedEntity", navInfo.getToTypeName()); + 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("r_ContainerEntity", navInfo.getFromRoleName()); + Assert.assertEquals("NavigationAnnotated", navInfo.getFromRoleName()); } @EdmEntityType From 1e5ea40d21b2336cab27a26260fb56d300d39538 Mon Sep 17 00:00:00 2001 From: Michael Strasser Date: Fri, 7 Oct 2016 16:50:49 +1100 Subject: [PATCH 10/10] Fixed reading of one-way relationships with collections. --- .../core/data/source/AnnotationDataSource.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 69974d2..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 @@ -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);