blob: 51a453bc7cf825929926f5c542df271180e14a3a [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-53-system-query-options-orderby">Part 5.3: System Query Options: <code>$orderby</code><a class="headerlink" href="#part-53-system-query-options-orderby" title="Permalink">&para;</a></h1>
<h2 id="introduction">Introduction<a class="headerlink" href="#introduction" title="Permalink">&para;</a></h2>
<p>In the present tutorial, we will continue implementing OData system query options, this time focusing on <code>$orderby</code></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 <em>\samples\tutorials\p7_queryoptions-o</em></p>
<p><strong>Disclaimer:</strong>
Again, in the present tutorial, we will focus only on the relevant implementation, in order to keep the code small and simple. The sample code as it is, shouldn&rsquo;t be reused for advanced scenarios.</p>
<p><strong>Table of Contents</strong></p>
<ol>
<li>Prerequisites</li>
<li>Preparation</li>
<li>Implementation
<ol>
<li>Implement <code>$orderby</code></li>
</ol>
</li>
<li>Run the implemented service</li>
<li>Summary</li>
<li>Links</li>
</ol>
<p>__</p>
<h1 id="1-prerequisites">1. Prerequisites<a class="headerlink" href="#1-prerequisites" title="Permalink">&para;</a></h1>
<p>Same prerequisites as in <a href="/doc/odata4/tutorials/read/tutorial_read.html">Tutorial Part 1: Read Entity Collection</a>
and <a href="/doc/odata4/tutorials/readep/tutorial_readep.html">Tutorial Part 2: Read Entity</a> as well as basic knowledge about the concepts presented in both tutorials.</p>
<p>Furthermore, Tutorial Part 5.1 should have been read.</p>
<h1 id="2-preparation">2. Preparation<a class="headerlink" href="#2-preparation" title="Permalink">&para;</a></h1>
<p>Follow <em>Tutorial Part 1: Read Entity Collection</em> and <em>Tutorial Part 2: Read Entity</em> or as shortcut import <em>Part 2: Read Entity, Read Property</em> into your Eclipse workspace.</p>
<p>Afterwards do a <em>Deploy and run</em>: it should be working.</p>
<h1 id="implementation">Implementation<a class="headerlink" href="#implementation" title="Permalink">&para;</a></h1>
<p>The system query options we&rsquo;re focusing on are applied to the entity collection only, therefore our
implementation for all query options is done in the class
<em>myservice.mynamespace.service.DemoEntityCollectionProcessor</em></p>
<p>The general sequence is again:</p>
<ol>
<li>Analyze the URI</li>
<li>Fetch data from backend</li>
<li>Apply the system query option</li>
<li>Serialize</li>
<li>Configure the response</li>
</ol>
<h2 id="31-implement-orderby">3.1. Implement <code>$orderby</code><a class="headerlink" href="#31-implement-orderby" title="Permalink">&para;</a></h2>
<p><strong>Background</strong></p>
<p>When requesting a list of entities from a service, it is up to the service implementation to decide in which order they are presented. This can depend on the backend data source, anyways, it is undefined.
But the consumer of an OData service might want to be able to specify the order, according to his needs.</p>
<p>For example, a usual case would be that the list of entities is sorted as per default by its ID number, but for a user, the ID is not relevant and he would prefer a sorting e.g. by the name
OData supports this requirement with the system query option <code>$orderby</code>
It is specified as follows:</p>
<pre><code>$orderby=&lt;propertyName&gt;
</code></pre>
<p>The order can be ascending or descending:</p>
<pre><code>$orderby=&lt;propertyName&gt; asc
$orderby=&lt;propertyName&gt; desc
</code></pre>
<p>If not specified, the default is ascending.</p>
<p>See here for more details:
<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#_Toc406398305">OData Version 4.0 Part 1: Protocol Plus Errata 02</a></p>
<p><strong>Note:</strong>
As of the OData specification, the <code>$orderby</code> system query option can be applied to multiple properties.
In that case, the value is specified as comma-separated list.</p>
<p><strong>Example:</strong></p>
<pre><code>&lt;http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name asc, Description desc&gt;
</code></pre>
<p>In this example, all the products are sorted by their name. Moreover, all products with the same name are sorted by their description in descending order.
Another example could be that I want to display all my customers, they should be sorted by their country. Additionally, within each country, they should be sorted by their name</p>
<p>In order to support such sorting, the OData service implementation has to make use of the <em>ExpressionVisitor</em> concept.
We haven&rsquo;t used it in the present tutorial, because the ExpressionVisitor will be explained in the $filter section</p>
<p><strong>Example</strong>
First, just to remember how the full payload looks like, the &ldquo;normal&rdquo; query of the product:
<a href="http://localhost:8080/DemoService/DemoService.svc/Products">http://localhost:8080/DemoService/DemoService.svc/Products</a></p>
<p><img alt="AllProductsNotSorted" src="products_unsorted.png" title="All products not sorted"/></p>
<p>The following request specifies the sorting by the name.
The order is ascending, if not specified elsewise.</p>
<p><a href="http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name">http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name</a></p>
<p><img alt="ProductsOrderedByNameAsc" src="products_bynameasc.png" title="All products sorted by name ascending"/></p>
<p><strong>Implementation</strong></p>
<p>The following section describes the simple approach to enable the <em>EntityCollectionProcessor</em> class and the <em>readEntityCollection()</em> method for <em>$orderby</em>.</p>
<p>Just like in the previous tutorials, the data is first fetched from the backend, then the system query option is applied.</p>
<pre><code class="language-java"> EntityCollection entityCollection = storage.readEntitySetData(edmEntitySet);
List&lt;Entity&gt; entityList = entityCollection.getEntities();
</code></pre>
<p>We will proceed according to these 4 steps:</p>
<ol>
<li>Get the query option from the <em>UriInfo</em>. If null is returned then nothing has to be done.</li>
<li>Get the value from the query option</li>
<li>Analyze the value</li>
<li>Modify the <em>EntityCollection</em></li>
</ol>
<p><strong>1. Get the OrderByOption from the UriInfo:</strong></p>
<pre><code class="language-java"> OrderByOption orderByOption = uriInfo.getOrderByOption();
if (orderByOption != null) {
</code></pre>
<p><strong>2. Get the value of the OrderByOption:</strong></p>
<pre><code class="language-java"> List&lt;OrderByItem&gt; orderItemList = orderByOption.getOrders();
final OrderByItem orderByItem = orderItemList.get(0);
</code></pre>
<p>The instance of an OrderByOption can be asked for the list of its <em>OrderByItems</em>.
Why a list?
Because the <code>$orderby</code> expression can be composed with multiple properties
For example, for the following URL, we get 2 OrderByItems:</p>
<pre><code>http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name asc, Description desc
</code></pre>
<p>In our example, we support only one property, therefore we directly access the first OrderByItem in the list.</p>
<p><strong>3. Analyze the value</strong></p>
<p>What do we want to do?
From the backend we got a list of entities that are products. We want to apply a sorter to that list and we want to sort by the property name that is given in the URI.
In our example, the property name that is provided in the URI can be &ldquo;Name&rdquo;, &ldquo;Description&rdquo; or &ldquo;ID&rdquo;
So we have to retrieve the property name from the URI.</p>
<pre><code class="language-java"> Expression expression = orderByItem.getExpression();
if(expression instanceof Member){
UriInfoResource resourcePath = ((Member)expression).getResourcePath();
UriResource uriResource = resourcePath.getUriResourceParts().get(0);
if (uriResource instanceof UriResourcePrimitiveProperty) {
EdmProperty edmProperty = ((UriResourcePrimitiveProperty)uriResource).getProperty();
final String sortPropertyName = edmProperty.getName();
</code></pre>
<p><strong>4. Modify the EntityCollection</strong></p>
<p>The remaining work is to do the sorting.
We have a list of entities that has to be sorted, therefore we create a java.util.Comparator for Entity:</p>
<pre><code class="language-java"> Collections.sort(entityList, new Comparator&lt;Entity&gt;() {
</code></pre>
<p>In the compare method, we extract the required property from the entity.
The required property is the one that we retrieved from the URI.
In our sample, the properties can be of type String or Integer, therefore we have to distinguish these 2 cases.
The actual work of comparing can then be delegated to the String and Integer classes.</p>
<pre><code class="language-java"> if(sortPropertyName.equals("ID")){
Integer integer1 = (Integer) entity1.getProperty(sortPropertyName).getValue();
Integer integer2 = (Integer) entity2.getProperty(sortPropertyName).getValue();
compareResult = integer1.compareTo(integer2);
}else{
String propertyValue1 = (String) entity1.getProperty(sortPropertyName).getValue();
String propertyValue2 = (String) entity2.getProperty(sortPropertyName).getValue();
compareResult = propertyValue1.compareTo(propertyValue2);
}
</code></pre>
<p>After the sorting is done, we still have to consider, if the required order is ascending or descending.
So we have to retrieve that information from the OrderByItem and then we can simply reverse the current order accordingly:</p>
<pre><code class="language-java"> if(orderByItem.isDescending()){
return - compareResult; // just convert the result to negative value to change the order
}
</code></pre>
<p>The full implementation of the readEntityCollection() method:</p>
<pre><code class="language-java"> public void readEntityCollection(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType responseFormat)
throws ODataApplicationException, SerializerException {
// 1st retrieve the requested EntitySet from the uriInfo
List&lt;UriResource&gt; resourcePaths = uriInfo.getUriResourceParts();
UriResourceEntitySet uriResourceEntitySet = (UriResourceEntitySet) resourcePaths.get(0);
EdmEntitySet edmEntitySet = uriResourceEntitySet.getEntitySet();
// 2nd: fetch the data from backend
EntityCollection entityCollection = storage.readEntitySetData(edmEntitySet);
List&lt;Entity&gt; entityList = entityCollection.getEntities();
// 3rd apply $orderby
OrderByOption orderByOption = uriInfo.getOrderByOption();
if (orderByOption != null) {
List&lt;OrderByItem&gt; orderItemList = orderByOption.getOrders();
final OrderByItem orderByItem = orderItemList.get(0); // we support only one
Expression expression = orderByItem.getExpression();
if(expression instanceof Member){
UriInfoResource resourcePath = ((Member)expression).getResourcePath();
UriResource uriResource = resourcePath.getUriResourceParts().get(0);
if (uriResource instanceof UriResourcePrimitiveProperty) {
EdmProperty edmProperty = ((UriResourcePrimitiveProperty)uriResource).getProperty();
final String sortPropertyName = edmProperty.getName();
// do the sorting for the list of entities
Collections.sort(entityList, new Comparator&lt;Entity&gt;() {
// delegate the sorting to native sorter of Integer and String
public int compare(Entity entity1, Entity entity2) {
int compareResult = 0;
if(sortPropertyName.equals("ID")){
Integer integer1 = (Integer) entity1.getProperty(sortPropertyName).getValue();
Integer integer2 = (Integer) entity2.getProperty(sortPropertyName).getValue();
compareResult = integer1.compareTo(integer2);
}else{
String propertyValue1 = (String) entity1.getProperty(sortPropertyName).getValue();
String propertyValue2 = (String) entity2.getProperty(sortPropertyName).getValue();
compareResult = propertyValue1.compareTo(propertyValue2);
}
// if 'desc' is specified in the URI, change the order
if(orderByItem.isDescending()){
return - compareResult; // just reverse order
}
return compareResult;
}
});
}
}
}
// 4th: create a serializer based on the requested format (json)
ODataSerializer serializer = odata.createSerializer(responseFormat);
// and serialize the content: transform from the EntitySet object to InputStream
EdmEntityType edmEntityType = edmEntitySet.getEntityType();
ContextURL contextUrl = ContextURL.with().entitySet(edmEntitySet).build();
final String id = request.getRawBaseUri() + "/" + edmEntitySet.getName();
EntityCollectionSerializerOptions opts = EntityCollectionSerializerOptions.with().contextURL(contextUrl).id(id).build();
SerializerResult serializerResult = serializer.entityCollection(serviceMetadata, edmEntityType, entityCollection, opts);
InputStream serializedContent = serializerResult.getContent();
// 5th: configure the response object: set the body, headers and status code
response.setContent(serializedContent);
response.setStatusCode(HttpStatusCode.OK.getStatusCode());
response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString());
}
</code></pre>
<p><strong>4. Run the implemented service</strong></p>
<p>After building and deploying your service to your server, you can try the following URLs:</p>
<ul>
<li>The &ldquo;normal&rdquo; payload without query option <a href="http://localhost:8080/DemoService/DemoService.svc/Products">http://localhost:8080/DemoService/DemoService.svc/Products</a></li>
<li>Sort by Name ascending <a href="http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name">http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name</a></li>
<li>Sort by Name descending [http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name desc](<a href="http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name">http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Name</a> desc)</li>
<li>Sort by Description ascending <a href="http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Description">http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Description</a></li>
<li>Sort by Description descending [http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Description desc](<a href="http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Description">http://localhost:8080/DemoService/DemoService.svc/Products?$orderby=Description</a> desc)</li>
</ul>
<h1 id="5-summary">5. Summary<a class="headerlink" href="#5-summary" title="Permalink">&para;</a></h1>
<p>In this tutorial we have learned how to implement a simple <code>$orderby</code>.
We have decided to not go for the advanced way of implementing <code>$orderby</code>, which would have been using an ExpressionVisitor, because that is treated in the <code>$filter</code> implementation.</p>
<h1 id="6-links">6. Links<a class="headerlink" href="#6-links" title="Permalink">&para;</a></h1>
<h3 id="tutorials">Tutorials<a class="headerlink" href="#tutorials" title="Permalink">&para;</a></h3>
<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: System Query Options $orderby (this page)</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: <a href="/doc/odata4/tutorials/action/tutorial_action.html">Action and Function Imports</a></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>