diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/DemoEntityActionResult.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/DemoEntityActionResult.java new file mode 100644 index 000000000..9d331c79e --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/DemoEntityActionResult.java @@ -0,0 +1,45 @@ +/* + * 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 myservice.mynamespace.data; + +import org.apache.olingo.commons.api.data.Entity; + +public class DemoEntityActionResult { + private Entity entity; + private boolean created = false; + + public Entity getEntity() { + return entity; + } + + public DemoEntityActionResult setEntity(final Entity entity) { + this.entity = entity; + return this; + } + + public boolean isCreated() { + return created; + } + + public DemoEntityActionResult setCreated(final boolean created) { + this.created = created; + return this; + } + +} diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/Storage.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/Storage.java index 12b373f6b..243dd8c02 100644 --- a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/Storage.java +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/Storage.java @@ -23,11 +23,14 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Locale; +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.EdmAction; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; @@ -47,6 +50,10 @@ public class Storage { private List productList; private List categoryList; + public static final String ACTION_PROVIDE_DISCOUNT = "DiscountProducts"; + public static final String ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT = "DiscountProduct"; + public static final String AMOUNT_PROPERTY = "Amount"; + public static final String PRICE_PROPERTY= "Price"; public Storage() { @@ -378,6 +385,7 @@ public class Storage { entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Notebook Basic 15")); entity.addProperty(new Property(null, "Description", ValueType.PRIMITIVE, "Notebook Basic, 1.7GHz - 15 XGA - 1024MB DDR2 SDRAM - 40GB")); + entity.addProperty(new Property(null, "Price", ValueType.PRIMITIVE, 100)); entity.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); entity.setId(createId(entity, "ID")); productList.add(entity); @@ -387,6 +395,7 @@ public class Storage { entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Notebook Professional 17")); entity.addProperty(new Property(null, "Description", ValueType.PRIMITIVE, "Notebook Professional, 2.8GHz - 15 XGA - 8GB DDR3 RAM - 500GB")); + entity.addProperty(new Property(null, "Price", ValueType.PRIMITIVE, 200)); entity.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); entity.setId(createId(entity, "ID")); productList.add(entity); @@ -396,6 +405,7 @@ public class Storage { entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "1UMTS PDA")); entity.addProperty(new Property(null, "Description", ValueType.PRIMITIVE, "Ultrafast 3G UMTS/HSDPA Pocket PC, supports GSM network")); + entity.addProperty(new Property(null, "Price", ValueType.PRIMITIVE, 300)); entity.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); entity.setId(createId(entity, "ID")); productList.add(entity); @@ -405,6 +415,7 @@ public class Storage { entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Comfort Easy")); entity.addProperty(new Property(null, "Description", ValueType.PRIMITIVE, "32 GB Digital Assitant with high-resolution color screen")); + entity.addProperty(new Property(null, "Price", ValueType.PRIMITIVE, 100)); entity.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); entity.setId(createId(entity, "ID")); productList.add(entity); @@ -414,6 +425,7 @@ public class Storage { entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Ergo Screen")); entity.addProperty(new Property(null, "Description", ValueType.PRIMITIVE, "19 Optimum Resolution 1024 x 768 @ 85Hz, resolution 1280 x 960")); + entity.addProperty(new Property(null, "Price", ValueType.PRIMITIVE, 400)); entity.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); entity.setId(createId(entity, "ID")); productList.add(entity); @@ -423,6 +435,7 @@ public class Storage { entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Flat Basic")); entity.addProperty(new Property(null, "Description", ValueType.PRIMITIVE, "Optimum Hi-Resolution max. 1600 x 1200 @ 85Hz, Dot Pitch: 0.24mm")); + entity.addProperty(new Property(null, "Price", ValueType.PRIMITIVE, 100)); entity.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); entity.setId(createId(entity, "ID")); productList.add(entity); @@ -479,4 +492,35 @@ public class Storage { } return entity.getType(); } + + public EntityCollection processBoundActionEntityCollection(EdmAction action, Map parameters) { + EntityCollection collection = new EntityCollection(); + if (ACTION_PROVIDE_DISCOUNT.equals(action.getName())) { + for (Entity entity : categoryList) { + Entity en = getRelatedEntity(entity, (EdmEntityType) action.getReturnType().getType()); + Integer currentValue = (Integer)en.getProperty(PRICE_PROPERTY).asPrimitive(); + Integer newValue = currentValue - (Integer)parameters.get(AMOUNT_PROPERTY).asPrimitive(); + en.getProperty(PRICE_PROPERTY).setValue(ValueType.PRIMITIVE, newValue); + collection.getEntities().add(en); + } + } + return collection; + } + + public DemoEntityActionResult processBoundActionEntity(EdmAction action, Map parameters, + List keyParams) throws ODataApplicationException { + DemoEntityActionResult result = new DemoEntityActionResult(); + if (ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT.equals(action.getName())) { + for (Entity entity : categoryList) { + Entity en = getRelatedEntity(entity, (EdmEntityType) action.getReturnType().getType(), keyParams); + Integer currentValue = (Integer)en.getProperty(PRICE_PROPERTY).asPrimitive(); + Integer newValue = currentValue - (Integer)parameters.get(AMOUNT_PROPERTY).asPrimitive(); + en.getProperty(PRICE_PROPERTY).setValue(ValueType.PRIMITIVE, newValue); + result.setEntity(en); + result.setCreated(true); + return result; + } + } + return null; + } } diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoActionProcessor.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoActionProcessor.java index e2aaed19c..067d3911f 100644 --- a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoActionProcessor.java +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoActionProcessor.java @@ -18,12 +18,21 @@ */ package myservice.mynamespace.service; +import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.ContextURL.Builder; +import org.apache.olingo.commons.api.data.ContextURL.Suffix; +import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Parameter; import org.apache.olingo.commons.api.edm.EdmAction; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; @@ -32,17 +41,28 @@ 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.ODataDeserializer; +import org.apache.olingo.server.api.prefer.Preferences.Return; +import org.apache.olingo.server.api.prefer.PreferencesApplied; +import org.apache.olingo.server.api.processor.ActionEntityCollectionProcessor; +import org.apache.olingo.server.api.processor.ActionEntityProcessor; import org.apache.olingo.server.api.processor.ActionVoidProcessor; +import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; +import org.apache.olingo.server.api.serializer.EntitySerializerOptions; import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourceAction; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import myservice.mynamespace.data.DemoEntityActionResult; import myservice.mynamespace.data.Storage; -public class DemoActionProcessor implements ActionVoidProcessor { +public class DemoActionProcessor implements ActionVoidProcessor, ActionEntityCollectionProcessor, ActionEntityProcessor { private OData odata; private Storage storage; - + private ServiceMetadata serviceMetadata; + public DemoActionProcessor(final Storage storage) { this.storage = storage; } @@ -50,6 +70,7 @@ public class DemoActionProcessor implements ActionVoidProcessor { @Override public void init(final OData odata, final ServiceMetadata serviceMetadata) { this.odata = odata; + this.serviceMetadata = serviceMetadata; } @Override @@ -82,4 +103,157 @@ public class DemoActionProcessor implements ActionVoidProcessor { response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); } + + @Override + public void processActionEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { + + EdmAction action = null; + Map parameters = new HashMap(); + DemoEntityActionResult entityResult = null; + if (requestFormat == null) { + throw new ODataApplicationException("The content type has not been set in the request.", + HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + + final ODataDeserializer deserializer = odata.createDeserializer(requestFormat); + final List resourcePaths = uriInfo.asUriInfoResource().getUriResourceParts(); + UriResourceEntitySet boundEntity = (UriResourceEntitySet) resourcePaths.get(0); + if (resourcePaths.size() > 1) { + if (resourcePaths.get(1) instanceof UriResourceNavigation) { + action = ((UriResourceAction) resourcePaths.get(2)) + .getAction(); + throw new ODataApplicationException("Action " + action.getName() + " is not yet implemented.", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH); + } else if (resourcePaths.get(0) instanceof UriResourceEntitySet) { + action = ((UriResourceAction) resourcePaths.get(1)) + .getAction(); + parameters = deserializer.actionParameters(request.getBody(), action) + .getActionParameters(); + entityResult = + storage.processBoundActionEntity(action, parameters, boundEntity.getKeyPredicates()); + } + } + final EdmEntitySet edmEntitySet = boundEntity.getEntitySet(); + final EdmEntityType type = (EdmEntityType) action.getReturnType().getType(); + + if (entityResult == null || entityResult.getEntity() == null) { + 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 not be executed.", + HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); + } + } else { + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + response.setContent(odata.createSerializer(responseFormat).entity( + serviceMetadata, + type, + entityResult.getEntity(), + EntitySerializerOptions.with() + .contextURL(isODataMetadataNone(responseFormat) ? null : + getContextUrl(action.getReturnedEntitySet(edmEntitySet), type, true)) + .build()) + .getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode((entityResult.isCreated() ? HttpStatusCode.CREATED : HttpStatusCode.OK) + .getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toValueString()); + } + if (entityResult.isCreated()) { + final String location = request.getRawBaseUri() + '/' + + odata.createUriHelper().buildCanonicalURL(edmEntitySet, entityResult.getEntity()); + response.setHeader(HttpHeader.LOCATION, location); + if (returnPreference == Return.MINIMAL) { + response.setHeader(HttpHeader.ODATA_ENTITY_ID, location); + } + } + if (entityResult.getEntity().getETag() != null) { + response.setHeader(HttpHeader.ETAG, entityResult.getEntity().getETag()); + } + } + } + + @Override + public void processActionEntityCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException { + + Map parameters = new HashMap(); + EdmAction action = null; + EntityCollection collection = null; + + if (requestFormat == null) { + throw new ODataApplicationException("The content type has not been set in the request.", + HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT); + } + + List resourcePaths = uriInfo.asUriInfoResource().getUriResourceParts(); + final ODataDeserializer deserializer = odata.createDeserializer(requestFormat); + UriResourceEntitySet boundEntitySet = (UriResourceEntitySet) resourcePaths.get(0); + if (resourcePaths.size() > 1) { + if (resourcePaths.get(1) instanceof UriResourceNavigation) { + action = ((UriResourceAction) resourcePaths.get(2)) + .getAction(); + throw new ODataApplicationException("Action " + action.getName() + " is not yet implemented.", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH); + } else { + action = ((UriResourceAction) resourcePaths.get(1)) + .getAction(); + parameters = deserializer.actionParameters(request.getBody(), action) + .getActionParameters(); + collection = + storage.processBoundActionEntityCollection(action, parameters); + } + } + // Collections must never be null. + // Not nullable return types must not contain a null value. + if (collection == null + || collection.getEntities().contains(null) && !action.getReturnType().isNullable()) { + throw new ODataApplicationException("The action could not be executed.", + HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), Locale.ROOT); + } + + final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); + if (returnPreference == null || returnPreference == Return.REPRESENTATION) { + final EdmEntitySet edmEntitySet = boundEntitySet.getEntitySet(); + final EdmEntityType type = (EdmEntityType) action.getReturnType().getType(); + final EntityCollectionSerializerOptions options = EntityCollectionSerializerOptions.with() + .contextURL(isODataMetadataNone(responseFormat) ? null : + getContextUrl(action.getReturnedEntitySet(edmEntitySet), type, false)) + .build(); + response.setContent(odata.createSerializer(responseFormat) + .entityCollection(serviceMetadata, type, collection, options).getContent()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + } else { + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + if (returnPreference != null) { + response.setHeader(HttpHeader.PREFERENCE_APPLIED, + PreferencesApplied.with().returnRepresentation(returnPreference).build().toValueString()); + } + } + + private ContextURL getContextUrl(final EdmEntitySet entitySet, final EdmEntityType entityType, + final boolean isSingleEntity) throws ODataLibraryException { + Builder builder = ContextURL.with(); + builder = entitySet == null ? + isSingleEntity ? builder.type(entityType) : builder.asCollection().type(entityType) : + builder.entitySet(entitySet); + builder = builder.suffix(isSingleEntity && entitySet != null ? Suffix.ENTITY : null); + return builder.build(); + } + + protected boolean isODataMetadataNone(final ContentType contentType) { + return contentType.isCompatible(ContentType.APPLICATION_JSON) + && ContentType.VALUE_ODATA_METADATA_NONE.equalsIgnoreCase( + contentType.getParameter(ContentType.PARAMETER_ODATA_METADATA)); + } } diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEdmProvider.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEdmProvider.java index d5982bfd8..3f2f53801 100644 --- a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEdmProvider.java +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEdmProvider.java @@ -70,6 +70,14 @@ public class DemoEdmProvider extends CsdlAbstractEdmProvider { public static final String ACTION_RESET = "Reset"; public static final FullQualifiedName ACTION_RESET_FQN = new FullQualifiedName(NAMESPACE, ACTION_RESET); + //Bound Action + public static final String ACTION_PROVIDE_DISCOUNT = "DiscountProducts"; + public static final FullQualifiedName ACTION_PROVIDE_DISCOUNT_FQN = new FullQualifiedName(NAMESPACE, ACTION_PROVIDE_DISCOUNT); + + //Bound Action + public static final String ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT = "DiscountProduct"; + public static final FullQualifiedName ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN = new FullQualifiedName(NAMESPACE, ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT); + // Function public static final String FUNCTION_COUNT_CATEGORIES = "CountCategories"; public static final FullQualifiedName FUNCTION_COUNT_CATEGORIES_FQN @@ -78,11 +86,14 @@ public class DemoEdmProvider extends CsdlAbstractEdmProvider { // Function/Action Parameters public static final String PARAMETER_AMOUNT = "Amount"; + //Bound Action Binding Parameter + public static final String PARAMETER_CATEGORY = "ParamCategory"; + @Override public List getActions(final FullQualifiedName actionName) { + // It is allowed to overload actions, so we have to provide a list of Actions for each action name + final List actions = new ArrayList(); if(actionName.equals(ACTION_RESET_FQN)) { - // It is allowed to overload actions, so we have to provide a list of Actions for each action name - final List actions = new ArrayList(); // Create parameters final List parameters = new ArrayList(); @@ -97,6 +108,50 @@ public class DemoEdmProvider extends CsdlAbstractEdmProvider { action.setParameters(parameters); actions.add(action); + return actions; + } else if (actionName.equals(ACTION_PROVIDE_DISCOUNT_FQN)) { + // Create parameters + final List parameters = new ArrayList(); + CsdlParameter parameter = new CsdlParameter(); + parameter.setName(PARAMETER_CATEGORY); + parameter.setType(ET_CATEGORY_FQN); + parameter.setCollection(true); + parameters.add(parameter); + parameter = new CsdlParameter(); + parameter.setName(PARAMETER_AMOUNT); + parameter.setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName()); + parameters.add(parameter); + + // Create the Csdl Action + final CsdlAction action = new CsdlAction(); + action.setName(ACTION_PROVIDE_DISCOUNT_FQN.getName()); + action.setBound(true); + action.setParameters(parameters); + action.setReturnType(new CsdlReturnType().setType(ET_PRODUCT_FQN).setCollection(true)); + actions.add(action); + + return actions; + } else if (actionName.equals(ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN)) { + // Create parameters + final List parameters = new ArrayList(); + CsdlParameter parameter = new CsdlParameter(); + parameter.setName(PARAMETER_CATEGORY); + parameter.setType(ET_CATEGORY_FQN); + parameter.setCollection(false); + parameters.add(parameter); + parameter = new CsdlParameter(); + parameter.setName(PARAMETER_AMOUNT); + parameter.setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName()); + parameters.add(parameter); + + // Create the Csdl Action + final CsdlAction action = new CsdlAction(); + action.setName(ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN.getName()); + action.setBound(true); + action.setParameters(parameters); + action.setReturnType(new CsdlReturnType().setType(ET_PRODUCT_FQN).setCollection(false)); + actions.add(action); + return actions; } @@ -174,6 +229,8 @@ public class DemoEdmProvider extends CsdlAbstractEdmProvider { .setType(EdmPrimitiveTypeKind.String.getFullQualifiedName()); CsdlProperty description = new CsdlProperty().setName("Description") .setType(EdmPrimitiveTypeKind.String.getFullQualifiedName()); + CsdlProperty price = new CsdlProperty().setName("Price") + .setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName()); // create PropertyRef for Key element CsdlPropertyRef propertyRef = new CsdlPropertyRef(); @@ -189,7 +246,7 @@ public class DemoEdmProvider extends CsdlAbstractEdmProvider { // configure EntityType entityType = new CsdlEntityType(); entityType.setName(ET_PRODUCT_NAME); - entityType.setProperties(Arrays.asList(id, name, description)); + entityType.setProperties(Arrays.asList(id, name, description, price)); entityType.setKey(Arrays.asList(propertyRef)); entityType.setNavigationProperties(navPropList); @@ -279,6 +336,8 @@ public class DemoEdmProvider extends CsdlAbstractEdmProvider { // add actions List actions = new ArrayList(); actions.addAll(getActions(ACTION_RESET_FQN)); + actions.addAll(getActions(ACTION_PROVIDE_DISCOUNT_FQN)); + actions.addAll(getActions(ACTION_PROVIDE_DISCOUNT_FOR_PRODUCT_FQN)); schema.setActions(actions); // add functions