blob: dcf47d2a1ed5daaf4484d10dc7b2c40389a43641 [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<!--
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.
-->
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Apache Olingo provides libraries which enable developers to implement OData producers and OData consumers. The available OData Java library implements OData version 2.0. In future on goal is to provide an OData 4.0 compliant library once the OData standard is published at OASIS. The focus within the community is currently on the Java technology but it is up to the community to discuss if other environments find interest.">
<meta name="author" content="">
<link rel="icon" href="/favicon.ico">
<title>Apache Olingo Library</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.css" rel="stylesheet" type="text/css"><!-- Custom styles for this template -->
<link href="/css/navbar.css" rel="stylesheet" type="text/css"><!-- Just for debugging purposes. Don't actually copy these 2 lines! -->
<link href="/css/offcanvas.css" rel="stylesheet" type="text/css"><!-- Custom styles for this template -->
<link rel="stylesheet" href="/css/main.css">
<!--[if lt IE 9]><script src="/js/ie8-responsive-file-warning.js"></script><![endif]-->
<style>
.headerlink {
visibility: hidden;
}
dt:hover > .headerlink, p:hover > .headerlink, td:hover > .headerlink, h1:hover > .headerlink, h2:hover > .headerlink, h3:hover > .headerlink, h4:hover > .headerlink, h5:hover > .headerlink, h6:hover > .headerlink {
visibility: visible
} </style>
<script src="/js/ie-emulation-modes-warning.js" type="text/javascript">
</script><!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<script src="/js/ie10-viewport-bug-workaround.js" type="text/javascript">
</script><!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="/js/html5shiv.min.js"></script>
<script src="/js/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<!-- Static navbar -->
<div class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<img class="navbar-brand" src="/img/OlingoOrangeTM.png" style="width:62px;" >
<a class="navbar-brand" href="/">Apache Olingo™</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">ASF <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="http://www.apache.org/foundation/">ASF Home</a></li>
<li><a href="http://projects.apache.org/">Projects</a></li>
<li><a href="http://people.apache.org/">People</a></li>
<li><a href="http://www.apache.org/foundation/getinvolved.html">Get Involved</a></li>
<li><a href="http://www.apache.org/dyn/closer.cgi">Download</a></li>
<li><a href="http://www.apache.org/security/">Security</a></li>
<li><a href="http://www.apache.org/foundation/sponsorship.html">Support Apache</a></li>
</ul>
</li>
<li><a href="http://www.apache.org/licenses/">License</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Download <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/doc/odata2/download.html">Download OData 2.0 Java</a></li>
<li><a href="/doc/odata4/download.html">Download OData 4.0 Java</a></li>
<li><a href="/doc/javascript/download.html">Download OData 4.0 JavaScript</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Documentation <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/doc/odata2/index.html">Documentation OData 2.0 Java</a></li>
<li><a href="/doc/odata4/index.html">Documentation OData 4.0 Java</a></li>
<li><a href="/doc/javascript/index.html">Documentation OData 4.0 JavaScript</a></li>
</ul>
</li>
<li><a href="/support.html">Support</a></li>
<li><a href="/contribute.html">Contribute</a></li>
</ul>
<a class="navbar-right" href="http://www.apache.org/foundation/" target="_blank">
<img class="navbar-right" height="50px" src="/img/asf_logo_url.svg" alt="Apache Software Foundation">
</a>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</div><!-- Main component for a primary marketing message or call to action -->
<h1 id="how-to-build-an-odata-service-with-olingo-v4">How to build an OData Service with Olingo V4<a class="headerlink" href="#how-to-build-an-odata-service-with-olingo-v4" title="Permalink">&para;</a></h1>
<h1 id="part-6-action-imports-and-function-imports">Part 6: Action Imports and Function Imports<a class="headerlink" href="#part-6-action-imports-and-function-imports" title="Permalink">&para;</a></h1>
<p><strong>Table of Contents</strong></p>
<div id="toc"><ul><li><a class="toc-href" href="#introduction" title="Introduction">Introduction</a></li><li><a class="toc-href" href="#preparation" title="Preparation">Preparation</a></li><li><a class="toc-href" href="#implementation" title="Implementation">Implementation</a><ul><li><a class="toc-href" href="#extend-the-metadata-model" title="Extend the Metadata model">Extend the Metadata model</a></li><li><a class="toc-href" href="#extend-the-data-store" title="Extend the data store">Extend the data store</a></li><li><a class="toc-href" href="#extend-the-entity-collection-and-the-entity-processor-to-handle-function-imports" title="Extend the entity collection and the entity processor to handle function imports">Extend the entity collection and the entity processor to handle function imports</a></li><li><a class="toc-href" href="#implement-an-action-processor" title="Implement an action processor">Implement an action processor</a></li></ul></li><li><a class="toc-href" href="#run-the-implemented-service" title="Run the implemented service">Run the implemented service</a></li><li><a class="toc-href" href="#links" title="Links">Links</a><ul><li><a class="toc-href" href="#tutorials" title="Tutorials">Tutorials</a></li><li><a class="toc-href" href="#code-and-repository" title="Code and Repository">Code and Repository</a></li><li><a class="toc-href" href="#further-reading" title="Further reading">Further reading</a></li></ul></li></ul></div>
<h2 id="introduction">Introduction<a class="headerlink" href="#introduction" title="Permalink">&para;</a></h2>
<p>In the present tutorial, we&rsquo;ll implement a function import and an action import as well.</p>
<p><strong>Note:</strong>
The final source code can be found in the project <a href="https://gitbox.apache.org/repos/asf/olingo-odata4">git repository</a>.
A detailed description how to checkout the tutorials can be found <a href="/doc/odata4/tutorials/prerequisites/prerequisites.html">here</a>.<br/>
This tutorial can be found in subdirectory <code>/samples/tutorials/p9_action</code></p>
<p>The <a href="http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part1-protocol/odata-v4.0-errata02-os-part1-protocol-complete.html#_Toc406398201">OData V4 specification</a> gives us a definition what <em>Functions</em>, <em>Actions</em> are:</p>
<blockquote>
<p>Operations allow the execution of custom logic on parts of a data
model. Functions are operations that do not have side effects and may
support further composition, for example, with additional filter
operations, functions or an action. Actions are operations that allow
side effects, such as data modification, and cannot be further
composed in order to avoid non-deterministic behavior. Actions and
functions are either bound to a type, enabling them to be called as
members of an instance of that type, or unbound, in which case they
are called as static operations. Action imports and function imports
enable unbound actions and functions to be called from the service
root.</p>
</blockquote>
<p>In this short definition are several terms which are to be explained first. As you might expect operation is the superordinate of functions and actions. The result of operations can be:</p>
<ul>
<li>An <em>entity</em> or a <em>collection of entities</em></li>
<li>A <em>primitive property</em> or a <em>collection of primitive properties</em></li>
<li>A <em>complex property</em> or a <em>collection of complex properties</em></li>
</ul>
<p>In addition an <em>Action</em> can return void that means there is no return value. A <em>Function</em> must return a value.</p>
<p>The definition gives us some parts where function and actions can be used. First an <em>Operation</em> can be bound or unbound. In this tutorial we will focus on unbound operations. Unbound operations are something like static methods in Java, so if one of these operations have parameters we have to pass all of them explicit to the operation.</p>
<p><strong>Example</strong></p>
<p>For example there could be a function that calculates the VAT. The result depends on the one hand from the net price and on the other hand from the country in which the customer lives.</p>
<p>Such a function can be expressed in the metadata document as follows</p>
<pre><code class="language-xml"> &lt;Function Name="CalculateVAT"&gt;
&lt;Parameter Name="NetPrice" Type="Edm.Decimal" Nullable="false"/&gt;
&lt;Parameter Name="Country" Type="Edm.String" Nullable="false"/&gt;
&lt;ReturnType Type="Edm.Decimal"/&gt;
&lt;/Function&gt;
</code></pre>
<p>To make this function statically callable we have to define a Function Import.</p>
<pre><code class="language-xml"> &lt;EntityContainer Name="Container"&gt;
&lt;FunctionImport Name="StaticCalculateVAT"
Function="CalculateVAT"
IncludeInServiceDocument="true"/&gt;
&lt;/EntityContainer&gt;
</code></pre>
<p>To call such a Function Import the client issues a GET requests to a URL identifying the function import. The parameters are passed by using the so called inline parameter syntax. In this simple case such a call could look like this:</p>
<pre><code>http://host/myService/StaticCalculateVAT(NetPrice=123.00,Country=&rsquo;US&rsquo;)
</code></pre>
<p>The definition talks about composing of functions. By default every function is Composable=false, that means there must be no further resource parts after the function call and there must also be no system query options. If a function is composable you are be able to use system query options. Which options are allowed in particular is based on the return type of the function. Further you can navigate to properties and use Navigation Properties to navigate to related entities as well.</p>
<p>The definition of Actions / Action Imports in metadata document is similar to functions.</p>
<pre><code class="language-xml"> &lt;Action Name="Reset"&gt;
&lt;Parameter Name="Amount" Type="Edm.Int32"/&gt;
&lt;/Action&gt;
&lt;EntityContainer Name="Container"&gt;
&lt;ActionImport Name="StaticReset" Action="Reset"/&gt;
&lt;/EntityContainer&gt;
</code></pre>
<p>As you can see, this function does not return any value and takes one parameter &ldquo;<em>Amount</em>&rdquo; To call this action import, you have to issue an POST request to</p>
<pre><code>http://host/myService/StaticReset
</code></pre>
<p>The parameters are passed within the body of the request. In this case such a body could look like the following (JSON - Syntax):</p>
<pre><code class="language-json"> {
"Amount": 2
}
</code></pre>
<p>As you read in the definition, actions can have side effects (modifying the data) but cannot be composed like functions.</p>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permalink">&para;</a></h2>
<p>You should read the previous tutorials first to have an idea how to read entities and entity collections. In addition the following code is based on the write tutorial merged with the navigation tutorial.</p>
<p>As a shortcut you should checkout the prepared tutorial project in the <a href="https://gitbox.apache.org/repos/asf/olingo-odata4">git repository</a> in folder /samples/tutorials/p9_action_preparation.</p>
<p>Afterwards do a Deploy and run: it should be working. At this state you can perform CRUD operations and do navigations between products and categories.</p>
<h2 id="implementation">Implementation<a class="headerlink" href="#implementation" title="Permalink">&para;</a></h2>
<p>We use the given data model you are familiar with. To keep things simple we implement one function import and one action import.</p>
<p><strong>Function Import: CountCategories</strong><br/>
This function takes a mandatory parameter &ldquo;<em>Amount</em>&rdquo;. The function returns a collection of categories with the very same number of related products.</p>
<p>After finishing the implementation the definition of the function should look like this:</p>
<pre><code class="language-xml"> &lt;Function Name="CountCategories"&gt;
&lt;Parameter Name="Amount" Type="Edm.Int32" Nullable="false"/&gt;
&lt;ReturnType Type="Collection(OData.Demo.Category)"/&gt;
&lt;/Function&gt;
</code></pre>
<p>As described in the previous tutorials, the type of the response determines which processor interface will be called by the Olingo library. It is <strong>important to know, that functions are dispatched to the traditional processor interfaces</strong>.
That means there are no special "FunctionProcessors". In our case, the function returns a collection of Categories. So we have to extend the <code>DemoEntityCollectionProcessor</code>. As you will see it is possible to address a single entity by its key. So we have to extend the <code>DemoEntityProcessor</code> as well to handle requests which responds a single entity.</p>
<p><strong>Action Import: Reset</strong><br/>
This action takes an optional parameter &ldquo;<em>Amount</em>&rdquo;. The actions resets the whole data of the service and creates <em># of Amount</em> products with the related categories.</p>
<p>After finishing the implementation the definition of the action should be like this:</p>
<pre><code class="language-xml"> &lt;Action Name="Reset" IsBound="false"&gt;
&lt;Parameter Name="Amount" Type="Edm.Int32"/&gt;
&lt;/Action&gt;
</code></pre>
<p>While actions are called by using HTTP Method POST is nessesary to introduce new processor interfaces for actions. So there exists a bunch of interfaces, for each return type strictly one.</p>
<p><strong>Steps</strong></p>
<ul>
<li>Extend the Metadata model</li>
<li>Extend the data store</li>
<li>Extend the entity collection and the entity processor to handle function imports</li>
<li>Implement an action processor</li>
</ul>
<h3 id="extend-the-metadata-model">Extend the Metadata model<a class="headerlink" href="#extend-the-metadata-model" title="Permalink">&para;</a></h3>
<p>Create the following constants in the DemoEdmProvider. These constants are used to address the operations.</p>
<pre><code class="language-java"> // 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";
</code></pre>
<p>The way to announce the operations is very similar to announcing EntityTypes. We have to override some methods. Those methods provide the definition of the Edm elements. We need methods for:</p>
<ul>
<li>Actions</li>
<li>Functions</li>
<li>Action Imports</li>
<li>Function Imports</li>
</ul>
<p>The code is simple and straight forward. First, we check which function we have to return. Then, a list of parameters and the return type are created. At the end all parts are fit together and get returned as new <code>CsdlFunction</code> Object.</p>
<pre><code class="language-java"> @Override
public List&lt;CsdlFunction&gt; 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&lt;CsdlFunction&gt; functions = new ArrayList&lt;CsdlFunction&gt;();
// 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;
}
</code></pre>
<p>We have created the function itself. To express that function can be called statically we have to override the method <code>getFunctionImport()</code>.</p>
<pre><code class="language-java"> @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;
}
</code></pre>
<p>To define the actions and the action imports the <code>getActions()</code> and <code>getActionImport()</code> methods have to be overriden and the necessary code is quite similar to the functions sample above:</p>
<pre><code class="language-java"> @Override
public List&lt;CsdlAction&gt; 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&lt;CsdlAction&gt; actions = new ArrayList&lt;CsdlAction&gt;();
// Create parameters
final List&lt;CsdlParameter&gt; parameters = new ArrayList&lt;CsdlParameter&gt;();
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;
}
</code></pre>
<p>Finally we have to announce these operations to the schema and the entity container.
Add the following lines to the method <code>getSchemas()</code>:</p>
<pre><code class="language-java"> // add actions
List&lt;CsdlAction&gt; actions = new ArrayList&lt;CsdlAction&gt;();
actions.addAll(getActions(ACTION_RESET_FQN));
schema.setActions(actions);
// add functions
List&lt;CsdlFunction&gt; functions = new ArrayList&lt;CsdlFunction&gt;();
functions.addAll(getFunctions(FUNCTION_COUNT_CATEGORIES_FQN));
schema.setFunctions(functions);
</code></pre>
<p>Also add the following lines to the method <code>getEntityContainer()</code></p>
<pre><code class="language-java"> // Create function imports
List&lt;CsdlFunctionImport&gt; functionImports = new ArrayList&lt;CsdlFunctionImport&gt;();
functionImports.add(getFunctionImport(CONTAINER, FUNCTION_COUNT_CATEGORIES));
// Create action imports
List&lt;CsdlActionImport&gt; actionImports = new ArrayList&lt;CsdlActionImport&gt;();
actionImports.add(getActionImport(CONTAINER, ACTION_RESET));
entityContainer.setFunctionImports(functionImports);
entityContainer.setActionImports(actionImports);
</code></pre>
<h3 id="extend-the-data-store">Extend the data store<a class="headerlink" href="#extend-the-data-store" title="Permalink">&para;</a></h3>
<p>We need two methods in the data store to read the function import <code>CountCategories</code>.</p>
<p>The first method returns a collection of entites and the second returns a single entity of this collection.</p>
<pre><code class="language-java"> 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&lt;Entity&gt; resultEntityList = new ArrayList&lt;Entity&gt;();
// 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 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());
}
</code></pre>
<p>We also create two methods to reset the data of our service.</p>
<pre><code class="language-java"> public void resetDataSet(final int amount) {
// Replace the old lists with empty ones
productList = new ArrayList&lt;Entity&gt;();
categoryList = new ArrayList&lt;Entity&gt;();
// Create new sample data
initProductSampleData();
initCategorySampleData();
// Truncate the lists
if(amount &lt; 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 void resetDataSet() {
resetDataSet(Integer.MAX_VALUE);
}
</code></pre>
<h3 id="extend-the-entity-collection-and-the-entity-processor-to-handle-function-imports">Extend the entity collection and the entity processor to handle function imports<a class="headerlink" href="#extend-the-entity-collection-and-the-entity-processor-to-handle-function-imports" title="Permalink">&para;</a></h3>
<p>We start with the entity collection processor <code>DemoEntityCollectionProcessor</code>.
To keep things simple, the first steps is to distinguish between entity collections and function imports.
A cleverer implementation can handle both cases in one method to avoid duplicated code.</p>
<p>The recent implementation of the <code>readEntityCollection()</code> has been moved to <code>readEntityCollectionInternal()</code></p>
<pre><code class="language-java"> 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);
}
}
</code></pre>
<p>Like by reading <em>entity collections</em>, the first step is to analyze the URI and then fetch the data (of the function import).</p>
<pre><code class="language-java"> 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);
</code></pre>
<p>Then the result has to be serialized. The only difference to entity sets is the way how the <code>EdmEntityType</code> is determined.</p>
<pre><code class="language-java"> // 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());
}
</code></pre>
<p>Next we will implement the processor to read a <em>single entity</em>. The implementation is quite similar to the implementation of the collection processor.</p>
<pre><code class="language-java"> 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());
}
</code></pre>
<h3 id="implement-an-action-processor">Implement an action processor<a class="headerlink" href="#implement-an-action-processor" title="Permalink">&para;</a></h3>
<p>Create a new class <code>DemoActionProcessor</code> make them implement the interface <code>ActionVoidProcessor</code>.</p>
<pre><code class="language-java"> 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;
}
</code></pre>
<p>First analyze the uri.</p>
<pre><code class="language-java"> 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();
</code></pre>
<p>Then deserialize the <em>action parameters</em>.</p>
<pre><code class="language-java"> // 2nd Deserialize the parameter
// In our case there is only one action. So we can be sure that parameter "Amount" 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&lt;String, Parameter&gt; actionParameter = deserializer.actionParameters(request.getBody(), edmAction)
.getActionParameters();
final Parameter parameterAmount = actionParameter.get(DemoEdmProvider.PARAMETER_AMOUNT);
</code></pre>
<p>Execute the action and set the response code.</p>
<pre><code class="language-java"> // 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());
}
</code></pre>
<h2 id="run-the-implemented-service">Run the implemented service<a class="headerlink" href="#run-the-implemented-service" title="Permalink">&para;</a></h2>
<p>After building and deploying your service to your server, you can try the following requests:</p>
<p><strong>Functions (Called via GET)</strong></p>
<ul>
<li><a href="http://localhost:8080/DemoService-Action/DemoService.svc/CountCategories(Amount=2)">http://localhost:8080/DemoService-Action/DemoService.svc/CountCategories(Amount=2)</a></li>
<li><a href="http://localhost:8080/DemoService-Action/DemoService.svc/CountCategories(Amount=2)(0)">http://localhost:8080/DemoService-Action/DemoService.svc/CountCategories(Amount=2)(0)</a></li>
</ul>
<p><strong>Actions (Called via POST)</strong><br/>
<em>Note:</em> Set the Content-Type header to: <code>Content-Type: application/json</code></p>
<ul>
<li>
<p><a href="http://localhost:8080/DemoService-Action/DemoService.svc/Reset">http://localhost:8080/DemoService-Action/DemoService.svc/Reset</a></p>
<p>Content:</p>
<p>{ }</p>
</li>
<li>
<p><a href="http://localhost:8080/DemoService-Action/DemoService.svc/Reset">http://localhost:8080/DemoService-Action/DemoService.svc/Reset</a></p>
<p>Content:</p>
<p>{ "Amount": 1 }</p>
</li>
</ul>
<p>To verify that the service has been reseted, you can request the collection of products</p>
<ul>
<li><a href="http://localhost:8080/DemoService-Action/DemoService.svc/Products">http://localhost:8080/DemoService-Action/DemoService.svc/Products</a></li>
</ul>
<h1 id="links">Links<a class="headerlink" href="#links" title="Permalink">&para;</a></h1>
<h3 id="tutorials">Tutorials<a class="headerlink" href="#tutorials" title="Permalink">&para;</a></h3>
<p>Further topics to be covered by follow-up tutorials:</p>
<ul>
<li>Tutorial OData V4 service part 1: <a href="/doc/odata4/tutorials/read/tutorial_read.html">Read Entity Collection</a></li>
<li>Tutorial OData V4 service part 2: <a href="/doc/odata4/tutorials/readep/tutorial_readep.html">Read Entity, Read Property</a></li>
<li>Tutorial OData V4 service part 3: <a href="/doc/odata4/tutorials/write/tutorial_write.html">Write (Create, Update, Delete Entity)</a></li>
<li>Tutorial OData V4 service, part 4: <a href="/doc/odata4/tutorials/navigation/tutorial_navigation.html">Navigation</a></li>
<li>Tutorial OData V4 service, part 5.1: <a href="/doc/odata4/tutorials/sqo_tcs/tutorial_sqo_tcs.html">System Query Options $top, $skip, $count (this page)</a></li>
<li>Tutorial OData V4 service, part 5.2: <a href="/doc/odata4/tutorials/sqo_es/tutorial_sqo_es.html">System Query Options $select, $expand</a></li>
<li>Tutorial OData V4 service, part 5.3: <a href="/doc/odata4/tutorials/sqo_o/tutorial_sqo_o.html">System Query Options $orderby</a></li>
<li>Tutorial OData V4 service, part 5.4: <a href="/doc/odata4/tutorials/sqo_f/tutorial_sqo_f.html">System Query Options $filter</a></li>
<li>Tutorial ODATA V4 service, part 6: Action and Function Imports</li>
<li>Tutorial ODATA V4 service, part 7: <a href="/doc/odata4/tutorials/media/tutorial_media.html">Media Entities</a></li>
<li>Tutorial OData V4 service, part 8: <a href="/doc/odata4/tutorials/batch/tutorial_batch.html">Batch Request support</a></li>
<li>Tutorial OData V4 service, part 9: <a href="/doc/odata4/tutorials/deep_insert/tutorial_deep_insert.html">Handling "Deep Insert" requests</a></li>
</ul>
<h3 id="code-and-repository">Code and Repository<a class="headerlink" href="#code-and-repository" title="Permalink">&para;</a></h3>
<ul>
<li><a href="https://gitbox.apache.org/repos/asf/olingo-odata4">Git Repository</a></li>
<li><a href="/doc/odata4/tutorials/prerequisites/prerequisites.html">Guide - To fetch the tutorial sources</a></li>
<li><a href="http://www.apache.org/dyn/closer.lua/olingo/odata4/4.0.0/DemoService_Tutorial.zip">Demo Service source code as zip file (contains all tutorials)</a></li>
</ul>
<h3 id="further-reading">Further reading<a class="headerlink" href="#further-reading" title="Permalink">&para;</a></h3>
<ul>
<li><a href="http://odata.org/">Official OData Homepage</a></li>
<li><a href="http://www.odata.org/documentation/">OData documentation</a></li>
<li><a href="/javadoc/odata4/index.html">Olingo Javadoc</a></li>
</ul>
<div align="center">
<p>Copyright © 2013-2022, The Apache Software Foundation<br>
Apache Olingo, Olingo, Apache, the Apache feather, and
the Apache Olingo project logo are trademarks of the Apache Software
Foundation.</p>
<small><a href="/doc/odata2/privacy.html">Privacy</a></small>
</div>
</div><!-- /container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="/js/jquery.js" type="text/javascript">
</script>
<script src="/js/bootstrap.js" type="text/javascript">
</script>
<script src="/js/offcanvas.js" type="text/javascript">
</script>
<link rel="stylesheet" href="/css/docco.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.0.1/build/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
</body>
</html>