From cd23eb96f8f5c6e0fdb8c91ceef8187a2642ad9b Mon Sep 17 00:00:00 2001 From: Christian Amend Date: Tue, 14 Apr 2015 14:52:36 +0200 Subject: [PATCH] [OLINGO-604] Implement Action imports in TechSvc part 1 --- .../api/deserializer/DeserializerResult.java | 9 +- .../api/deserializer/ODataDeserializer.java | 2 +- .../api/serializer/ODataSerializer.java | 2 - .../server/api/uri/UriResourceAction.java | 3 +- .../server/api/uri/UriResourceFunction.java | 4 +- .../olingo/server/core/ContentNegotiator.java | 25 +- .../olingo/server/core/ODataHandler.java | 8 +- .../deserializer/DeserializerResultImpl.java | 12 +- .../json/ODataJsonDeserializer.java | 32 ++- .../olingo/server/tecsvc/data/ActionData.java | 242 ++++++++++++++++++ .../server/tecsvc/data/DataCreator.java | 39 ++- .../server/tecsvc/data/DataProvider.java | 17 +- .../TechnicalPrimitiveComplexProcessor.java | 70 ++++- ...aJsonDeserializerActionParametersTest.java | 20 +- 14 files changed, 413 insertions(+), 72 deletions(-) create mode 100644 lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/DeserializerResult.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/DeserializerResult.java index 9fcc3fcbc..c58612a21 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/DeserializerResult.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/DeserializerResult.java @@ -20,12 +20,13 @@ package org.apache.olingo.server.api.deserializer; import java.net.URI; import java.util.List; +import java.util.Map; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Parameter; -import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; /** * Result type for {@link ODataDeserializer} methods @@ -53,7 +54,11 @@ public interface DeserializerResult { * Returns the deserialized action-parameters of an {@link Entity} object. * @return a collection {@link Parameter} */ - List getActionParameter(); + /** + * Returns the deserialized action-parameters as key value pairs. + * @return the action parameters + */ + Map getActionParameters(); /** * Returns a Property or collections of properties (primitive & complex) diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java index 5a927ed37..c6ba00095 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/ODataDeserializer.java @@ -56,7 +56,7 @@ public interface ODataDeserializer { * Validates: parameter types, no double parameters, correct json types. * @param stream * @param edmAction - * @return {@link DeserializerResult#getActionParameter()} + * @return {@link DeserializerResult#getActionParameters()} * @throws DeserializerException */ DeserializerResult actionParameters(InputStream stream, EdmAction edmAction) throws DeserializerException; diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java index 3a2fbe0f8..c7f7bba4d 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/ODataSerializer.java @@ -18,8 +18,6 @@ */ package org.apache.olingo.server.api.serializer; -import java.io.InputStream; - import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Property; diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceAction.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceAction.java index 3ba7ce749..e916ce534 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceAction.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceAction.java @@ -28,7 +28,8 @@ import org.apache.olingo.commons.api.edm.EdmActionImport; public interface UriResourceAction extends UriResourcePartTyped { /** - * @return Action used in the resource path + * If the resource path specifies an action import this method will deliver the unbound action for the action import. + * @return Action used in the resource path or action import */ EdmAction getAction(); diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceFunction.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceFunction.java index 00db971c9..1caf000cd 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceFunction.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/UriResourceFunction.java @@ -31,7 +31,9 @@ import java.util.List; public interface UriResourceFunction extends UriResourcePartTyped { /** - * @return Function used in the resource path + * If the resource path specifies a function import this method will deliver the unbound function for the function + * import. + * @return Function used in the resource path or function import */ EdmFunction getFunction(); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java index 6a9d770c3..351b76913 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ContentNegotiator.java @@ -49,13 +49,14 @@ public class ContentNegotiator { default: return Arrays.asList( ODataFormat.JSON.getContentType(), - ODataFormat.JSON_NO_METADATA.getContentType()); + ODataFormat.JSON_NO_METADATA.getContentType(), + ODataFormat.APPLICATION_JSON.getContentType()); } } private static List getSupportedContentTypes( final CustomContentTypeSupport customContentTypeSupport, final RepresentationType representationType) - throws ContentNegotiatorException { + throws ContentNegotiatorException { final List defaultSupportedContentTypes = getDefaultSupportedContentTypes(representationType); final List result = customContentTypeSupport == null ? defaultSupportedContentTypes : customContentTypeSupport.modifySupportedContentTypes(defaultSupportedContentTypes, representationType); @@ -69,7 +70,7 @@ public class ContentNegotiator { public static ContentType doContentNegotiation(final FormatOption formatOption, final ODataRequest request, final CustomContentTypeSupport customContentTypeSupport, final RepresentationType representationType) - throws ContentNegotiatorException { + throws ContentNegotiatorException { final List supportedContentTypes = getSupportedContentTypes(customContentTypeSupport, representationType); final String acceptHeaderValue = request.getHeader(HttpHeader.ACCEPT); @@ -79,15 +80,15 @@ public class ContentNegotiator { final String formatString = formatOption.getFormat().trim(); final ODataFormat format = ODataFormat.JSON.name().equalsIgnoreCase(formatString) ? ODataFormat.JSON : - ODataFormat.XML.name().equalsIgnoreCase(formatString) ? ODataFormat.XML : - ODataFormat.ATOM.name().equalsIgnoreCase(formatString) ? ODataFormat.ATOM : null; + ODataFormat.XML.name().equalsIgnoreCase(formatString) ? ODataFormat.XML : + ODataFormat.ATOM.name().equalsIgnoreCase(formatString) ? ODataFormat.ATOM : null; try { result = getAcceptedType( AcceptType.fromContentType(format == null ? ContentType.create(formatOption.getFormat()) : format.getContentType()), supportedContentTypes); } catch (final IllegalArgumentException e) { - //Exception results in result = null for next check. + // Exception results in result = null for next check. } if (result == null) { throw new ContentNegotiatorException("Unsupported $format = " + formatString, @@ -142,9 +143,8 @@ public class ContentNegotiator { public static void checkSupport(final ContentType contentType, final CustomContentTypeSupport customContentTypeSupport, final RepresentationType representationType) - throws ContentNegotiatorException { - for (final ContentType supportedContentType : - getSupportedContentTypes(customContentTypeSupport, representationType)) { + throws ContentNegotiatorException { + for (ContentType supportedContentType : getSupportedContentTypes(customContentTypeSupport, representationType)) { if (AcceptType.fromContentType(supportedContentType).get(0).matches(contentType)) { return; } @@ -154,11 +154,10 @@ public class ContentNegotiator { } public static boolean isSupported(final ContentType contentType, - final CustomContentTypeSupport customContentTypeSupport, - final RepresentationType representationType) throws ContentNegotiatorException { + final CustomContentTypeSupport customContentTypeSupport, + final RepresentationType representationType) throws ContentNegotiatorException { - for (final ContentType supportedContentType : - getSupportedContentTypes(customContentTypeSupport, representationType)) { + for (ContentType supportedContentType : getSupportedContentTypes(customContentTypeSupport, representationType)) { if (AcceptType.fromContentType(supportedContentType).get(0).matches(contentType)) { return true; } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java index b01970d86..ad0bba040 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java @@ -322,7 +322,7 @@ public class ODataHandler { .processActionEntity(request, response, uriInfo, requestFormat, responseFormat); } break; - + case PRIMITIVE: responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, customContentTypeSupport, @@ -623,11 +623,7 @@ public class ODataHandler { private void checkContentTypeSupport(ContentType requestFormat, RepresentationType representationType) throws ODataHandlerException, ContentNegotiatorException { - if (!ContentNegotiator.isSupported(requestFormat, customContentTypeSupport, representationType)) { - final String contentTypeString = requestFormat.toContentTypeString(); - throw new ODataHandlerException("ContentType " + contentTypeString + " is not supported.", - ODataHandlerException.MessageKeys.UNSUPPORTED_CONTENT_TYPE, contentTypeString); - } + ContentNegotiator.checkSupport(requestFormat, customContentTypeSupport, representationType); } private void validateODataVersion(final ODataRequest request, final ODataResponse response) diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/DeserializerResultImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/DeserializerResultImpl.java index 1c85f3e7b..acfbe6965 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/DeserializerResultImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/DeserializerResultImpl.java @@ -20,7 +20,9 @@ package org.apache.olingo.server.core.deserializer; import java.net.URI; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; @@ -34,7 +36,7 @@ public class DeserializerResultImpl implements DeserializerResult { private EntityCollection entitySet; private ExpandOption expandOption; private Property property; - private List actionParametes; + private Map actionParametes; private List entityReferences; private DeserializerResultImpl() {} @@ -55,7 +57,7 @@ public class DeserializerResultImpl implements DeserializerResult { } @Override - public List getActionParameter() { + public Map getActionParameters() { return actionParametes; } @@ -78,7 +80,7 @@ public class DeserializerResultImpl implements DeserializerResult { private EntityCollection entitySet; private ExpandOption expandOption; private Property property; - private List actionParametes; + private Map actionParametes; private List entityReferences; public DeserializerResult build() { @@ -88,7 +90,7 @@ public class DeserializerResultImpl implements DeserializerResult { result.expandOption = expandOption; result.property = property; result.entityReferences = (entityReferences == null) ? new ArrayList() : entityReferences; - result.actionParametes = (actionParametes == null) ? new ArrayList() : actionParametes; + result.actionParametes = (actionParametes == null) ? new LinkedHashMap() : actionParametes; return result; } @@ -118,7 +120,7 @@ public class DeserializerResultImpl implements DeserializerResult { return this; } - public DeserializerResultBuilder actionParameters(final List actionParameters) { + public DeserializerResultBuilder actionParameters(final Map actionParameters) { this.actionParametes = actionParameters; return this; } 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 d92ac0c68..66da3fcfa 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 @@ -24,6 +24,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -191,9 +192,11 @@ public class ODataJsonDeserializer implements ODataDeserializer { throws DeserializerException { try { ObjectNode tree = parseJsonTree(stream); - List parameters = new ArrayList(); - consumeParameters(edmAction, tree, parameters); - assertJsonNodeIsEmpty(tree); + Map parameters = new LinkedHashMap(); + if (tree != null) { + consumeParameters(edmAction, tree, parameters); + assertJsonNodeIsEmpty(tree); + } return DeserializerResultImpl.with().actionParameters(parameters).build(); } catch (final JsonParseException e) { @@ -217,18 +220,18 @@ public class ODataJsonDeserializer implements ODataDeserializer { return tree; } - private void consumeParameters(final EdmAction edmAction, ObjectNode node, List parameters) + private void consumeParameters(final EdmAction edmAction, ObjectNode node, Map parameters) throws DeserializerException { List parameterNames = edmAction.getParameterNames(); if (edmAction.isBound()) { // The binding parameter must not occur in the payload. parameterNames = parameterNames.subList(1, parameterNames.size()); } - for (final String name : parameterNames) { - final EdmParameter edmParameter = edmAction.getParameter(name); + for (final String paramName : parameterNames) { + final EdmParameter edmParameter = edmAction.getParameter(paramName); Parameter parameter = new Parameter(); - parameter.setName(name); - JsonNode jsonNode = node.get(name); + parameter.setName(paramName); + JsonNode jsonNode = node.get(paramName); switch (edmParameter.getType().getKind()) { case PRIMITIVE: @@ -237,11 +240,11 @@ public class ODataJsonDeserializer implements ODataDeserializer { if (jsonNode == null || jsonNode.isNull()) { if (!edmParameter.isNullable()) { throw new DeserializerException("Non-nullable parameter not present or null", - DeserializerException.MessageKeys.INVALID_NULL_PARAMETER, name); + DeserializerException.MessageKeys.INVALID_NULL_PARAMETER, paramName); } if (edmParameter.isCollection()) { - throw new DeserializerException("Collection must not be null for parameter: " + name, - DeserializerException.MessageKeys.INVALID_NULL_PARAMETER, name); + throw new DeserializerException("Collection must not be null for parameter: " + paramName, + DeserializerException.MessageKeys.INVALID_NULL_PARAMETER, paramName); } parameter.setValue(ValueType.PRIMITIVE, null); } else { @@ -250,8 +253,8 @@ public class ODataJsonDeserializer implements ODataDeserializer { edmParameter.isNullable(), edmParameter.getMaxLength(), edmParameter.getPrecision(), edmParameter .getScale(), true, edmParameter.getMapping(), jsonNode); parameter.setValue(consumePropertyNode.getValueType(), consumePropertyNode.getValue()); - parameters.add(parameter); - node.remove(name); + parameters.put(paramName, parameter); + node.remove(paramName); } break; case COMPLEX: @@ -260,7 +263,8 @@ public class ODataJsonDeserializer implements ODataDeserializer { DeserializerException.MessageKeys.NOT_IMPLEMENTED); default: throw new DeserializerException("Invalid type kind " + edmParameter.getType().getKind().toString() - + " for action parameter: " + name, DeserializerException.MessageKeys.INVALID_ACTION_PARAMETER_TYPE, name); + + " for action parameter: " + paramName, DeserializerException.MessageKeys.INVALID_ACTION_PARAMETER_TYPE, + paramName); } } } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java new file mode 100644 index 000000000..d2a852529 --- /dev/null +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/ActionData.java @@ -0,0 +1,242 @@ +/* + * 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.tecsvc.data; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.EntityCollection; +import org.apache.olingo.commons.api.data.Parameter; +import org.apache.olingo.commons.api.data.Property; +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.http.HttpStatusCode; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.tecsvc.data.DataProvider.DataProviderException; + +public class ActionData { + + protected static Property primitiveAction(String name, Map parameters) + throws DataProviderException { + if ("UARTString".equals(name)) { + return DataCreator.createPrimitive(null, "UARTString string value"); + } + throw new DataProviderException("Action " + name + " is not yet implemented."); + } + + protected static Property primitiveCollectionAction(String name, Map parameters) + throws DataProviderException { + if ("UARTCollStringTwoParam".equals(name)) { + List collectionValues = new ArrayList(); + int loopCount = (Integer) parameters.get("ParameterInt16").asPrimitive(); + EdmPrimitiveType primDuration = OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Duration); + BigDecimal duration = (BigDecimal) parameters.get("ParameterDuration").asPrimitive(); + BigDecimal addValue = new BigDecimal(1); + for (int i = 0; i < loopCount; i++) { + try { + String value = primDuration.valueToString(duration, false, null, null, null, null); + collectionValues.add(value); + } catch (EdmPrimitiveTypeException e) { + throw new DataProviderException("EdmPrimitiveTypeException", e); + } + duration = duration.add(addValue); + } + return DataCreator.createPrimitiveCollection(null, collectionValues); + } + throw new DataProviderException("Action " + name + " is not yet implemented."); + } + + protected static Property complexAction(String name, Map parameters) throws DataProviderException { + if ("UARTCTTwoPrimParam".equals(name)) { + Integer number = (Integer) parameters.get("ParameterInt16").asPrimitive(); + if (number == null) { + number = new Integer(32767); + } + Property complexProp = createCTTwoPrimComplexProperty(number, "UARTCTTwoPrimParam string value"); + + return complexProp; + } + throw new DataProviderException("Action " + name + " is not yet implemented."); + } + + private static Property createCTTwoPrimComplexProperty(Integer number, String text) { + List props = new ArrayList(); + Property propInt = new Property(); + propInt.setName("PropertyInt16"); + propInt.setValue(ValueType.PRIMITIVE, number); + props.add(propInt); + Property propString = new Property(); + propString.setName("PropertyString"); + propString.setValue(ValueType.PRIMITIVE, text); + props.add(propString); + + Property complexProp = new Property(); + complexProp.setValue(ValueType.COMPLEX, props); + return complexProp; + } + + protected static Property complexCollectionAction(String name, Map parameters) + throws DataProviderException { + if ("UARTCollCTTwoPrimParam".equals(name)) { + ArrayList complexCollection = new ArrayList(); + complexCollection.add(createCTTwoPrimComplexProperty(16, "Test123")); + complexCollection.add(createCTTwoPrimComplexProperty(17, "Test456")); + complexCollection.add(createCTTwoPrimComplexProperty(18, "Test678")); + + Integer number = (Integer) parameters.get("ParameterInt16").asPrimitive(); + if (number != null && number >= 0 && number < complexCollection.size()) { + complexCollection.subList(number, complexCollection.size() - 1).clear(); + } + + Property complexCollProperty = new Property(); + complexCollProperty.setValue(ValueType.COLLECTION_COMPLEX, complexCollection); + return complexCollProperty; + } + throw new DataProviderException("Action " + name + " is not yet implemented."); + } + + protected static Entity entityAction(String name, Map parameters) throws DataProviderException { + if ("UARTETTwoKeyTwoPrimParam".equals(name)) { + Integer number = (Integer) parameters.get("ParameterInt16").asPrimitive(); + if (number == null) { + number = 0; + } + EntityCollection entityCollection = new DataCreator().getData().get("ESTwoKeyTwoPrim"); + for (Entity entity : entityCollection.getEntities()) { + if (number.equals(entity.getProperty("PropertyInt16").asPrimitive())) { + return entity; + } + } + // Entity Not found + throw new DataProviderException("Entity not found with key: " + number, HttpStatusCode.NOT_FOUND); + } else if ("UARTETAllPrimParam".equals(name)) { + Calendar date = (Calendar) parameters.get("ParameterDate").asPrimitive(); + EntityCollection entityCollection = new DataCreator().getData().get("ESAllPrim"); + if (date != null) { + boolean freeKey; + Short key = 0; + do { + freeKey = true; + for (Entity entity : entityCollection.getEntities()) { + if (key.equals(entity.getProperty("PropertyInt16"))) { + freeKey = false; + break; + } + } + key++; + } while (!freeKey); + // TODO: Set create response code + return createAllPrimEntity(key, "UARTETAllPrimParam string value", date); + } else { + return entityCollection.getEntities().get(0); + } + } + throw new DataProviderException("Action " + name + " is not yet implemented."); + } + + private static Entity createAllPrimEntity(Short key, String val, Calendar date) { + return new Entity().addProperty(DataCreator.createPrimitive("PropertyInt16", key)) + .addProperty(DataCreator.createPrimitive("PropertyString", val)) + .addProperty(DataCreator.createPrimitive("PropertyBoolean", false)) + .addProperty(DataCreator.createPrimitive("PropertyByte", null)) + .addProperty(DataCreator.createPrimitive("PropertySByte", null)) + .addProperty(DataCreator.createPrimitive("PropertyInt32", null)) + .addProperty(DataCreator.createPrimitive("PropertyInt64", null)) + .addProperty(DataCreator.createPrimitive("PropertySingle", null)) + .addProperty(DataCreator.createPrimitive("PropertyDouble", null)) + .addProperty(DataCreator.createPrimitive("PropertyDecimal", null)) + .addProperty(DataCreator.createPrimitive("PropertyBinary", null)) + .addProperty(DataCreator.createPrimitive("PropertyDate", date)) + .addProperty(DataCreator.createPrimitive("PropertyDateTimeOffset", null)) + .addProperty(DataCreator.createPrimitive("PropertyDuration", null)) + .addProperty(DataCreator.createPrimitive("PropertyGuid", null)) + .addProperty(DataCreator.createPrimitive("PropertyTimeOfDay", null)); + } + + protected static EntityCollection entityCollectionAction(String name, Map parameters) + throws DataProviderException { + if ("UARTCollETKeyNavParam".equals(name)) { + Short number = (Short) parameters.get("ParameterInt16").asPrimitive(); + EntityCollection collection = new EntityCollection(); + if (number != null && number > 0) { + for (int i = 1; i <= number; i++) { + collection.getEntities().add(createETKeyNavEntity(number)); + } + } else { + return collection; + } + } else if ("UARTCollETAllPrimParam".equals(name)) { + Calendar timeOfDay = (Calendar) parameters.get("ParameterTimeOfDay").asPrimitive(); + EntityCollection collection = new EntityCollection(); + if (timeOfDay != null) { + int count = timeOfDay.get(Calendar.HOUR_OF_DAY); + for (short i = 1; i <= count; i++) { + collection.getEntities().add(createAllPrimEntity(i, "UARTCollETAllPrimParam int16 value: " + i, null)); + } + } else { + return collection; + } + } + throw new DataProviderException("Action " + name + " is not yet implemented."); + } + + @SuppressWarnings("unchecked") + private static Entity createETKeyNavEntity(Short number) { + return new Entity() + .addProperty(DataCreator.createPrimitive("PropertyInt16", number)) + .addProperty(DataCreator.createPrimitive("PropertyString", "UARTCollETKeyNavParam int16 value: " + number)) + .addProperty( + DataCreator.createComplex("PropertyCompNav", DataCreator.createPrimitive("PropertyInt16", 0))) + .addProperty(createKeyNavAllPrimComplexValue("PropertyCompAllPrim")).addProperty( + DataCreator.createComplex("PropertyCompTwoPrim", DataCreator.createPrimitive("PropertyInt16", 0), + DataCreator.createPrimitive("PropertyString", ""))).addProperty( + DataCreator.createPrimitiveCollection("CollPropertyString")) + .addProperty(DataCreator.createPrimitiveCollection("CollPropertyInt16")).addProperty( + DataCreator.createComplexCollection("CollPropertyComp")) + .addProperty( + DataCreator.createComplex("PropertyCompCompNav", DataCreator.createPrimitive("PropertyString", ""), + DataCreator.createComplex("PropertyCompNav", DataCreator.createPrimitive("PropertyInt16", 0)))); + } + + protected static Property createKeyNavAllPrimComplexValue(final String name) { + return DataCreator.createComplex(name, + DataCreator.createPrimitive("PropertyString", ""), + DataCreator.createPrimitive("PropertyBinary", new byte[] {}), + DataCreator.createPrimitive("PropertyBoolean", false), + DataCreator.createPrimitive("PropertyByte", 0), + DataCreator.createPrimitive("PropertyDate", null), + DataCreator.createPrimitive("PropertyDateTimeOffset", null), + DataCreator.createPrimitive("PropertyDecimal", 0), + DataCreator.createPrimitive("PropertySingle", 0), + DataCreator.createPrimitive("PropertyDouble", 0), + DataCreator.createPrimitive("PropertyDuration", 0), + DataCreator.createPrimitive("PropertyGuid", null), + DataCreator.createPrimitive("PropertyInt16", null), + DataCreator.createPrimitive("PropertyInt32", null), + DataCreator.createPrimitive("PropertyInt64", null), + DataCreator.createPrimitive("PropertySByte", null), + DataCreator.createPrimitive("PropertyTimeOfDay", null)); + } +} diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java index 3cef7e621..37b775249 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataCreator.java @@ -57,6 +57,7 @@ public class DataCreator { data.put("ESTwoKeyNav", createESTwoKeyNav()); data.put("ESCompCollComp", createESCompCollComp()); data.put("ESServerSidePaging", createESServerSidePaging()); + data.put("ESTwoKeyTwoPrim", createESTwoKeyTwoPrim()); // No data available but to allow an insert operation create empty EntitySets data.put("ESAllNullable", new EntityCollection()); @@ -64,7 +65,6 @@ public class DataCreator { data.put("ESTwoBase", new EntityCollection()); data.put("ESBaseTwoKeyNav", new EntityCollection()); data.put("ESBaseTwoKeyTwoPrim", new EntityCollection()); - data.put("ESTwoKeyTwoPrim", new EntityCollection()); data.put("ESCompCollAllPrim", new EntityCollection()); data.put("ESKeyTwoKeyComp", new EntityCollection()); data.put("ESFourKeyAlias", new EntityCollection()); @@ -83,6 +83,19 @@ public class DataCreator { return data; } + private EntityCollection createESTwoKeyTwoPrim() { + EntityCollection entitySet = new EntityCollection(); + entitySet.getEntities().add(createETTwoKeyTwoPrimEntity(32767, "Test String1")); + entitySet.getEntities().add(createETKeyNavEntity(-365, "Test String2")); + entitySet.getEntities().add(createETKeyNavEntity(-32766, "Test String3")); + return entitySet; + } + + private Entity createETTwoKeyTwoPrimEntity(int propertyInt16, String propertyString) { + return new Entity().addProperty(createPrimitive("PropertyInt16", propertyInt16)) + .addProperty(createPrimitive("PropertyString", propertyString)); + } + private EntityCollection createESServerSidePaging() { EntityCollection entitySet = new EntityCollection(); @@ -136,8 +149,10 @@ public class DataCreator { @SuppressWarnings("unchecked") private Entity createESTwoKeyNavEntity(int propertyInt16, String propertyString) { - return new Entity().addProperty(createPrimitive("PropertyInt16", propertyInt16)) - .addProperty(createPrimitive("PropertyString", propertyString)).addProperty( + return new Entity() + .addProperty(createPrimitive("PropertyInt16", propertyInt16)) + .addProperty(createPrimitive("PropertyString", propertyString)) + .addProperty( createComplex("PropertyComp", createPrimitive("PropertyInt16", 11), createComplex("PropertyComp", createPrimitive("PropertyString", "StringValue"), createPrimitive("PropertyBinary", new byte[] { 1, 35, 69, 103, -119, -85, -51, -17 }), @@ -149,7 +164,8 @@ public class DataCreator { createPrimitive("PropertyInt16", Short.MAX_VALUE), createPrimitive("PropertyInt32", Integer.MAX_VALUE), createPrimitive("PropertyInt64", Long.MAX_VALUE), createPrimitive("PropertySByte", Byte.MAX_VALUE), - createPrimitive("PropertyTimeOfDay", getTime(21, 5, 59))))).addProperty( + createPrimitive("PropertyTimeOfDay", getTime(21, 5, 59))))) + .addProperty( createComplex("PropertyCompNav", createPrimitive("PropertyInt16", 1), createKeyNavAllPrimComplexValue("PropertyComp"))) .addProperty(createComplexCollection("CollPropertyComp")) @@ -159,7 +175,7 @@ public class DataCreator { createPrimitive("PropertyString", "11"))); } - private Property createKeyNavAllPrimComplexValue(final String name) { + protected Property createKeyNavAllPrimComplexValue(final String name) { return createComplex(name, createPrimitive("PropertyString", "First Resource - positive values"), createPrimitive("PropertyBinary", new byte[] { 1, 35, 69, 103, -119, -85, -51, -17 }), createPrimitive("PropertyBoolean", true), createPrimitive("PropertyByte", 255), @@ -335,7 +351,7 @@ public class DataCreator { 3.2100000000000000E+03)).addProperty(createPrimitiveCollection("CollPropertyDecimal", 12, -2, 1234)) .addProperty( createPrimitiveCollection("CollPropertyBinary", new byte[] { (byte) 0xAB, (byte) 0xCD, (byte) 0xEF }, new - byte[] { 0x01, 0x23, 0x45 }, + byte[] { 0x01, 0x23, 0x45 }, new byte[] { 0x54, 0x67, (byte) 0x89 })).addProperty( createPrimitiveCollection("CollPropertyDate", getDateTime(1958, 12, 3, 0, 0, 0), getDateTime(1999, 8, 5, 0, 0, 0), getDateTime(2013, 6, 25, 0, 0, 0))).addProperty( @@ -362,7 +378,8 @@ public class DataCreator { } private EntityCollection createESMixPrimCollComp() { - @SuppressWarnings("unchecked") final Property complexCollection = createComplexCollection("CollPropertyComp", + @SuppressWarnings("unchecked") + final Property complexCollection = createComplexCollection("CollPropertyComp", Arrays.asList(createPrimitive("PropertyInt16", 123), createPrimitive("PropertyString", "TEST 1")), Arrays.asList(createPrimitive("PropertyInt16", 456), createPrimitive("PropertyString", "TEST 2")), Arrays.asList(createPrimitive("PropertyInt16", 789), createPrimitive("PropertyString", "TEST 3"))); @@ -522,7 +539,7 @@ public class DataCreator { setLink(entitySet.getEntities().get(0), "NavPropertyETMediaOne", esMediaTargets.get(0)); setLink(entitySet.getEntities().get(1), "NavPropertyETMediaOne", esMediaTargets.get(1)); setLink(entitySet.getEntities().get(2), "NavPropertyETMediaOne", esMediaTargets.get(2)); - + // NavPropertyETMediaMany setLinks(entitySet.getEntities().get(0), "NavPropertyETMediaMany", esMediaTargets.get(0), esMediaTargets.get(2)); setLinks(entitySet.getEntities().get(1), "NavPropertyETMediaMany", esMediaTargets.get(2)); @@ -583,7 +600,7 @@ public class DataCreator { return new Property(null, name, ValueType.COLLECTION_COMPLEX, complexCollection); } - private Calendar getDateTime(final int year, final int month, final int day, + private static Calendar getDateTime(final int year, final int month, final int day, final int hour, final int minute, final int second) { Calendar dateTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")); dateTime.clear(); @@ -591,7 +608,7 @@ public class DataCreator { return dateTime; } - private Calendar getTime(final int hour, final int minute, final int second) { + private static Calendar getTime(final int hour, final int minute, final int second) { Calendar time = Calendar.getInstance(TimeZone.getTimeZone("GMT")); time.clear(); time.set(Calendar.HOUR_OF_DAY, hour); @@ -600,7 +617,7 @@ public class DataCreator { return time; } - private Timestamp getTimestamp(final int year, final int month, final int day, + private static Timestamp getTimestamp(final int year, final int month, final int day, final int hour, final int minute, final int second, final int nanosecond) { Timestamp timestamp = new Timestamp(getDateTime(year, month, day, hour, minute, second).getTimeInMillis()); timestamp.setNanos(nanosecond); diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java index 6bf79fa3b..878efce76 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/data/DataProvider.java @@ -31,6 +31,7 @@ import org.apache.olingo.commons.api.data.ComplexValue; 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.Parameter; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmComplexType; @@ -72,8 +73,9 @@ public class DataProvider { return entitySet == null ? null : read(edmEntitySet.getEntityType(), entitySet, keys); } - public Entity read(final EdmEntityType edmEntityType, final EntityCollection entitySet, final List keys) - throws DataProviderException { + public Entity + read(final EdmEntityType edmEntityType, final EntityCollection entitySet, final List keys) + throws DataProviderException { try { for (final Entity entity : entitySet.getEntities()) { boolean found = true; @@ -488,6 +490,16 @@ public class DataProvider { return FunctionData.primitiveComplexFunction(function.getName(), parameters, data); } + public Property processActionPrimitive(String name, Map actionParameters) + throws DataProviderException { + return ActionData.primitiveAction(name, actionParameters); + } + + public Property processActionPrimitiveCollection(String name, Map actionParameters) + throws DataProviderException { + return ActionData.primitiveCollectionAction(name, actionParameters); + } + public void setEdm(final Edm edm) { this.edm = edm; } @@ -511,4 +523,5 @@ public class DataProvider { super(message, statusCode.getStatusCode(), Locale.ROOT); } } + } diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java index 303800f7b..d8116705b 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java @@ -27,6 +27,7 @@ import org.apache.olingo.commons.api.data.ContextURL; import org.apache.olingo.commons.api.data.ContextURL.Builder; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmAction; import org.apache.olingo.commons.api.edm.EdmComplexType; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmPrimitiveType; @@ -45,6 +46,7 @@ import org.apache.olingo.server.api.ODataRequest; import org.apache.olingo.server.api.ODataResponse; import org.apache.olingo.server.api.ServiceMetadata; import org.apache.olingo.server.api.deserializer.DeserializerException; +import org.apache.olingo.server.api.deserializer.DeserializerResult; import org.apache.olingo.server.api.processor.ActionComplexCollectionProcessor; import org.apache.olingo.server.api.processor.ActionComplexProcessor; import org.apache.olingo.server.api.processor.ActionPrimitiveCollectionProcessor; @@ -61,10 +63,12 @@ import org.apache.olingo.server.api.serializer.PrimitiveSerializerOptions; import org.apache.olingo.server.api.serializer.PrimitiveValueSerializerOptions; import org.apache.olingo.server.api.serializer.RepresentationType; import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerResult; import org.apache.olingo.server.api.uri.UriHelper; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceAction; import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.UriResourceProperty; @@ -110,8 +114,43 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor public void processActionPrimitive(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo, final ContentType requestFormat, final ContentType responseFormat) throws ODataApplicationException, DeserializerException, SerializerException { - throw new ODataApplicationException("Not supported yet.", - HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + EdmAction action = checkBoundAndExtractAction(uriInfo); + DeserializerResult deserializerResult = + odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) + .actionParameters(request.getBody(), action); + + Property property = dataProvider.processActionPrimitive(action.getName(), deserializerResult.getActionParameters()); + EdmPrimitiveType type = (EdmPrimitiveType) action.getReturnType().getType(); + if (property.isNull()) { + if (action.getReturnType().isNullable()) { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } else { + // Not nullable return type so we have to give back a 500 + throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); + } + } else { + ContextURL contextURL = ContextURL.with().type(type).build(); + PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextURL).build(); + + SerializerResult result = odata.createSerializer(ODataFormat.fromContentType(responseFormat)).primitive(type, + property, options); + + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } + } + + private EdmAction checkBoundAndExtractAction(final UriInfo uriInfo) throws ODataApplicationException { + final UriInfoResource resource = uriInfo.asUriInfoResource(); + List uriResourceParts = resource.getUriResourceParts(); + if (uriResourceParts.size() > 1) { + throw new ODataApplicationException("Bound acctions not supported yet.", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + UriResourceAction uriResourceAction = (UriResourceAction) uriResourceParts.get(0); + EdmAction action = uriResourceAction.getAction(); + return action; } @Override @@ -188,8 +227,31 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor public void processActionComplexCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, DeserializerException, SerializerException { - throw new ODataApplicationException("Not supported yet.", - HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + EdmAction action = checkBoundAndExtractAction(uriInfo); + DeserializerResult deserializerResult = + odata.createDeserializer(ODataFormat.fromContentType(requestFormat)) + .actionParameters(request.getBody(), action); + + Property property = dataProvider.processActionPrimitive(action.getName(), deserializerResult.getActionParameters()); + + if (property.isNull()) { + // Collection Propertys must never be null + throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); + } else if (property.asCollection().contains(null) && !action.getReturnType().isNullable()) { + // Not nullable return type but array contains a null value + throw new ODataApplicationException("The action could no be executed", 500, Locale.ROOT); + } + EdmComplexType type = (EdmComplexType) action.getReturnType().getType(); + ContextURL contextURL = ContextURL.with().type(type).asCollection().build(); + ComplexSerializerOptions options = ComplexSerializerOptions.with().contextURL(contextURL).build(); + + SerializerResult result = + odata.createSerializer(ODataFormat.fromContentType(responseFormat)).complexCollection(serviceMetadata, type, + property, options); + + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setContent(result.getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); } @Override diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java index 80c7c4748..01166a220 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerActionParametersTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.math.BigDecimal; -import java.util.List; +import java.util.Map; import org.apache.olingo.commons.api.data.Parameter; import org.apache.olingo.commons.api.edm.FullQualifiedName; @@ -38,7 +38,7 @@ public class ODataJsonDeserializerActionParametersTest extends AbstractODataDese @Test public void empty() throws Exception { final String input = "{}"; - final List parameters = deserialize(input, "UART"); + final Map parameters = deserialize(input, "UART"); assertNotNull(parameters); assertTrue(parameters.isEmpty()); } @@ -46,13 +46,13 @@ public class ODataJsonDeserializerActionParametersTest extends AbstractODataDese @Test public void primitive() throws Exception { final String input = "{\"ParameterDuration\":\"P42DT11H22M33S\",\"ParameterInt16\":42}"; - final List parameters = deserialize(input, "UARTTwoParam"); + final Map parameters = deserialize(input, "UARTTwoParam"); assertNotNull(parameters); assertEquals(2, parameters.size()); - Parameter parameter = parameters.get(0); + Parameter parameter = parameters.get("ParameterInt16"); assertNotNull(parameter); assertEquals((short) 42, parameter.getValue()); - parameter = parameters.get(1); + parameter = parameters.get("ParameterDuration"); assertNotNull(parameter); assertEquals(BigDecimal.valueOf(3669753), parameter.getValue()); } @@ -60,7 +60,7 @@ public class ODataJsonDeserializerActionParametersTest extends AbstractODataDese @Test public void boundEmpty() throws Exception { final String input = "{}"; - final List parameters = deserialize(input, "BAETAllPrimRT", "ETAllPrim"); + final Map parameters = deserialize(input, "BAETAllPrimRT", "ETAllPrim"); assertNotNull(parameters); assertTrue(parameters.isEmpty()); } @@ -95,17 +95,17 @@ public class ODataJsonDeserializerActionParametersTest extends AbstractODataDese deserialize("{\"ParameterInt16\":\"42\"}", "UARTParam"); } - private List deserialize(final String input, final String actionName) throws DeserializerException { + private Map deserialize(final String input, final String actionName) throws DeserializerException { return OData.newInstance().createDeserializer(ODataFormat.JSON) .actionParameters(new ByteArrayInputStream(input.getBytes()), - edm.getUnboundAction(new FullQualifiedName("Namespace1_Alias", actionName))).getActionParameter(); + edm.getUnboundAction(new FullQualifiedName("Namespace1_Alias", actionName))).getActionParameters(); } - private List deserialize(final String input, final String actionName, final String typeName) + private Map deserialize(final String input, final String actionName, final String typeName) throws DeserializerException { return OData.newInstance().createDeserializer(ODataFormat.JSON) .actionParameters(new ByteArrayInputStream(input.getBytes()), edm.getBoundAction(new FullQualifiedName("Namespace1_Alias", actionName), - new FullQualifiedName("Namespace1_Alias", typeName), false)).getActionParameter(); + new FullQualifiedName("Namespace1_Alias", typeName), false)).getActionParameters(); } }