From 82215450857f551a9ffd6a4ef6c30ed8dfd6e6f9 Mon Sep 17 00:00:00 2001 From: Michael Bolz Date: Sat, 28 Jun 2014 14:55:33 +0200 Subject: [PATCH] [OLINGO-317] Minor bugfixes --- .../olingo/commons/api/data/Entity.java | 8 ++ .../olingo/commons/api/format/AcceptType.java | 4 +- .../olingo/commons/core/data/EntityImpl.java | 6 + .../commons/core/data/PropertyImpl.java | 14 ++ .../core/edm/primitivetype/EdmDateTime.java | 38 ++---- .../apache/olingo/server/core/ODataImpl.java | 2 +- .../{ => json}/ODataJsonSerializer.java | 124 +++++++++++++----- .../json/ODataJsonSerializerTest.java | 97 ++++++++++++++ .../tecsvc/processor/SampleJsonProcessor.java | 8 +- 9 files changed, 239 insertions(+), 62 deletions(-) rename lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/{ => json}/ODataJsonSerializer.java (59%) create mode 100644 lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java index 6a598842a..ce6bf7101 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Entity.java @@ -108,6 +108,14 @@ public interface Entity extends Linked, Annotatable { */ List getOperations(); + /** + * Add property to this Entity. + * + * @param property property which is added + * @return this Entity for fluid/flow adding + */ + Entity addProperty(Property property); + /** * Gets properties. * diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/AcceptType.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/AcceptType.java index 07a5452bb..e8658abc2 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/AcceptType.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/AcceptType.java @@ -50,7 +50,7 @@ public class AcceptType { private static final String PARAMETER_Q = "q"; private static final Pattern Q_PARAMETER_VALUE_PATTERN = Pattern.compile("1|0|1\\.0{1,3}|0\\.\\d{1,3}"); - public static final AcceptType WILDCARD = create(MEDIA_TYPE_WILDCARD, MEDIA_TYPE_WILDCARD, null, 1F); + public static final AcceptType WILDCARD = create(MEDIA_TYPE_WILDCARD, MEDIA_TYPE_WILDCARD, createParameterMap(), 1F); private final String type; private final String subtype; @@ -66,7 +66,7 @@ public class AcceptType { this.quality = quality; } - private TreeMap createParameterMap() { + private static TreeMap createParameterMap() { return new TreeMap(new Comparator() { @Override public int compare(final String o1, final String o2) { diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/EntityImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/EntityImpl.java index 003c72d60..952962899 100755 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/EntityImpl.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/EntityImpl.java @@ -131,6 +131,12 @@ public class EntityImpl extends AbstractODataObject implements Entity { return operations; } + @Override + public Entity addProperty(Property property) { + properties.add(property); + return this; + } + @Override public List getProperties() { return properties; diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/PropertyImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/PropertyImpl.java index 4220506c1..b6f2f3638 100755 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/PropertyImpl.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/PropertyImpl.java @@ -19,12 +19,26 @@ package org.apache.olingo.commons.core.data; import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.data.ValueType; public class PropertyImpl extends AbstractValuable implements Property { private String name; private String type; + public PropertyImpl() { + } + + public PropertyImpl(String type, String name) { + this.name = name; + this.type = type; + } + + public PropertyImpl(String type, String name, ValueType valueType, Object value) { + this(name, type); + setValue(valueType, value); + } + @Override public String getName() { return name; diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTime.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTime.java index f5d3a6e99..1feca5477 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTime.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTime.java @@ -95,7 +95,7 @@ public final class EdmDateTime extends SingletonPrimitiveType { if (!decimals.isEmpty()) { final int nanos = Integer.parseInt(decimals.length() > 9 ? decimals.substring(0, 9) : - decimals + "000000000".substring(decimals.length())); + decimals + "000000000".substring(decimals.length())); timestamp.setNanos(nanos); } } @@ -115,32 +115,22 @@ public final class EdmDateTime extends SingletonPrimitiveType { final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, final Boolean isUnicode) throws EdmPrimitiveTypeException { - Date date = null; - Integer fractionalSecs = null; if (value instanceof Calendar) { final Calendar calendar = (Calendar) value; - date = calendar.getTime(); - fractionalSecs = calendar.get(Calendar.MILLISECOND); - } - if (value instanceof Timestamp) { + Date date = calendar.getTime(); + Integer fractionalSecs = calendar.get(Calendar.MILLISECOND); + final StringBuilder result = new StringBuilder().append(DATE_FORMAT.get().format(date)); + EdmDateTimeOffset.appendMilliseconds(result, fractionalSecs, precision); + return result.toString(); + } else if (value instanceof Timestamp) { final Timestamp timestamp = (Timestamp) value; - date = new Date(timestamp.getTime()); - fractionalSecs = timestamp.getNanos(); + Date date = new Date(timestamp.getTime()); + Integer fractionalSecs = timestamp.getNanos(); + final StringBuilder result = new StringBuilder().append(DATE_FORMAT.get().format(date)); + EdmDateTimeOffset.appendFractionalSeconds(result, fractionalSecs, precision); + return result.toString(); + } else { + throw new EdmPrimitiveTypeException("EdmDateTime only supports conversion from Calendar and Timestamp"); } - - final StringBuilder result = new StringBuilder().append(DATE_FORMAT.get().format(date)); - - try { - if (value instanceof Timestamp) { - EdmDateTimeOffset.appendFractionalSeconds(result, fractionalSecs, precision); - } else { - EdmDateTimeOffset.appendMilliseconds(result, fractionalSecs, precision); - } - } catch (final IllegalArgumentException e) { - throw new EdmPrimitiveTypeException( - "EdmPrimitiveTypeException.VALUE_FACETS_NOT_MATCHED.addContent(value, facets)", e); - } - - return result.toString(); } } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java index 814459adf..d3e3b5f46 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java @@ -26,7 +26,7 @@ import org.apache.olingo.server.api.ODataHttpHandler; import org.apache.olingo.server.api.edm.provider.EdmProvider; import org.apache.olingo.server.api.serializer.ODataSerializer; import org.apache.olingo.server.core.edm.provider.EdmProviderImpl; -import org.apache.olingo.server.core.serializer.ODataJsonSerializer; +import org.apache.olingo.server.core.serializer.json.ODataJsonSerializer; import org.apache.olingo.server.core.serializer.ODataXmlSerializerImpl; public class ODataImpl extends OData { diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/ODataJsonSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java similarity index 59% rename from lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/ODataJsonSerializer.java rename to lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java index bdd6b54cb..8cb5f0c1c 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/ODataJsonSerializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializer.java @@ -16,20 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.olingo.server.core.serializer; +package org.apache.olingo.server.core.serializer.json; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; +import java.util.List; import org.apache.olingo.commons.api.Constants; import org.apache.olingo.commons.api.ODataRuntimeException; import org.apache.olingo.commons.api.data.ContextURL; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntitySet; +import org.apache.olingo.commons.api.data.LinkedComplexValue; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.edm.EdmComplexType; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmPrimitiveType; @@ -38,7 +41,6 @@ import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.edm.EdmProperty; import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; import org.apache.olingo.server.api.serializer.ODataSerializer; -import org.apache.olingo.server.core.serializer.json.ServiceDocumentJsonSerializer; import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -128,44 +130,106 @@ public class ODataJsonSerializer implements ODataSerializer { json.writeStringField("@odata.mediaContentType", entity.getMediaContentType()); } for (final String propertyName : entityType.getPropertyNames()) { - json.writeFieldName(propertyName); final EdmProperty edmProperty = (EdmProperty) entityType.getProperty(propertyName); final Property property = entity.getProperty(propertyName); - if (property == null) { - if (edmProperty.isNullable() == Boolean.FALSE) { - throw new ODataRuntimeException("Non-nullable property not present!"); - } else { - json.writeNull(); - } + writeProperty(edmProperty, property, json); + } + json.writeEndObject(); + } + + protected void writeProperty(final EdmProperty edmProperty, final Property property, JsonGenerator json) + throws IOException, EdmPrimitiveTypeException { + json.writeFieldName(edmProperty.getName()); + if (property == null || property.isNull()) { + if (edmProperty.isNullable() == Boolean.FALSE) { + throw new ODataRuntimeException("Non-nullable property not present!"); } else { - if (edmProperty.isPrimitive()) { - final EdmPrimitiveType type = (EdmPrimitiveType) edmProperty.getType(); - final String value = type.valueToString(property.asPrimitive(), - edmProperty.isNullable(), edmProperty.getMaxLength(), - edmProperty.getPrecision(), edmProperty.getScale(), - edmProperty.isUnicode()); - if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)) { - json.writeBoolean(Boolean.parseBoolean(value)); - } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Byte) - || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal) - || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double) - || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16) - || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32) - || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64) - || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.SByte) - || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Single)) { - json.writeNumber(value); - } else { - json.writeString(value); - } + json.writeNull(); + } + } else { + if (edmProperty.isPrimitive()) { + if (property.isPrimitive()) { + writePrimitiveValue(edmProperty, property.asPrimitive(), json); + } else if (property.isGeospatial()) { + throw new ODataRuntimeException("Property type not yet supported!"); + } else if (property.isEnum()) { + json.writeString(property.asEnum().toString()); } else { - throw new ODataRuntimeException("Non-primitive properties not yet supported!"); + throw new ODataRuntimeException("Inconsistent property type!"); + } + } else if (edmProperty.isCollection()) { + json.writeStartArray(); + for (Object value : property.asCollection()) { + switch (property.getValueType()) { + case COLLECTION_PRIMITIVE: + writePrimitiveValue(edmProperty, value, json); + break; + case COLLECTION_GEOSPATIAL: + throw new ODataRuntimeException("Property type not yet supported!"); + case COLLECTION_ENUM: + json.writeString(value.toString()); + break; + case COLLECTION_LINKED_COMPLEX: + writeLinkedComplexValue(edmProperty, (LinkedComplexValue) value, json); + break; + default: + throw new ODataRuntimeException("Property type not yet supported!"); + } + } + json.writeEndArray(); + } else { + if (property.isLinkedComplex()) { + writeLinkedComplexValue(edmProperty, property.asLinkedComplex(), json); + } else { + throw new ODataRuntimeException("Property type not yet supported!"); } } } + } + + protected void writePrimitiveValue(final EdmProperty edmProperty, final Object primitiveValue, JsonGenerator json) + throws EdmPrimitiveTypeException, IOException { + final EdmPrimitiveType type = (EdmPrimitiveType) edmProperty.getType(); + final String value = type.valueToString(primitiveValue, + edmProperty.isNullable(), edmProperty.getMaxLength(), + edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode()); + if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Boolean)) { + json.writeBoolean(Boolean.parseBoolean(value)); + } else if (type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Byte) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Double) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int16) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int32) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Int64) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.SByte) + || type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Single)) { + json.writeNumber(value); + } else { + json.writeString(value); + } + } + + private void writeLinkedComplexValue(final EdmProperty edmProperty, final LinkedComplexValue linkedComplexValue, + JsonGenerator json) throws IOException, EdmPrimitiveTypeException { + final EdmComplexType type = (EdmComplexType) edmProperty.getType(); + final List properties = linkedComplexValue.getValue(); + json.writeStartObject(); + for (final String propertyName : type.getPropertyNames()) { + final Property property = findProperty(propertyName, properties); + writeProperty((EdmProperty) type.getProperty(propertyName), property, json); + } json.writeEndObject(); } + private Property findProperty(String propertyName, List properties) { + for (final Property property : properties) { + if (propertyName.equals(property.getName())) { + return property; + } + } + return null; + } + @Override public InputStream entitySet(final EdmEntitySet edmEntitySet, final EntitySet entitySet, final ContextURL contextURL) { diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java new file mode 100644 index 000000000..79a1b6514 --- /dev/null +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/json/ODataJsonSerializerTest.java @@ -0,0 +1,97 @@ +/* + * 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 org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.ValueType; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.core.data.EntityImpl; +import org.apache.olingo.commons.core.data.PropertyImpl; +import org.apache.olingo.commons.core.edm.primitivetype.EdmString; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +public class ODataJsonSerializerTest { + + public static final String PROPERTY_1 = "Property1"; + + private ContextURL contextUrl; + private EdmEntityType edmEntityType; + private final Logger LOG = LoggerFactory.getLogger(ODataJsonSerializerTest.class); + + private ODataJsonSerializer serializer = new ODataJsonSerializer(); + + @Before + public void prepare() throws Exception { + contextUrl = ContextURL.getInstance(new URI("http://localhost:8080/test.svc")); + edmEntityType = Mockito.mock(EdmEntityType.class); + List propertyNames = Arrays.asList(PROPERTY_1); + Mockito.when(edmEntityType.getPropertyNames()).thenReturn(propertyNames); + + EdmProperty edmElement = Mockito.mock(EdmProperty.class); + Mockito.when(edmElement.getName()).thenReturn(PROPERTY_1); + Mockito.when(edmElement.isPrimitive()).thenReturn(true); + Mockito.when(edmElement.getMaxLength()).thenReturn(20); + Mockito.when(edmElement.getType()).thenReturn(EdmString.getInstance()); + Mockito.when(edmEntityType.getProperty(PROPERTY_1)).thenReturn(edmElement); + + } + + @Test + public void entitySimple() throws Exception { + +// Entity entity = new EntityImpl(); +// entity.addProperty(new PropertyImpl("Edm.String", PROPERTY_1, ValueType.PRIMITIVE, "Value_1")); + Entity entity = new EntityImpl(); + PropertyImpl property = new PropertyImpl("Edm.String", PROPERTY_1); + property.setValue(ValueType.PRIMITIVE, "Value_1"); + entity.addProperty(property); + + InputStream result = serializer.entity(edmEntityType, entity, contextUrl); + String resultString = streamToString(result); +// System.out.println(resultString); + Assert.assertEquals("{\"@odata.context\":\"http://localhost:8080/test.svc\",\"Property1\":\"Value_1\"}", + resultString); + } + + private String streamToString(InputStream result) throws IOException { + byte[] buffer = new byte[8192]; + StringBuilder sb = new StringBuilder(); + + int count = result.read(buffer); + while (count >= 0) { + sb.append(new String(buffer, 0, count, "UTF-8")); + count = result.read(buffer); + } + + return sb.toString(); + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java index ebe30e1b5..8cf00e9f1 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java @@ -30,6 +30,7 @@ import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.format.ODataFormat; +import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.commons.core.data.EntityImpl; import org.apache.olingo.commons.core.data.EntitySetImpl; import org.apache.olingo.commons.core.data.PropertyImpl; @@ -71,7 +72,7 @@ public class SampleJsonProcessor implements EntitySetProcessor, EntityProcessor ContextURL.getInstance(URI.create("dummyContextURL")))); LOG.info("Finished in " + (System.nanoTime() - time) / 1000 + " microseconds"); - response.setStatusCode(200); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toContentTypeString()); } @@ -90,7 +91,7 @@ public class SampleJsonProcessor implements EntitySetProcessor, EntityProcessor ContextURL.getInstance(URI.create("dummyContextURL")))); LOG.info("Finished in " + (System.nanoTime() - time) / 1000 + " microseconds"); - response.setStatusCode(200); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toContentTypeString()); } @@ -98,17 +99,14 @@ public class SampleJsonProcessor implements EntitySetProcessor, EntityProcessor Entity entity = new EntityImpl(); Property property = new PropertyImpl(); property.setName("PropertyString"); - property.setType("String"); //"dummyType"); property.setValue(ValueType.PRIMITIVE, "dummyValue"); entity.getProperties().add(property); Property propertyInt = new PropertyImpl(); propertyInt.setName("PropertyInt16"); - // propertyInt.setType("Edm.Int32"); propertyInt.setValue(ValueType.PRIMITIVE, 42); entity.getProperties().add(propertyInt); Property propertyGuid = new PropertyImpl(); propertyGuid.setName("PropertyGuid"); - propertyGuid.setType("Edm.Guid"); propertyGuid.setValue(ValueType.PRIMITIVE, UUID.randomUUID()); entity.getProperties().add(propertyGuid); return entity;