[OLINGO-264] Atom and JSON (de)serializers now dealing with instance annotations

This commit is contained in:
Francesco Chicchiriccò 2014-05-06 16:44:14 +02:00
parent 936e19ca49
commit ec9e8cabf1
21 changed files with 531 additions and 100 deletions

View File

@ -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 {

View File

@ -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=\""}

View File

@ -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"
}
}

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<entry xml:base="http://odatae2etest.azurewebsites.net/javatest/DefaultService/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://odatae2etest.azurewebsites.net/javatest/DefaultService/$metadata#Products/$entity">
<id>http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)</id>
<category term="#Microsoft.Test.OData.Services.ODataWCFService.Customer" scheme="http://docs.oasis-open.org/odata/ns/scheme"/>
<link rel="edit" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)"/>
<link rel="http://docs.oasis-open.org/odata/ns/related/Parent" type="application/atom+xml;type=entry" title="Parent" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Parent"/>
<link rel="http://docs.oasis-open.org/odata/ns/related/Orders" type="application/atom+xml;type=feed" title="Orders" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Orders">
<m:annotation term="com.contoso.display.style" m:type="#com.contoso.display.styleType">
<d:order m:type="Int32">2</d:order>
</m:annotation>
</link>
<link rel="http://docs.oasis-open.org/odata/ns/related/Company" type="application/atom+xml;type=entry" title="Company" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Company"/>
<title/>
<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>

View File

@ -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";

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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 =

View File

@ -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));
}
}
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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()));
}
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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())) {

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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());
}
}