[OLINGO-935] new server serializer for dynamic data Signed-off-by: Christian Amend <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/bc61da3d Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/bc61da3d Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/bc61da3d Branch: refs/heads/master Commit: bc61da3dfd753ff5ac03b1686a73b7bd3c23f36d Parents: 61b2f72 Author: Klaus Straubinger <[email protected]> Authored: Fri Jul 22 13:40:12 2016 +0200 Committer: Christian Amend <[email protected]> Committed: Mon Jul 25 11:10:40 2016 +0200 ---------------------------------------------------------------------- .../json/EdmAssistedJsonSerializer.java | 406 +++++++++++++++++++ .../json/EdmAssistedJsonSerializerTest.java | 382 +++++++++++++++++ 2 files changed, 788 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bc61da3d/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializer.java new file mode 100644 index 0000000..79d221b --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializer.java @@ -0,0 +1,406 @@ +/* + * 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.io.OutputStream; +import java.net.URI; +import java.util.List; + +import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.data.AbstractEntityCollection; +import org.apache.olingo.commons.api.data.AbstractODataObject; +import org.apache.olingo.commons.api.data.Annotatable; +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.Entity; +import org.apache.olingo.commons.api.data.Link; +import org.apache.olingo.commons.api.data.Linked; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.data.Valuable; +import org.apache.olingo.commons.api.data.ValueType; +import org.apache.olingo.commons.api.edm.EdmComplexType; +import org.apache.olingo.commons.api.edm.EdmEntityType; +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.edm.EdmType; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.core.edm.EdmTypeInfo; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerException.MessageKeys; +import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.core.serializer.SerializerResultImpl; +import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer; +import org.apache.olingo.server.core.serializer.utils.ContentTypeHelper; +import org.apache.olingo.server.core.serializer.utils.ContextURLBuilder; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +public class EdmAssistedJsonSerializer { + + private static final String IO_EXCEPTION_TEXT = "An I/O exception occurred."; + + protected final boolean isIEEE754Compatible; + protected final boolean isODataMetadataNone; + + public EdmAssistedJsonSerializer(final ContentType contentType) { + this.isIEEE754Compatible = ContentTypeHelper.isODataIEEE754Compatible(contentType); + this.isODataMetadataNone = ContentTypeHelper.isODataMetadataNone(contentType); + } + + public SerializerResult entityCollection(final ServiceMetadata metadata, final EdmEntityType entityType, + final AbstractEntityCollection entityCollection, final ContextURL contextURL) throws SerializerException { + return serialize(metadata, entityType, entityCollection, contextURL); + } + + public SerializerResult entity(final ServiceMetadata metadata, final EdmEntityType entityType, final Entity entity, + final ContextURL contextURL) throws SerializerException { + return serialize(metadata, entityType, entity, contextURL); + } + + protected SerializerResult serialize(final ServiceMetadata metadata, final EdmEntityType entityType, final AbstractODataObject obj, + final ContextURL contextURL) throws SerializerException { + final String metadataETag = + isODataMetadataNone || metadata == null || metadata.getServiceMetadataETagSupport() == null ? + null : + metadata.getServiceMetadataETagSupport().getMetadataETag(); + final String contextURLString = isODataMetadataNone || contextURL == null ? + null : + ContextURLBuilder.create(contextURL).toASCIIString(); + OutputStream outputStream = null; + SerializerException cachedException = null; + try { + CircleStreamBuffer buffer = new CircleStreamBuffer(); + outputStream = buffer.getOutputStream(); + JsonGenerator json = new JsonFactory().createGenerator(outputStream); + if (obj instanceof AbstractEntityCollection) { + doSerialize(entityType, (AbstractEntityCollection) obj, contextURLString, metadataETag, json); + } else if (obj instanceof Entity) { + doSerialize(entityType, (Entity) obj, contextURLString, metadataETag, json); + } else { + throw new SerializerException("Input type not supported.", MessageKeys.NOT_IMPLEMENTED); + } + json.flush(); + json.close(); + outputStream.close(); + return SerializerResultImpl.with().content(buffer.getInputStream()).build(); + } catch (final IOException e) { + cachedException = new SerializerException(IO_EXCEPTION_TEXT, e, SerializerException.MessageKeys.IO_EXCEPTION); + throw cachedException; + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (final IOException e) { + throw cachedException == null ? + new SerializerException(IO_EXCEPTION_TEXT, e, SerializerException.MessageKeys.IO_EXCEPTION) : + cachedException; + } + } + } + } + + protected void doSerialize(final EdmEntityType entityType, final AbstractEntityCollection entityCollection, + final String contextURLString, final String metadataETag, JsonGenerator json) + throws IOException, SerializerException { + + json.writeStartObject(); + + metadata(contextURLString, metadataETag, null, null, entityCollection.getId(), false, json); + + if (entityCollection.getCount() != null) { + if (isIEEE754Compatible) { + json.writeStringField(Constants.JSON_COUNT, Integer.toString(entityCollection.getCount())); + } else { + json.writeNumberField(Constants.JSON_COUNT, entityCollection.getCount()); + } + } + if (entityCollection.getDeltaLink() != null) { + json.writeStringField(Constants.JSON_DELTA_LINK, entityCollection.getDeltaLink().toASCIIString()); + } + + for (final Annotation annotation : entityCollection.getAnnotations()) { + valuable(json, annotation, '@' + annotation.getTerm(), null, null); + } + + json.writeArrayFieldStart(Constants.VALUE); + for (final Entity entity : entityCollection) { + doSerialize(entityType, entity, null, null, json); + } + json.writeEndArray(); + + if (entityCollection.getNext() != null) { + json.writeStringField(Constants.JSON_NEXT_LINK, entityCollection.getNext().toASCIIString()); + } + + json.writeEndObject(); + } + + protected void doSerialize(final EdmEntityType entityType, final Entity entity, + final String contextURLString, final String metadataETag, JsonGenerator json) + throws IOException, SerializerException { + + json.writeStartObject(); + + final String typeName = entity.getType() == null ? + null : + new EdmTypeInfo.Builder().setTypeExpression(entity.getType()).build().external(); + metadata(contextURLString, metadataETag, entity.getETag(), typeName, entity.getId(), true, json); + + for (final Annotation annotation : entity.getAnnotations()) { + valuable(json, annotation, '@' + annotation.getTerm(), null, null); + } + + for (final Property property : entity.getProperties()) { + final String name = property.getName(); + final EdmProperty edmProperty = entityType == null || entityType.getStructuralProperty(name) == null ? + null : + entityType.getStructuralProperty(name); + valuable(json, property, name, edmProperty == null ? null : edmProperty.getType(), edmProperty); + } + + if (!isODataMetadataNone) { + if (entity.getEditLink() != null && entity.getEditLink().getHref() != null) { + json.writeStringField(Constants.JSON_EDIT_LINK, entity.getEditLink().getHref()); + + if (entity.isMediaEntity()) { + json.writeStringField(Constants.JSON_MEDIA_READ_LINK, entity.getEditLink().getHref() + "/$value"); + } + } + } + + links(entity, entityType, json); + + json.writeEndObject(); + } + + private void metadata(final String contextURLString, final String metadataETag, final String eTag, + final String type, final URI id, final boolean writeNullId, JsonGenerator json) + throws IOException, SerializerException { + if (!isODataMetadataNone) { + if (contextURLString != null) { + json.writeStringField(Constants.JSON_CONTEXT, contextURLString); + } + if (metadataETag != null) { + json.writeStringField(Constants.JSON_METADATA_ETAG, metadataETag); + } + if (eTag != null) { + json.writeStringField(Constants.JSON_ETAG, eTag); + } + if (type != null) { + json.writeStringField(Constants.JSON_TYPE, type); + } + if (id == null) { + if (writeNullId) { + json.writeNullField(Constants.JSON_ID); + } + } else { + json.writeStringField(Constants.JSON_ID, id.toASCIIString()); + } + } + } + + private void links(final Linked linked, final EdmEntityType entityType, JsonGenerator json) + throws IOException, SerializerException { + + for (final Link link : linked.getNavigationLinks()) { + final String name = link.getTitle(); + for (final Annotation annotation : link.getAnnotations()) { + valuable(json, annotation, name + '@' + annotation.getTerm(), null, null); + } + + final EdmEntityType targetType = + entityType == null || name == null || entityType.getNavigationProperty(name) == null ? + null : + entityType.getNavigationProperty(name).getType(); + if (link.getInlineEntity() != null) { + json.writeFieldName(name); + doSerialize(targetType, link.getInlineEntity(), null, null, json); + } else if (link.getInlineEntitySet() != null) { + json.writeArrayFieldStart(name); + for (final Entity subEntry : link.getInlineEntitySet().getEntities()) { + doSerialize(targetType, subEntry, null, null, json); + } + json.writeEndArray(); + } + } + } + + private void collection(final JsonGenerator json, final EdmType itemType, final String typeName, + final EdmProperty edmProperty, final ValueType valueType, final List<?> value) + throws IOException, SerializerException { + + json.writeStartArray(); + + for (final Object item : value) { + switch (valueType) { + case COLLECTION_PRIMITIVE: + primitiveValue(json, (EdmPrimitiveType) itemType, typeName, edmProperty, item); + break; + + case COLLECTION_GEOSPATIAL: + case COLLECTION_ENUM: + throw new SerializerException("Geo and enum types are not supported.", MessageKeys.NOT_IMPLEMENTED); + + case COLLECTION_COMPLEX: + complexValue(json, (EdmComplexType) itemType, typeName, (ComplexValue) item); + break; + + default: + } + } + + json.writeEndArray(); + } + + protected void primitiveValue(final JsonGenerator json, final EdmPrimitiveType valueType, final String typeName, + final EdmProperty edmProperty, final Object value) throws IOException, SerializerException { + + EdmPrimitiveType type = valueType; + if (type == null) { + final EdmPrimitiveTypeKind kind = + typeName == null ? + EdmTypeInfo.determineTypeKind(value) : + new EdmTypeInfo.Builder().setTypeExpression(typeName).build().getPrimitiveTypeKind(); + type = kind == null ? null : EdmPrimitiveTypeFactory.getInstance(kind); + } + + if (value == null) { + json.writeNull(); + } else if (type == null) { + throw new SerializerException("The primitive type could not be determined.", + MessageKeys.INCONSISTENT_PROPERTY_TYPE, ""); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)) { + json.writeBoolean((Boolean) value); + } else { + String serialized = null; + try { + serialized = type.valueToString(value, + edmProperty == null ? null : edmProperty.isNullable(), + edmProperty == null ? null : edmProperty.getMaxLength(), + edmProperty == null ? Constants.DEFAULT_PRECISION : edmProperty.getPrecision(), + edmProperty == null ? Constants.DEFAULT_SCALE : edmProperty.getScale(), + edmProperty == null ? null : edmProperty.isUnicode()); + } catch (final EdmPrimitiveTypeException e) { + final String name = edmProperty == null ? "" : edmProperty.getName(); + throw new SerializerException("Wrong value for property '" + name + "'!", e, + SerializerException.MessageKeys.WRONG_PROPERTY_VALUE, name, value.toString()); + } + if (isIEEE754Compatible && + (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)) + || type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Byte) + && type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.SByte) + && type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Single) + && type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double) + && type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16) + && type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32) + && type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64) + && type != EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)) { + json.writeString(serialized); + } else { + json.writeNumber(serialized); + } + } + } + + private void complexValue(final JsonGenerator json, final EdmComplexType valueType, final String typeName, + final ComplexValue value) throws IOException, SerializerException { + json.writeStartObject(); + + if (typeName != null && !isODataMetadataNone) { + json.writeStringField(Constants.JSON_TYPE, typeName); + } + + for (final Property property : value.getValue()) { + final String name = property.getName(); + final EdmProperty edmProperty = valueType == null || valueType.getStructuralProperty(name) == null ? + null : + valueType.getStructuralProperty(name); + valuable(json, property, name, edmProperty == null ? null : edmProperty.getType(), edmProperty); + } + links(value, null, json); + + json.writeEndObject(); + } + + private void value(final JsonGenerator json, final Valuable value, final EdmType type, final EdmProperty edmProperty) + throws IOException, SerializerException { + final String typeName = value.getType() == null ? + null : + new EdmTypeInfo.Builder().setTypeExpression(value.getType()).build().external(); + + if (value.isNull()) { + json.writeNull(); + } else if (value.isCollection()) { + collection(json, type, typeName, edmProperty, value.getValueType(), value.asCollection()); + } else if (value.isPrimitive()) { + primitiveValue(json, (EdmPrimitiveType) type, typeName, edmProperty, value.asPrimitive()); + } else if (value.isComplex()) { + complexValue(json, (EdmComplexType) type, typeName, value.asComplex()); + } else if (value.isEnum() || value.isGeospatial()) { + throw new SerializerException("Geo and enum types are not supported.", MessageKeys.NOT_IMPLEMENTED); + } + } + + protected void valuable(JsonGenerator json, final Valuable valuable, final String name, final EdmType type, + final EdmProperty edmProperty) throws IOException, SerializerException { + + if (!isODataMetadataNone + && !(valuable instanceof Annotation) && !valuable.isComplex()) { + + String typeName = valuable.getType(); + if (typeName == null && type == null) { + if (valuable.isPrimitive()) { + if (valuable.isCollection()) { + if (!valuable.asCollection().isEmpty()) { + final EdmPrimitiveTypeKind kind = EdmTypeInfo.determineTypeKind(valuable.asCollection().get(0)); + if (kind != null) { + typeName = "Collection(" + kind.getFullQualifiedName().getFullQualifiedNameAsString() + ')'; + } + } + } else { + final EdmPrimitiveTypeKind kind = EdmTypeInfo.determineTypeKind(valuable.asPrimitive()); + if (kind != null) { + typeName = kind.getFullQualifiedName().getFullQualifiedNameAsString(); + } + } + } + } + if (typeName != null) { + json.writeStringField( + name + Constants.JSON_TYPE, + new EdmTypeInfo.Builder().setTypeExpression(typeName).build().external()); + } + } + + for (final Annotation annotation : ((Annotatable) valuable).getAnnotations()) { + valuable(json, annotation, name + '@' + annotation.getTerm(), null, null); + } + + json.writeFieldName(name); + value(json, valuable, type, edmProperty); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bc61da3d/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializerTest.java ---------------------------------------------------------------------- diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializerTest.java new file mode 100644 index 0000000..d6f4e73 --- /dev/null +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/json/EdmAssistedJsonSerializerTest.java @@ -0,0 +1,382 @@ +/* + * 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.math.BigDecimal; +import java.net.URI; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.TimeZone; +import java.util.UUID; + +import org.apache.commons.io.IOUtils; +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.EntityCollection; +import org.apache.olingo.commons.api.data.Link; +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.SerializerException; +import org.apache.olingo.server.tecsvc.MetadataETagSupport; +import org.apache.olingo.server.tecsvc.provider.EdmTechProvider; +import org.junit.Assert; +import org.junit.Test; + +public class EdmAssistedJsonSerializerTest { + private static final OData oData = OData.newInstance(); + private static final ServiceMetadata metadata = oData.createServiceMetadata( + new EdmTechProvider(), Collections.<EdmxReference> emptyList(), null); + private static final EdmEntityContainer entityContainer = metadata.getEdm().getEntityContainer(); + private final EdmAssistedJsonSerializer serializer = new EdmAssistedJsonSerializer(ContentType.JSON); + + @Test + public void entity() throws Exception { + Entity entity = new Entity(); + entity.setId(null); + entity.addProperty(new Property(null, "Property1", ValueType.PRIMITIVE, 1.25F)); + final String resultString = IOUtils.toString( + serializer.entity(metadata, null, entity, + ContextURL.with().entitySetOrSingletonOrType("EntitySet").selectList("Property1") + .suffix(Suffix.ENTITY) + .build()) + .getContent()); + final String expectedResult = + "{\"@odata.context\":\"$metadata#EntitySet(Property1)/$entity\"," + + "\"@odata.id\":null," + + "\"[email protected]\":\"Single\",\"Property1\":1.25" + + "}"; + Assert.assertEquals(expectedResult, resultString); + } + + @Test + public void entityWithEdm() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESTwoPrim"); + Entity entity = new Entity(); + entity.setId(null); + entity.addProperty(new Property(null, "PropertyInt16", ValueType.PRIMITIVE, (short) 1)) + .addProperty(new Property(null, "PropertyString", ValueType.PRIMITIVE, "test")) + .addProperty(new Property(null, "AdditionalProperty", ValueType.PRIMITIVE, (byte) 42)); + final String resultString = IOUtils.toString( + serializer.entity(metadata, entitySet.getEntityType(), entity, + ContextURL.with().entitySet(entitySet).suffix(Suffix.ENTITY).build()) + .getContent()); + final String expectedResult = + "{\"@odata.context\":\"$metadata#ESTwoPrim/$entity\"," + + "\"@odata.id\":null," + + "\"PropertyInt16\":1,\"PropertyString\":\"test\"," + + "\"[email protected]\":\"SByte\",\"AdditionalProperty\":42" + + "}"; + Assert.assertEquals(expectedResult, resultString); + } + + @Test + public void entityCollection() throws Exception { + Entity entity = new Entity(); + entity.setId(null); + entity.addProperty(new Property(null, "Property0", ValueType.PRIMITIVE, null)) + .addProperty(new Property(null, "Property1", ValueType.PRIMITIVE, 1)); + Calendar date = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + date.clear(); + date.set(2000, 1, 29); + entity.addProperty(new Property("Edm.Date", "Property2", ValueType.PRIMITIVE, date)) + .addProperty(new Property("Edm.DateTimeOffset", "Property3", ValueType.PRIMITIVE, date)) + .addProperty(new Property(null, "Property4", ValueType.COLLECTION_PRIMITIVE, + Arrays.asList(true, false, null))); + EntityCollection entityCollection = new EntityCollection(); + entityCollection.getEntities().add(entity); + entityCollection.setCount(2); + entityCollection.setNext(URI.create("nextLink")); + final String resultString = IOUtils.toString( + serializer.entityCollection(metadata, null, entityCollection, + ContextURL.with().entitySetOrSingletonOrType("EntitySet") + .selectList("Property0,Property1,Property2,Property3,Property4") + .build()) + .getContent()); + final String expectedResult = + "{\"@odata.context\":\"$metadata#EntitySet(Property0,Property1,Property2,Property3,Property4)\"," + + "\"@odata.count\":2," + + "\"value\":[{\"@odata.id\":null," + + "\"Property0\":null," + + "\"[email protected]\":\"Int32\",\"Property1\":1," + + "\"[email protected]\":\"Date\",\"Property2\":\"2000-02-29\"," + + "\"[email protected]\":\"DateTimeOffset\",\"Property3\":\"2000-02-29T00:00:00Z\"," + + "\"[email protected]\":\"#Collection(Boolean)\",\"Property4\":[true,false,null]" + + "}]," + + "\"@odata.nextLink\":\"nextLink\"" + + "}"; + Assert.assertEquals(expectedResult, resultString); + } + + @Test + public void entityCollectionIEEE754Compatible() throws Exception { + EntityCollection entityCollection = new EntityCollection(); + entityCollection.getEntities().add(new Entity() + .addProperty(new Property(null, "Property1", ValueType.PRIMITIVE, Long.MIN_VALUE)) + .addProperty(new Property(null, "Property2", ValueType.PRIMITIVE, BigDecimal.valueOf(Long.MAX_VALUE, 10))) + .addProperty(new Property("Edm.Byte", "Property3", ValueType.PRIMITIVE, 20))); + entityCollection.setCount(3); + Assert.assertEquals( + "{\"@odata.context\":\"$metadata#EntitySet(Property1,Property2,Property3)\"," + + "\"@odata.count\":\"3\"," + + "\"value\":[{\"@odata.id\":null," + + "\"[email protected]\":\"Int64\",\"Property1\":\"-9223372036854775808\"," + + "\"[email protected]\":\"Decimal\",\"Property2\":\"922337203.6854775807\"," + + "\"[email protected]\":\"Byte\",\"Property3\":20}]}", + IOUtils.toString( + new EdmAssistedJsonSerializer( + ContentType.create(ContentType.JSON, ContentType.PARAMETER_IEEE754_COMPATIBLE, "true")) + .entityCollection(metadata, null, entityCollection, + ContextURL.with().entitySetOrSingletonOrType("EntitySet") + .selectList("Property1,Property2,Property3") + .build()) + .getContent())); + } + + @Test + public void entityCollectionWithComplexProperty() throws Exception { + Entity entity = new Entity(); + entity.setId(null); + entity.addProperty(new Property(null, "Property1", ValueType.PRIMITIVE, 1L)); + ComplexValue complexValue = new ComplexValue(); + complexValue.getValue().add(new Property(null, "Inner1", ValueType.PRIMITIVE, + BigDecimal.TEN.scaleByPowerOfTen(-5))); + Calendar time = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + time.clear(); + time.set(Calendar.HOUR_OF_DAY, 13); + time.set(Calendar.SECOND, 59); + time.set(Calendar.MILLISECOND, 999); + complexValue.getValue().add(new Property("Edm.TimeOfDay", "Inner2", ValueType.PRIMITIVE, time)); + entity.addProperty(new Property("Namespace.ComplexType", "Property2", ValueType.COMPLEX, complexValue)); + EntityCollection entityCollection = new EntityCollection(); + entityCollection.getEntities().add(entity); + final String resultString = IOUtils.toString( + serializer.entityCollection(metadata, null, entityCollection, + ContextURL.with().entitySetOrSingletonOrType("EntitySet").selectList("Property1,Property2").build()) + .getContent()); + final String expectedResult = "{\"@odata.context\":\"$metadata#EntitySet(Property1,Property2)\"," + + "\"value\":[{\"@odata.id\":null," + + "\"[email protected]\":\"Int64\",\"Property1\":1," + + "\"Property2\":{" + + "\"@odata.type\":\"#Namespace.ComplexType\"," + + "\"[email protected]\":\"Decimal\",\"Inner1\":0.00010," + + "\"[email protected]\":\"TimeOfDay\",\"Inner2\":\"13:00:59.999\"" + + "}}]}"; + Assert.assertEquals(expectedResult, resultString); + } + + @Test + public void entityWithComplexCollection() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESMixPrimCollComp"); + ComplexValue complexValue1 = new ComplexValue(); + complexValue1.getValue().add(new Property(null, "PropertyInt16", ValueType.PRIMITIVE, 1)); + complexValue1.getValue().add(new Property(null, "PropertyString", ValueType.PRIMITIVE, "one")); + ComplexValue complexValue2 = new ComplexValue(); + complexValue2.getValue().add(new Property(null, "PropertyInt16", ValueType.PRIMITIVE, 2)); + complexValue2.getValue().add(new Property(null, "PropertyString", ValueType.PRIMITIVE, "two")); + ComplexValue complexValue3 = new ComplexValue(); + complexValue3.getValue().add(new Property(null, "PropertyInt16", ValueType.PRIMITIVE, 3)); + complexValue3.getValue().add(new Property(null, "PropertyString", ValueType.PRIMITIVE, "three")); + final Entity entity = new Entity() + .addProperty(new Property(null, "CollPropertyComp", ValueType.COLLECTION_COMPLEX, + Arrays.asList(complexValue1, complexValue2, complexValue3))); + final String resultString = IOUtils.toString( + serializer.entity(metadata, entitySet.getEntityType(), entity, + ContextURL.with().entitySet(entitySet).selectList("CollPropertyComp").build()) + .getContent()); + final String expectedResult = "{\"@odata.context\":\"$metadata#ESMixPrimCollComp(CollPropertyComp)\"," + + "\"@odata.id\":null," + + "\"CollPropertyComp\":[" + + "{\"PropertyInt16\":1,\"PropertyString\":\"one\"}," + + "{\"PropertyInt16\":2,\"PropertyString\":\"two\"}," + + "{\"PropertyInt16\":3,\"PropertyString\":\"three\"}]}"; + Assert.assertEquals(expectedResult, resultString); + } + + @Test + public void entityWithEmptyCollection() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESMixPrimCollComp"); + final Entity entity = new Entity() + .addProperty(new Property(null, "CollPropertyString", ValueType.COLLECTION_PRIMITIVE, + Collections.emptyList())); + Assert.assertEquals( + "{\"@odata.context\":\"$metadata#ESMixPrimCollComp(CollPropertyString)\"," + + "\"@odata.id\":null,\"CollPropertyString\":[]}", + IOUtils.toString( + serializer.entity(metadata, entitySet.getEntityType(), entity, + ContextURL.with().entitySet(entitySet).selectList("CollPropertyString").build()) + .getContent())); + } + + @Test + public void expand() throws Exception { + final Entity relatedEntity1 = new Entity().addProperty(new Property(null, "Related1", ValueType.PRIMITIVE, 1.5)); + final Entity relatedEntity2 = new Entity().addProperty(new Property(null, "Related1", ValueType.PRIMITIVE, 2.75)); + EntityCollection target = new EntityCollection(); + target.getEntities().add(relatedEntity1); + target.getEntities().add(relatedEntity2); + Link link = new Link(); + link.setTitle("NavigationProperty"); + link.setInlineEntitySet(target); + Entity entity = new Entity(); + entity.setId(null); + entity.addProperty(new Property(null, "Property1", ValueType.PRIMITIVE, (short) 1)); + entity.getNavigationLinks().add(link); + EntityCollection entityCollection = new EntityCollection(); + entityCollection.getEntities().add(entity); + final String resultString = IOUtils.toString( + serializer.entityCollection(metadata, null, entityCollection, + ContextURL.with().entitySetOrSingletonOrType("EntitySet") + .selectList("Property1,NavigationProperty(Related1)").build()) + .getContent()); + final String expectedResult = + "{\"@odata.context\":\"$metadata#EntitySet(Property1,NavigationProperty(Related1))\"," + + "\"value\":[{\"@odata.id\":null," + + "\"[email protected]\":\"Int16\",\"Property1\":1," + + "\"NavigationProperty\":[" + + "{\"@odata.id\":null,\"[email protected]\":\"Double\",\"Related1\":1.5}," + + "{\"@odata.id\":null,\"[email protected]\":\"Double\",\"Related1\":2.75}" + + "]}]}"; + Assert.assertEquals(expectedResult, resultString); + } + + @Test + public void expandWithEdm() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESTwoPrim"); + Entity entity = new Entity() + .addProperty(new Property(null, "PropertyInt16", ValueType.PRIMITIVE, (short) 42)) + .addProperty(new Property(null, "PropertyString", ValueType.PRIMITIVE, "test")); + final Entity target = new Entity() + .addProperty(new Property(null, "PropertyInt16", ValueType.PRIMITIVE, (short) 2)) + .addProperty(new Property(null, "PropertyByte", ValueType.PRIMITIVE, 3L)); + Link link = new Link(); + link.setTitle("NavPropertyETAllPrimOne"); + link.setInlineEntity(target); + entity.getNavigationLinks().add(link); + final String resultString = IOUtils.toString( + serializer.entity(metadata, entitySet.getEntityType(), entity, + ContextURL.with().entitySet(entitySet).suffix(Suffix.ENTITY).build()) + .getContent()); + final String expectedResult = + "{\"@odata.context\":\"$metadata#ESTwoPrim/$entity\"," + + "\"@odata.id\":null," + + "\"PropertyInt16\":42,\"PropertyString\":\"test\"," + + "\"NavPropertyETAllPrimOne\":{\"@odata.id\":null,\"PropertyInt16\":2,\"PropertyByte\":3}" + + "}"; + Assert.assertEquals(expectedResult, resultString); + } + + @Test + public void metadata() throws Exception { + final ServiceMetadata metadata = oData.createServiceMetadata(null, Collections.<EdmxReference> emptyList(), + new MetadataETagSupport("W/\"42\"")); + Entity entity = new Entity(); + entity.setType("Namespace.EntityType"); + entity.setId(URI.create("ID")); + entity.setETag("W/\"1000\""); + Link link = new Link(); + link.setHref("editLink"); + entity.setEditLink(link); + entity.setMediaContentSource(URI.create("media")); + entity.addProperty(new Property(null, "Property1", ValueType.PRIMITIVE, + UUID.fromString("12345678-ABCD-1234-CDEF-123456789012"))); + final ContextURL contextURL = ContextURL.with().entitySetOrSingletonOrType("EntitySet").selectList("Property1") + .suffix(Suffix.ENTITY).build(); + final String resultString = IOUtils.toString(serializer.entity(metadata, null, entity, contextURL).getContent()); + final String expectedResult = + "{\"@odata.context\":\"$metadata#EntitySet(Property1)/$entity\"," + + "\"@odata.metadataEtag\":\"W/\\\"42\\\"\"," + + "\"@odata.etag\":\"W/\\\"1000\\\"\"," + + "\"@odata.type\":\"#Namespace.EntityType\"," + + "\"@odata.id\":\"ID\"," + + "\"[email protected]\":\"Guid\",\"Property1\":\"12345678-abcd-1234-cdef-123456789012\"," + + "\"@odata.editLink\":\"editLink\"," + + "\"@odata.mediaReadLink\":\"editLink/$value\"" + + "}"; + Assert.assertEquals(expectedResult, resultString); + + Assert.assertEquals("{\"Property1\":\"12345678-abcd-1234-cdef-123456789012\"}", + IOUtils.toString(new EdmAssistedJsonSerializer(ContentType.JSON_NO_METADATA) + .entity(metadata, null, entity, contextURL).getContent())); + } + + @Test(expected = SerializerException.class) + public void enumType() throws Exception { + serializer.entity(metadata, null, + new Entity().addProperty(new Property(null, "Property1", ValueType.ENUM, 42)), null); + } + + @Test(expected = SerializerException.class) + public void collectionEnumType() throws Exception { + serializer.entity(metadata, null, + new Entity().addProperty(new Property(null, "Property1", ValueType.COLLECTION_ENUM, Arrays.asList(42))), + null); + } + + @Test(expected = SerializerException.class) + public void geoType() throws Exception { + serializer.entity(metadata, null, + new Entity().addProperty(new Property(null, "Property1", ValueType.GEOSPATIAL, 1)), null); + } + + @Test(expected = SerializerException.class) + public void unsupportedType() throws Exception { + serializer.entity(metadata, null, + new Entity().addProperty(new Property(null, "Property1", ValueType.PRIMITIVE, TimeZone.getDefault())), + null); + } + + @Test(expected = SerializerException.class) + public void wrongValueForType() throws Exception { + serializer.entity(metadata, null, + new Entity().addProperty(new Property("Edm.SByte", "Property1", ValueType.PRIMITIVE, "-1")), + null); + } + + @Test(expected = SerializerException.class) + public void wrongValueForPropertyFacet() throws Exception { + serializer.entity(metadata, entityContainer.getEntitySet("ESAllPrim").getEntityType(), + new Entity().addProperty( + new Property(null, "PropertyDecimal", ValueType.PRIMITIVE, BigDecimal.ONE.scaleByPowerOfTen(-11))), + null); + } + + @Test(expected = SerializerException.class) + public void wrongValueForPropertyFacetInComplexProperty() throws Exception { + ComplexValue innerComplexValue = new ComplexValue(); + innerComplexValue.getValue().add(new Property(null, "PropertyDecimal", ValueType.PRIMITIVE, + BigDecimal.ONE.scaleByPowerOfTen(-6))); + ComplexValue complexValue = new ComplexValue(); + complexValue.getValue().add(new Property(null, "PropertyComp", ValueType.COMPLEX, + innerComplexValue)); + serializer.entity(metadata, entityContainer.getEntitySet("ESKeyNav").getEntityType(), + new Entity().addProperty( + new Property(null, "CollPropertyComp", ValueType.COLLECTION_COMPLEX, + Collections.singletonList(complexValue))), + null); + } +}
