[OLINGO-713] Tutorial: Merged version of the tutorials added

This commit is contained in:
Christia Holzer 2015-09-08 10:50:01 +02:00
parent 170d561c04
commit 1558273f52
4 changed files with 321 additions and 43 deletions

View File

@ -20,6 +20,7 @@ package myservice.mynamespace.data;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import myservice.mynamespace.service.DemoEdmProvider;
import myservice.mynamespace.util.Util;
@ -30,7 +31,11 @@ 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.http.HttpMethod;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.UriParameter;
public class Storage {
@ -132,7 +137,44 @@ public class Storage {
return navigationTargetEntityCollection;
}
public Entity createEntityData(EdmEntitySet edmEntitySet, Entity entityToCreate) {
EdmEntityType edmEntityType = edmEntitySet.getEntityType();
// actually, this is only required if we have more than one Entity Type
if (edmEntityType.getName().equals(DemoEdmProvider.ET_PRODUCT_NAME)) {
return createProduct(edmEntityType, entityToCreate);
}
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();
// actually, this is only required if we have more than one Entity Type
if (edmEntityType.getName().equals(DemoEdmProvider.ET_PRODUCT_NAME)) {
updateProduct(edmEntityType, keyParams, updateEntity, httpMethod);
}
}
public void deleteEntityData(EdmEntitySet edmEntitySet, List<UriParameter> keyParams)
throws ODataApplicationException {
EdmEntityType edmEntityType = edmEntitySet.getEntityType();
// actually, this is only required if we have more than one Entity Type
if (edmEntityType.getName().equals(DemoEdmProvider.ET_PRODUCT_NAME)) {
deleteProduct(edmEntityType, keyParams);
}
}
/* INTERNAL */
private EntityCollection getProducts() {
@ -172,9 +214,104 @@ public class Storage {
/* generic approach to find the requested entity */
return Util.findEntity(edmEntityType, entitySet, keyParams);
}
private void updateProduct(EdmEntityType edmEntityType, List<UriParameter> keyParams, Entity entity,
HttpMethod httpMethod) throws ODataApplicationException {
Entity productEntity = getProduct(edmEntityType, keyParams);
if (productEntity == 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 = productEntity.getProperties();
for (Property existingProp : existingProperties) {
String propName = existingProp.getName();
// ignore the key properties, they aren't updateable
if (isKey(edmEntityType, propName)) {
continue;
}
Property updateProperty = entity.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 deleteProduct(EdmEntityType edmEntityType, List<UriParameter> keyParams)
throws ODataApplicationException {
Entity productEntity = getProduct(edmEntityType, keyParams);
if (productEntity == null) {
throw new ODataApplicationException("Entity not found", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ENGLISH);
}
this.productList.remove(productEntity);
}
private Entity createProduct(EdmEntityType edmEntityType, Entity entity) {
// the ID of the newly created product entity is generated automatically
int newId = 1;
while (productIdExists(newId)) {
newId++;
}
Property idProperty = entity.getProperty("ID");
if (idProperty != null) {
idProperty.setValue(ValueType.PRIMITIVE, new Integer(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));
}
this.productList.add(entity);
return entity;
}
private boolean productIdExists(int id) {
for (Entity entity : this.productList) {
Integer existingID = (Integer) entity.getProperty("ID").getValue();
if (existingID.intValue() == id) {
return true;
}
}
return false;
}
/* 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();

View File

@ -18,11 +18,10 @@
*/
package myservice.mynamespace.service;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import myservice.mynamespace.data.Storage;
import org.apache.olingo.commons.api.Constants;
import org.apache.olingo.commons.api.data.ContextURL;
import org.apache.olingo.commons.api.data.ContextURL.Suffix;
@ -36,6 +35,7 @@ import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmNavigationPropertyBinding;
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;
@ -43,6 +43,8 @@ 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;
@ -57,10 +59,13 @@ import org.apache.olingo.server.api.uri.queryoption.ExpandItem;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
import org.apache.olingo.server.api.uri.queryoption.SelectOption;
import myservice.mynamespace.data.Storage;
import myservice.mynamespace.util.Util;
public class DemoEntityProcessor implements EntityProcessor {
private OData odata;
private ServiceMetadata srvMetadata;
private ServiceMetadata serviceMetadata;
private Storage storage;
public DemoEntityProcessor(Storage storage) {
@ -69,21 +74,73 @@ public class DemoEntityProcessor implements EntityProcessor {
public void init(OData odata, ServiceMetadata serviceMetadata) {
this.odata = odata;
this.srvMetadata = serviceMetadata;
this.serviceMetadata = serviceMetadata;
}
public void readEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat)
throws ODataApplicationException, SerializerException {
// 1. retrieve the Entity Type
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 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
// 2. retrieve the data from backend
List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates();
Entity entity = storage.readEntityData(edmEntitySet, keyPredicates);
// 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
if (!(uriResource instanceof UriResourceEntitySet)) {
throw new ODataApplicationException("Only EntitySet is supported",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ENGLISH);
}
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. apply system query options
@ -102,13 +159,13 @@ public class DemoEntityProcessor implements EntityProcessor {
EdmNavigationProperty edmNavigationProperty = null;
ExpandItem expandItem = expandOption.getExpandItems().get(0);
if(expandItem.isStar()) {
List<EdmNavigationPropertyBinding> bindings = edmEntitySet.getNavigationPropertyBindings();
List<EdmNavigationPropertyBinding> bindings = responseEdmEntitySet.getNavigationPropertyBindings();
// we know that there are navigation bindings
// however normally in this case a check if navigation bindings exists is done
if(!bindings.isEmpty()) {
// can in our case only be 'Category' or 'Products', so we can take the first
EdmNavigationPropertyBinding binding = bindings.get(0);
EdmElement property = edmEntitySet.getEntityType().getProperty(binding.getPath());
EdmElement property = responseEdmEntitySet.getEntityType().getProperty(binding.getPath());
// we don't need to handle error cases, as it is done in the Olingo library
if(property instanceof EdmNavigationProperty) {
edmNavigationProperty = (EdmNavigationProperty) property;
@ -116,10 +173,10 @@ public class DemoEntityProcessor implements EntityProcessor {
}
} else {
// can be 'Category' or 'Products', no path supported
UriResource uriResource = expandItem.getResourcePath().getUriResourceParts().get(0);
UriResource expandUriResource = expandItem.getResourcePath().getUriResourceParts().get(0);
// we don't need to handle error cases, as it is done in the Olingo library
if(uriResource instanceof UriResourceNavigation) {
edmNavigationProperty = ((UriResourceNavigation) uriResource).getProperty();
if(expandUriResource instanceof UriResourceNavigation) {
edmNavigationProperty = ((UriResourceNavigation) expandUriResource).getProperty();
}
}
@ -137,26 +194,26 @@ public class DemoEntityProcessor implements EntityProcessor {
if(edmNavigationProperty.isCollection()){ // in case of Categories(1)/$expand=Products
// fetch the data for the $expand (to-many navigation) from backend
// here we get the data for the expand
EntityCollection expandEntityCollection = storage.getRelatedEntityCollection(entity, expandEdmEntityType);
EntityCollection expandEntityCollection = storage.getRelatedEntityCollection(responseEntity, expandEdmEntityType);
link.setInlineEntitySet(expandEntityCollection);
} else { // in case of Products(1)?$expand=Category
// fetch the data for the $expand (to-one navigation) from backend
// here we get the data for the expand
Entity expandEntity = storage.getRelatedEntity(entity, expandEdmEntityType);
Entity expandEntity = storage.getRelatedEntity(responseEntity, expandEdmEntityType);
link.setInlineEntity(expandEntity);
}
// set the link - containing the expanded data - to the current entity
entity.getNavigationLinks().add(link);
responseEntity.getNavigationLinks().add(link);
}
}
// 4. serialize
EdmEntityType edmEntityType = edmEntitySet.getEntityType();
EdmEntityType edmEntityType = responseEdmEntitySet.getEntityType();
// we need the property names of the $select, in order to build the context URL
String selectList = odata.createUriHelper().buildContextURLSelectList(edmEntityType, expandOption, selectOption);
ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet)
ContextURL contextUrl = ContextURL.with().entitySet(responseEdmEntitySet)
.selectList(selectList)
.suffix(Suffix.ENTITY).build();
@ -169,34 +226,99 @@ public class DemoEntityProcessor implements EntityProcessor {
.build();
ODataSerializer serializer = this.odata.createSerializer(responseFormat);
SerializerResult serializerResult = serializer.entity(srvMetadata, edmEntityType, entity, opts);
SerializerResult serializerResult = serializer.entity(serviceMetadata, edmEntityType, responseEntity, opts);
// 5. configure the response object
response.setContent(serializerResult.getContent());
response.setStatusCode(HttpStatusCode.OK.getStatusCode());
response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
}
/*
* These processor methods are not handled in this tutorial
*/
/*
* 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 {
throw new ODataApplicationException("Not supported.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
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 = this.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 = this.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 {
throw new ODataApplicationException("Not supported.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
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 = this.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 {
throw new ODataApplicationException("Not supported.", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
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());
}
}

View File

@ -33,10 +33,29 @@ 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) {
@ -123,14 +142,14 @@ public class Util {
* 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 startEntitySet,
public static EdmEntitySet getNavigationTargetEntitySet(EdmEntitySet startEdmEntitySet,
EdmNavigationProperty edmNavigationProperty)
throws ODataApplicationException {
EdmEntitySet navigationTargetEntitySet = null;
String navPropName = edmNavigationProperty.getName();
EdmBindingTarget edmBindingTarget = startEntitySet.getRelatedBindingTarget(navPropName);
EdmBindingTarget edmBindingTarget = startEdmEntitySet.getRelatedBindingTarget(navPropName);
if (edmBindingTarget == null) {
throw new ODataApplicationException("Not supported.",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);

View File

@ -125,14 +125,14 @@ public class Util {
* 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 startEntitySet,
public static EdmEntitySet getNavigationTargetEntitySet(EdmEntitySet startEdmEntitySet,
EdmNavigationProperty edmNavigationProperty)
throws ODataApplicationException {
EdmEntitySet navigationTargetEntitySet = null;
String navPropName = edmNavigationProperty.getName();
EdmBindingTarget edmBindingTarget = startEntitySet.getRelatedBindingTarget(navPropName);
EdmBindingTarget edmBindingTarget = startEdmEntitySet.getRelatedBindingTarget(navPropName);
if (edmBindingTarget == null) {
throw new ODataApplicationException("Not supported.",
HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);