This How To Guide shows how to extend the basic read scenario with support for the $expand system query option.
It shows how to call the EntityProvider.writeEntry(...)
and EntityProvider.writeEntrySet(...)
methods with the necessary EntityProviderWriteProperties
set and how to implement the necessary OnWriteEntryContent OnWriteFeedContent
callbacks.
Setup of Basic Read Scenario
If you like to directly experiment with the results of the extented basic read scenario, you can use this shortcut:
$ODATA_PROJECT_HOME
in the tutorial).$ODATA_PROJECT_HOME
mvn eclipse:eclipse clean install
$ODATA_PROJECT_HOME
folderIf Basic Read Scenario is already set up there is nothing additional to do. Otherwise please refer to the Prerequisites section of the Basic Read Scenario.
The steps to extend the basic read with $expand support for the Car and Manufacturer entities (not entity sets) are to provide the expanded data via ODataCallbacks and register these for the corresponding navigation properties.
To support $expand
for a single entry the interface org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent
must be implemented. This provides the method WriteEntryCallbackResult retrieveEntryResult(WriteEntryCallbackContext context) throws ODataApplicationException;
which is called during processing from the EntityProvider
to receive the necessary data which than is inlined in the response.
In our sample we create a class MyCallback
which implements org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent
in following way:
@Override
public WriteEntryCallbackResult retrieveEntryResult(WriteEntryCallbackContext context) throws ODataApplicationException {
WriteEntryCallbackResult result = new WriteEntryCallbackResult();
try {
if (isNavigationFromTo(context, ENTITY_SET_NAME_CARS, ENTITY_NAME_MANUFACTURER)) {
EntityProviderWriteProperties inlineProperties = EntityProviderWriteProperties.serviceRoot(serviceRoot)
.expandSelectTree(context.getCurrentExpandSelectTreeNode())
.build();
Map<String, Object> keys = context.extractKeyFromEntryData();
Integer carId = (Integer) keys.get("Id");
result.setEntryData(dataStore.getManufacturerFor(carId));
result.setInlineProperties(inlineProperties);
}
} catch (EdmException e) {
// TODO: should be handled and not only logged
LOG.error("Error in $expand handling.", e);
} catch (EntityProviderException e) {
// TODO: should be handled and not only logged
LOG.error("Error in $expand handling.", e);
}
return result;
}
Within this method we first check if the source entity and navigation property are correct for our case (via the method isNavigationFromTo(...):boolean)
, then we create the EntityProviderWriteProperties
with the new (current) ExpandSelectTreeNode
, receive the data from our DataStore
and put all into the result which then will be further processed by the EntityProvider
.
To support $expand
for a feed of entries (entity set) the interface org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent
must be implemented. These provides the method WriteFeedCallbackResult retrieveFeedResult(WriteFeedCallbackContext context) throws ODataApplicationException;
which is called during processing from the EntityProvider
to receive the necessary data which than is inlined in the response.
It is possible to create an additional callback class but for convenience we expand our already created callback (MyCallback
) to implement org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent
and provide the method implementation in following way:
@Override
public WriteFeedCallbackResult retrieveFeedResult(WriteFeedCallbackContext context) throws ODataApplicationException {
WriteFeedCallbackResult result = new WriteFeedCallbackResult();
try {
if(isNavigationFromTo(context, ENTITY_SET_NAME_MANUFACTURERS, ENTITY_SET_NAME_CARS)) {
EntityProviderWriteProperties inlineProperties = EntityProviderWriteProperties.serviceRoot(serviceRoot)
.expandSelectTree(context.getCurrentExpandSelectTreeNode())
.selfLink(context.getSelfLink())
.build();
Map<String, Object> keys = context.extractKeyFromEntryData();
Integer manufacturerId = (Integer) keys.get("Id");
result.setFeedData(dataStore.getCarsFor(manufacturerId));
result.setInlineProperties(inlineProperties);
}
} catch (EdmException e) {
// TODO: should be handled and not only logged
LOG.error("Error in $expand handling.", e);
} catch (EntityProviderException e) {
// TODO: should be handled and not only logged
LOG.error("Error in $expand handling.", e);
}
return result;
}
Within this method we first check if the source entity and navigation property are correct for our case (via the method isNavigationFromTo(...):boolean)
, then we create the EntityProviderWriteProperties
with the new (current) ExpandSelectTreeNode
, receive the data from our DataStore
and put all into the result which then will be further processed by the EntityProvider
.
This example shows that the basic callback logic between OnWriteEntryConten
t and OnWriteFeedContent
is very similar. Validation of current element (optional), preparing of EntityProviderWriteProperties
, receive of data and putting all together into corresponding result object (WriteEntryCallbackResult
or WriteFeedCallbackResult
).
To improve code readability the isNavigationFromTo(...):boolean
method was also added to the class. The method is used to check if the retrieved request is related to given entity set and navigation:
private boolean isNavigationFromTo(WriteCallbackContext context, String entitySetName, String navigationPropertyName) throws EdmException {
if(entitySetName == null || navigationPropertyName == null) {
return false;
}
EdmEntitySet sourceEntitySet = context.getSourceEntitySet();
EdmNavigationProperty navigationProperty = context.getNavigationProperty();
return entitySetName.equals(sourceEntitySet.getName()) && navigationPropertyName.equals(navigationProperty.getName());
}
The necessary callbacks (MyCallback
class) now has to be registered during the corresponding readEntity(...)
call. Therefore we first create a map with the property name as key and the according callback as value. Additional we need to create the ExpandSelectTreeNode
based on current element position. Both then have to be set in the EntityProviderWritePropertiesBuilder
.
The following code show the few lines we need for extending the read of a car with its expanded manufacturer.
// create and register callback
Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
callbacks.put(ENTITY_NAME_MANUFACTURER, new MyCallback(dataStore, serviceRoot));
ExpandSelectTreeNode expandSelectTreeNode = UriParser.createExpandSelectTree(uriInfo.getSelect(), uriInfo.getExpand());
propertiesBuilder.expandSelectTree(expandSelectTreeNode).callbacks(callbacks);
The following code show the few lines we need for extending the read of a manufacturer with its expanded cars.
// create and register callback
Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
callbacks.put(ENTITY_SET_NAME_CARS, new MyCallback(dataStore, serviceRoot));
ExpandSelectTreeNode expandSelectTreeNode = UriParser.createExpandSelectTree(uriInfo.getSelect(), uriInfo.getExpand());
propertiesBuilder.expandSelectTree(expandSelectTreeNode).callbacks(callbacks);
The complete readEntity(...)
method should now look like:
public ODataResponse readEntity(GetEntityUriInfo uriInfo, String contentType) throws ODataException {
if (uriInfo.getNavigationSegments().size() == 0) {
EdmEntitySet entitySet = uriInfo.getStartEntitySet();
if (ENTITY_SET_NAME_CARS.equals(entitySet.getName())) {
int id = getKeyValue(uriInfo.getKeyPredicates().get(0));
Map<String, Object> data = dataStore.getCar(id);
if (data != null) {
URI serviceRoot = getContext().getPathInfo().getServiceRoot();
ODataEntityProviderPropertiesBuilder propertiesBuilder = EntityProviderWriteProperties.serviceRoot(serviceRoot);
// create and register callback
Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
callbacks.put(ENTITY_NAME_MANUFACTURER, new MyCallback(dataStore, serviceRoot));
ExpandSelectTreeNode expandSelectTreeNode = UriParser.createExpandSelectTree(uriInfo.getSelect(), uriInfo.getExpand());
//
propertiesBuilder.expandSelectTree(expandSelectTreeNode).callbacks(callbacks);
return EntityProvider.writeEntry(contentType, entitySet, data, propertiesBuilder.build());
}
} else if (ENTITY_SET_NAME_MANUFACTURERS.equals(entitySet.getName())) {
int id = getKeyValue(uriInfo.getKeyPredicates().get(0));
Map<String, Object> data = dataStore.getManufacturer(id);
if (data != null) {
URI serviceRoot = getContext().getPathInfo().getServiceRoot();
ODataEntityProviderPropertiesBuilder propertiesBuilder = EntityProviderWriteProperties.serviceRoot(serviceRoot);
// create and register callback
Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
callbacks.put(ENTITY_SET_NAME_CARS, new MyCallback(dataStore, serviceRoot));
ExpandSelectTreeNode expandSelectTreeNode = UriParser.createExpandSelectTree(uriInfo.getSelect(), uriInfo.getExpand());
//
propertiesBuilder.expandSelectTree(expandSelectTreeNode).callbacks(callbacks);
return EntityProvider.writeEntry(contentType, entitySet, data, propertiesBuilder.build());
}
}
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
} else if (uriInfo.getNavigationSegments().size() == 1) {
//navigation first level, simplified example for illustration purposes only
EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
if (ENTITY_SET_NAME_MANUFACTURERS.equals(entitySet.getName())) {
int carKey = getKeyValue(uriInfo.getKeyPredicates().get(0));
return EntityProvider.writeEntry(contentType, uriInfo.getTargetEntitySet(), dataStore.getManufacturer(carKey), EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).build());
}
throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
}
throw new ODataNotImplementedException();
}
Now we can test out $expand
extension in the web application.
Like in the basic read scenario follow these steps:
mvn clean install
Next extension step for read scenario are read of Media Resources.
Copyright © 2013-2023, The Apache Software Foundation
Apache Olingo, Olingo, Apache, the Apache feather, and
the Apache Olingo project logo are trademarks of the Apache Software
Foundation.