diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java index 65d85e2d9..39e7a1e09 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java @@ -166,6 +166,7 @@ public class JSONTest extends AbstractTest { entity("Advertisements_f89dee73-af9f-4cd4-b330-db93c25ff3c7", getODataPubFormat()); entity("entityReference", getODataPubFormat()); entity("entity.withcomplexnavigation", getODataPubFormat()); + entity("annotated", getODataPubFormat()); } protected void property(final String filename, final ODataFormat format) throws Exception { diff --git a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json index 61f020869..2d66e2324 100644 --- a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json +++ b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json @@ -1 +1 @@ -{"odata.metadata":"http://services.odata.org/V3/OData/OData.svc/$metadata#PersonDetails/@Element","odata.type":"ODataDemo.PersonDetail","odata.id":"http://services.odata.org/V3/OData/OData.svc/PersonDetails(0)","odata.editLink":"PersonDetails(0)","Person@odata.navigationLinkUrl":"PersonDetails(0)/Person","Person@odata.associationLinkUrl":"PersonDetails(0)/$links/Person","Person":{"odata.type":"ODataDemo.Person","odata.id":"http://services.odata.org/V3/OData/OData.svc/Persons(0)","odata.editLink":"Persons(0)","PersonDetail@odata.navigationLinkUrl":"Persons(0)/PersonDetail","PersonDetail@odata.associationLinkUrl":"Persons(0)/$links/PersonDetail","ID":0,"Name":"Paula Wilson"},"PersonID":0,"Age@odata.type":"Edm.Byte","Age":21,"Gender":false,"Phone":"(505) 555-5939","Address":{"odata.type":"ODataDemo.Address","Street":"2817 Milton Dr.","City":"Albuquerque","State":"NM","ZipCode":"87110","Country":"USA"},"Photo@odata.mediaEditLink":"PersonDetails(0)/Photo","Photo@odata.mediaETag":"\"nCP1Tf4Uax96eYIWjvoC/6ZflG8=\""} +{"odata.metadata":"http://services.odata.org/V3/OData/OData.svc/$metadata#PersonDetails/@Element","odata.type":"ODataDemo.PersonDetail","odata.id":"http://services.odata.org/V3/OData/OData.svc/PersonDetails(0)","odata.editLink":"PersonDetails(0)","Person@odata.navigationLinkUrl":"PersonDetails(0)/Person","Person@odata.associationLinkUrl":"PersonDetails(0)/$links/Person","Person":{"odata.type":"ODataDemo.Person","odata.id":"http://services.odata.org/V3/OData/OData.svc/Persons(0)","odata.editLink":"Persons(0)","PersonDetail@odata.navigationLinkUrl":"Persons(0)/PersonDetail","PersonDetail@odata.associationLinkUrl":"Persons(0)/$links/PersonDetail","ID":0,"Name":"Paula Wilson"},"PersonID":0,"Age@odata.type":"Edm.Byte","Age":21,"Gender":false,"Phone":"(505) 555-5939","Address":{"odata.type":"ODataDemo.Address","Street":"2817 Milton Dr.","City":"Albuquerque","State":"NM","ZipCode":"87110","Country":"USA"},"Photo@odata.mediaEditLink":"PersonDetails(0)/Photo","Photo@odata.mediaEtag":"\"nCP1Tf4Uax96eYIWjvoC/6ZflG8=\""} diff --git a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.json b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.json new file mode 100644 index 000000000..a69884b04 --- /dev/null +++ b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.json @@ -0,0 +1,62 @@ +{ + "@odata.context": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/$metadata#Customers/$entity", + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Customer", + "@odata.id": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)", + "@odata.editLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)", + "@com.contoso.display.highlight": true, + "@com.contoso.PersonalInfo.PhoneNumbers": ["(203)555-1718", "(203)555-1719"], + "PersonID": 1, + "FirstName": "Bob", + "LastName@com.contoso.display.style": { + "@odata.type": "#com.contoso.display.styleType", + "title": true, + "order": 1 + }, + "LastName": "Cat", + "MiddleName": null, + "HomeAddress": { + "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.HomeAddress", + "Street": "1 Microsoft Way", + "City": "London", + "PostalCode": "98052", + "FamilyName": "Cats" + }, + "Home@odata.type": "#GeographyPoint", + "Home": { + "type": "Point", + "coordinates": [23.1, 32.1], + "crs": { + "type": "name", + "properties": { + "name": "EPSG:4326" + } + } + }, + "Numbers@odata.type": "#Collection(String)", + "Numbers": ["111-111-1111", "0-12", "3-10", "bca", "ayz"], + "Emails@odata.type": "#Collection(String)", + "Emails": ["abc@abc.com"], + "City": "London", + "Birthday@odata.type": "#DateTimeOffset", + "Birthday": "1957-04-03T00:00:00Z", + "TimeBetweenLastTwoOrders@odata.type": "#Duration", + "TimeBetweenLastTwoOrders": "PT0.0000001S", + "Parent@odata.associationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Parent/$ref", + "Parent@odata.navigationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Parent", + "Orders@com.contoso.display.style": { + "@odata.type": "#com.contoso.display.styleType", + "order": 2 + }, + "Orders@odata.associationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Orders/$ref", + "Orders@odata.navigationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Orders", + "Company@odata.associationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Company/$ref", + "Company@odata.navigationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Company", + "#Microsoft.Test.OData.Services.ODataWCFService.ResetAddress": { + "title": "Microsoft.Test.OData.Services.ODataWCFService.ResetAddress", + "target": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Microsoft.Test.OData.Services.ODataWCFService.ResetAddress" + }, + "#Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress": { + "title": "Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress", + "target": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress" + } +} \ No newline at end of file diff --git a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.xml b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.xml new file mode 100644 index 000000000..851f96088 --- /dev/null +++ b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.xml @@ -0,0 +1,76 @@ + + + + http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1) + + + + + + 2 + + + + + <updated>2014-03-31T09:35:14Z</updated> + <author> + <name/> + </author> + <content type="application/xml"> + <m:properties> + <d:PersonID m:type="Int32">1</d:PersonID> + <d:FirstName>Bob</d:FirstName> + <d:LastName>Cat</d:LastName> + <m:annotation term="com.contoso.display.style" target="LastName" m:type="#com.contoso.display.styleType"> + <d:title m:type="Boolean">true</d:title> + <d:order m:type="Int32">1</d:order> + </m:annotation> + <d:MiddleName m:null="true"/> + <d:HomeAddress m:type="#Microsoft.Test.OData.Services.ODataWCFService.HomeAddress"> + <d:Street>1 Microsoft Way</d:Street> + <d:City>London</d:City> + <d:PostalCode>98052</d:PostalCode> + <d:FamilyName>Cats</d:FamilyName> + </d:HomeAddress> + <d:Home m:type="GeographyPoint"> + <gml:Point gml:srsName="http://www.opengis.net/def/crs/EPSG/0/4326"> + <gml:pos>32.1 23.1</gml:pos> + </gml:Point> + </d:Home> + <d:Numbers m:type="#Collection(String)"> + <m:element>111-111-1111</m:element> + </d:Numbers> + <d:Emails m:type="#Collection(String)"> + <m:element>abc@abc.com</m:element> + </d:Emails> + <d:City>London</d:City> + <d:Birthday m:type="DateTimeOffset">1957-04-03T00:00:00Z</d:Birthday> + <d:TimeBetweenLastTwoOrders m:type="Duration">PT0.0000001S</d:TimeBetweenLastTwoOrders> + </m:properties> + </content> + <m:annotation term="com.contoso.display.highlight" m:type="Boolean">true</m:annotation> + <m:annotation term="com.contoso.PersonalInfo.PhoneNumbers" m:type="#Collection(String)"> + <m:element>(203)555-1718</m:element> + <m:element>(203)555-1719</m:element> + </m:annotation> +</entry> + diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java index 17a65eb16..c0612af15 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java @@ -152,6 +152,8 @@ public interface Constants { public static final String ATTR_RELATIONSHIP = "relationship"; + public static final String ANNOTATION = "annotation"; + // JSON stuff public final static String JSON_METADATA = "odata.metadata"; diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java index 96db64634..33f393f9b 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java @@ -21,17 +21,9 @@ package org.apache.olingo.commons.api.data; /** * Represents an instance annotation. */ -public interface Annotation { +public interface Annotation extends Valuable { String getTerm(); void setTerm(String term); - - String getType(); - - void setType(String type); - - Value getValue(); - - void setValue(Value value); } diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java index 3f1ca03dd..36c6b74ed 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java @@ -18,17 +18,9 @@ */ package org.apache.olingo.commons.api.data; -public interface Property extends Annotatable { +public interface Property extends Valuable, Annotatable { String getName(); void setName(String name); - - String getType(); - - void setType(String type); - - Value getValue(); - - void setValue(Value value); } diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Valuable.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Valuable.java new file mode 100644 index 000000000..11a4bf031 --- /dev/null +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Valuable.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.commons.api.data; + +public interface Valuable { + + String getType(); + + void setType(String type); + + Value getValue(); + + void setValue(Value value); +} diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java index 2f3ebb3ae..9c3fec247 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java @@ -54,6 +54,8 @@ abstract class AbstractAtomDealer { protected final QName nextQName; + protected final QName annotationQName; + protected final QName contextQName; protected final QName entryRefQName; @@ -98,6 +100,8 @@ abstract class AbstractAtomDealer { new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_DATASERVICES), Constants.ELEM_URI); this.nextQName = new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_DATASERVICES), Constants.NEXT_LINK_REL); + this.annotationQName = + new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.ANNOTATION); this.contextQName = new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.CONTEXT); this.entryRefQName = diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java index 82f8ae05c..f94ff6821 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java @@ -27,14 +27,21 @@ import java.io.IOException; import java.util.AbstractMap.SimpleEntry; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotatable; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.CollectionValue; import org.apache.olingo.commons.api.data.ComplexValue; -import org.apache.olingo.commons.api.data.ResWrap; import org.apache.olingo.commons.api.data.Linked; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.data.ResWrap; +import org.apache.olingo.commons.api.data.Valuable; import org.apache.olingo.commons.api.data.Value; import org.apache.olingo.commons.api.domain.ODataLinkType; import org.apache.olingo.commons.api.domain.ODataPropertyType; @@ -44,6 +51,8 @@ import org.apache.olingo.commons.core.edm.EdmTypeInfo; abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResWrap<T>> { + protected final Pattern CUSTOM_ANNOTATION = Pattern.compile("(.+)@(.+)\\.(.+)"); + private JSONGeoValueDeserializer geoDeserializer; private JSONGeoValueDeserializer getGeoDeserializer() { @@ -203,6 +212,48 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW return new SimpleEntry<ODataPropertyType, EdmTypeInfo>(type, typeInfo); } + protected void populate(final Annotatable annotatable, final List<Property> properties, + final ObjectNode tree, final ObjectCodec codec) throws IOException { + + String type = null; + Annotation annotation = null; + for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) { + final Map.Entry<String, JsonNode> field = itor.next(); + final Matcher customAnnotation = CUSTOM_ANNOTATION.matcher(field.getKey()); + + if (field.getKey().charAt(0) == '@') { + final Annotation entityAnnot = new AnnotationImpl(); + entityAnnot.setTerm(field.getKey().substring(1)); + + value(entityAnnot, field.getValue(), codec); + if (annotatable != null) { + annotatable.getAnnotations().add(entityAnnot); + } + } else if (type == null && field.getKey().endsWith(getJSONAnnotation(jsonType))) { + type = field.getValue().asText(); + } else if (annotation == null && customAnnotation.matches() && !"odata".equals(customAnnotation.group(2))) { + annotation = new AnnotationImpl(); + annotation.setTerm(customAnnotation.group(2) + "." + customAnnotation.group(3)); + value(annotation, field.getValue(), codec); + } else { + final JSONPropertyImpl property = new JSONPropertyImpl(); + property.setName(field.getKey()); + property.setType(type == null + ? null + : new EdmTypeInfo.Builder().setTypeExpression(type).build().internal()); + type = null; + + value(property, field.getValue(), codec); + properties.add(property); + + if (annotation != null) { + property.getAnnotations().add(annotation); + annotation = null; + } + } + } + } + private Value fromPrimitive(final JsonNode node, final EdmTypeInfo typeInfo) { final Value value; @@ -234,22 +285,7 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW node.remove(toRemove); } - String type = null; - for (final Iterator<Map.Entry<String, JsonNode>> itor = node.fields(); itor.hasNext();) { - final Map.Entry<String, JsonNode> field = itor.next(); - - if (type == null && field.getKey().endsWith(getJSONAnnotation(jsonType))) { - type = field.getValue().asText(); - } else { - final JSONPropertyImpl property = new JSONPropertyImpl(); - property.setName(field.getKey()); - property.setType(type); - type = null; - - value(property, field.getValue(), codec); - value.get().add(property); - } - } + populate(value.asLinkedComplex(), value.get(), node, codec); return value; } @@ -283,12 +319,12 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW return value; } - protected void value(final JSONPropertyImpl property, final JsonNode node, final ObjectCodec codec) + protected void value(final Valuable valuable, final JsonNode node, final ObjectCodec codec) throws IOException { - EdmTypeInfo typeInfo = StringUtils.isBlank(property.getType()) + EdmTypeInfo typeInfo = StringUtils.isBlank(valuable.getType()) ? null - : new EdmTypeInfo.Builder().setTypeExpression(property.getType()).build(); + : new EdmTypeInfo.Builder().setTypeExpression(valuable.getType()).build(); final Map.Entry<ODataPropertyType, EdmTypeInfo> guessed = guessPropertyType(node); if (typeInfo == null) { @@ -307,31 +343,31 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW switch (propType) { case COLLECTION: - property.setValue(fromCollection(node.elements(), typeInfo, codec)); + valuable.setValue(fromCollection(node.elements(), typeInfo, codec)); break; case COMPLEX: if (node.has(jsonType)) { - property.setType(node.get(jsonType).asText()); + valuable.setType(node.get(jsonType).asText()); ((ObjectNode) node).remove(jsonType); } - property.setValue(fromComplex((ObjectNode) node, codec)); + valuable.setValue(fromComplex((ObjectNode) node, codec)); break; case ENUM: - property.setValue(new EnumValueImpl(node.asText())); + valuable.setValue(new EnumValueImpl(node.asText())); break; case PRIMITIVE: - if (property.getType() == null && typeInfo != null) { - property.setType(typeInfo.getFullQualifiedName().toString()); + if (valuable.getType() == null && typeInfo != null) { + valuable.setType(typeInfo.getFullQualifiedName().toString()); } - property.setValue(fromPrimitive(node, typeInfo)); + valuable.setValue(fromPrimitive(node, typeInfo)); break; case EMPTY: default: - property.setValue(new PrimitiveValueImpl(StringUtils.EMPTY)); + valuable.setValue(new PrimitiveValueImpl(StringUtils.EMPTY)); } } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java index 9efc67cae..942516c14 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java @@ -29,12 +29,15 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotatable; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.CollectionValue; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.Link; import org.apache.olingo.commons.api.data.Linked; import org.apache.olingo.commons.api.data.PrimitiveValue; import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.data.Valuable; import org.apache.olingo.commons.api.data.Value; import org.apache.olingo.commons.api.domain.ODataLinkType; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; @@ -62,6 +65,10 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> { protected void clientLinks(final Linked linked, final JsonGenerator jgen) throws IOException { final Map<String, List<String>> entitySetLinks = new HashMap<String, List<String>>(); for (Link link : linked.getNavigationLinks()) { + for (Annotation annotation : link.getAnnotations()) { + valuable(jgen, annotation, link.getTitle() + "@" + annotation.getTerm()); + } + ODataLinkType type = null; try { type = ODataLinkType.fromString(version, link.getRel(), link.getType()); @@ -185,7 +192,7 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> { } else if (value.isComplex()) { jgen.writeStartObject(); for (Property property : value.asComplex().get()) { - property(jgen, property, property.getName()); + valuable(jgen, property, property.getName()); } if (value.isLinkedComplex()) { links(value.asLinkedComplex(), jgen); @@ -194,12 +201,10 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> { } } - protected void property(final JsonGenerator jgen, final Property property, final String name) throws IOException { + protected void valuable(final JsonGenerator jgen, final Valuable valuable, final String name) throws IOException { if (serverMode && !Constants.VALUE.equals(name)) { - String type = property.getType(); - if (StringUtils.isBlank(type) - && property.getValue().isPrimitive() || property.getValue().isNull()) { - + String type = valuable.getType(); + if (StringUtils.isBlank(type) && valuable.getValue().isPrimitive() || valuable.getValue().isNull()) { type = EdmPrimitiveTypeKind.String.getFullQualifiedName().toString(); } if (StringUtils.isNotBlank(type)) { @@ -209,7 +214,13 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> { } } + if (valuable instanceof Annotatable) { + for (Annotation annotation : ((Annotatable) valuable).getAnnotations()) { + valuable(jgen, annotation, name + "@" + annotation.getTerm()); + } + } + jgen.writeFieldName(name); - value(jgen, property.getType(), property.getValue()); + value(jgen, valuable.getType(), valuable.getValue()); } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java index 3f8da74ef..2363c0b3e 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java @@ -23,6 +23,8 @@ import org.apache.olingo.commons.api.data.Value; public abstract class AbstractProperty extends AbstractAnnotatedObject implements Property { + private static final long serialVersionUID = -7175704800169997060L; + private String name; private String type; diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AnnotationImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AnnotationImpl.java new file mode 100644 index 000000000..e6a277eb4 --- /dev/null +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AnnotationImpl.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.commons.core.data; + +import org.apache.olingo.commons.api.data.Annotation; +import org.apache.olingo.commons.api.data.Value; + +public class AnnotationImpl extends AbstractAnnotatedObject implements Annotation { + + private static final long serialVersionUID = -2532246000091187020L; + + private String term; + + private String type; + + private Value value; + + @Override + public String getTerm() { + return term; + } + + @Override + public void setTerm(final String term) { + this.term = term; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(final String type) { + this.type = type; + } + + @Override + public Value getValue() { + return value; + } + + @Override + public void setValue(final Value value) { + this.value = value; + } +} diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java index a5171bad8..27379da86 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java @@ -23,6 +23,10 @@ import org.apache.olingo.commons.core.data.v4.AtomDeltaImpl; import java.io.InputStream; import java.net.URI; import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; @@ -32,10 +36,13 @@ import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.CollectionValue; import org.apache.olingo.commons.api.data.DeletedEntity.Reason; import org.apache.olingo.commons.api.data.EntitySet; +import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.data.ResWrap; +import org.apache.olingo.commons.api.data.Valuable; import org.apache.olingo.commons.api.data.Value; import org.apache.olingo.commons.api.domain.ODataOperation; import org.apache.olingo.commons.api.domain.ODataPropertyType; @@ -242,6 +249,14 @@ public class AtomDeserializer extends AbstractAtomDealer { property.setName(start.getName().getLocalPart()); } + valuable(property, reader, start); + + return property; + } + + private void valuable(final Valuable valuable, final XMLEventReader reader, final StartElement start) + throws XMLStreamException { + final Attribute nullAttr = start.getAttributeByName(this.nullQName); Value value; @@ -254,7 +269,7 @@ public class AtomDeserializer extends AbstractAtomDealer { : new EdmTypeInfo.Builder().setTypeExpression(typeAttrValue).build(); if (typeInfo != null) { - property.setType(typeInfo.internal()); + valuable.setType(typeInfo.internal()); } final ODataPropertyType propType = typeInfo == null @@ -277,7 +292,7 @@ public class AtomDeserializer extends AbstractAtomDealer { case PRIMITIVE: // No type specified? Defaults to Edm.String if (typeInfo == null) { - property.setType(EdmPrimitiveTypeKind.String.getFullQualifiedName().toString()); + valuable.setType(EdmPrimitiveTypeKind.String.getFullQualifiedName().toString()); } value = fromPrimitive(reader, start, typeInfo); break; @@ -290,9 +305,7 @@ public class AtomDeserializer extends AbstractAtomDealer { value = new NullValueImpl(); } - property.setValue(value); - - return property; + valuable.setValue(value); } private ResWrap<AtomPropertyImpl> property(final InputStream input) throws XMLStreamException { @@ -344,23 +357,27 @@ public class AtomDeserializer extends AbstractAtomDealer { while (reader.hasNext() && !foundEndElement) { final XMLEvent event = reader.nextEvent(); - if (event.isStartElement() && inlineQName.equals(event.asStartElement().getName())) { - StartElement inline = null; - while (reader.hasNext() && inline == null) { - final XMLEvent innerEvent = reader.peek(); - if (innerEvent.isCharacters() && innerEvent.asCharacters().isWhiteSpace()) { - reader.nextEvent(); - } else if (innerEvent.isStartElement()) { - inline = innerEvent.asStartElement(); + if (event.isStartElement()) { + if (inlineQName.equals(event.asStartElement().getName())) { + StartElement inline = null; + while (reader.hasNext() && inline == null) { + final XMLEvent innerEvent = reader.peek(); + if (innerEvent.isCharacters() && innerEvent.asCharacters().isWhiteSpace()) { + reader.nextEvent(); + } else if (innerEvent.isStartElement()) { + inline = innerEvent.asStartElement(); + } } - } - if (inline != null) { - if (Constants.QNAME_ATOM_ELEM_ENTRY.equals(inline.getName())) { - link.setInlineEntity(entity(reader, inline)); - } - if (Constants.QNAME_ATOM_ELEM_FEED.equals(inline.getName())) { - link.setInlineEntitySet(entitySet(reader, inline)); + if (inline != null) { + if (Constants.QNAME_ATOM_ELEM_ENTRY.equals(inline.getName())) { + link.setInlineEntity(entity(reader, inline)); + } + if (Constants.QNAME_ATOM_ELEM_FEED.equals(inline.getName())) { + link.setInlineEntitySet(entitySet(reader, inline)); + } } + } else if (annotationQName.equals(event.asStartElement().getName())) { + link.getAnnotations().add(annotation(reader, event.asStartElement())); } } @@ -502,18 +519,47 @@ public class AtomDeserializer extends AbstractAtomDealer { private void properties(final XMLEventReader reader, final StartElement start, final AtomEntityImpl entity) throws XMLStreamException { + + final Map<String, List<Annotation>> annotations = new HashMap<String, List<Annotation>>(); + boolean foundEndProperties = false; while (reader.hasNext() && !foundEndProperties) { final XMLEvent event = reader.nextEvent(); if (event.isStartElement()) { - entity.getProperties().add(property(reader, event.asStartElement())); + if (annotationQName.equals(event.asStartElement().getName())) { + final String target = event.asStartElement(). + getAttributeByName(QName.valueOf(Constants.ATTR_TARGET)).getValue(); + if (!annotations.containsKey(target)) { + annotations.put(target, new ArrayList<Annotation>()); + } + annotations.get(target).add(annotation(reader, event.asStartElement())); + } else { + entity.getProperties().add(property(reader, event.asStartElement())); + } } if (event.isEndElement() && start.getName().equals(event.asEndElement().getName())) { foundEndProperties = true; } } + + for (Property property : entity.getProperties()) { + if (annotations.containsKey(property.getName())) { + property.getAnnotations().addAll(annotations.get(property.getName())); + } + } + } + + private Annotation annotation(final XMLEventReader reader, final StartElement start) + throws XMLStreamException { + + final Annotation annotation = new AnnotationImpl(); + + annotation.setTerm(start.getAttributeByName(QName.valueOf(Constants.ATOM_ATTR_TERM)).getValue()); + valuable(annotation, reader, start); + + return annotation; } private AtomEntityImpl entityRef(final StartElement start) throws XMLStreamException { @@ -636,6 +682,8 @@ public class AtomDeserializer extends AbstractAtomDealer { } } else if (propertiesQName.equals(event.asStartElement().getName())) { properties(reader, event.asStartElement(), entity); + } else if (annotationQName.equals(event.asStartElement().getName())) { + entity.getAnnotations().add(annotation(reader, event.asStartElement())); } } @@ -643,8 +691,6 @@ public class AtomDeserializer extends AbstractAtomDealer { foundEndEntry = true; } } - - return entity; } else { entity = null; } @@ -719,6 +765,8 @@ public class AtomDeserializer extends AbstractAtomDealer { entitySet.getEntities().add(entity(reader, event.asStartElement())); } else if (entryRefQName.equals(event.asStartElement().getName())) { entitySet.getEntities().add(entityRef(event.asStartElement())); + } else if (annotationQName.equals(event.asStartElement().getName())) { + entitySet.getAnnotations().add(annotation(reader, event.asStartElement())); } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java index 0fdab4230..0f9750af7 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java @@ -28,6 +28,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.CollectionValue; import org.apache.olingo.commons.api.data.ResWrap; import org.apache.olingo.commons.api.data.Entity; @@ -124,6 +125,10 @@ public class AtomSerializer extends AbstractAtomDealer { } writer.writeEndElement(); + + for (Annotation annotation : property.getAnnotations()) { + annotation(writer, annotation, property.getName()); + } } private void property(final XMLStreamWriter writer, final Property property) throws XMLStreamException { @@ -185,6 +190,10 @@ public class AtomSerializer extends AbstractAtomDealer { writer.writeEndElement(); } + for (Annotation annotation : link.getAnnotations()) { + annotation(writer, annotation, null); + } + writer.writeEndElement(); } } @@ -211,6 +220,36 @@ public class AtomSerializer extends AbstractAtomDealer { } } + private void annotation(final XMLStreamWriter writer, final Annotation annotation, final String target) + throws XMLStreamException { + + writer.writeStartElement(Constants.PREFIX_METADATA, Constants.ANNOTATION, + version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA)); + + writer.writeAttribute(Constants.ATOM_ATTR_TERM, annotation.getTerm()); + + if (target != null) { + writer.writeAttribute(Constants.ATTR_TARGET, target); + } + + if (StringUtils.isNotBlank(annotation.getType())) { + final EdmTypeInfo typeInfo = new EdmTypeInfo.Builder().setTypeExpression(annotation.getType()).build(); + if (!EdmPrimitiveTypeKind.String.getFullQualifiedName().toString().equals(typeInfo.internal())) { + writer.writeAttribute(Constants.PREFIX_METADATA, version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), + Constants.ATTR_TYPE, typeInfo.external(version)); + } + } + + if (annotation.getValue().isNull()) { + writer.writeAttribute(Constants.PREFIX_METADATA, version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), + Constants.ATTR_NULL, Boolean.TRUE.toString()); + } else { + value(writer, annotation.getValue()); + } + + writer.writeEndElement(); + } + private void entity(final XMLStreamWriter writer, final Entity entity) throws XMLStreamException { if (entity.getBaseURI() != null) { writer.writeAttribute(XMLConstants.XML_NS_URI, Constants.ATTR_XML_BASE, entity.getBaseURI().toASCIIString()); @@ -284,6 +323,10 @@ public class AtomSerializer extends AbstractAtomDealer { writer.writeEndElement(); } writer.writeEndElement(); + + for (Annotation annotation : entity.getAnnotations()) { + annotation(writer, annotation, null); + } } private void entityRef(final XMLStreamWriter writer, final Entity entity) throws XMLStreamException { diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java index 4fd7ace9e..567fadbcd 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java @@ -26,12 +26,17 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.ResWrap; import org.apache.olingo.commons.api.data.Link; import org.apache.olingo.commons.api.domain.ODataLinkType; @@ -106,7 +111,6 @@ public class JSONEntityDeserializer extends AbstractJsonDeserializer<JSONEntityI if (tree.hasNonNull(jsonEditLink)) { final LinkImpl link = new LinkImpl(); - // Server mode if (serverMode) { link.setRel(Constants.EDIT_LINK_REL); } @@ -134,8 +138,11 @@ public class JSONEntityDeserializer extends AbstractJsonDeserializer<JSONEntityI } final Set<String> toRemove = new HashSet<String>(); + + final Map<String, List<Annotation>> annotations = new HashMap<String, List<Annotation>>(); for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) { final Map.Entry<String, JsonNode> field = itor.next(); + final Matcher customAnnotation = CUSTOM_ANNOTATION.matcher(field.getKey()); links(field, entity, toRemove, tree, parser.getCodec()); if (field.getKey().endsWith(getJSONAnnotation(jsonMediaEditLink))) { @@ -172,29 +179,39 @@ public class JSONEntityDeserializer extends AbstractJsonDeserializer<JSONEntityI entity.getOperations().add(operation); toRemove.add(field.getKey()); + } else if (customAnnotation.matches() && !"odata".equals(customAnnotation.group(2))) { + final Annotation annotation = new AnnotationImpl(); + annotation.setTerm(customAnnotation.group(2) + "." + customAnnotation.group(3)); + value(annotation, field.getValue(), parser.getCodec()); + + if (!annotations.containsKey(customAnnotation.group(1))) { + annotations.put(customAnnotation.group(1), new ArrayList<Annotation>()); + } + annotations.get(customAnnotation.group(1)).add(annotation); } } + + for (Link link : entity.getNavigationLinks()) { + if (annotations.containsKey(link.getTitle())) { + link.getAnnotations().addAll(annotations.get(link.getTitle())); + for (Annotation annotation : annotations.get(link.getTitle())) { + toRemove.add(link.getTitle() + "@" + annotation.getTerm()); + } + } + } + for (Link link : entity.getMediaEditLinks()) { + if (annotations.containsKey(link.getTitle())) { + link.getAnnotations().addAll(annotations.get(link.getTitle())); + for (Annotation annotation : annotations.get(link.getTitle())) { + toRemove.add(link.getTitle() + "@" + annotation.getTerm()); + } + } + } + tree.remove(toRemove); - String type = null; - for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) { - final Map.Entry<String, JsonNode> field = itor.next(); + populate(entity, entity.getProperties(), tree, parser.getCodec()); - if (type == null && field.getKey().endsWith(getJSONAnnotation(jsonType))) { - type = field.getValue().asText(); - } else { - final JSONPropertyImpl property = new JSONPropertyImpl(); - property.setName(field.getKey()); - property.setType(type == null - ? null - : new EdmTypeInfo.Builder().setTypeExpression(type).build().internal()); - type = null; - - value(property, field.getValue(), parser.getCodec()); - entity.getProperties().add(property); - } - } - return new ResWrap<JSONEntityImpl>(contextURL, metadataETag, entity); } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java index 043978344..cb0280d70 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.net.URI; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.ResWrap; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.Link; @@ -78,8 +79,12 @@ public class JSONEntitySerializer extends AbstractJsonSerializer<JSONEntityImpl> jgen.writeStringField(version.getJSONMap().get(ODataServiceVersion.JSON_ID), entity.getId()); } + for (Annotation annotation : entity.getAnnotations()) { + valuable(jgen, annotation, "@" + annotation.getTerm()); + } + for (Property property : entity.getProperties()) { - property(jgen, property, property.getName()); + valuable(jgen, property, property.getName()); } if (serverMode && entity.getEditLink() != null && StringUtils.isNotBlank(entity.getEditLink().getHref())) { diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java index df5d40a80..241dcbc43 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java @@ -27,8 +27,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.net.URI; import java.util.Iterator; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.ResWrap; /** @@ -74,12 +76,15 @@ public class JSONEntitySetDeserializer extends AbstractJsonDeserializer<JSONEnti if (tree.hasNonNull(jsonCount)) { entitySet.setCount(tree.get(jsonCount).asInt()); + tree.remove(jsonCount); } if (tree.hasNonNull(jsonNextLink)) { entitySet.setNext(URI.create(tree.get(jsonNextLink).textValue())); + tree.remove(jsonNextLink); } if (tree.hasNonNull(jsonDeltaLink)) { entitySet.setDeltaLink(URI.create(tree.get(jsonDeltaLink).textValue())); + tree.remove(jsonDeltaLink); } if (tree.hasNonNull(Constants.VALUE)) { @@ -89,6 +94,19 @@ public class JSONEntitySetDeserializer extends AbstractJsonDeserializer<JSONEnti new TypeReference<JSONEntityImpl>() { }).getPayload()); } + tree.remove(Constants.VALUE); + } + + // any remaining entry is supposed to be an annotation or is ignored + for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) { + final Map.Entry<String, JsonNode> field = itor.next(); + if (field.getKey().charAt(0) == '@') { + final Annotation annotation = new AnnotationImpl(); + annotation.setTerm(field.getKey().substring(1)); + + value(annotation, field.getValue(), parser.getCodec()); + entitySet.getAnnotations().add(annotation); + } } return new ResWrap<JSONEntitySetImpl>(contextURL, metadataETag, entitySet); diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java index f3195abc5..206e385c6 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.net.URI; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.ResWrap; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; @@ -78,6 +79,10 @@ public class JSONEntitySetSerializer extends AbstractJsonSerializer<JSONEntitySe } } + for (Annotation annotation : entitySet.getAnnotations()) { + valuable(jgen, annotation, "@" + annotation.getTerm()); + } + jgen.writeArrayFieldStart(Constants.VALUE); for (Entity entity : entitySet.getEntities()) { jgen.writeObject(entity); diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java index 74ae1d40c..ea2a9df55 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java @@ -21,11 +21,15 @@ package org.apache.olingo.commons.core.data; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.net.URI; +import java.util.Iterator; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.ResWrap; import org.apache.olingo.commons.core.edm.EdmTypeInfo; @@ -68,14 +72,29 @@ public class JSONPropertyDeserializer extends AbstractJsonDeserializer<JSONPrope if (tree.has(jsonType)) { property.setType(new EdmTypeInfo.Builder().setTypeExpression(tree.get(jsonType).textValue()).build().internal()); + tree.remove(jsonType); } if (tree.has(Constants.JSON_NULL) && tree.get(Constants.JSON_NULL).asBoolean()) { property.setValue(new NullValueImpl()); + tree.remove(Constants.JSON_NULL); } if (property.getValue() == null) { value(property, tree.has(Constants.VALUE) ? tree.get(Constants.VALUE) : tree, parser.getCodec()); + tree.remove(Constants.VALUE); + } + + // any remaining entry is supposed to be an annotation or is ignored + for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) { + final Map.Entry<String, JsonNode> field = itor.next(); + if (field.getKey().charAt(0) == '@') { + final Annotation annotation = new AnnotationImpl(); + annotation.setTerm(field.getKey().substring(1)); + + value(annotation, field.getValue(), parser.getCodec()); + property.getAnnotations().add(annotation); + } } return new ResWrap<JSONPropertyImpl>(contextURL, metadataETag, property); diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java index 74e8cf008..1a7b9085a 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.net.URI; import org.apache.commons.lang3.StringUtils; import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.Annotation; import org.apache.olingo.commons.api.data.ResWrap; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; @@ -40,7 +41,7 @@ public class JSONPropertySerializer extends AbstractJsonSerializer<JSONPropertyI @Override protected void doSerialize(final JSONPropertyImpl property, final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException { - + doContainerSerialize(new ResWrap<JSONPropertyImpl>((URI) null, null, property), jgen, provider); } @@ -64,6 +65,10 @@ public class JSONPropertySerializer extends AbstractJsonSerializer<JSONPropertyI new EdmTypeInfo.Builder().setTypeExpression(property.getType()).build().external(version)); } + for (Annotation annotation : property.getAnnotations()) { + valuable(jgen, annotation, "@" + annotation.getTerm()); + } + if (property.getValue().isNull()) { jgen.writeBooleanField(Constants.JSON_NULL, true); } else if (property.getValue().isPrimitive()) { @@ -76,10 +81,10 @@ public class JSONPropertySerializer extends AbstractJsonSerializer<JSONPropertyI } else if (property.getValue().isEnum()) { jgen.writeStringField(Constants.VALUE, property.getValue().asEnum().get()); } else if (property.getValue().isGeospatial() || property.getValue().isCollection()) { - property(jgen, property, Constants.VALUE); + valuable(jgen, property, Constants.VALUE); } else if (property.getValue().isComplex()) { for (Property cproperty : property.getValue().asComplex().get()) { - property(jgen, cproperty, cproperty.getName()); + valuable(jgen, cproperty, cproperty.getName()); } }