[OLINGO-1485]Server side support for instance annotations
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java
index 9b4fc8e..babc5a0 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java
@@ -39,6 +39,7 @@
import org.apache.olingo.commons.api.IConstants;
import org.apache.olingo.commons.api.constants.Constantsv00;
import org.apache.olingo.commons.api.constants.Constantsv01;
+import org.apache.olingo.commons.api.data.Annotation;
import org.apache.olingo.commons.api.data.ComplexValue;
import org.apache.olingo.commons.api.data.DeletedEntity;
import org.apache.olingo.commons.api.data.DeletedEntity.Reason;
@@ -118,6 +119,7 @@
private final boolean isIEEE754Compatible;
private ServiceMetadata serviceMetadata;
private IConstants constants;
+ private ODataJsonInstanceAnnotationDeserializer instanceAnnotDeserializer;
public ODataJsonDeserializer(final ContentType contentType) {
this(contentType, null, new Constantsv00());
@@ -127,17 +129,20 @@
isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType);
this.serviceMetadata = serviceMetadata;
this.constants = new Constantsv00();
+ instanceAnnotDeserializer = new ODataJsonInstanceAnnotationDeserializer();
}
public ODataJsonDeserializer(ContentType contentType, ServiceMetadata serviceMetadata, IConstants constants) {
isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType);
this.serviceMetadata = serviceMetadata;
this.constants = constants;
+ instanceAnnotDeserializer = new ODataJsonInstanceAnnotationDeserializer();
}
public ODataJsonDeserializer(ContentType contentType, IConstants constants) {
isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType);
this.constants = constants;
+ instanceAnnotDeserializer = new ODataJsonInstanceAnnotationDeserializer();
}
@Override
@@ -451,6 +456,29 @@
Link bindingLink = consumeBindingLink(field.getKey(), field.getValue(), edmEntityType);
entity.getNavigationBindings().add(bindingLink);
toRemove.add(field.getKey());
+ } else if (!field.getKey().contains(ODATA_CONTROL_INFORMATION_PREFIX) &&
+ field.getKey().contains(ODATA_ANNOTATION_MARKER) &&
+ field.getKey().substring(field.getKey().indexOf(ODATA_ANNOTATION_MARKER))
+ .contains(".")) {
+ // Instance annotations start with @ sign followed by
+ // alias or namespace
+ // followed by a dot and then term name
+ String[] keySplit = field.getKey().split(ODATA_ANNOTATION_MARKER);
+ String termName = keySplit[1];
+ Annotation annotation = instanceAnnotDeserializer.consumeInstanceAnnotation(termName, field.getValue());
+ // If keySplit has a value at zeroth index then instance annotation is specified like
+ // propertyName@Term
+ if (!keySplit[0].isEmpty()) {
+ if (edmEntityType.getPropertyNames().contains(keySplit[0])) {
+ entity.getProperty(keySplit[0]).getAnnotations().add(annotation);
+ } else if (edmEntityType.getNavigationPropertyNames().contains(keySplit[0])) {
+ Link link = entity.getNavigationLink(keySplit[0]);
+ link.getAnnotations().add(annotation);
+ }
+ } else {
+ entity.getAnnotations().add(annotation);
+ }
+ toRemove.add(field.getKey());
}
}
// remove here to avoid iterator issues.
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonInstanceAnnotationDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonInstanceAnnotationDeserializer.java
new file mode 100644
index 0000000..0ac5251
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonInstanceAnnotationDeserializer.java
@@ -0,0 +1,234 @@
+/*
+ * 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.server.core.deserializer.json;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.AbstractMap.SimpleEntry;
+
+import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
+import org.apache.olingo.commons.api.data.ComplexValue;
+import org.apache.olingo.commons.api.data.Property;
+import org.apache.olingo.commons.api.data.PropertyType;
+import org.apache.olingo.commons.api.data.Valuable;
+import org.apache.olingo.commons.api.data.ValueType;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.geo.Geospatial;
+import org.apache.olingo.commons.core.edm.EdmTypeInfo;
+import org.apache.olingo.server.api.deserializer.DeserializerException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class ODataJsonInstanceAnnotationDeserializer {
+
+ private static final String ODATA_CONTROL_INFORMATION_PREFIX = "@odata.";
+
+ /**
+ * Consume the instance annotation of an entity or a property
+ * @param key String
+ * @param value JsonNode
+ * @return Annotation of an entity
+ * @throws DeserializerException
+ */
+ public Annotation consumeInstanceAnnotation(String key, JsonNode value)
+ throws DeserializerException {
+ Annotation annotation = new Annotation();
+ annotation.setTerm(key);
+ try {
+ value(annotation, value);
+ } catch (EdmPrimitiveTypeException | IOException e) {
+ throw new DeserializerException("Property: " + key,
+ DeserializerException.MessageKeys.INVALID_VALUE_FOR_PROPERTY, key);
+ }
+ return annotation;
+ }
+
+ private void value(final Valuable valuable, final JsonNode node)
+ throws IOException, EdmPrimitiveTypeException {
+
+ EdmTypeInfo typeInfo = null;
+
+ final Map.Entry<PropertyType, EdmTypeInfo> guessed = guessPropertyType(node);
+ if (typeInfo == null) {
+ typeInfo = guessed.getValue();
+ }
+
+ final PropertyType propType = typeInfo == null ? guessed.getKey()
+ : typeInfo.isCollection() ? PropertyType.COLLECTION
+ : typeInfo.isPrimitiveType() ? PropertyType.PRIMITIVE
+ : node.isValueNode() ? PropertyType.ENUM : PropertyType.COMPLEX;
+
+ switch (propType) {
+ case COLLECTION:
+ fromCollection(valuable, node.elements(), typeInfo);
+ break;
+
+ case COMPLEX:
+ if (node.has(Constants.JSON_TYPE)) {
+ valuable.setType(node.get(Constants.JSON_TYPE).asText());
+ ((ObjectNode) node).remove(Constants.JSON_TYPE);
+ }
+ final Object value = fromComplex((ObjectNode) node);
+ if (value instanceof ComplexValue) {
+ ((ComplexValue) value).setTypeName(valuable.getType());
+ }
+ valuable.setValue(ValueType.COMPLEX, value);
+ break;
+
+ case ENUM:
+ if (!node.isNull()) {
+ valuable.setValue(ValueType.ENUM, node.asText());
+ }
+ break;
+
+ case PRIMITIVE:
+ if (valuable.getType() == null && typeInfo != null) {
+ valuable.setType(typeInfo.getFullQualifiedName().toString());
+ }
+ final Object primitiveValue = fromPrimitive(node, typeInfo);
+ valuable.setValue(primitiveValue instanceof Geospatial ?
+ ValueType.GEOSPATIAL : ValueType.PRIMITIVE,
+ primitiveValue);
+ break;
+
+ case EMPTY:
+ default:
+ valuable.setValue(ValueType.PRIMITIVE, "");
+ }
+ }
+
+ private Object fromPrimitive(final JsonNode node, final EdmTypeInfo typeInfo)
+ throws EdmPrimitiveTypeException {
+ return node.isNull() ? null
+ : typeInfo == null ? node.asText()
+ : ((EdmPrimitiveType) typeInfo.getType()).valueOfString(node.asText(), true, null,
+ Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, true,
+ ((EdmPrimitiveType) typeInfo.getType()).getDefaultType());
+ }
+
+ private Object fromComplex(final ObjectNode node) throws IOException, EdmPrimitiveTypeException {
+
+ final ComplexValue complexValue = new ComplexValue();
+ final Set<String> toRemove = new HashSet<>();
+ for (final Iterator<Map.Entry<String, JsonNode>> itor = node.fields(); itor.hasNext();) {
+ final Map.Entry<String, JsonNode> field = itor.next();
+ if (field.getKey().contains(ODATA_CONTROL_INFORMATION_PREFIX)) {
+ toRemove.add(field.getKey());
+ } else {
+ Property property = new Property();
+ property.setName(field.getKey());
+ value(property, field.getValue());
+ complexValue.getValue().add(property);
+ }
+ }
+ node.remove(toRemove);
+ return complexValue;
+ }
+
+ private void fromCollection(final Valuable valuable, final Iterator<JsonNode> nodeItor,
+ final EdmTypeInfo typeInfo)
+ throws IOException, EdmPrimitiveTypeException {
+
+ final List<Object> values = new ArrayList<>();
+ ValueType valueType = ValueType.COLLECTION_PRIMITIVE;
+
+ final EdmTypeInfo type = typeInfo == null ? null
+ : new EdmTypeInfo.Builder().setTypeExpression(
+ typeInfo.getFullQualifiedName().toString()).build();
+
+ while (nodeItor.hasNext()) {
+ final JsonNode child = nodeItor.next();
+
+ if (child.isValueNode()) {
+ if (typeInfo == null || typeInfo.isPrimitiveType()) {
+ final Object value = fromPrimitive(child, type);
+ valueType = value instanceof Geospatial ? ValueType.COLLECTION_GEOSPATIAL
+ : ValueType.COLLECTION_PRIMITIVE;
+ values.add(value);
+ } else {
+ valueType = ValueType.COLLECTION_ENUM;
+ values.add(child.asText());
+ }
+ } else if (child.isContainerNode()) {
+ EdmTypeInfo childType = null;
+ if (child.has(Constants.JSON_TYPE)) {
+ String typeName = child.get(Constants.JSON_TYPE).asText();
+ childType = typeName == null ? null : new EdmTypeInfo.Builder()
+ .setTypeExpression(typeName).build();
+ ((ObjectNode) child).remove(Constants.JSON_TYPE);
+ }
+ final Object value = fromComplex((ObjectNode) child);
+ if (childType != null) {
+ ((ComplexValue) value).setTypeName(childType.external());
+ }
+ valueType = ValueType.COLLECTION_COMPLEX;
+ values.add(value);
+ }
+ }
+ valuable.setValue(valueType, values);
+ }
+
+ private Map.Entry<PropertyType, EdmTypeInfo> guessPropertyType(final JsonNode node) {
+ PropertyType type;
+ String typeExpression = null;
+
+ if (node.isValueNode() || node.isNull()) {
+ type = PropertyType.PRIMITIVE;
+ typeExpression = guessPrimitiveTypeKind(node).getFullQualifiedName().toString();
+ } else if (node.isArray()) {
+ type = PropertyType.COLLECTION;
+ if (node.has(0) && node.get(0).isValueNode()) {
+ typeExpression = "Collection(" + guessPrimitiveTypeKind(node.get(0)) + ')';
+ }
+ } else if (node.isObject()) {
+ if (node.has(Constants.ATTR_TYPE)) {
+ type = PropertyType.PRIMITIVE;
+ typeExpression = "Edm.Geography" + node.get(Constants.ATTR_TYPE).asText();
+ } else {
+ type = PropertyType.COMPLEX;
+ }
+ } else {
+ type = PropertyType.EMPTY;
+ }
+
+ final EdmTypeInfo typeInfo = typeExpression == null ? null
+ : new EdmTypeInfo.Builder().setTypeExpression(typeExpression).build();
+ return new SimpleEntry<>(type, typeInfo);
+ }
+
+ private EdmPrimitiveTypeKind guessPrimitiveTypeKind(final JsonNode node) {
+ return node.isShort() ? EdmPrimitiveTypeKind.Int16
+ : node.isInt() ? EdmPrimitiveTypeKind.Int32
+ : node.isLong() ? EdmPrimitiveTypeKind.Int64
+ : node.isBoolean() ? EdmPrimitiveTypeKind.Boolean
+ : node.isFloat() ? EdmPrimitiveTypeKind.Single
+ : node.isDouble() ? EdmPrimitiveTypeKind.Double
+ : node.isBigDecimal() ? EdmPrimitiveTypeKind.Decimal
+ : EdmPrimitiveTypeKind.String;
+ }
+}
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonInstanceAnnotationSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonInstanceAnnotationSerializer.java
new file mode 100644
index 0000000..d197f99
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonInstanceAnnotationSerializer.java
@@ -0,0 +1,230 @@
+/*
+ * 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.server.core.serializer.json;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.IConstants;
+import org.apache.olingo.commons.api.data.Annotation;
+import org.apache.olingo.commons.api.data.ComplexValue;
+import org.apache.olingo.commons.api.data.Link;
+import org.apache.olingo.commons.api.data.Property;
+import org.apache.olingo.commons.api.data.Valuable;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.EdmProperty;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory;
+import org.apache.olingo.server.api.serializer.SerializerException;
+import org.apache.olingo.server.core.serializer.utils.ContentTypeHelper;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+
+public class ODataJsonInstanceAnnotationSerializer {
+
+ private final boolean isODataMetadataNone;
+ private final boolean isODataMetadataFull;
+ private IConstants constants;
+ private final boolean isIEEE754Compatible;
+
+ public ODataJsonInstanceAnnotationSerializer(final ContentType contentType, final IConstants constants) {
+ isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType);
+ isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType);
+ isODataMetadataFull = ContentTypeHelper.isODataMetadataFull(contentType);
+ this.constants = constants;
+ }
+
+ /**
+ * Write the instance annotation of an entity
+ * @param annotations List of annotations
+ * @param json JsonGenerator
+ * @throws IOException
+ * @throws SerializerException
+ * @throws DecoderException
+ */
+ public void writeInstanceAnnotationsOnEntity(final List<Annotation> annotations, final JsonGenerator json)
+ throws IOException, SerializerException, DecoderException {
+ for (Annotation annotation : annotations) {
+ if (isODataMetadataFull) {
+ json.writeStringField(constants.getType(), "#" + annotation.getType());
+ }
+ json.writeFieldName("@" + annotation.getTerm());
+ writeInstanceAnnotation(json, annotation, "");
+ }
+ }
+
+ /**
+ * Write instance annotation of a property
+ * @param edmProperty EdmProperty
+ * @param property Property
+ * @param json JsonGenerator
+ * @throws IOException
+ * @throws SerializerException
+ * @throws DecoderException
+ */
+ public void writeInstanceAnnotationsOnProperties(final EdmProperty edmProperty, final Property property,
+ final JsonGenerator json) throws IOException, SerializerException, DecoderException {
+ if (property != null) {
+ for (Annotation annotation : property.getAnnotations()) {
+ json.writeFieldName(edmProperty.getName() + "@" + annotation.getTerm());
+ writeInstanceAnnotation(json, annotation, "");
+ }
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private void writeInstanceAnnotation(final JsonGenerator json, Valuable annotation, String name)
+ throws IOException, SerializerException, DecoderException {
+ try {
+ switch (annotation.getValueType()) {
+ case PRIMITIVE:
+ if (isODataMetadataFull && name.length() > 0) {
+ json.writeStringField(name + constants.getType(), "#" + annotation.getType());
+ }
+ if (name.length() > 0) {
+ json.writeFieldName(name);
+ }
+ writeInstanceAnnotOnPrimitiveProperty(json, annotation, annotation.getValue());
+ break;
+ case COLLECTION_PRIMITIVE:
+ if (isODataMetadataFull && name.length() > 0) {
+ json.writeStringField(name + constants.getType(),
+ "#Collection(" + annotation.getType() + ")");
+ }
+ if (name.length() > 0) {
+ json.writeFieldName(name);
+ }
+ json.writeStartArray();
+ List list = annotation.asCollection();
+ for (Object value : list) {
+ writeInstanceAnnotOnPrimitiveProperty(json, annotation, value);
+ }
+ json.writeEndArray();
+ break;
+ case COMPLEX:
+ if (isODataMetadataFull && name.length() > 0) {
+ json.writeStringField(name + constants.getType(), "#" + annotation.getType());
+ }
+ if (name.length() > 0) {
+ json.writeFieldName(name);
+ }
+ ComplexValue complexValue = annotation.asComplex();
+ writeInstanceAnnotOnComplexProperty(json, annotation, complexValue);
+ break;
+ case COLLECTION_COMPLEX:
+ if (isODataMetadataFull && name.length() > 0) {
+ json.writeStringField(name + constants.getType(),
+ "#Collection(" + annotation.getType() + ")");
+ }
+ if (name.length() > 0) {
+ json.writeFieldName(name);
+ }
+ json.writeStartArray();
+ List<ComplexValue> complexValues = (List<ComplexValue>) annotation.asCollection();
+ for (ComplexValue complxValue : complexValues) {
+ writeInstanceAnnotOnComplexProperty(json, annotation, complxValue);
+ }
+ json.writeEndArray();
+ break;
+ default:
+ }
+ } catch (final EdmPrimitiveTypeException e) {
+ throw new SerializerException("Wrong value for instance annotation!", e,
+ SerializerException.MessageKeys.WRONG_PROPERTY_VALUE,
+ ((Annotation) annotation).getTerm(),
+ annotation.getValue().toString());
+ }
+ }
+
+ private void writeInstanceAnnotOnComplexProperty(final JsonGenerator json, Valuable annotation,
+ ComplexValue complexValue) throws IOException, SerializerException, DecoderException {
+ json.writeStartObject();
+ if (isODataMetadataFull) {
+ json.writeStringField(constants.getType(), "#" + complexValue.getTypeName());
+ }
+ List<Property> properties = complexValue.getValue();
+ for (Property prop : properties) {
+ writeInstanceAnnotation(json, prop, prop.getName());
+ }
+ json.writeEndObject();
+ }
+
+ private void writeInstanceAnnotOnPrimitiveProperty(final JsonGenerator json, Valuable annotation, Object value)
+ throws IOException, EdmPrimitiveTypeException {
+ writePrimitiveValue("",
+ EdmPrimitiveTypeFactory.getInstance(
+ EdmPrimitiveTypeKind.getByName(annotation.getType())), value, null,
+ null, null, null, true, json);
+ }
+
+ protected void writePrimitiveValue(final String name, final EdmPrimitiveType type, final Object primitiveValue,
+ final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale,
+ final Boolean isUnicode, final JsonGenerator json)
+ throws EdmPrimitiveTypeException, IOException {
+ final String value = type.valueToString(
+ primitiveValue, isNullable, maxLength, precision, scale, isUnicode);
+ if (value == null) {
+ json.writeNull();
+ } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)) {
+ json.writeBoolean(Boolean.parseBoolean(value));
+ } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Byte)
+ || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double)
+ || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16)
+ || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32)
+ || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.SByte)
+ || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Single)
+ || (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)
+ || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64))
+ && !isIEEE754Compatible) {
+ json.writeNumber(value);
+ } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Stream)) {
+ if (primitiveValue instanceof Link) {
+ Link stream = (Link) primitiveValue;
+ if (!isODataMetadataNone) {
+ if (stream.getMediaETag() != null) {
+ json.writeStringField(name + constants.getMediaEtag(),
+ stream.getMediaETag());
+ }
+ if (stream.getType() != null) {
+ json.writeStringField(name + constants.getMediaContentType(),
+ stream.getType());
+ }
+ }
+ if (isODataMetadataFull) {
+ if (stream.getRel() != null &&
+ stream.getRel().equals(Constants.NS_MEDIA_READ_LINK_REL)) {
+ json.writeStringField(name + constants.getMediaReadLink(),
+ stream.getHref());
+ }
+ if (stream.getRel() == null ||
+ stream.getRel().equals(Constants.NS_MEDIA_EDIT_LINK_REL)) {
+ json.writeStringField(name + constants.getMediaEditLink(),
+ stream.getHref());
+ }
+ }
+ }
+ } else {
+ json.writeString(value);
+ }
+ }
+}
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java
index e9fdfd7..ec75da0 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java
@@ -115,12 +115,14 @@
private final boolean isODataMetadataNone;
private final boolean isODataMetadataFull;
private IConstants constants;
+ private ODataJsonInstanceAnnotationSerializer instanceAnnotSerializer;
public ODataJsonSerializer(final ContentType contentType, final IConstants constants) {
isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType);
isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType);
isODataMetadataFull = ContentTypeHelper.isODataMetadataFull(contentType);
this.constants = constants;
+ instanceAnnotSerializer = new ODataJsonInstanceAnnotationSerializer(contentType, constants);
}
public ODataJsonSerializer(final ContentType contentType) {
@@ -128,6 +130,7 @@
isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType);
isODataMetadataFull = ContentTypeHelper.isODataMetadataFull(contentType);
this.constants = new Constantsv00();
+ instanceAnnotSerializer = new ODataJsonInstanceAnnotationSerializer(contentType, constants);
}
@Override
@@ -431,7 +434,7 @@
json.writeStringField(constants.getEditLink(), entity.getEditLink().getHref());
}
}
-
+ instanceAnnotSerializer.writeInstanceAnnotationsOnEntity(entity.getAnnotations(), json);
writeProperties(metadata, resolvedType, entity.getProperties(), select, json, entity, expand);
writeNavigationProperties(metadata, resolvedType, entity, expand, toDepth, ancestors, name, json);
writeOperations(entity.getOperations(), json);
@@ -507,7 +510,7 @@
protected void writeProperties(final ServiceMetadata metadata, final EdmStructuredType type,
final List<Property> properties,
final SelectOption select, final JsonGenerator json, Linked linked, ExpandOption expand)
- throws IOException, SerializerException {
+ throws IOException, SerializerException, DecoderException {
final boolean all = ExpandSelectHelper.isAll(select);
final Set<String> selected = all ? new HashSet<>() :
ExpandSelectHelper.getSelectedPropertyNames(select.getSelectItems());
@@ -679,7 +682,9 @@
final EdmProperty edmProperty, final Property property,
final Set<List<String>> selectedPaths, final JsonGenerator json,
Set<List<String>> expandedPaths, Linked linked, ExpandOption expand)
- throws IOException, SerializerException {
+ throws IOException, SerializerException, DecoderException {
+
+ instanceAnnotSerializer.writeInstanceAnnotationsOnProperties(edmProperty, property, json);
boolean isStreamProperty = isStreamProperty(edmProperty);
writePropertyType(edmProperty, json);
if (!isStreamProperty) {
@@ -745,7 +750,7 @@
private void writePropertyValue(final ServiceMetadata metadata, final EdmProperty edmProperty,
final Property property, final Set<List<String>> selectedPaths, final JsonGenerator json,
Set<List<String>> expandedPaths, Linked linked, ExpandOption expand)
- throws IOException, SerializerException {
+ throws IOException, SerializerException, DecoderException {
final EdmType type = edmProperty.getType();
try {
if (edmProperty.isPrimitive()
@@ -790,7 +795,7 @@
private void writeComplex(final ServiceMetadata metadata, final EdmComplexType type,
final Property property, final Set<List<String>> selectedPaths, final JsonGenerator json,
Set<List<String>> expandedPaths, Linked linked, ExpandOption expand)
- throws IOException, SerializerException{
+ throws IOException, SerializerException, DecoderException{
json.writeStartObject();
String derivedName = property.getType();
EdmComplexType resolvedType = null;
@@ -864,7 +869,7 @@
final Property property,
final Set<List<String>> selectedPaths, final JsonGenerator json,
Set<List<String>> expandedPaths, Linked linked, ExpandOption expand)
- throws IOException, SerializerException {
+ throws IOException, SerializerException, DecoderException {
json.writeStartArray();
EdmComplexType derivedType = type;
Set<List<String>> expandedPaths1 = expandedPaths != null && !expandedPaths.isEmpty() ?
@@ -1063,7 +1068,7 @@
final EdmComplexType type, final List<Property> properties,
final Set<List<String>> selectedPaths, final JsonGenerator json,
Set<List<String>> expandedPaths, Linked linked, ExpandOption expand, String complexPropName)
- throws IOException, SerializerException {
+ throws IOException, SerializerException, DecoderException {
if (null != expandedPaths) {
for(List<String> paths : expandedPaths) {
@@ -1237,7 +1242,8 @@
@Override
public SerializerResult complexCollection(final ServiceMetadata metadata, final EdmComplexType type,
- final Property property, final ComplexSerializerOptions options) throws SerializerException {
+ final Property property, final ComplexSerializerOptions options)
+ throws SerializerException {
OutputStream outputStream = null;
SerializerException cachedException = null;
@@ -1270,7 +1276,7 @@
json.close();
return SerializerResultImpl.with().content(buffer.getInputStream()).build();
- } catch (final IOException e) {
+ } catch (final IOException | DecoderException e) {
cachedException =
new SerializerException(IO_EXCEPTION_TEXT, e, SerializerException.MessageKeys.IO_EXCEPTION);
throw cachedException;
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java
index e684c0f..d673af5 100644
--- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java
@@ -21,13 +21,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
import java.io.IOException;
+import java.util.List;
import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.Link;
+import org.apache.olingo.commons.api.data.ValueType;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.server.api.deserializer.DeserializerException;
import org.apache.olingo.server.api.deserializer.DeserializerResult;
@@ -126,22 +128,24 @@
@Test
public void esAllPrimExpandedToOneWithCustomAnnotations() throws Exception {
- try {
- deserialize("EntityESAllPrimExpandedNavPropertyETTwoPrimOneWithCustomAnnotations.json");
- fail("Expected exception not thrown.");
- } catch (final DeserializerException e) {
- assertEquals(DeserializerException.MessageKeys.NOT_IMPLEMENTED, e.getMessageKey());
- }
+ Entity entity = deserialize("EntityESAllPrimExpandedNavPropertyETTwoPrimOneWithCustomAnnotations.json");
+ assertNotNull(entity);
+ List<Annotation> annotations = entity.getNavigationLink("NavPropertyETTwoPrimOne").getAnnotations();
+ assertEquals(1, annotations.size());
+ assertEquals("custom.annotation", annotations.get(0).getTerm());
+ assertEquals("customValue", annotations.get(0).getValue());
+ assertEquals(ValueType.PRIMITIVE, annotations.get(0).getValueType());
}
@Test
public void esAllPrimExpandedToManyWithCustomAnnotations() throws Exception {
- try {
- deserialize("EntityESAllPrimExpandedNavPropertyETTwoPrimManyWithCustomAnnotations.json");
- fail("Expected exception not thrown.");
- } catch (final DeserializerException e) {
- assertEquals(DeserializerException.MessageKeys.NOT_IMPLEMENTED, e.getMessageKey());
- }
+ Entity entity = deserialize("EntityESAllPrimExpandedNavPropertyETTwoPrimManyWithCustomAnnotations.json");
+ assertNotNull(entity);
+ List<Annotation> annotations = entity.getNavigationLink("NavPropertyETTwoPrimMany").getAnnotations();
+ assertEquals(1, annotations.size());
+ assertEquals("custom.annotation", annotations.get(0).getTerm());
+ assertEquals("customValue", annotations.get(0).getValue());
+ assertEquals(ValueType.PRIMITIVE, annotations.get(0).getValueType());
}
@Test
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java
index 25ce502..9de9b10 100644
--- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java
@@ -37,6 +37,7 @@
import java.util.List;
import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
import org.apache.olingo.commons.api.data.ComplexValue;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.Link;
@@ -1269,8 +1270,13 @@
+ "{\"PropertyInt16\":123,\"PropertyString\":\"TEST 1\"},"
+ "{\"PropertyInt16\":456,\"PropertyString\":\"TEST 2\"},"
+ "{\"PropertyInt16\":789,\"PropertyString\":\"TEST 3\"}]}";
- expectException(entityString, "ETMixPrimCollComp",
- DeserializerException.MessageKeys.NOT_IMPLEMENTED);
+ Entity entity = deserialize(entityString, "ETMixPrimCollComp", ContentType.APPLICATION_JSON);
+ assertNotNull(entity);
+ List<Annotation> annotations = entity.getProperty("CollPropertyString").getAnnotations();
+ assertEquals(1, annotations.size());
+ assertEquals("custom.annotation", annotations.get(0).getTerm());
+ assertEquals(12, annotations.get(0).getValue());
+ assertEquals(ValueType.PRIMITIVE, annotations.get(0).getValueType());
}
@Test
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerWithInstanceAnnotationsTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerWithInstanceAnnotationsTest.java
new file mode 100644
index 0000000..ab4b2eb
--- /dev/null
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerWithInstanceAnnotationsTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.server.core.deserializer.json;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.data.Annotation;
+import org.apache.olingo.commons.api.data.ComplexValue;
+import org.apache.olingo.commons.api.data.Entity;
+import org.apache.olingo.commons.api.data.Property;
+import org.apache.olingo.commons.api.data.ValueType;
+import org.apache.olingo.commons.api.edm.EdmEntityType;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.deserializer.DeserializerException;
+import org.apache.olingo.server.api.deserializer.DeserializerResult;
+import org.apache.olingo.server.core.deserializer.AbstractODataDeserializerTest;
+import org.junit.Test;
+
+public class ODataJsonDeserializerWithInstanceAnnotationsTest extends AbstractODataDeserializerTest {
+
+ private static final ContentType CONTENT_TYPE_JSON_IEEE754Compatible =
+ ContentType.create(ContentType.JSON, ContentType.PARAMETER_IEEE754_COMPATIBLE, "true");
+ private static final OData odata = OData.newInstance();
+
+ @Test
+ public void instanceAnnotOnEntity() throws Exception {
+ final String entityString = "{"
+ + "\"@context\":\"$metadata#ESAllPrim/$entity\","
+ + "\"@metadataEtag\":\"W/\\\"metadataETag\\\"\","
+ + "\"@com.contoso.display.highlight\":true,"
+ + "\"@com.contoso.PersonalInfo.PhoneNumbers\":"
+ + "[\"(203)555-1718\",\"(203)555-1719\"],"
+ + "\"PropertyInt16\":32767,"
+ + "\"NavPropertyETTwoPrimOne@bind\": \"ETTwoPrim(1)\","
+ + "\"PropertyString\":\"First Resource - positive values\","
+ + "\"PropertyBoolean\":true,"
+ + "\"PropertyByte\":255,"
+ + "\"PropertySByte\":127,"
+ + "\"PropertyInt32\":2147483647,"
+ + "\"PropertyInt64\":9223372036854775807,"
+ + "\"PropertySingle\":1.79E20,"
+ + "\"PropertyDouble\":-1.79E19,"
+ + "\"PropertyDecimal\":34,"
+ + "\"PropertyBinary\":\"ASNFZ4mrze8=\","
+ + "\"PropertyDate\":\"2012-12-03\","
+ + "\"PropertyDateTimeOffset\":\"2012-12-03T07:16:23Z\","
+ + "\"PropertyDuration\":\"PT6S\","
+ + "\"PropertyGuid\":\"01234567-89ab-cdef-0123-456789abcdef\","
+ + "\"PropertyTimeOfDay\":\"03:26:05\""
+ + "}";
+ final Entity entity = deserializeWithResultWithConstantV401(
+ new ByteArrayInputStream(entityString.getBytes()),
+ "ETAllPrim", ContentType.APPLICATION_JSON).getEntity();
+ assertNotNull(entity);
+ List<Annotation> annotations = entity.getAnnotations();
+ assertEquals(2, annotations.size());
+ assertEquals("com.contoso.display.highlight", annotations.get(0).getTerm());
+ assertTrue((Boolean)annotations.get(0).getValue());
+ assertEquals(ValueType.PRIMITIVE, annotations.get(0).getValueType());
+ assertEquals("com.contoso.PersonalInfo.PhoneNumbers", annotations.get(1).getTerm());
+ assertEquals(ValueType.COLLECTION_PRIMITIVE, annotations.get(1).getValueType());
+ assertEquals(2, annotations.get(1).asCollection().size());
+ assertEquals(1, entity.getNavigationBindings().size());
+ assertEquals("NavPropertyETTwoPrimOne", entity
+ .getNavigationBinding("NavPropertyETTwoPrimOne").getTitle());
+ }
+
+ @Test
+ public void instanceAnnotOnEntityProperty() throws Exception {
+ final String entityString = "{"
+ + "\"@odata.context\":\"$metadata#ESAllPrim/$entity\","
+ + "\"@odata.metadataEtag\":\"W/\\\"metadataETag\\\"\","
+ + "\"@odata.type\":\"#olingo.odata.test1.ETAllPrim\","
+ + "\"@odata.id\":\"ESAllPrim(32767)\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":32767,"
+ + "\"PropertyString@com.contoso.display.style\":{"
+ + "\"@odata.type\":\"#com.contoso.display.styleType\","
+ + "\"title@odata.type\":\"#Boolean\","
+ + "\"title\":true,"
+ + "\"Order@odata.type\":\"#Int16\","
+ + "\"Order\":1"
+ + "},"
+ + "\"PropertyString\":\"First Resource - positive values\","
+ + "\"PropertyBoolean\":true,"
+ + "\"PropertyByte@odata.type\":\"#Byte\","
+ + "\"PropertyByte\":255,"
+ + "\"PropertySByte@odata.type\":\"#SByte\","
+ + "\"PropertySByte\":127,"
+ + "\"PropertyInt32@odata.type\":\"#Int32\","
+ + "\"PropertyInt32\":2147483647,"
+ + "\"PropertyInt64@odata.type\":\"#Int64\","
+ + "\"PropertyInt64\":9223372036854775807,"
+ + "\"PropertySingle@odata.type\":\"#Single\","
+ + "\"PropertySingle\":1.79E20,"
+ + "\"PropertyDouble\":-1.79E19,"
+ + "\"PropertyDecimal@odata.type\":\"#Decimal\","
+ + "\"PropertyDecimal\":34,"
+ + "\"PropertyBinary@odata.type\":\"#Binary\","
+ + "\"PropertyBinary\":\"ASNFZ4mrze8=\","
+ + "\"PropertyDate@odata.type\":\"#Date\","
+ + "\"PropertyDate\":\"2012-12-03\","
+ + "\"PropertyDateTimeOffset@odata.type\":\"#DateTimeOffset\","
+ + "\"PropertyDateTimeOffset\":\"2012-12-03T07:16:23Z\","
+ + "\"PropertyDuration@odata.type\":\"#Duration\","
+ + "\"PropertyDuration\":\"PT6S\","
+ + "\"PropertyGuid@odata.type\":\"#Guid\","
+ + "\"PropertyGuid\":\"01234567-89ab-cdef-0123-456789abcdef\","
+ + "\"PropertyTimeOfDay@odata.type\":\"#TimeOfDay\","
+ + "\"PropertyTimeOfDay\":\"03:26:05\","
+ + "\"NavPropertyETTwoPrimOne@odata.navigationLink\":\"ESTwoPrim(32767)\","
+ + "\"NavPropertyETTwoPrimMany@odata.navigationLink\":"
+ + "\"ESAllPrim(32767)/NavPropertyETTwoPrimMany\""
+ +"}";
+ final Entity entity = deserialize(entityString, "ETAllPrim");
+ assertNotNull(entity);
+ Property property = entity.getProperties().get(1);
+ List<Annotation> annotations = property.getAnnotations();
+ assertEquals(1, annotations.size());
+ assertEquals("com.contoso.display.style", annotations.get(0).getTerm());
+ assertEquals(ValueType.COMPLEX, annotations.get(0).getValueType());
+ ComplexValue value = annotations.get(0).asComplex();
+ assertEquals("#com.contoso.display.styleType", value.getTypeName());
+ List<Property> complxProperties = value.getValue();
+ assertEquals(2, complxProperties.size());
+ assertEquals("title", complxProperties.get(0).getName());
+ assertTrue((Boolean)complxProperties.get(0).getValue());
+ assertEquals("Order", complxProperties.get(1).getName());
+ assertEquals(1, complxProperties.get(1).getValue());
+ }
+
+ @Test
+ public void instanceAnnotOnComplexProperty() throws Exception {
+ final String entityString = "{"
+ + "\"@odata.context\":\"$metadata#ESMixPrimCollComp/$entity\","
+ + "\"@odata.metadataEtag\":\"W/\\\"metadataETag\\\"\","
+ + "\"@odata.type\":\"#olingo.odata.test1.ETMixPrimCollComp\","
+ + "\"@odata.id\":\"ESMixPrimCollComp(32767)\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":32767,"
+ + "\"CollPropertyString@odata.type\":\"#Collection(String)\","
+ + "\"CollPropertyString\":[\"Employee1@company.example\","
+ + "\"Employee2@company.example\",\"Employee3@company.example\"],"
+ + "\"PropertyComp@com.contoso.display.style\":{"
+ + "\"@odata.type\":\"#com.contoso.display.styleType\","
+ + "\"title@odata.type\":\"#Boolean\","
+ + "\"title\":true,"
+ + "\"Order@odata.type\":\"#Collection(Order)\","
+ + "\"Order\":[{"
+ + "\"@odata.type\":\"#com.contoso.display.orderDetails\""
+ + "},{"
+ + "\"@odata.type\":\"#com.contoso.display.orderDetails\","
+ + "\"name@odata.type\":\"#String\","
+ + "\"name\":\"Cars\","
+ + "\"brand@odata.type\":\"#String\","
+ + "\"brand\":\"BMW\""
+ + "}]"
+ + "},"
+ + "\"PropertyComp\":{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":111,"
+ + "\"PropertyString\":\"TEST A\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='1')\""
+ + "},"
+ + "\"CollPropertyComp@odata.type\":\"#Collection(olingo.odata.test1.CTTwoPrim)\","
+ + "\"CollPropertyComp\":[{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":123,"
+ + "\"PropertyString\":\"TEST 1\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='2')\""
+ + "},{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":456,"
+ + "\"PropertyString\":\"TEST 2\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='2')\""
+ + "},{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTBase\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":789,"
+ + "\"PropertyString\":\"TEST 3\","
+ + "\"AdditionalPropString\":\"ADD TEST\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='2')\""
+ + "}]"
+ +"}";
+ final Entity entity = deserialize(entityString, "ETMixPrimCollComp");
+ assertNotNull(entity);
+ Property property = entity.getProperties().get(2);
+ List<Annotation> annotations = property.getAnnotations();
+ assertEquals(1, annotations.size());
+ assertEquals("com.contoso.display.style", annotations.get(0).getTerm());
+ assertEquals(ValueType.COMPLEX, annotations.get(0).getValueType());
+ ComplexValue value = annotations.get(0).asComplex();
+ assertEquals("#com.contoso.display.styleType", value.getTypeName());
+ List<Property> complxProperties = value.getValue();
+ assertEquals(2, complxProperties.size());
+ assertEquals("title", complxProperties.get(0).getName());
+ assertTrue((Boolean)complxProperties.get(0).getValue());
+ assertEquals("Order", complxProperties.get(1).getName());
+ assertEquals(ValueType.COLLECTION_COMPLEX, complxProperties.get(1).getValueType());
+ assertEquals(2, complxProperties.get(1).asCollection().size());
+ }
+
+ protected static DeserializerResult deserializeWithResultWithConstantV401(final InputStream stream,
+ final String entityTypeName, final ContentType contentType)
+ throws DeserializerException {
+ final EdmEntityType entityType = edm.getEntityType(new FullQualifiedName(NAMESPACE, entityTypeName));
+ List<String> odataVersions = new ArrayList<>();
+ odataVersions.add("4.01");
+ return odata.createDeserializer(contentType, metadata, odataVersions).entity(stream, entityType);
+ }
+
+ protected static Entity deserialize(final InputStream stream, final String entityTypeName,
+ final ContentType contentType) throws DeserializerException {
+ return deserializeWithResult(stream, entityTypeName, contentType).getEntity();
+ }
+
+ protected static DeserializerResult deserializeWithResult(final InputStream stream,
+ final String entityTypeName, final ContentType contentType)
+ throws DeserializerException {
+ final EdmEntityType entityType = edm.getEntityType(new FullQualifiedName(NAMESPACE, entityTypeName));
+ return deserializeWithResult(stream, entityType, contentType);
+ }
+
+ protected static DeserializerResult deserializeWithResult(final InputStream stream,
+ final EdmEntityType entityType, final ContentType contentType)
+ throws DeserializerException {
+ return odata.createDeserializer(contentType, metadata).entity(stream, entityType);
+ }
+
+ private static Entity deserialize(final String entityString, final String entityTypeName,
+ final ContentType contentType) throws DeserializerException {
+ return deserialize(new ByteArrayInputStream(entityString.getBytes()), entityTypeName, contentType);
+ }
+
+ protected static Entity deserialize(final String entityString, final String entityTypeName)
+ throws DeserializerException {
+ return deserialize(entityString, entityTypeName, ContentType.JSON);
+ }
+}
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerWithInstanceAnnotationsTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerWithInstanceAnnotationsTest.java
new file mode 100644
index 0000000..11c650a
--- /dev/null
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerWithInstanceAnnotationsTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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.server.core.serializer.json;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.olingo.commons.api.data.Annotation;
+import org.apache.olingo.commons.api.data.ComplexValue;
+import org.apache.olingo.commons.api.data.ContextURL;
+import org.apache.olingo.commons.api.data.ContextURL.Suffix;
+import org.apache.olingo.commons.api.data.Entity;
+import org.apache.olingo.commons.api.data.Property;
+import org.apache.olingo.commons.api.data.ValueType;
+import org.apache.olingo.commons.api.edm.EdmEntityContainer;
+import org.apache.olingo.commons.api.edm.EdmEntitySet;
+import org.apache.olingo.commons.api.edmx.EdmxReference;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.serializer.EntitySerializerOptions;
+import org.apache.olingo.server.api.serializer.ODataSerializer;
+import org.apache.olingo.server.api.uri.UriHelper;
+import org.apache.olingo.server.tecsvc.MetadataETagSupport;
+import org.apache.olingo.server.tecsvc.data.DataProvider;
+import org.apache.olingo.server.tecsvc.provider.EdmTechProvider;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ODataJsonSerializerWithInstanceAnnotationsTest {
+ private static final OData odata = OData.newInstance();
+ private static final ServiceMetadata metadata = odata.createServiceMetadata(
+ new EdmTechProvider(), Collections.<EdmxReference> emptyList(), new MetadataETagSupport("W/\"metadataETag\""));
+ private static final EdmEntityContainer entityContainer = metadata.getEdm().getEntityContainer();
+ private final DataProvider data = new DataProvider(odata, metadata.getEdm());
+ private final ODataSerializer serializer = new ODataJsonSerializer(ContentType.JSON);
+ private final ODataSerializer serializerNoMetadata = new ODataJsonSerializer(ContentType.JSON_NO_METADATA);
+ private final ODataSerializer serializerFullMetadata = new ODataJsonSerializer(ContentType.JSON_FULL_METADATA);
+ private final ODataSerializer serializerIEEECompatible =
+ new ODataJsonSerializer(ContentType.create(ContentType.JSON, ContentType.PARAMETER_IEEE754_COMPATIBLE, "true"));
+ private final UriHelper helper = odata.createUriHelper();
+
+ @Test
+ public void entityWithInstanceAnnotations() throws Exception {
+ final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim");
+ final Entity entity = data.readAll(edmEntitySet).getEntities().get(0);
+ Annotation annotation = new Annotation();
+ annotation.setTerm("com.contoso.display.highlight");
+ annotation.setType("Boolean");
+ annotation.setValue(ValueType.PRIMITIVE, true);
+ entity.getAnnotations().add(annotation);
+ annotation = new Annotation();
+ annotation.setTerm("com.contoso.PersonalInfo.PhoneNumbers");
+ annotation.setType("String");
+ annotation.setValue(ValueType.COLLECTION_PRIMITIVE, Arrays.asList("(203)555-1718", "(203)555-1719"));
+ entity.getAnnotations().add(annotation);
+ InputStream result = serializer.entity(metadata, edmEntitySet.getEntityType(), entity,
+ EntitySerializerOptions.with()
+ .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build())
+ .build()).getContent();
+ final String resultString = IOUtils.toString(result);
+ final String expectedResult = "{"
+ + "\"@odata.context\":\"$metadata#ESAllPrim/$entity\","
+ + "\"@odata.metadataEtag\":\"W/\\\"metadataETag\\\"\","
+ + "\"@com.contoso.display.highlight\":true,"
+ + "\"@com.contoso.PersonalInfo.PhoneNumbers\":"
+ + "[\"(203)555-1718\",\"(203)555-1719\"],"
+ + "\"PropertyInt16\":32767,"
+ + "\"PropertyString\":\"First Resource - positive values\","
+ + "\"PropertyBoolean\":true,"
+ + "\"PropertyByte\":255,"
+ + "\"PropertySByte\":127,"
+ + "\"PropertyInt32\":2147483647,"
+ + "\"PropertyInt64\":9223372036854775807,"
+ + "\"PropertySingle\":1.79E20,"
+ + "\"PropertyDouble\":-1.79E19,"
+ + "\"PropertyDecimal\":34,"
+ + "\"PropertyBinary\":\"ASNFZ4mrze8=\","
+ + "\"PropertyDate\":\"2012-12-03\","
+ + "\"PropertyDateTimeOffset\":\"2012-12-03T07:16:23Z\","
+ + "\"PropertyDuration\":\"PT6S\","
+ + "\"PropertyGuid\":\"01234567-89ab-cdef-0123-456789abcdef\","
+ + "\"PropertyTimeOfDay\":\"03:26:05\""
+ + "}";
+ Assert.assertEquals(expectedResult, resultString);
+ }
+
+ @Test
+ public void entityPropertyWithInstanceAnnotations() throws Exception {
+ final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESAllPrim");
+ final Entity entity = data.readAll(edmEntitySet).getEntities().get(0);
+ Annotation annotation = new Annotation();
+ annotation.setTerm("com.contoso.display.style");
+ annotation.setType("com.contoso.display.styleType");
+ List<Property> properties = new ArrayList<>();
+ properties.add(new Property("Boolean", "title", ValueType.PRIMITIVE, true));
+ properties.add(new Property("Int16", "Order", ValueType.PRIMITIVE, 1));
+ ComplexValue complexValue = new ComplexValue();
+ complexValue.setTypeName("com.contoso.display.styleType");
+ complexValue.getValue().addAll(properties);
+ annotation.setValue(ValueType.COMPLEX, complexValue);
+
+ Property property = entity.getProperty("PropertyString");
+ property.getAnnotations().add(annotation);
+
+ InputStream result = serializerFullMetadata.entity(metadata, edmEntitySet.getEntityType(), entity,
+ EntitySerializerOptions.with()
+ .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build())
+ .build()).getContent();
+ final String resultString = IOUtils.toString(result);
+ final String expectedResult = "{"
+ + "\"@odata.context\":\"$metadata#ESAllPrim/$entity\","
+ + "\"@odata.metadataEtag\":\"W/\\\"metadataETag\\\"\","
+ + "\"@odata.type\":\"#olingo.odata.test1.ETAllPrim\","
+ + "\"@odata.id\":\"ESAllPrim(32767)\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":32767,"
+ + "\"PropertyString@com.contoso.display.style\":{"
+ + "\"@odata.type\":\"#com.contoso.display.styleType\","
+ + "\"title@odata.type\":\"#Boolean\","
+ + "\"title\":true,"
+ + "\"Order@odata.type\":\"#Int16\","
+ + "\"Order\":1"
+ + "},"
+ + "\"PropertyString\":\"First Resource - positive values\","
+ + "\"PropertyBoolean\":true,"
+ + "\"PropertyByte@odata.type\":\"#Byte\","
+ + "\"PropertyByte\":255,"
+ + "\"PropertySByte@odata.type\":\"#SByte\","
+ + "\"PropertySByte\":127,"
+ + "\"PropertyInt32@odata.type\":\"#Int32\","
+ + "\"PropertyInt32\":2147483647,"
+ + "\"PropertyInt64@odata.type\":\"#Int64\","
+ + "\"PropertyInt64\":9223372036854775807,"
+ + "\"PropertySingle@odata.type\":\"#Single\","
+ + "\"PropertySingle\":1.79E20,"
+ + "\"PropertyDouble\":-1.79E19,"
+ + "\"PropertyDecimal@odata.type\":\"#Decimal\","
+ + "\"PropertyDecimal\":34,"
+ + "\"PropertyBinary@odata.type\":\"#Binary\","
+ + "\"PropertyBinary\":\"ASNFZ4mrze8=\","
+ + "\"PropertyDate@odata.type\":\"#Date\","
+ + "\"PropertyDate\":\"2012-12-03\","
+ + "\"PropertyDateTimeOffset@odata.type\":\"#DateTimeOffset\","
+ + "\"PropertyDateTimeOffset\":\"2012-12-03T07:16:23Z\","
+ + "\"PropertyDuration@odata.type\":\"#Duration\","
+ + "\"PropertyDuration\":\"PT6S\","
+ + "\"PropertyGuid@odata.type\":\"#Guid\","
+ + "\"PropertyGuid\":\"01234567-89ab-cdef-0123-456789abcdef\","
+ + "\"PropertyTimeOfDay@odata.type\":\"#TimeOfDay\","
+ + "\"PropertyTimeOfDay\":\"03:26:05\","
+ + "\"NavPropertyETTwoPrimOne@odata.navigationLink\":\"ESTwoPrim(32767)\","
+ + "\"NavPropertyETTwoPrimMany@odata.navigationLink\":\"ESAllPrim(32767)/NavPropertyETTwoPrimMany\","
+ + "\"#olingo.odata.test1.BAETAllPrimRT\":{"
+ + "\"title\":\"olingo.odata.test1.BAETAllPrimRT\","
+ + "\"target\":\"ESAllPrim(32767)/olingo.odata.test1.BAETAllPrimRT\""
+ + "}"
+ +"}";
+ Assert.assertEquals(expectedResult, resultString);
+ }
+
+ @Test
+ public void entityComplexPropertyWithInstanceAnnotations() throws Exception {
+ final EdmEntitySet edmEntitySet = entityContainer.getEntitySet("ESMixPrimCollComp");
+ final Entity entity = data.readAll(edmEntitySet).getEntities().get(0);
+ Annotation annotation = new Annotation();
+ annotation.setTerm("com.contoso.display.style");
+ annotation.setType("com.contoso.display.styleType");
+ List<Property> properties = new ArrayList<>();
+ properties.add(new Property("Boolean", "title", ValueType.PRIMITIVE, true));
+
+ List<ComplexValue> complexValues = new ArrayList<>();
+ ComplexValue orderComplexValue = new ComplexValue();
+ orderComplexValue.setTypeName("com.contoso.display.orderDetails");
+ complexValues.add(orderComplexValue);
+
+ orderComplexValue = new ComplexValue();
+ orderComplexValue.setTypeName("com.contoso.display.orderDetails");
+ List<Property> orderProperties = new ArrayList<>();
+ orderProperties.add(new Property("String", "name", ValueType.PRIMITIVE, "Cars"));
+ orderProperties.add(new Property("String", "brand", ValueType.PRIMITIVE, "BMW"));
+ orderComplexValue.getValue().addAll(orderProperties);
+ complexValues.add(orderComplexValue);
+ properties.add(new Property("Order", "Order", ValueType.COLLECTION_COMPLEX, complexValues));
+
+
+ ComplexValue complexValue = new ComplexValue();
+ complexValue.setTypeName("com.contoso.display.styleType");
+ complexValue.getValue().addAll(properties);
+ annotation.setValue(ValueType.COMPLEX, complexValue);
+
+ Property property = entity.getProperty("PropertyComp");
+ property.getAnnotations().add(annotation);
+
+ InputStream result = serializerFullMetadata.entity(metadata, edmEntitySet.getEntityType(), entity,
+ EntitySerializerOptions.with()
+ .contextURL(ContextURL.with().entitySet(edmEntitySet).suffix(Suffix.ENTITY).build())
+ .build()).getContent();
+ final String resultString = IOUtils.toString(result);
+ final String expectedResult = "{"
+ + "\"@odata.context\":\"$metadata#ESMixPrimCollComp/$entity\","
+ + "\"@odata.metadataEtag\":\"W/\\\"metadataETag\\\"\","
+ + "\"@odata.type\":\"#olingo.odata.test1.ETMixPrimCollComp\","
+ + "\"@odata.id\":\"ESMixPrimCollComp(32767)\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":32767,"
+ + "\"CollPropertyString@odata.type\":\"#Collection(String)\","
+ + "\"CollPropertyString\":[\"Employee1@company.example\","
+ + "\"Employee2@company.example\",\"Employee3@company.example\"],"
+ + "\"PropertyComp@com.contoso.display.style\":{"
+ + "\"@odata.type\":\"#com.contoso.display.styleType\","
+ + "\"title@odata.type\":\"#Boolean\","
+ + "\"title\":true,"
+ + "\"Order@odata.type\":\"#Collection(Order)\","
+ + "\"Order\":[{"
+ + "\"@odata.type\":\"#com.contoso.display.orderDetails\""
+ + "},{"
+ + "\"@odata.type\":\"#com.contoso.display.orderDetails\","
+ + "\"name@odata.type\":\"#String\","
+ + "\"name\":\"Cars\","
+ + "\"brand@odata.type\":\"#String\","
+ + "\"brand\":\"BMW\""
+ + "}]"
+ + "},"
+ + "\"PropertyComp\":{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":111,"
+ + "\"PropertyString\":\"TEST A\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='1')\""
+ + "},"
+ + "\"CollPropertyComp@odata.type\":\"#Collection(olingo.odata.test1.CTTwoPrim)\","
+ + "\"CollPropertyComp\":[{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":123,"
+ + "\"PropertyString\":\"TEST 1\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='2')\""
+ + "},{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTTwoPrim\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":456,"
+ + "\"PropertyString\":\"TEST 2\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='2')\""
+ + "},{"
+ + "\"@odata.type\":\"#olingo.odata.test1.CTBase\","
+ + "\"PropertyInt16@odata.type\":\"#Int16\","
+ + "\"PropertyInt16\":789,"
+ + "\"PropertyString\":\"TEST 3\","
+ + "\"AdditionalPropString\":\"ADD TEST\","
+ + "\"NavPropertyETTwoKeyNavOne@odata.navigationLink\":"
+ + "\"ESTwoKeyNav(PropertyInt16=1,PropertyString='2')\""
+ + "}],"
+ + "\"#olingo.odata.test1.BAETMixPrimCollCompRTCTTwoPrim\":{"
+ + "\"title\":\"olingo.odata.test1.BAETMixPrimCollCompRTCTTwoPrim\","
+ + "\"target\":"
+ + "\"ESMixPrimCollComp(32767)/olingo.odata.test1.BAETMixPrimCollCompRTCTTwoPrim\""
+ + "}"
+ +"}";
+ Assert.assertEquals(expectedResult, resultString);
+ }
+}