[OLINGO-713] First proposal action/function import tutorial
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/d483c8fd Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/d483c8fd Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/d483c8fd Branch: refs/heads/TutorialAction Commit: d483c8fd1e7e96af96c419117bc9c18ea163ffa0 Parents: 26c923b Author: Christian Holzer <[email protected]> Authored: Wed Sep 30 15:03:50 2015 +0200 Committer: Christian Holzer <[email protected]> Committed: Tue Oct 6 15:39:22 2015 +0200 ---------------------------------------------------------------------- samples/tutorials/p9_action/pom.xml | 85 ++++ .../myservice/mynamespace/data/Storage.java | 486 +++++++++++++++++++ .../service/DemoActionProcessor.java | 85 ++++ .../mynamespace/service/DemoEdmProvider.java | 336 +++++++++++++ .../service/DemoEntityCollectionProcessor.java | 195 ++++++++ .../service/DemoEntityProcessor.java | 296 +++++++++++ .../service/DemoPrimitiveProcessor.java | 146 ++++++ .../java/myservice/mynamespace/util/Util.java | 161 ++++++ .../myservice/mynamespace/web/DemoServlet.java | 75 +++ .../p9_action/src/main/webapp/WEB-INF/web.xml | 40 ++ .../p9_action/src/main/webapp/index.jsp | 26 + 11 files changed, 1931 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/pom.xml ---------------------------------------------------------------------- diff --git a/samples/tutorials/p9_action/pom.xml b/samples/tutorials/p9_action/pom.xml new file mode 100644 index 0000000..5c950d4 --- /dev/null +++ b/samples/tutorials/p9_action/pom.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>my.group.id</groupId> + <artifactId>DemoService-Action</artifactId> + <packaging>war</packaging> + <version>4.0.0</version> + + <name>${project.artifactId}-Webapp</name> + + <build> + <finalName>DemoService</finalName> + </build> + + <properties> + <javax.version>2.5</javax.version> + <odata.version>4.1.0-SNAPSHOT</odata.version> + <slf4j.version>1.7.7</slf4j.version> + </properties> + + <dependencies> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>${javax.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.olingo</groupId> + <artifactId>odata-server-api</artifactId> + <version>${odata.version}</version> + </dependency> + <dependency> + <groupId>org.apache.olingo</groupId> + <artifactId>odata-server-core</artifactId> + <version>${odata.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.olingo</groupId> + <artifactId>odata-commons-api</artifactId> + <version>${odata.version}</version> + </dependency> + <dependency> + <groupId>org.apache.olingo</groupId> + <artifactId>odata-commons-core</artifactId> + <version>${odata.version}</version> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>${slf4j.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.11</version> + <scope>compile</scope> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/Storage.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..62efa7f --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/data/Storage.java @@ -0,0 +1,486 @@ +/* + * 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 java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.EntityCollection; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.data.ValueType; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.ex.ODataRuntimeException; +import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResourceFunction; + +import myservice.mynamespace.service.DemoEdmProvider; +import myservice.mynamespace.util.Util; + +public class Storage { + + private List<Entity> productList; + private List<Entity> categoryList; + + public Storage() { + + productList = new ArrayList<Entity>(); + categoryList = new ArrayList<Entity>(); + + initProductSampleData(); + initCategorySampleData(); + } + + /* PUBLIC FACADE */ + + + public Entity readFunctionImportEntity(final UriResourceFunction uriResourceFunction, + final ServiceMetadata serviceMetadata) throws ODataApplicationException { + + final EntityCollection entityCollection = readFunctionImportCollection(uriResourceFunction, serviceMetadata); + final EdmEntityType edmEntityType = (EdmEntityType) uriResourceFunction.getFunction().getReturnType().getType(); + + return Util.findEntity(edmEntityType, entityCollection, uriResourceFunction.getKeyPredicates()); + } + + public EntityCollection readFunctionImportCollection(final UriResourceFunction uriResourceFunction, + final ServiceMetadata serviceMetadata) throws ODataApplicationException { + + if(DemoEdmProvider.FUNCTION_COUNT_CATEGORIES.equals(uriResourceFunction.getFunctionImport().getName())) { + // Get the parameter of the function + final UriParameter parameterAmount = uriResourceFunction.getParameters().get(0); + // Try to convert the parameter to an Integer. + // We have to take care, that the type of parameter fits to its EDM declaration + int amount; + try { + amount = Integer.parseInt(parameterAmount.getText()); + } catch(NumberFormatException e) { + throw new ODataApplicationException("Type of parameter Amount must be Edm.Int32", HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ENGLISH); + } + + final EdmEntityType productEntityType = serviceMetadata.getEdm().getEntityType(DemoEdmProvider.ET_PRODUCT_FQN); + final List<Entity> resultEntityList = new ArrayList<Entity>(); + + // Loop over all categories and check how many products are linked + for(final Entity category : categoryList) { + final EntityCollection products = getRelatedEntityCollection(category, productEntityType); + if(products.getEntities().size() == amount) { + resultEntityList.add(category); + } + } + + final EntityCollection resultCollection = new EntityCollection(); + resultCollection.getEntities().addAll(resultEntityList); + return resultCollection; + } else { + throw new ODataApplicationException("Function not implemented", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), + Locale.ROOT); + } + } + + public void resetDataSet() { + resetDataSet(Integer.MAX_VALUE); + } + + public void resetDataSet(final Integer amount) { + // Replace the old lists with empty ones + productList = new ArrayList<Entity>(); + categoryList = new ArrayList<Entity>(); + + // Create new sample data + initProductSampleData(); + initCategorySampleData(); + + // Truncate the lists + if(amount < productList.size()) { + productList = productList.subList(0, amount); + // Products 0, 1 are linked to category 0 + // Products 2, 3 are linked to category 1 + // Products 4, 5 are linked to category 2 + categoryList = categoryList.subList(0, (amount / 2) + 1); + } + } + + public EntityCollection readEntitySetData(EdmEntitySet edmEntitySet) throws ODataApplicationException { + + if (edmEntitySet.getName().equals(DemoEdmProvider.ES_PRODUCTS_NAME)) { + return getEntityCollection(productList); + } else if(edmEntitySet.getName().equals(DemoEdmProvider.ES_CATEGORIES_NAME)) { + return getEntityCollection(categoryList); + } + + return null; + } + + public Entity readEntityData(EdmEntitySet edmEntitySet, List<UriParameter> keyParams) + throws ODataApplicationException { + + EdmEntityType edmEntityType = edmEntitySet.getEntityType(); + + if (edmEntityType.getName().equals(DemoEdmProvider.ET_PRODUCT_NAME)) { + return getEntity(edmEntityType, keyParams, productList); + } else if(edmEntityType.getName().equals(DemoEdmProvider.ET_CATEGORY_NAME)) { + return getEntity(edmEntityType, keyParams, categoryList); + } + + return null; + } + + public Entity createEntityData(EdmEntitySet edmEntitySet, Entity entityToCreate) { + + EdmEntityType edmEntityType = edmEntitySet.getEntityType(); + + if (edmEntityType.getName().equals(DemoEdmProvider.ET_PRODUCT_NAME)) { + return createEntity(edmEntityType, entityToCreate, productList); + } else if(edmEntityType.getName().equals(DemoEdmProvider.ET_CATEGORY_NAME)) { + return createEntity(edmEntityType, entityToCreate, categoryList); + } + + return null; + } + + /** + * This method is invoked for PATCH or PUT requests + * */ + public void updateEntityData(EdmEntitySet edmEntitySet, List<UriParameter> keyParams, Entity updateEntity, + HttpMethod httpMethod) throws ODataApplicationException { + + EdmEntityType edmEntityType = edmEntitySet.getEntityType(); + + if (edmEntityType.getName().equals(DemoEdmProvider.ET_PRODUCT_NAME)) { + updateEntity(edmEntityType, keyParams, updateEntity, httpMethod, productList); + } else if(edmEntityType.getName().equals(DemoEdmProvider.ET_CATEGORY_NAME)) { + updateEntity(edmEntityType, keyParams, updateEntity, httpMethod, categoryList); + } + } + + public void deleteEntityData(EdmEntitySet edmEntitySet, List<UriParameter> keyParams) + throws ODataApplicationException { + + EdmEntityType edmEntityType = edmEntitySet.getEntityType(); + + if (edmEntityType.getName().equals(DemoEdmProvider.ET_PRODUCT_NAME)) { + deleteEntity(edmEntityType, keyParams, productList); + } else if(edmEntityType.getName().equals(DemoEdmProvider.ET_CATEGORY_NAME)) { + deleteEntity(edmEntityType, keyParams, categoryList); + } + } + + // Navigation + public Entity getRelatedEntity(Entity entity, EdmEntityType relatedEntityType) { + EntityCollection collection = getRelatedEntityCollection(entity, relatedEntityType); + if (collection.getEntities().isEmpty()) { + return null; + } + return collection.getEntities().get(0); + } + + public Entity getRelatedEntity(Entity entity, EdmEntityType relatedEntityType, List<UriParameter> keyPredicates) + throws ODataApplicationException { + + EntityCollection relatedEntities = getRelatedEntityCollection(entity, relatedEntityType); + return Util.findEntity(relatedEntityType, relatedEntities, keyPredicates); + } + + public EntityCollection getRelatedEntityCollection(Entity sourceEntity, EdmEntityType targetEntityType) { + EntityCollection navigationTargetEntityCollection = new EntityCollection(); + + FullQualifiedName relatedEntityFqn = targetEntityType.getFullQualifiedName(); + String sourceEntityFqn = sourceEntity.getType(); + + if (sourceEntityFqn.equals(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()) + && relatedEntityFqn.equals(DemoEdmProvider.ET_CATEGORY_FQN)) { + // relation Products->Category (result all categories) + int productID = (Integer) sourceEntity.getProperty("ID").getValue(); + if (productID == 0 || productID == 1) { + navigationTargetEntityCollection.getEntities().add(categoryList.get(0)); + } else if (productID == 2 || productID == 3) { + navigationTargetEntityCollection.getEntities().add(categoryList.get(1)); + } else if (productID == 4 || productID == 5) { + navigationTargetEntityCollection.getEntities().add(categoryList.get(2)); + } + } else if (sourceEntityFqn.equals(DemoEdmProvider.ET_CATEGORY_FQN.getFullQualifiedNameAsString()) + && relatedEntityFqn.equals(DemoEdmProvider.ET_PRODUCT_FQN)) { + // relation Category->Products (result all products) + int categoryID = (Integer) sourceEntity.getProperty("ID").getValue(); + if (categoryID == 0) { + // the first 2 products are notebooks + navigationTargetEntityCollection.getEntities().addAll(productList.subList(0, 2)); + } else if (categoryID == 1) { + // the next 2 products are organizers + navigationTargetEntityCollection.getEntities().addAll(productList.subList(2, 4)); + } else if (categoryID == 2) { + // the first 2 products are monitors + navigationTargetEntityCollection.getEntities().addAll(productList.subList(4, 6)); + } + } + + if (navigationTargetEntityCollection.getEntities().isEmpty()) { + return null; + } + + return navigationTargetEntityCollection; + } + + /* INTERNAL */ + + private EntityCollection getEntityCollection(final List<Entity> entityList) { + + EntityCollection retEntitySet = new EntityCollection(); + retEntitySet.getEntities().addAll(entityList); + + return retEntitySet; + } + + private Entity getEntity(EdmEntityType edmEntityType, List<UriParameter> keyParams, List<Entity> entityList) + throws ODataApplicationException { + + // the list of entities at runtime + EntityCollection entitySet = getEntityCollection(entityList); + + /* generic approach to find the requested entity */ + Entity requestedEntity = Util.findEntity(edmEntityType, entitySet, keyParams); + + if (requestedEntity == null) { + // this variable is null if our data doesn't contain an entity for the requested key + // Throw suitable exception + throw new ODataApplicationException("Entity for requested key doesn't exist", + HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); + } + + return requestedEntity; + } + + private Entity createEntity(EdmEntityType edmEntityType, Entity entity, List<Entity> entityList) { + + // the ID of the newly created entity is generated automatically + int newId = 1; + while (entityIdExists(newId, entityList)) { + newId++; + } + + Property idProperty = entity.getProperty("ID"); + if (idProperty != null) { + idProperty.setValue(ValueType.PRIMITIVE, Integer.valueOf(newId)); + } else { + // as of OData v4 spec, the key property can be omitted from the POST request body + entity.getProperties().add(new Property(null, "ID", ValueType.PRIMITIVE, newId)); + } + entity.setId(createId(entity, "ID")); + entityList.add(entity); + + return entity; + } + + private boolean entityIdExists(int id, List<Entity> entityList) { + + for (Entity entity : entityList) { + Integer existingID = (Integer) entity.getProperty("ID").getValue(); + if (existingID.intValue() == id) { + return true; + } + } + + return false; + } + + private void updateEntity(EdmEntityType edmEntityType, List<UriParameter> keyParams, Entity updateEntity, + HttpMethod httpMethod, List<Entity> entityList) throws ODataApplicationException { + + Entity entity = getEntity(edmEntityType, keyParams, entityList); + if (entity == null) { + throw new ODataApplicationException("Entity not found", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); + } + + // loop over all properties and replace the values with the values of the given payload + // Note: ignoring ComplexType, as we don't have it in our odata model + List<Property> existingProperties = entity.getProperties(); + for (Property existingProp : existingProperties) { + String propName = existingProp.getName(); + + // ignore the key properties, they aren't updateable + if (isKey(edmEntityType, propName)) { + continue; + } + + Property updateProperty = updateEntity.getProperty(propName); + // the request payload might not consider ALL properties, so it can be null + if (updateProperty == null) { + // if a property has NOT been added to the request payload + // depending on the HttpMethod, our behavior is different + if (httpMethod.equals(HttpMethod.PATCH)) { + // as of the OData spec, in case of PATCH, the existing property is not touched + continue; // do nothing + } else if (httpMethod.equals(HttpMethod.PUT)) { + // as of the OData spec, in case of PUT, the existing property is set to null (or to default value) + existingProp.setValue(existingProp.getValueType(), null); + continue; + } + } + + // change the value of the properties + existingProp.setValue(existingProp.getValueType(), updateProperty.getValue()); + } + } + + private void deleteEntity(EdmEntityType edmEntityType, List<UriParameter> keyParams, List<Entity> entityList) + throws ODataApplicationException { + + Entity entity = getEntity(edmEntityType, keyParams, entityList); + if (entity == null) { + throw new ODataApplicationException("Entity not found", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); + } + + entityList.remove(entity); + } + + /* HELPER */ + + private boolean isKey(EdmEntityType edmEntityType, String propertyName) { + + List<EdmKeyPropertyRef> keyPropertyRefs = edmEntityType.getKeyPropertyRefs(); + for (EdmKeyPropertyRef propRef : keyPropertyRefs) { + String keyPropertyName = propRef.getName(); + if (keyPropertyName.equals(propertyName)) { + return true; + } + } + return false; + } + + private void initProductSampleData() { + + Entity entity = new Entity(); + + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 0)); + 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.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + productList.add(entity); + + entity = new Entity(); + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 1)); + 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.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + productList.add(entity); + + entity = new Entity(); + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 2)); + 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.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + productList.add(entity); + + entity = new Entity(); + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 3)); + 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.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + productList.add(entity); + + entity = new Entity(); + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 4)); + 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.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + productList.add(entity); + + entity = new Entity(); + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 5)); + 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.setType(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + productList.add(entity); + } + + private void initCategorySampleData() { + + Entity entity = new Entity(); + + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 0)); + entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Notebooks")); + entity.setType(DemoEdmProvider.ET_CATEGORY_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + categoryList.add(entity); + + entity = new Entity(); + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 1)); + entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Organizers")); + entity.setType(DemoEdmProvider.ET_CATEGORY_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + categoryList.add(entity); + + entity = new Entity(); + entity.addProperty(new Property(null, "ID", ValueType.PRIMITIVE, 2)); + entity.addProperty(new Property(null, "Name", ValueType.PRIMITIVE, "Monitors")); + entity.setType(DemoEdmProvider.ET_CATEGORY_FQN.getFullQualifiedNameAsString()); + entity.setId(createId(entity, "ID")); + categoryList.add(entity); + } + + private URI createId(Entity entity, String idPropertyName) { + return createId(entity, idPropertyName, null); + } + + private URI createId(Entity entity, String idPropertyName, String navigationName) { + try { + StringBuilder sb = new StringBuilder(getEntitySetName(entity)).append("("); + final Property property = entity.getProperty(idPropertyName); + sb.append(property.asPrimitive()).append(")"); + if(navigationName != null) { + sb.append("/").append(navigationName); + } + return new URI(sb.toString()); + } catch (URISyntaxException e) { + throw new ODataRuntimeException("Unable to create (Atom) id for entity: " + entity, e); + } + } + + private String getEntitySetName(Entity entity) { + if(DemoEdmProvider.ET_CATEGORY_FQN.getFullQualifiedNameAsString().equals(entity.getType())) { + return DemoEdmProvider.ES_CATEGORIES_NAME; + } else if(DemoEdmProvider.ET_PRODUCT_FQN.getFullQualifiedNameAsString().equals(entity.getType())) { + return DemoEdmProvider.ES_PRODUCTS_NAME; + } + return entity.getType(); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoActionProcessor.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..0de6681 --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoActionProcessor.java @@ -0,0 +1,85 @@ +/* + * 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.service; + +import java.util.Locale; +import java.util.Map; + +import org.apache.olingo.commons.api.data.Parameter; +import org.apache.olingo.commons.api.edm.EdmAction; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataLibraryException; +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.processor.ActionVoidProcessor; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriResourceAction; + +import myservice.mynamespace.data.Storage; + +public class DemoActionProcessor implements ActionVoidProcessor { + + private OData odata; + private Storage storage; + + public DemoActionProcessor(final Storage storage) { + this.storage = storage; + } + + @Override + public void init(final OData odata, final ServiceMetadata serviceMetadata) { + this.odata = odata; + } + + @Override + public void processActionVoid(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType requestFormat) throws ODataApplicationException, ODataLibraryException { + + // 1st Get the action from the resource path + final EdmAction edmAction = ((UriResourceAction) uriInfo.asUriInfoResource().getUriResourceParts() + .get(0)).getAction(); + + // 2nd Deserialize the parameter + // In our case there is only one action. So we can be sure that parameter has been provided by the client + 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 Map<String, Parameter> actionParameter = deserializer.actionParameters(request.getBody(), edmAction) + .getActionParameters(); + final Parameter parameterAmount = actionParameter.get(DemoEdmProvider.PARAMETER_AMOUNT); + + // The parameter amount is nullable + if(parameterAmount.isNull()) { + storage.resetDataSet(); + } else { + final Integer amount = (Integer) parameterAmount.asPrimitive(); + storage.resetDataSet(amount); + } + + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEdmProvider.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..2caaf31 --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEdmProvider.java @@ -0,0 +1,336 @@ +/* + * 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.service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.provider.CsdlAbstractEdmProvider; +import org.apache.olingo.commons.api.edm.provider.CsdlAction; +import org.apache.olingo.commons.api.edm.provider.CsdlActionImport; +import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainer; +import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainerInfo; +import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet; +import org.apache.olingo.commons.api.edm.provider.CsdlEntityType; +import org.apache.olingo.commons.api.edm.provider.CsdlFunction; +import org.apache.olingo.commons.api.edm.provider.CsdlFunctionImport; +import org.apache.olingo.commons.api.edm.provider.CsdlNavigationProperty; +import org.apache.olingo.commons.api.edm.provider.CsdlNavigationPropertyBinding; +import org.apache.olingo.commons.api.edm.provider.CsdlParameter; +import org.apache.olingo.commons.api.edm.provider.CsdlProperty; +import org.apache.olingo.commons.api.edm.provider.CsdlPropertyRef; +import org.apache.olingo.commons.api.edm.provider.CsdlReturnType; +import org.apache.olingo.commons.api.edm.provider.CsdlSchema; + +/* + * this class is supposed to declare the metadata of the OData service + * it is invoked by the Olingo framework e.g. when the metadata document of the service is invoked + * e.g. http://localhost:8080/ExampleService1/ExampleService1.svc/$metadata + */ +public class DemoEdmProvider extends CsdlAbstractEdmProvider { + + // Service Namespace + public static final String NAMESPACE = "OData.Demo"; + + // EDM Container + public static final String CONTAINER_NAME = "Container"; + public static final FullQualifiedName CONTAINER = new FullQualifiedName(NAMESPACE, CONTAINER_NAME); + + // Entity Types Names + public static final String ET_PRODUCT_NAME = "Product"; + public static final FullQualifiedName ET_PRODUCT_FQN = new FullQualifiedName(NAMESPACE, ET_PRODUCT_NAME); + + public static final String ET_CATEGORY_NAME = "Category"; + public static final FullQualifiedName ET_CATEGORY_FQN = new FullQualifiedName(NAMESPACE, ET_CATEGORY_NAME); + + // Entity Set Names + public static final String ES_PRODUCTS_NAME = "Products"; + public static final String ES_CATEGORIES_NAME = "Categories"; + + // Action + public static final String ACTION_RESET = "Reset"; + public static final FullQualifiedName ACTION_RESET_FQN = new FullQualifiedName(NAMESPACE, ACTION_RESET); + + // Function + public static final String FUNCTION_COUNT_CATEGORIES = "CountCategories"; + public static final FullQualifiedName FUNCTION_COUNT_CATEGORIES_FQN + = new FullQualifiedName(NAMESPACE, FUNCTION_COUNT_CATEGORIES); + + // Function/Action Parameters + public static final String PARAMETER_AMOUNT = "Amount"; + + @Override + public List<CsdlAction> getActions(final FullQualifiedName actionName) { + 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<CsdlAction> actions = new ArrayList<CsdlAction>(); + + // Create parameters + final List<CsdlParameter> parameters = new ArrayList<CsdlParameter>(); + final CsdlParameter 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_RESET_FQN.getName()); + action.setParameters(parameters); + actions.add(action); + + return actions; + } + + return null; + } + + @Override + public CsdlActionImport getActionImport(final FullQualifiedName entityContainer, final String actionImportName) { + if(entityContainer.equals(CONTAINER)) { + if(actionImportName.equals(ACTION_RESET_FQN.getName())) { + return new CsdlActionImport() + .setName(actionImportName) + .setAction(ACTION_RESET_FQN); + } + } + + return null; + } + + @Override + public List<CsdlFunction> getFunctions(final FullQualifiedName functionName) { + if (functionName.equals(FUNCTION_COUNT_CATEGORIES_FQN)) { + // It is allowed to overload functions, so we have to provide a list of functions for each function name + final List<CsdlFunction> functions = new ArrayList<CsdlFunction>(); + + // Create the parameter for the function + final CsdlParameter parameterAmount = new CsdlParameter(); + parameterAmount.setName(PARAMETER_AMOUNT); + parameterAmount.setNullable(false); + parameterAmount.setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName()); + + // Create the return type of the function + final CsdlReturnType returnType = new CsdlReturnType(); + returnType.setCollection(true); + returnType.setType(ET_CATEGORY_FQN); + + // Create the function + final CsdlFunction function = new CsdlFunction(); + function.setName(FUNCTION_COUNT_CATEGORIES_FQN.getName()) + .setParameters(Arrays.asList(parameterAmount)) + .setReturnType(returnType); + functions.add(function); + + return functions; + } + + return null; + } + + @Override + public CsdlFunctionImport getFunctionImport(FullQualifiedName entityContainer, String functionImportName) { + if(entityContainer.equals(CONTAINER)) { + if(functionImportName.equals(FUNCTION_COUNT_CATEGORIES_FQN.getName())) { + return new CsdlFunctionImport() + .setName(functionImportName) + .setFunction(FUNCTION_COUNT_CATEGORIES_FQN) + .setEntitySet(ES_CATEGORIES_NAME) + .setIncludeInServiceDocument(true); + } + } + + return null; + } + + public CsdlEntityType getEntityType(FullQualifiedName entityTypeName) { + + // this method is called for each EntityType that are configured in the Schema + CsdlEntityType entityType = null; + + if (entityTypeName.equals(ET_PRODUCT_FQN)) { + // create EntityType properties + CsdlProperty id = new CsdlProperty().setName("ID") + .setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName()); + CsdlProperty name = new CsdlProperty().setName("Name") + .setType(EdmPrimitiveTypeKind.String.getFullQualifiedName()); + CsdlProperty description = new CsdlProperty().setName("Description") + .setType(EdmPrimitiveTypeKind.String.getFullQualifiedName()); + + // create PropertyRef for Key element + CsdlPropertyRef propertyRef = new CsdlPropertyRef(); + propertyRef.setName("ID"); + + // navigation property: many-to-one, null not allowed (product must have a category) + CsdlNavigationProperty navProp = new CsdlNavigationProperty().setName("Category") + .setType(ET_CATEGORY_FQN).setNullable(false) + .setPartner("Products"); + List<CsdlNavigationProperty> navPropList = new ArrayList<CsdlNavigationProperty>(); + navPropList.add(navProp); + + // configure EntityType + entityType = new CsdlEntityType(); + entityType.setName(ET_PRODUCT_NAME); + entityType.setProperties(Arrays.asList(id, name, description)); + entityType.setKey(Arrays.asList(propertyRef)); + entityType.setNavigationProperties(navPropList); + + } else if (entityTypeName.equals(ET_CATEGORY_FQN)) { + // create EntityType properties + CsdlProperty id = new CsdlProperty().setName("ID") + .setType(EdmPrimitiveTypeKind.Int32.getFullQualifiedName()); + CsdlProperty name = new CsdlProperty().setName("Name") + .setType(EdmPrimitiveTypeKind.String.getFullQualifiedName()); + + // create PropertyRef for Key element + CsdlPropertyRef propertyRef = new CsdlPropertyRef(); + propertyRef.setName("ID"); + + // navigation property: one-to-many + CsdlNavigationProperty navProp = new CsdlNavigationProperty().setName("Products") + .setType(ET_PRODUCT_FQN).setCollection(true) + .setPartner("Category"); + List<CsdlNavigationProperty> navPropList = new ArrayList<CsdlNavigationProperty>(); + navPropList.add(navProp); + + // configure EntityType + entityType = new CsdlEntityType(); + entityType.setName(ET_CATEGORY_NAME); + entityType.setProperties(Arrays.asList(id, name)); + entityType.setKey(Arrays.asList(propertyRef)); + entityType.setNavigationProperties(navPropList); + } + + return entityType; + } + + @Override + public CsdlEntitySet getEntitySet(FullQualifiedName entityContainer, String entitySetName) { + + CsdlEntitySet entitySet = null; + + if (entityContainer.equals(CONTAINER)) { + + if (entitySetName.equals(ES_PRODUCTS_NAME)) { + + entitySet = new CsdlEntitySet(); + entitySet.setName(ES_PRODUCTS_NAME); + entitySet.setType(ET_PRODUCT_FQN); + + // navigation + CsdlNavigationPropertyBinding navPropBinding = new CsdlNavigationPropertyBinding(); + navPropBinding.setTarget("Categories"); // the target entity set, where the navigation property points to + navPropBinding.setPath("Category"); // the path from entity type to navigation property + List<CsdlNavigationPropertyBinding> navPropBindingList = new ArrayList<CsdlNavigationPropertyBinding>(); + navPropBindingList.add(navPropBinding); + entitySet.setNavigationPropertyBindings(navPropBindingList); + + } else if (entitySetName.equals(ES_CATEGORIES_NAME)) { + + entitySet = new CsdlEntitySet(); + entitySet.setName(ES_CATEGORIES_NAME); + entitySet.setType(ET_CATEGORY_FQN); + + // navigation + CsdlNavigationPropertyBinding navPropBinding = new CsdlNavigationPropertyBinding(); + navPropBinding.setTarget("Products"); // the target entity set, where the navigation property points to + navPropBinding.setPath("Products"); // the path from entity type to navigation property + List<CsdlNavigationPropertyBinding> navPropBindingList = new ArrayList<CsdlNavigationPropertyBinding>(); + navPropBindingList.add(navPropBinding); + entitySet.setNavigationPropertyBindings(navPropBindingList); + } + } + + return entitySet; + + } + + @Override + public List<CsdlSchema> getSchemas() { + + // create Schema + CsdlSchema schema = new CsdlSchema(); + schema.setNamespace(NAMESPACE); + + // add EntityTypes + List<CsdlEntityType> entityTypes = new ArrayList<CsdlEntityType>(); + entityTypes.add(getEntityType(ET_PRODUCT_FQN)); + entityTypes.add(getEntityType(ET_CATEGORY_FQN)); + schema.setEntityTypes(entityTypes); + + // add actions + List<CsdlAction> actions = new ArrayList<CsdlAction>(); + actions.addAll(getActions(ACTION_RESET_FQN)); + schema.setActions(actions); + + // add functions + List<CsdlFunction> functions = new ArrayList<CsdlFunction>(); + functions.addAll(getFunctions(FUNCTION_COUNT_CATEGORIES_FQN)); + schema.setFunctions(functions); + + // add EntityContainer + schema.setEntityContainer(getEntityContainer()); + + // finally + List<CsdlSchema> schemas = new ArrayList<CsdlSchema>(); + schemas.add(schema); + + return schemas; + } + + public CsdlEntityContainer getEntityContainer() { + // create EntitySets + List<CsdlEntitySet> entitySets = new ArrayList<CsdlEntitySet>(); + entitySets.add(getEntitySet(CONTAINER, ES_PRODUCTS_NAME)); + entitySets.add(getEntitySet(CONTAINER, ES_CATEGORIES_NAME)); + + // Create function imports + List<CsdlFunctionImport> functionImports = new ArrayList<CsdlFunctionImport>(); + functionImports.add(getFunctionImport(CONTAINER, FUNCTION_COUNT_CATEGORIES)); + + // Create action imports + List<CsdlActionImport> actionImports = new ArrayList<CsdlActionImport>(); + actionImports.add(getActionImport(CONTAINER, ACTION_RESET)); + + // create EntityContainer + CsdlEntityContainer entityContainer = new CsdlEntityContainer(); + entityContainer.setName(CONTAINER_NAME); + entityContainer.setEntitySets(entitySets); + entityContainer.setFunctionImports(functionImports); + entityContainer.setActionImports(actionImports); + + return entityContainer; + + } + + @Override + public CsdlEntityContainerInfo getEntityContainerInfo(FullQualifiedName entityContainerName) { + + // This method is invoked when displaying the service document at + // e.g. http://localhost:8080/DemoService/DemoService.svc + if (entityContainerName == null || entityContainerName.equals(CONTAINER)) { + CsdlEntityContainerInfo entityContainerInfo = new CsdlEntityContainerInfo(); + entityContainerInfo.setContainerName(CONTAINER); + return entityContainerInfo; + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityCollectionProcessor.java ---------------------------------------------------------------------- diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityCollectionProcessor.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityCollectionProcessor.java new file mode 100644 index 0000000..471f2d1 --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityCollectionProcessor.java @@ -0,0 +1,195 @@ +/* + * 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.service; + +import java.util.List; +import java.util.Locale; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.EntityCollection; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +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; +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.processor.EntityCollectionProcessor; +import org.apache.olingo.server.api.serializer.EntityCollectionSerializerOptions; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.api.uri.UriResourceNavigation; + +import myservice.mynamespace.data.Storage; +import myservice.mynamespace.util.Util; + +public class DemoEntityCollectionProcessor implements EntityCollectionProcessor { + + + private OData odata; + private ServiceMetadata serviceMetadata; + // our database-mock + private Storage storage; + + public DemoEntityCollectionProcessor(Storage storage) { + this.storage = storage; + } + + public void init(OData odata, ServiceMetadata serviceMetadata) { + this.odata = odata; + this.serviceMetadata = serviceMetadata; + } + + /* + * This method is invoked when a collection of entities has to be read. + * In our example, this can be either a "normal" read operation, or a navigation: + * + * Example for "normal" read entity set operation: + * http://localhost:8080/DemoService/DemoService.svc/Categories + * + * Example for navigation + * http://localhost:8080/DemoService/DemoService.svc/Categories(3)/Products + */ + public void readEntityCollection(ODataRequest request, ODataResponse response, + UriInfo uriInfo, ContentType responseFormat) + throws ODataApplicationException, SerializerException { + + final UriResource firstResourceSegment = uriInfo.getUriResourceParts().get(0); + + if(firstResourceSegment instanceof UriResourceEntitySet) { + readEntityCollectionInternal(request, response, uriInfo, responseFormat); + } else if(firstResourceSegment instanceof UriResourceFunction) { + readFunctionImportCollection(request, response, uriInfo, responseFormat); + } else { + throw new ODataApplicationException("Not implemented", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), + Locale.ENGLISH); + } + } + + private void readFunctionImportCollection(final ODataRequest request, final ODataResponse response, + final UriInfo uriInfo, final ContentType responseFormat) throws ODataApplicationException, SerializerException { + + // 1st step: Analyze the URI and fetch the entity collection returned by the function import + // Function Imports are always the first segment of the resource path + final UriResource firstSegment = uriInfo.getUriResourceParts().get(0); + + if(!(firstSegment instanceof UriResourceFunction)) { + throw new ODataApplicationException("Not implemented", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), + Locale.ENGLISH); + } + + final UriResourceFunction uriResourceFunction = (UriResourceFunction) firstSegment; + final EntityCollection entityCol = storage.readFunctionImportCollection(uriResourceFunction, serviceMetadata); + + // 2nd step: Serialize the response entity + final EdmEntityType edmEntityType = (EdmEntityType) uriResourceFunction.getFunction().getReturnType().getType(); + final ContextURL contextURL = ContextURL.with().asCollection().type(edmEntityType).build(); + EntityCollectionSerializerOptions opts = EntityCollectionSerializerOptions.with().contextURL(contextURL).build(); + final ODataSerializer serializer = odata.createSerializer(responseFormat); + final SerializerResult serializerResult = serializer.entityCollection(serviceMetadata, edmEntityType, entityCol, + opts); + + // 3rd configure the response object + response.setContent(serializerResult.getContent()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } + + private void readEntityCollectionInternal(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType responseFormat) throws ODataApplicationException, SerializerException { + + EdmEntitySet responseEdmEntitySet = null; // we'll need this to build the ContextURL + EntityCollection responseEntityCollection = null; // we'll need this to set the response body + + // 1st retrieve the requested EntitySet from the uriInfo (representation of the parsed URI) + List<UriResource> resourceParts = uriInfo.getUriResourceParts(); + int segmentCount = resourceParts.size(); + + UriResource uriResource = resourceParts.get(0); // in our example, the first segment is the EntitySet + if (!(uriResource instanceof UriResourceEntitySet)) { + throw new ODataApplicationException("Only EntitySet is supported", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + + UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) uriResource; + EdmEntitySet startEdmEntitySet = uriResourceEntitySet.getEntitySet(); + + if (segmentCount == 1) { // this is the case for: DemoService/DemoService.svc/Categories + responseEdmEntitySet = startEdmEntitySet; // the response body is built from the first (and only) entitySet + + // 2nd: fetch the data from backend for this requested EntitySetName and deliver as EntitySet + responseEntityCollection = storage.readEntitySetData(startEdmEntitySet); + } else if (segmentCount == 2) { // in case of navigation: DemoService.svc/Categories(3)/Products + + UriResource lastSegment = resourceParts.get(1); // in our example we don't support more complex URIs + if (lastSegment instanceof UriResourceNavigation) { + UriResourceNavigation uriResourceNavigation = (UriResourceNavigation) lastSegment; + EdmNavigationProperty edmNavigationProperty = uriResourceNavigation.getProperty(); + EdmEntityType targetEntityType = edmNavigationProperty.getType(); + // from Categories(1) to Products + responseEdmEntitySet = Util.getNavigationTargetEntitySet(startEdmEntitySet, edmNavigationProperty); + + // 2nd: fetch the data from backend + // first fetch the entity where the first segment of the URI points to + List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates(); + // e.g. for Categories(3)/Products we have to find the single entity: Category with ID 3 + Entity sourceEntity = storage.readEntityData(startEdmEntitySet, keyPredicates); + // error handling for e.g. DemoService.svc/Categories(99)/Products + if (sourceEntity == null) { + throw new ODataApplicationException("Entity not found.", + HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT); + } + // then fetch the entity collection where the entity navigates to + // note: we don't need to check uriResourceNavigation.isCollection(), + // because we are the EntityCollectionProcessor + responseEntityCollection = storage.getRelatedEntityCollection(sourceEntity, targetEntityType); + } + } else { // this would be the case for e.g. Products(1)/Category/Products + throw new ODataApplicationException("Not supported", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + + // 3rd: create and configure a serializer + ContextURL contextUrl = ContextURL.with().entitySet(responseEdmEntitySet).build(); + final String id = request.getRawBaseUri() + "/" + responseEdmEntitySet.getName(); + EntityCollectionSerializerOptions opts = EntityCollectionSerializerOptions.with() + .contextURL(contextUrl).id(id).build(); + EdmEntityType edmEntityType = responseEdmEntitySet.getEntityType(); + + ODataSerializer serializer = odata.createSerializer(responseFormat); + SerializerResult serializerResult = serializer.entityCollection(serviceMetadata, edmEntityType, + responseEntityCollection, opts); + + // 4th: configure the response object: set the body, headers and status code + response.setContent(serializerResult.getContent()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityProcessor.java ---------------------------------------------------------------------- diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityProcessor.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityProcessor.java new file mode 100644 index 0000000..ffdee3f --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoEntityProcessor.java @@ -0,0 +1,296 @@ +/* + * 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.service; + +import java.io.InputStream; +import java.util.List; +import java.util.Locale; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.ContextURL.Suffix; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.format.ContentType; +import org.apache.olingo.commons.api.http.HttpHeader; +import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataApplicationException; +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.deserializer.ODataDeserializer; +import org.apache.olingo.server.api.processor.EntityProcessor; +import org.apache.olingo.server.api.serializer.EntitySerializerOptions; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.api.uri.UriResourceNavigation; + +import myservice.mynamespace.data.Storage; +import myservice.mynamespace.util.Util; + +public class DemoEntityProcessor implements EntityProcessor { + + private OData odata; + private Storage storage; + private ServiceMetadata serviceMetadata; + + public DemoEntityProcessor(Storage storage) { + this.storage = storage; + } + + public void init(OData odata, ServiceMetadata serviceMetadata) { + this.odata = odata; + this.serviceMetadata = serviceMetadata; + } + + + /** + * This method is invoked when a single entity has to be read. + * In our example, this can be either a "normal" read operation, or a navigation: + * + * Example for "normal" read operation: + * http://localhost:8080/DemoService/DemoService.svc/Products(1) + * + * Example for navigation + * http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category + */ + public void readEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat) + throws ODataApplicationException, SerializerException { + + // The sample service supports only functions imports and entity sets. + // We do not care about bound functions and composable functions. + + UriResource uriResource = uriInfo.getUriResourceParts().get(0); + + if(uriResource instanceof UriResourceEntitySet) { + readEntityInternal(request, response, uriInfo, responseFormat); + } else if(uriResource instanceof UriResourceFunction) { + readFunctionImportInternal(request, response, uriInfo, responseFormat); + } else { + throw new ODataApplicationException("Only EntitySet is supported", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH); + } + + } + + private void readFunctionImportInternal(final ODataRequest request, final ODataResponse response, + final UriInfo uriInfo, final ContentType responseFormat) throws ODataApplicationException, SerializerException { + + // 1st step: Analyze the URI and fetch the entity returned by the function import + // Function Imports are always the first segment of the resource path + final UriResource firstSegment = uriInfo.getUriResourceParts().get(0); + + if(!(firstSegment instanceof UriResourceFunction)) { + throw new ODataApplicationException("Not implemented", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), + Locale.ENGLISH); + } + + final UriResourceFunction uriResourceFunction = (UriResourceFunction) firstSegment; + final Entity entity = storage.readFunctionImportEntity(uriResourceFunction, serviceMetadata); + + if(entity == null) { + throw new ODataApplicationException("Nothing found.", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT); + } + + // 2nd step: Serialize the response entity + final EdmEntityType edmEntityType = (EdmEntityType) uriResourceFunction.getFunction().getReturnType().getType(); + final ContextURL contextURL = ContextURL.with().type(edmEntityType).build(); + final EntitySerializerOptions opts = EntitySerializerOptions.with().contextURL(contextURL).build(); + final ODataSerializer serializer = odata.createSerializer(responseFormat); + final SerializerResult serializerResult = serializer.entity(serviceMetadata, edmEntityType, entity, opts); + + // 3rd configure the response object + response.setContent(serializerResult.getContent()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } + + private void readEntityInternal(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType responseFormat) throws ODataApplicationException, SerializerException { + + EdmEntityType responseEdmEntityType = null; // we'll need this to build the ContextURL + Entity responseEntity = null; // required for serialization of the response body + EdmEntitySet responseEdmEntitySet = null; // we need this for building the contextUrl + + // 1st step: retrieve the requested Entity: can be "normal" read operation, or navigation (to-one) + List<UriResource> resourceParts = uriInfo.getUriResourceParts(); + int segmentCount = resourceParts.size(); + + UriResource uriResource = resourceParts.get(0); // in our example, the first segment is the EntitySet + UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) uriResource; + EdmEntitySet startEdmEntitySet = uriResourceEntitySet.getEntitySet(); + + // Analyze the URI segments + if (segmentCount == 1) { // no navigation + responseEdmEntityType = startEdmEntitySet.getEntityType(); + responseEdmEntitySet = startEdmEntitySet; // since we have only one segment + + // 2. step: retrieve the data from backend + List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates(); + responseEntity = storage.readEntityData(startEdmEntitySet, keyPredicates); + } else if (segmentCount == 2) { // navigation + UriResource navSegment = resourceParts.get(1); // in our example we don't support more complex URIs + if (navSegment instanceof UriResourceNavigation) { + UriResourceNavigation uriResourceNavigation = (UriResourceNavigation) navSegment; + EdmNavigationProperty edmNavigationProperty = uriResourceNavigation.getProperty(); + responseEdmEntityType = edmNavigationProperty.getType(); + // contextURL displays the last segment + responseEdmEntitySet = Util.getNavigationTargetEntitySet(startEdmEntitySet, edmNavigationProperty); + + // 2nd: fetch the data from backend. + // e.g. for the URI: Products(1)/Category we have to find the correct Category entity + List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates(); + // e.g. for Products(1)/Category we have to find first the Products(1) + Entity sourceEntity = storage.readEntityData(startEdmEntitySet, keyPredicates); + + // now we have to check if the navigation is + // a) to-one: e.g. Products(1)/Category + // b) to-many with key: e.g. Categories(3)/Products(5) + // the key for nav is used in this case: Categories(3)/Products(5) + List<UriParameter> navKeyPredicates = uriResourceNavigation.getKeyPredicates(); + + if (navKeyPredicates.isEmpty()) { // e.g. DemoService.svc/Products(1)/Category + responseEntity = storage.getRelatedEntity(sourceEntity, responseEdmEntityType); + } else { // e.g. DemoService.svc/Categories(3)/Products(5) + responseEntity = storage.getRelatedEntity(sourceEntity, responseEdmEntityType, navKeyPredicates); + } + } + } else { + // this would be the case for e.g. Products(1)/Category/Products(1)/Category + throw new ODataApplicationException("Not supported", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + + if (responseEntity == null) { + // this is the case for e.g. DemoService.svc/Categories(4) or DemoService.svc/Categories(3)/Products(999) + throw new ODataApplicationException("Nothing found.", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT); + } + + // 3. serialize + ContextURL contextUrl = ContextURL.with().entitySet(responseEdmEntitySet).suffix(Suffix.ENTITY).build(); + EntitySerializerOptions opts = EntitySerializerOptions.with().contextURL(contextUrl).build(); + + ODataSerializer serializer = odata.createSerializer(responseFormat); + SerializerResult serializerResult = serializer.entity(serviceMetadata, + responseEdmEntityType, responseEntity, opts); + + // 4. configure the response object + response.setContent(serializerResult.getContent()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } + + /* + * Example request: + * + * POST URL: http://localhost:8080/DemoService/DemoService.svc/Products + * Header: Content-Type: application/json; odata.metadata=minimal + * Request body: + { + "ID":3, + "Name":"Ergo Screen", + "Description":"17 Optimum Resolution 1024 x 768 @ 85Hz, resolution 1280 x 960" + } + * */ + public void createEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType requestFormat, ContentType responseFormat) + throws ODataApplicationException, DeserializerException, SerializerException { + + // 1. Retrieve the entity type from the URI + EdmEntitySet edmEntitySet = Util.getEdmEntitySet(uriInfo); + EdmEntityType edmEntityType = edmEntitySet.getEntityType(); + + // 2. create the data in backend + // 2.1. retrieve the payload from the POST request for the entity to create and deserialize it + InputStream requestInputStream = request.getBody(); + ODataDeserializer deserializer = odata.createDeserializer(requestFormat); + DeserializerResult result = deserializer.entity(requestInputStream, edmEntityType); + Entity requestEntity = result.getEntity(); + // 2.2 do the creation in backend, which returns the newly created entity + Entity createdEntity = storage.createEntityData(edmEntitySet, requestEntity); + + // 3. serialize the response (we have to return the created entity) + ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).build(); + EntitySerializerOptions options = EntitySerializerOptions.with().contextURL(contextUrl).build(); // expand and select currently not supported + + ODataSerializer serializer = odata.createSerializer(responseFormat); + SerializerResult serializedResponse = serializer.entity(serviceMetadata, edmEntityType, createdEntity, options); + + //4. configure the response object + response.setContent(serializedResponse.getContent()); + response.setStatusCode(HttpStatusCode.CREATED.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + } + + + public void updateEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType requestFormat, ContentType responseFormat) + throws ODataApplicationException, DeserializerException, SerializerException { + + // 1. Retrieve the entity set which belongs to the requested entity + List<UriResource> resourcePaths = uriInfo.getUriResourceParts(); + // Note: only in our example we can assume that the first segment is the EntitySet + UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); + EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet(); + EdmEntityType edmEntityType = edmEntitySet.getEntityType(); + + // 2. update the data in backend + // 2.1. retrieve the payload from the PUT request for the entity to be updated + InputStream requestInputStream = request.getBody(); + ODataDeserializer deserializer = odata.createDeserializer(requestFormat); + DeserializerResult result = deserializer.entity(requestInputStream, edmEntityType); + Entity requestEntity = result.getEntity(); + // 2.2 do the modification in backend + List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates(); + // Note that this updateEntity()-method is invoked for both PUT or PATCH operations + HttpMethod httpMethod = request.getMethod(); + storage.updateEntityData(edmEntitySet, keyPredicates, requestEntity, httpMethod); + + //3. configure the response object + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + + + public void deleteEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo) + throws ODataApplicationException { + + // 1. Retrieve the entity set which belongs to the requested entity + List<UriResource> resourcePaths = uriInfo.getUriResourceParts(); + // Note: only in our example we can assume that the first segment is the EntitySet + UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0); + EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet(); + + // 2. delete the data in backend + List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates(); + storage.deleteEntityData(edmEntitySet, keyPredicates); + + //3. configure the response object + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoPrimitiveProcessor.java ---------------------------------------------------------------------- diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoPrimitiveProcessor.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoPrimitiveProcessor.java new file mode 100644 index 0000000..8766b8d --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/service/DemoPrimitiveProcessor.java @@ -0,0 +1,146 @@ +/* + * 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.service; + +import java.util.List; +import java.util.Locale; + +import myservice.mynamespace.data.Storage; + +import org.apache.olingo.commons.api.data.ContextURL; +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.Property; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmProperty; +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; +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.processor.PrimitiveProcessor; +import org.apache.olingo.server.api.serializer.ODataSerializer; +import org.apache.olingo.server.api.serializer.PrimitiveSerializerOptions; +import org.apache.olingo.server.api.serializer.SerializerException; +import org.apache.olingo.server.api.serializer.SerializerResult; +import org.apache.olingo.server.api.uri.UriInfo; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceProperty; + +public class DemoPrimitiveProcessor implements PrimitiveProcessor { + + private OData odata; + private Storage storage; + private ServiceMetadata serviceMetadata; + + public DemoPrimitiveProcessor(Storage storage) { + this.storage = storage; + } + + public void init(OData odata, ServiceMetadata serviceMetadata) { + this.odata = odata; + this.serviceMetadata = serviceMetadata; + } + + /* + * In our example, the URL would be: http://localhost:8080/DemoService/DemoService.svc/Products(1)/Name + * and the response: + * { + * @odata.context: "$metadata#Products/Name", + * value: "Notebook Basic 15" + * } + * */ + public void readPrimitive(ODataRequest request, ODataResponse response, + UriInfo uriInfo, ContentType responseFormat) + throws ODataApplicationException, SerializerException { + + // 1. Retrieve info from URI + // 1.1. retrieve the info about the requested entity set + List<UriResource> resourceParts = uriInfo.getUriResourceParts(); + // Note: only in our example we can rely that the first segment is the EntitySet + UriResourceEntitySet uriEntityset = (UriResourceEntitySet) resourceParts.get(0); + EdmEntitySet edmEntitySet = uriEntityset.getEntitySet(); + // the key for the entity + List<UriParameter> keyPredicates = uriEntityset.getKeyPredicates(); + + // 1.2. retrieve the requested (Edm) property + UriResourceProperty uriProperty = (UriResourceProperty)resourceParts.get(resourceParts.size() -1); // the last segment is the Property + EdmProperty edmProperty = uriProperty.getProperty(); + String edmPropertyName = edmProperty.getName(); + // in our example, we know we have only primitive types in our model + EdmPrimitiveType edmPropertyType = (EdmPrimitiveType) edmProperty.getType(); + + + // 2. retrieve data from backend + // 2.1. retrieve the entity data, for which the property has to be read + Entity entity = storage.readEntityData(edmEntitySet, keyPredicates); + if (entity == null) { // Bad request + throw new ODataApplicationException("Entity not found", + HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); + } + + // 2.2. retrieve the property data from the entity + Property property = entity.getProperty(edmPropertyName); + if (property == null) { + throw new ODataApplicationException("Property not found", + HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH); + } + + // 3. serialize + Object value = property.getValue(); + if (value != null) { + // 3.1. configure the serializer + ODataSerializer serializer = odata.createSerializer(responseFormat); + + ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).navOrPropertyPath(edmPropertyName).build(); + PrimitiveSerializerOptions options = PrimitiveSerializerOptions.with().contextURL(contextUrl).build(); + // 3.2. serialize + SerializerResult result = serializer.primitive(serviceMetadata, edmPropertyType, property, options); + + //4. configure the response object + response.setContent(result.getContent()); + response.setStatusCode(HttpStatusCode.OK.getStatusCode()); + response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); + }else{ + // in case there's no value for the property, we can skip the serialization + response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); + } + } + + /* + * These processor methods are not handled in this tutorial + * + * */ + public void updatePrimitive(ODataRequest request, ODataResponse response, UriInfo uriInfo, + ContentType requestFormat, ContentType responseFormat) + throws ODataApplicationException, DeserializerException, SerializerException { + throw new ODataApplicationException("Not supported.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + + public void deletePrimitive(ODataRequest request, ODataResponse response, UriInfo uriInfo) + throws ODataApplicationException { + throw new ODataApplicationException("Not supported.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/util/Util.java ---------------------------------------------------------------------- diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/util/Util.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/util/Util.java new file mode 100644 index 0000000..97e8eff --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/util/Util.java @@ -0,0 +1,161 @@ +/* + * 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.util; + +import java.util.List; +import java.util.Locale; + +import org.apache.olingo.commons.api.data.Entity; +import org.apache.olingo.commons.api.data.EntityCollection; +import org.apache.olingo.commons.api.edm.EdmBindingTarget; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.http.HttpStatusCode; +import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.uri.UriInfoResource; +import org.apache.olingo.server.api.uri.UriParameter; +import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; + +public class Util { + + public static EdmEntitySet getEdmEntitySet(UriInfoResource uriInfo) throws ODataApplicationException { + + List<UriResource> resourcePaths = uriInfo.getUriResourceParts(); + // To get the entity set we have to interpret all URI segments + if (!(resourcePaths.get(0) instanceof UriResourceEntitySet)) { + // Here we should interpret the whole URI but in this example we do not support navigation so we throw an + // exception + throw new ODataApplicationException("Invalid resource type for first segment.", HttpStatusCode.NOT_IMPLEMENTED + .getStatusCode(), Locale.ENGLISH); + } + + UriResourceEntitySet uriResource = (UriResourceEntitySet) resourcePaths.get(0); + + return uriResource.getEntitySet(); + } + + public static Entity findEntity(EdmEntityType edmEntityType, EntityCollection entitySet, + List<UriParameter> keyParams) throws ODataApplicationException { + + List<Entity> entityList = entitySet.getEntities(); + + // loop over all entities in order to find that one that matches + // all keys in request e.g. contacts(ContactID=1, CompanyID=1) + for (Entity entity : entityList) { + boolean foundEntity = entityMatchesAllKeys(edmEntityType, entity, keyParams); + if (foundEntity) { + return entity; + } + } + + return null; + } + + public static boolean entityMatchesAllKeys(EdmEntityType edmEntityType, Entity entity, List<UriParameter> keyParams) + throws ODataApplicationException { + + // loop over all keys + for (final UriParameter key : keyParams) { + // key + String keyName = key.getName(); + String keyText = key.getText(); + + // Edm: we need this info for the comparison below + EdmProperty edmKeyProperty = (EdmProperty) edmEntityType.getProperty(keyName); + Boolean isNullable = edmKeyProperty.isNullable(); + Integer maxLength = edmKeyProperty.getMaxLength(); + Integer precision = edmKeyProperty.getPrecision(); + Boolean isUnicode = edmKeyProperty.isUnicode(); + Integer scale = edmKeyProperty.getScale(); + // get the EdmType in order to compare + EdmType edmType = edmKeyProperty.getType(); + EdmPrimitiveType edmPrimitiveType = (EdmPrimitiveType) edmType; + + // Runtime data: the value of the current entity + // don't need to check for null, this is done in olingo library + Object valueObject = entity.getProperty(keyName).getValue(); + + // now need to compare the valueObject with the keyText String + // this is done using the type.valueToString // + String valueAsString = null; + try { + valueAsString = edmPrimitiveType.valueToString(valueObject, isNullable, maxLength, precision, scale, isUnicode); + } catch (EdmPrimitiveTypeException e) { + throw new ODataApplicationException("Failed to retrieve String value", HttpStatusCode.INTERNAL_SERVER_ERROR + .getStatusCode(), Locale.ENGLISH, e); + } + + if (valueAsString == null) { + return false; + } + + boolean matches = valueAsString.equals(keyText); + if (!matches) { + // if any of the key properties is not found in the entity, we don't need to search further + return false; + } + } + + return true; + } + + /** + * Example: + * For the following navigation: DemoService.svc/Categories(1)/Products + * we need the EdmEntitySet for the navigation property "Products" + * + * This is defined as follows in the metadata: + * <code> + * + * <EntitySet Name="Categories" EntityType="OData.Demo.Category"> + * <NavigationPropertyBinding Path="Products" Target="Products"/> + * </EntitySet> + * </code> + * The "Target" attribute specifies the target EntitySet + * Therefore we need the startEntitySet "Categories" in order to retrieve the target EntitySet "Products" + */ + public static EdmEntitySet getNavigationTargetEntitySet(EdmEntitySet startEdmEntitySet, + EdmNavigationProperty edmNavigationProperty) + throws ODataApplicationException { + + EdmEntitySet navigationTargetEntitySet = null; + + String navPropName = edmNavigationProperty.getName(); + EdmBindingTarget edmBindingTarget = startEdmEntitySet.getRelatedBindingTarget(navPropName); + if (edmBindingTarget == null) { + throw new ODataApplicationException("Not supported.", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + + if (edmBindingTarget instanceof EdmEntitySet) { + navigationTargetEntitySet = (EdmEntitySet) edmBindingTarget; + } else { + throw new ODataApplicationException("Not supported.", + HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT); + } + + return navigationTargetEntitySet; + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/d483c8fd/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/web/DemoServlet.java ---------------------------------------------------------------------- diff --git a/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/web/DemoServlet.java b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/web/DemoServlet.java new file mode 100644 index 0000000..190da4f --- /dev/null +++ b/samples/tutorials/p9_action/src/main/java/myservice/mynamespace/web/DemoServlet.java @@ -0,0 +1,75 @@ +/* + * 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.web; + +import java.io.IOException; +import java.lang.Override;import java.lang.RuntimeException;import java.util.ArrayList; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import myservice.mynamespace.data.Storage; +import myservice.mynamespace.service.DemoActionProcessor; +import myservice.mynamespace.service.DemoEdmProvider; +import myservice.mynamespace.service.DemoEntityCollectionProcessor; +import myservice.mynamespace.service.DemoEntityProcessor; +import myservice.mynamespace.service.DemoPrimitiveProcessor; + +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataHttpHandler; +import org.apache.olingo.server.api.ServiceMetadata; +import org.apache.olingo.server.api.edmx.EdmxReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DemoServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + private static final Logger LOG = LoggerFactory.getLogger(DemoServlet.class); + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + HttpSession session = req.getSession(true); + Storage storage = (Storage) session.getAttribute(Storage.class.getName()); + if (storage == null) { + storage = new Storage(); + session.setAttribute(Storage.class.getName(), storage); + } + + // create odata handler and configure it with EdmProvider and Processor + OData odata = OData.newInstance(); + ServiceMetadata edm = odata.createServiceMetadata(new DemoEdmProvider(), new ArrayList<EdmxReference>()); + ODataHttpHandler handler = odata.createHandler(edm); + handler.register(new DemoEntityCollectionProcessor(storage)); + handler.register(new DemoEntityProcessor(storage)); + handler.register(new DemoPrimitiveProcessor(storage)); + handler.register(new DemoActionProcessor(storage)); + + // let the handler do the work + handler.process(req, resp); + } catch (RuntimeException e) { + LOG.error("Server Error occurred in ExampleServlet", e); + throw new ServletException(e); + } + } +}
