You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2015/07/09 08:14:07 UTC
svn commit: r1689991 - in /olingo/site/trunk/content/doc/odata4/tutorials:
navigation/tutorial_navigation.mdtext read/tutorial_read.mdtext
readep/tutorial_readep.mdtext write/tutorial_write.mdtext
Author: mibo
Date: Thu Jul 9 06:14:07 2015
New Revision: 1689991
URL: http://svn.apache.org/r1689991
Log:
Fixed links and formating
Modified:
olingo/site/trunk/content/doc/odata4/tutorials/navigation/tutorial_navigation.mdtext
olingo/site/trunk/content/doc/odata4/tutorials/read/tutorial_read.mdtext
olingo/site/trunk/content/doc/odata4/tutorials/readep/tutorial_readep.mdtext
olingo/site/trunk/content/doc/odata4/tutorials/write/tutorial_write.mdtext
Modified: olingo/site/trunk/content/doc/odata4/tutorials/navigation/tutorial_navigation.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata4/tutorials/navigation/tutorial_navigation.mdtext?rev=1689991&r1=1689990&r2=1689991&view=diff
==============================================================================
--- olingo/site/trunk/content/doc/odata4/tutorials/navigation/tutorial_navigation.mdtext (original)
+++ olingo/site/trunk/content/doc/odata4/tutorials/navigation/tutorial_navigation.mdtext Thu Jul 9 06:14:07 2015
@@ -1,3 +1,21 @@
+Title: Tutorial - Read service with Olingo V4
+Notice: 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.
+
# How to build an OData Service with Olingo V4
# Part 4: Navigation
@@ -6,8 +24,8 @@
In the present tutorial, weâll learn how to implement navigation between 2 Entity Types in an OData V4 service.
-**Background**
-Say, we have an electronics shop and we have a lot of products which weâre selling and these products can be notebooks or monitors or organizers, which are the categories.
+**Background**
+Say, we have an electronics shop and we have a lot of products which weâre selling and these products can be notebooks or monitors or organizers, which are the categories.
We would have 3 requirements:
1. We want to show a list of all our categories, then select one and display a list of all products that belong to this category, e.g. all monitors
@@ -15,7 +33,7 @@ We would have 3 requirements:
2. From the list of our products, we want to choose one and display its category
3. We want to navigate from a category to its products and perform a READ operation on one of them.
-**Example for navigating in a service**
+**Example for navigating in a service**
We open the Categories collection: <http://localhost:8080/DemoService/DemoService.svc/Categories>
![CategoryCollection](browser_categories.JPG "The Category collection")
@@ -25,17 +43,17 @@ http://localhost:8080/DemoService/DemoSe
![CategoryEntity](browser_categories1.jpg "Read single Category entity")
-In order to display all products that are notebooks, we can navigate from the selected category to its products:
+In order to display all products that are notebooks, we can navigate from the selected category to its products:
http://localhost:8080/DemoService/DemoService.svc/Categories(1)/Products
![ProductsOfCategory](browser_categories1_products.jpg "After navigating from a Category to the related Products")
-In the above example weâve executed a one-to-many navigation.
+In the above example weâve executed a one-to-many navigation.
As mentioned in the Background section, it is also required to navigate from a selected product to its category, which is a to-one relation, like:
http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category
-And finally, it is possible to navigate to a list of products and directly access one of them, e.g.
+And finally, it is possible to navigate to a list of products and directly access one of them, e.g.
http://localhost:8080/DemoService/DemoService.svc/Categories(1)/Products(1)
All three cases are covered by the present tutorial.
@@ -86,8 +104,8 @@ ___
# 3. Implementing the navigation
-In our sample scenario, we want to navigate from a product to its category and from a category to a list of products.
-In order to achieve this, we need to create a second Entity Type, "Category", and we need to specify _Navigation Properties_ in both Entity Types.
+In our sample scenario, we want to navigate from a product to its category and from a category to a list of products.
+In order to achieve this, we need to create a second Entity Type, "Category", and we need to specify _Navigation Properties_ in both Entity Types.
Our model looks as follows:
![ODataModelNavigation](model.JPG "Our OData Model with link between 2 Entity Types")
@@ -95,8 +113,8 @@ Our model looks as follows:
**Note**
When designing the OData model, we could think of specifying a property âProductCategoryâ in the entity type âProductâ.
-E.g. a product with name âBrilliant flat and wideâ would have the category âMonitorsâ.
-But this is not necessary, because that information can be obtained by navigating to the respective âCategoryâ-entity using the navigation property.
+E.g. a product with name âBrilliant flat and wideâ would have the category âMonitorsâ.
+But this is not necessary, because that information can be obtained by navigating to the respective âCategoryâ-entity using the navigation property.
That way, we can keep the entity types lightweight, which is one of the intentions of OData.
@@ -118,32 +136,32 @@ In the previous tutorial weâve alre
<Property Name="Description" Type="Edm.String" Nullable="false" />
</EntityType>
-Now we have to add a navigation property.
-That navigation property element has the following attributes:
+Now we have to add a navigation property.
+That navigation property element has the following attributes:
**Name**
-Tthe name of the navigation property is used as segment in the URI
-e.g. for the following URL: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
-The segment âCategoryâ is the name of the navigation property
+Tthe name of the navigation property is used as segment in the URI
+e.g. for the following URL: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
+The segment âCategoryâ is the name of the navigation property
**Type**
-Here we specify the Entity Type to which weâre navigating.
-e.g. for the following URL: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
-weâre navigating to an entity which has the entity type âOData.Demo.Categoryâ
-(we still have to create this entity type in this tutorial)
-Note that the fully qualified name has to be specified.
+Here we specify the Entity Type to which weâre navigating.
+e.g. for the following URL: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
+weâre navigating to an entity which has the entity type âOData.Demo.Categoryâ
+(we still have to create this entity type in this tutorial)
+Note that the fully qualified name has to be specified.
Note that here we donât specify a collection, so we have a to-one relationship.
-**Nullable**
-Specifies if the navigation target is required.
-If we donât specify it, then the default is assumed to be âtrueâ.
+**Nullable**
+Specifies if the navigation target is required.
+If we donât specify it, then the default is assumed to be âtrueâ.
In our example we want to declare that every product must have a category, so we have to set it to âfalseâ
-**Partner**
-An optional attribute, used to define a bi-directional relationship.
+**Partner**
+An optional attribute, used to define a bi-directional relationship.
Specifies a path from the entity type (specified here) to the navigation property (defined there).
-In our example, we can navigate from product to category and from category to product
+In our example, we can navigate from product to category and from category to product
In our example, the metadata of our âProductâ entity type looks as follows:
@@ -182,17 +200,17 @@ That list becomes relevant for the entit
entityType.setNavigationProperties(navPropList);
-Thereâs one more step to consider with respect to the navigation: the entity set.
-At runtime, we need to know how to implement the navigation, when an entity set is invoked.
-For this purpose, the OData specifies the _NavigationPropertyBinding_ element, which is a child element of the entity set and should be defined for each navigation property.
-That _NavigationPropertyBinding_ has the following attributes:
+Thereâs one more step to consider with respect to the navigation: the entity set.
+At runtime, we need to know how to implement the navigation, when an entity set is invoked.
+For this purpose, the OData specifies the _NavigationPropertyBinding_ element, which is a child element of the entity set and should be defined for each navigation property.
+That _NavigationPropertyBinding_ has the following attributes:
-**Path**
+**Path**
Here we specify the name of the corresponding navigation property.
In our example, the navigation property that weâve defined above is named âCategoryâ
-**Target**
-Here we specify the entity set where weâre navigating to.
+**Target**
+Here we specify the entity set where weâre navigating to.
In our example it is the entity set âCategoriesâ (which weâll create below)
In our example, the definition of our âProductsâ entity set looks as follows:
@@ -215,25 +233,25 @@ Code-wise, the getEntitySet method is ex
-### 3.1.2. Create the Entity Type âCategoryâ
+### 3.1.2. Create the Entity Type âCategoryâ
-Now we have to create the second entity type, the âCategoryâ.
-In order to keep our sample as simple as possible, we define only 2 properties, the âIDâ and a âNameâ.
-Since we want to be able to navigate from one given category (e.g. âMonitorsâ) to a list of products (e.g. all products that are monitors), we have to specify a navigation property in this entity type as well.
-Here, the navigation property has the following attributes:
+Now we have to create the second entity type, the âCategoryâ.
+In order to keep our sample as simple as possible, we define only 2 properties, the âIDâ and a âNameâ.
+Since we want to be able to navigate from one given category (e.g. âMonitorsâ) to a list of products (e.g. all products that are monitors), we have to specify a navigation property in this entity type as well.
+Here, the navigation property has the following attributes:
-**Name**
-In our example, we specify âProductsâ, in plural because we want to get multiple entities.
+**Name**
+In our example, we specify âProductsâ, in plural because we want to get multiple entities.
-**Type**
-The âTypeâ attribute can be either an âentity typeâ or a âcollection of entity typesâ
-In our example this time, we specify a âcollectionâ of âOData.Demo.Productâ
+**Type**
+The âTypeâ attribute can be either an âentity typeâ or a âcollection of entity typesâ
+In our example this time, we specify a âcollectionâ of âOData.Demo.Productâ
-**Nullable**
+**Nullable**
According to the OData specification (see odata.org), this attribute is not allowed for a collection
A collection can be empty, but never null.
-**Partner**
+**Partner**
In our example, weâre defining a bi-directional navigation, so here we specify âCategoryâ, the name of the navigation property defined above.
In our example, the metadata of our âCategoryâ entity type looks as follows:
@@ -283,12 +301,12 @@ The code for the âCategoryâ en
}
-The _NavigationPropertyBinding_ element and its attributes for the entity set âCategoriesâ:
+The _NavigationPropertyBinding_ element and its attributes for the entity set âCategoriesâ:
-**Path**
+**Path**
In our example, the navigation property that weâve defined above is named âProductsâ
-**Target**
+**Target**
In our example it is the entity set âProductsâ
@@ -317,34 +335,34 @@ And the implementation in the _getEntity
## 3.2. Implement the to-many navigation
Letâs again have a look at our example, as described in the introduction section above.
-The user of our service invokes the âCategoriesâ collection and chooses one âCategoryâ.
+The user of our service invokes the âCategoriesâ collection and chooses one âCategoryâ.
This is done with e.g. the following URL: <http://localhost:8080/DemoService/DemoService.svc/Categories(1)>
-The returned response payload doesnât contain any information about possible navigation.
-So the user has to check the metadata document, where he can see that the entity type âCategoryâ defines one navigation property:
+The returned response payload doesnât contain any information about possible navigation.
+So the user has to check the metadata document, where he can see that the entity type âCategoryâ defines one navigation property:
![CategoryMetadata](browser_metadataCategory.JPG "The definition of a Navigation Property in the $metadata document")
This means, that he can append the navigation property name to his URL, which takes him to the set of âProductsâ that belong to the chosen âCategoryâ: <http://localhost:8080/DemoService/DemoService.svc/Categories(1)/Products>
-From the metadata we can see that the âTypeâ attribute defines a collection.
+From the metadata we can see that the âTypeâ attribute defines a collection.
This means that the implementation has to be done in the _EntityCollectionProcessor_, since we have to provide a collection of entities.
Open the class `myservice.mynamespace.service.DemoEntityCollectionProcessor.java`
-There, the implementation for a ânormalâ read operation is already in place and we have to add the case when an entity collection is expected after navigation.
-Note that we want to keep our tutorial and our code simple, so we decide that only one step navigation is to be supported by our service.
-This means that we can navigate only once from one entity to another one.
-For example:
-Categories(1)/Products
-We donât support navigation from one entity to an entity and then to another entity and so on
+There, the implementation for a ânormalâ read operation is already in place and we have to add the case when an entity collection is expected after navigation.
+Note that we want to keep our tutorial and our code simple, so we decide that only one step navigation is to be supported by our service.
+This means that we can navigate only once from one entity to another one.
For example:
-Categories(1)/Products(1)/Category
+Categories(1)/Products
+We donât support navigation from one entity to an entity and then to another entity and so on
+For example:
+Categories(1)/Products(1)/Category
-Based on this assumption, in our _EntityCollectionProcessor_, we can rely on the fact that the URI can have either one or two segments.
-This means: we can be called for the following kind of URLs:
+Based on this assumption, in our _EntityCollectionProcessor_, we can rely on the fact that the URI can have either one or two segments.
+This means: we can be called for the following kind of URLs:
-Example URL for one sement: <http://localhost:8080/DemoService/DemoService.svc/Categories>
+Example URL for one sement: <http://localhost:8080/DemoService/DemoService.svc/Categories>
Example URL for two segments: <http://localhost:8080/DemoService/DemoService.svc/Categories(1)/Products>
@@ -367,7 +385,7 @@ The segments of the URI are retrieved fr
List<UriResource> resourceParts = uriInfo.getUriResourceParts();
int segmentCount = resourceParts.size();
-In both cases, we have to retrieve the list of entities to be returned.
+In both cases, we have to retrieve the list of entities to be returned.
For the first case, we have only one entitySet, so the implementation is straight forward:
:::java
@@ -382,35 +400,35 @@ For the first case, we have only one ent
Now letâs focus on the second case, the navigation.
Our tasks are:
- 1. depending on the chosen key of the first segment, we have to compute which and how many entities exactly have to be returned. With other words, find the right data in the backend
+ 1. depending on the chosen key of the first segment, we have to compute which and how many entities exactly have to be returned. With other words, find the right data in the backend
e.g. for the category âmonitorsâ, we have to find the right products that are monitors
- 2. find out, which entity set has to be returned (can be products, categories, etc)
+ 2. find out, which entity set has to be returned (can be products, categories, etc)
This _EdmEntitySet_ is required in order to properly build the context URL
The following sections explain how to do that.
### 3.2.1. Get the data for the response
-Getting the data for the response is reylized in 2 steps:
+Getting the data for the response is reylized in 2 steps:
-**A)** get the data for the first URI segment
-in our example, we have to perform a read operation for retrieving the Category with ID 3, which is "Monitors"
+**A)** get the data for the first URI segment
+in our example, we have to perform a read operation for retrieving the Category with ID 3, which is "Monitors"
-**B)** get the data for the navigation
-in our example, we have to find the products that are monitors.
+**B)** get the data for the navigation
+in our example, we have to find the products that are monitors.
-With respect to data, remember that we're using sample data that we create in our Storage class which represents our kind of database-mock.
-On startup of our service, we initialize some sample products and categories.
-During initialization, thereâs no assignment of products to its categories.
-In our sample code, weâre doing this when requested in a hard-coded method in our _Storage_ class.
+With respect to data, remember that we're using sample data that we create in our Storage class which represents our kind of database-mock.
+On startup of our service, we initialize some sample products and categories.
+During initialization, thereâs no assignment of products to its categories.
+In our sample code, weâre doing this when requested in a hard-coded method in our _Storage_ class.
**A) get the data for the first URI segment**
-In our example, the URL would be: <http://localhost:8080/DemoService/DemoService.svc/Categories(3)/Products>
-For this example, we would have to retrieve the Category with ID=3
-The code looks like a normal READ operation:
+In our example, the URL would be: <http://localhost:8080/DemoService/DemoService.svc/Categories(3)/Products>
+For this example, we would have to retrieve the Category with ID=3
+The code looks like a normal READ operation:
:::java
List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates();
@@ -421,30 +439,30 @@ In our example, the result is an entity
**B) get the data for the navigation**
-Now we have to follow the navigation, based on the retrieved entity.
-In our example, we have to retrieve all products that are monitors.
+Now we have to follow the navigation, based on the retrieved entity.
+In our example, we have to retrieve all products that are monitors.
-This is backend logic, so we can directly call a helper method in our Storage class:
+This is backend logic, so we can directly call a helper method in our Storage class:
- :::java
+ :::java
responseEntityCollection = storage.getRelatedEntityCollection(sourceEntity, targetEntityType);
-This helper method requires the source entity and returns the target collection.
-Additionally, the method needs the _EdmEntityType_ that corresponds to the requested target.
-In our example, we pass the âCategoryâ (i.e. "Monitors") as source entity and the navigation target entity type, which is âProductâ.
-As a result, we get the desired âProductsâ collection, all products that are monitors.
-
-After this step, weâre almost done, because we have the entity collection that our OData service returns in the response body.
-We only need to do some more hand work: the response entity collection has to be serialized and the serializer which is in charge of doing that has to be configured properly.
-For that we need the _EdmEntitySet_ that corresponds to the response.
-Since it is different in case of navigation and non-navigation, we still need to retrieve it for the case of navigation.
+This helper method requires the source entity and returns the target collection.
+Additionally, the method needs the _EdmEntityType_ that corresponds to the requested target.
+In our example, we pass the âCategoryâ (i.e. "Monitors") as source entity and the navigation target entity type, which is âProductâ.
+As a result, we get the desired âProductsâ collection, all products that are monitors.
+
+After this step, weâre almost done, because we have the entity collection that our OData service returns in the response body.
+We only need to do some more hand work: the response entity collection has to be serialized and the serializer which is in charge of doing that has to be configured properly.
+For that we need the _EdmEntitySet_ that corresponds to the response.
+Since it is different in case of navigation and non-navigation, we still need to retrieve it for the case of navigation.
### 3.2.2. Retrieve the EdmEntitySet for the response
-First, we have to analyze the URI, and find out if the URI segment is used for navigation.
-As mentioned, in our simple example we assume that the second segment is used for navigation (in advanced services, a segment could as well be an action or function import, etc).
-The navigation URI segment can then be asked for the corresponding _EdmNavigationProperty_
+First, we have to analyze the URI, and find out if the URI segment is used for navigation.
+As mentioned, in our simple example we assume that the second segment is used for navigation (in advanced services, a segment could as well be an action or function import, etc).
+The navigation URI segment can then be asked for the corresponding _EdmNavigationProperty_
:::java
UriResource lastSegment = resourceParts.get(1);
@@ -453,7 +471,7 @@ The navigation URI segment can then be a
EdmNavigationProperty edmNavigationProperty = uriResourceNavigation.getProperty();
-The bad news is that the _EdmNavigationProperty_ doesnât know about the target _EdmEntitySet_.
+The bad news is that the _EdmNavigationProperty_ doesnât know about the target _EdmEntitySet_.
This is as per design, just check the metadata:
:::xml
@@ -463,15 +481,15 @@ This is as per design, just check the me
</EntityType>
-The navigation property is defined on entity-type-level and as such, it does know the target entity type.
-The target entity set is defined in the navigation property binding element on entity-set-level:
+The navigation property is defined on entity-type-level and as such, it does know the target entity type.
+The target entity set is defined in the navigation property binding element on entity-set-level:
:::xml
<EntitySet Name="Categories" EntityType="OData.Demo.Category">
<NavigationPropertyBinding Path="Products" Target="Products"/>
</EntitySet>
-This is where we get the information that we need.
+This is where we get the information that we need.
For our implementation, this means:
1. we need the _EdmEntiySet_ that corresponds to the first segment of the URI
@@ -479,22 +497,22 @@ For our implementation, this means:
2. we need the navigation property that corresponds to the second segment of the URI
in our example: Products
-As shown below, from the source _EdmEntitySet_ we get the binding target, based on the navigation property.
+As shown below, from the source _EdmEntitySet_ we get the binding target, based on the navigation property.
:::java
EdmBindingTarget edmBindingTarget = startEntitySet.getRelatedBindingTarget(navPropName);
if(edmBindingTarget instanceof EdmEntitySet){
navigationTargetEntitySet = (EdmEntitySet)edmBindingTarget;
-This target is the entity set that we need.
+This target is the entity set that we need.
We move the code into the utility method _Util.getNavigationTargetEntitySet(startEdmEntitySet, edmNavigationProperty)_
-Reason is that we'll need it again, later in this tutorial.
+Reason is that we'll need it again, later in this tutorial.
### 3.2.3 Remaining tasks
In the previous tutorials weâve already learned what else has to be done: transform the retrieve data into an _InputStream_ i.e. serialize the content.
-Furthermore, configure the response object, i.e. set the response body, the content type and the header.
+Furthermore, configure the response object, i.e. set the response body, the content type and the header.
The following snippet shows the implementation of the _readEntityCollection(â¦)_ method.
@@ -547,7 +565,7 @@ public void readEntityCollection(ODataRe
throw new ODataApplicationException("Not supported", HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(),Locale.ROOT);
}
- // 3rd: create and configure a serializer
+ // 3rd: create and configure a serializer
ContextURL contextUrl = ContextURL.with().entitySet(responseEdmEntitySet).build();
EntityCollectionSerializerOptions opts = EntityCollectionSerializerOptions.with().contextURL(contextUrl).build();
EdmEntityType edmEntityType = responseEdmEntitySet.getEntityType();
@@ -563,11 +581,11 @@ public void readEntityCollection(ODataRe
## 3.3. Implement the to-one navigation
-As for the to-one navigation, it is the case if the navigation target is a single entity, not a collection.
-In our example, the following URL represents a to-one navigation: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
+As for the to-one navigation, it is the case if the navigation target is a single entity, not a collection.
+In our example, the following URL represents a to-one navigation: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
The user of our service has chosen a product and wants to know to which category it belongs. He can find it out by following the navigation property.
-As per design, a product can only belong to **one** category (obviously, a product can only be a Notebook or a Monitor, not both). Therefore in our service, weâve defined a navigation property that is not of type collection:
+As per design, a product can only belong to **one** category (obviously, a product can only be a Notebook or a Monitor, not both). Therefore in our service, weâve defined a navigation property that is not of type collection:
:::xml
<NavigationProperty
@@ -576,13 +594,13 @@ As per design, a product can only belong
Nullable="false"
Partner="Products"/>
-So when the user follows the navigation property in order to display the product category, he expects a response that contains only one entry.
+So when the user follows the navigation property in order to display the product category, he expects a response that contains only one entry.
This means that we have to do the implementation in the _EntityProcessor_.
Open the class _myservice.mynamespace.service.DemoEntityProcessor.java_
-As usual, we first have to analyze the URI.
-Just like we did in the _EntityCollectionProcessor_, we have to distinguish between navigation and ânormalâ read of an entity:
+As usual, we first have to analyze the URI.
+Just like we did in the _EntityCollectionProcessor_, we have to distinguish between navigation and ânormalâ read of an entity:
:::java
if(segmentCount == 1){
@@ -591,16 +609,16 @@ Just like we did in the _EntityCollectio
// this is reached in case of navigation
}
-In the following section, weâll focus on the navigation case only.
-In our example, our task is to find the category of a chosen product.
-Again, we have to first fetch the chosen product (first URI segment) from our database-mock and in a second step, we have to ask our database-mock for the corresponding category.
-This final entity is then serialized and set as response body for the _readEntity_ method, which weâre implementing.
+In the following section, weâll focus on the navigation case only.
+In our example, our task is to find the category of a chosen product.
+Again, we have to first fetch the chosen product (first URI segment) from our database-mock and in a second step, we have to ask our database-mock for the corresponding category.
+This final entity is then serialized and set as response body for the _readEntity_ method, which weâre implementing.
**A) get the data for the first URI segment**
-In our example, we have to perform a read operation for retrieving the product with ID 1: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
+In our example, we have to perform a read operation for retrieving the product with ID 1: <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
-The code is the same like in the previous chapter (to-many navigation):
+The code is the same like in the previous chapter (to-many navigation):
:::java
List<UriParameter> keyPredicates = uriResourceEntitySet.getKeyPredicates();
@@ -609,9 +627,9 @@ The code is the same like in the previou
**B) get the data for the navigation**
-Now we have to follow the navigation, based on the retrieved entity.
-In our example, we have to find the category corresponding to the chosen product.
-Therefore, we invoke our helper method and pass the source Entity (Product) and the required target entity type (Category). The method will find the category which is related to the chosen product.
+Now we have to follow the navigation, based on the retrieved entity.
+In our example, we have to find the category corresponding to the chosen product.
+Therefore, we invoke our helper method and pass the source Entity (Product) and the required target entity type (Category). The method will find the category which is related to the chosen product.
:::java
responseEntity = storage.getRelatedEntity(sourceEntity, responseEdmEntityType);
@@ -627,7 +645,7 @@ The procedure is the same like in the ch
In our example, the value of the variable _responseEdmEntitySet_ will be âCategoriesâ and it will be used for building the contextURL, which will look as follows:
- :::xml
+ :::xml
"$metadata#Categories/$entity"
@@ -637,15 +655,15 @@ In our example, the value of the variabl
but in addition, we want to read only one of the collected products, which is directly addressed by its key: <http://localhost:8080/DemoService/DemoService.svc/Categories(3)/Products(5)>
-From this URL, we can assume that the _EntityProcessor.java_ is the relevant place to handle this request in our code.
+From this URL, we can assume that the _EntityProcessor.java_ is the relevant place to handle this request in our code.
-The steps to find the requested entity are:
+The steps to find the requested entity are:
- 1. Do a read operation for the first segment (same as in the previous chapter)
+ 1. Do a read operation for the first segment (same as in the previous chapter)
In our example, this would be read entity for: */Categories(3)*
- 2. Follow the navigation to get the collection of the second segment
+ 2. Follow the navigation to get the collection of the second segment
In our example, this would be get the entity collection for: */Categories(3)/Products*
- 3. Pick the requested entity from the collection
+ 3. Pick the requested entity from the collection
In our example, retrieve the product with ID=5, which is contained in the collection */Categories(3)/Products(5)*
@@ -653,21 +671,21 @@ The steps to find the requested entity a
We can assume, that our database-mock is able to perform step 2 and 3 together.
In our class _myservice.mynamespace.service.DemoEntityProcessor.java_, weâve already added the navigation capability for to-one navigation.
-How can we find out that we arenât called for a to-one navigation, but instead, weâre responding to a to-many navigation with key access?
-The difference is the âkey predicateâ.
-The necessary info about it can be obtained from the URI segment.
-In the first chapter, weâve already learned that thereâs a special interface responsible for navigation segments, the _org.apache.olingo.server.api.uri.UriResourceNavigation_
+How can we find out that we arenât called for a to-one navigation, but instead, weâre responding to a to-many navigation with key access?
+The difference is the âkey predicateâ.
+The necessary info about it can be obtained from the URI segment.
+In the first chapter, weâve already learned that thereâs a special interface responsible for navigation segments, the _org.apache.olingo.server.api.uri.UriResourceNavigation_
It also provides a method _getKeyPredicates()_
-We can make use of it in order to distinguish between âto-one navigationâ and ânavigation with key accessâ.
+We can make use of it in order to distinguish between âto-one navigationâ and ânavigation with key accessâ.
If the call to
:::java
List<UriParmeter> navKeyPredicates = uriResourceNavigation.getKeyPredicates();
-returns an empty list, then we can assume that our OData service has been called for a âto-one navigationâ.
+returns an empty list, then we can assume that our OData service has been called for a âto-one navigationâ.
This to-one navigation has been explained in the chapter 3.3. above.
If the service request is like
-/Categories(3)/Products(5)
+/Categories(3)/Products(5)
then the method _getKeyPredicates()_ will return a list of with one element that contains ID=5
In our implementation of the _EntityProcessor_, we add the following code:
@@ -680,10 +698,10 @@ In our implementation of the _EntityProc
responseEntity = storage.getRelatedEntity(sourceEntity, responseEdmEntityType, navKeyPredicates);
}
-We get the key predicates for the navigation segment.
-Then we check if returned list is empty.
-If yes, we use the line that we implemented in chapter 3.3.
-If not, we have to create a new helper method that uses the key predicates for retrieving the desired entity.
+We get the key predicates for the navigation segment.
+Then we check if returned list is empty.
+If yes, we use the line that we implemented in chapter 3.3.
+If not, we have to create a new helper method that uses the key predicates for retrieving the desired entity.
The new helper method
:::java
@@ -693,15 +711,15 @@ will take care of getting the collection
One last thing to consider:
As we mentioned above, the user of our service is expected to specify a key of a product that is contained in the collection of products that is addressed by e.g.
-/Categories(3)/Products
+/Categories(3)/Products
e.g.
-/Categories(3)/Products(5)
+/Categories(3)/Products(5)
But he might specify a product ID that is existing, but not valid for the addressed navigation.
e.g.
-/Categories(3)/Products(1)
+/Categories(3)/Products(1)
With other words: it is not valid to navigate from category "Monitors" to a product like "Notebook Basic 15"
-If this is the case, we have to throw an appropriate exception.
+If this is the case, we have to throw an appropriate exception.
However, in our simple example weâre satisfied with simply checking if an entity was found at all:
:::java
@@ -709,15 +727,15 @@ However, in our simple example weâr
throw new ODataApplicationException("Nothing found.", HttpStatusCode.NOT_FOUND.getStatusCode(), Locale.ROOT);
}
-**Note**
-When implementing this navigation for the first time, our first intention might have been:
-Letâs just ignore the first segment and simply do a read operation for /Products(5)
-Which would mean, from the list of all products, pick the one with ID=5
-Why not?
+**Note**
+When implementing this navigation for the first time, our first intention might have been:
+Letâs just ignore the first segment and simply do a read operation for /Products(5)
+Which would mean, from the list of all products, pick the one with ID=5
+Why not?
The answer is that we cannot assume that the requested Product is automatically belonging to the specified Category.
E.g. in our example, the following URI should throw an error:
-Categories(3)/Products(1)
-As we know, Categories(3) is âMonitorsâ and Product(1) is a âNotebookâ
+Categories(3)/Products(1)
+As we know, Categories(3) is âMonitorsâ and Product(1) is a âNotebookâ
Thatâs it.
@@ -796,7 +814,7 @@ So now we can finally have a look at the
// 3. serialize
ContextURL contextUrl = ContextURL.with().entitySet(responseEdmEntitySet).suffix(Suffix.ENTITY).build();
- EntitySerializerOptions opts = EntitySerializerOptions.with().contextURL(contextUrl).build();
+ EntitySerializerOptions opts = EntitySerializerOptions.with().contextURL(contextUrl).build();
ODataFormat oDataFormat = ODataFormat.fromContentType(responseFormat);
ODataSerializer serializer = this.odata.createSerializer(oDataFormat);
@@ -812,24 +830,24 @@ So now we can finally have a look at the
# 4. Run the implemented service
-After building and deploying your service to your server, you can try the following URLs:
+After building and deploying your service to your server, you can try the following URLs:
- * Metadata and Service documents
+ * Metadata and Service documents
* <http://localhost:8080/DemoService/DemoService.svc/$metadata>
* <http://localhost:8080/DemoService/DemoService.svc>
- * âNormalâ query of both entity sets
+ * âNormalâ query of both entity sets
* <http://localhost:8080/DemoService/DemoService.svc/Products>
* <http://localhost:8080/DemoService/DemoService.svc/Categories>
- * âNormalâ read of both entity types
+ * âNormalâ read of both entity types
* <http://localhost:8080/DemoService/DemoService.svc/Products(1)>
* <http://localhost:8080/DemoService/DemoService.svc/Categories(3)>
- * âto-manyâ navigation
+ * âto-manyâ navigation
* <http://localhost:8080/DemoService/DemoService.svc/Categories(3)/Products>
- * âto-oneâ navigation
+ * âto-oneâ navigation
* <http://localhost:8080/DemoService/DemoService.svc/Products(1)/Category>
- * âto-manyâ navigation with key access
+ * âto-manyâ navigation with key access
* <http://localhost:8080/DemoService/DemoService.svc/Categories(1)/Products(2)>
- * âto-manyâ navigation with key access of invalid key, throwing an error
+ * âto-manyâ navigation with key access of invalid key, throwing an error
* <http://localhost:8080/DemoService/DemoService.svc/Categories(1)/Products(3)>
@@ -837,30 +855,31 @@ After building and deploying your servic
# 5. Summary
-In this tutorial we have learned how to add navigation capabilities to an OData service.
-Weâve implemented the to-many and to-one relationship and also the READ access to one entity after a to-many navigation.
-Weâve restricted the navigation to two segments, no more than navigating from one entity to another one.
+In this tutorial we have learned how to add navigation capabilities to an OData service.
+Weâve implemented the to-many and to-one relationship and also the READ access to one entity after a to-many navigation.
+Weâve restricted the navigation to two segments, no more than navigating from one entity to another one.
The modification of relations has not been covered by this tutorial.
-Check the *Links* section for more OData V4 tutorials.
+Check the *Links* section for more OData V4 tutorials.
---
# 6. Links
-
-Tutorial OData V4 service, part 1: [Read Entity Collection](/doc/odata4/tutorials/read/tutorial_read.html) | [sample project zip](http://olingo.apache.org/doc/odata4/tutorials/read/sample/DemoService_Tutorial_Read.zip)
-Tutorial OData V4 service, part 2: [Read Entity, Read Property](/doc/odata4/tutorials/readep/tutorial_readep.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip)
-Tutorial OData V4 service, part 3: [Write (Create, Update, Delete Entity)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip)
-Tutorial OData V4 service, part 4: Navigation (this page) | sample project zip
-Tutorial OData V4 service, part 5: System Query Options (to be announced)
-
-OData specification: http://odata.org/
-Olingo Javadoc: http://olingo.apache.org/javadoc/odata4/index.html
+### Tutorials
+ * Tutorial OData V4 service part 1: [Read Entity Collection](/doc/odata4/tutorials/read/tutorial_read.html) | [sample project](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.asc)).
+ * Tutorial OData V4 service part 2: [Read Entity, Read Property](/doc/odata4/tutorials/readep/tutorial_readep.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.asc))
+ * Tutorial OData V4 service part 3: [Write (Create, Update, Delete Entity)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
+ * Tutorial OData V4 service, part 4: [Navigation (this page)](/doc/odata4/tutorials/write/tutorial_navigation.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Navigation.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Navigation.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Navigation.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
+ * Tutorial OData V4 service, part 5: System Query Options (to be announced)
+
+### Further reading
+ * OData specification: [http://odata.org/](http://odata.org/)
+ * [Olingo Javadoc](/javadoc/odata4/index.html)
---
# 7. Appendix: code snippets
-When reaching the point where your OData service has to become productive and support complex scenarions, youâll find the following code snippets useful.
+When reaching the point where your OData service has to become productive and support complex scenarions, youâll find the following code snippets useful.
## 7.1. Find the EdmEntitySet for the navigation target
Modified: olingo/site/trunk/content/doc/odata4/tutorials/read/tutorial_read.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata4/tutorials/read/tutorial_read.mdtext?rev=1689991&r1=1689990&r2=1689991&view=diff
==============================================================================
--- olingo/site/trunk/content/doc/odata4/tutorials/read/tutorial_read.mdtext (original)
+++ olingo/site/trunk/content/doc/odata4/tutorials/read/tutorial_read.mdtext Thu Jul 9 06:14:07 2015
@@ -999,17 +999,19 @@ Our first OData service is very simple;
The project as final result can be download [here](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.asc)).
-**Outlook**
+# Links
+
+### Tutorials
Further topics to be covered by follow-up tutorials:
* Tutorial OData V4 service part 1: [Read Entity Collection (this page)](/doc/odata4/tutorials/read/tutorial_read.html) | [sample project](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.asc)).
* Tutorial OData V4 service part 2: [Read Entity, Read Property](/doc/odata4/tutorials/readep/tutorial_readep.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.asc))
- * Tutorial OData V4 service part 3: [Write (Create, Update, Delete Entity)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
+ * Tutorial OData V4 service part 3: [Write (Create, Update, Delete Entity)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
* Tutorial OData V4 service part 4: [Navigation - (To Be Announced)](...)
-**Further reading**
+### Further reading
-OData specification: <http://odata.org/>
-Olingo Javadoc: [http://olingo.apache.org/javadoc/odata4/index.html](/javadoc/odata4/index.html)
+ * OData specification: <http://odata.org/>
+ * Olingo Javadoc: [http://olingo.apache.org/javadoc/odata4/index.html](/javadoc/odata4/index.html)
Modified: olingo/site/trunk/content/doc/odata4/tutorials/readep/tutorial_readep.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata4/tutorials/readep/tutorial_readep.mdtext?rev=1689991&r1=1689990&r2=1689991&view=diff
==============================================================================
--- olingo/site/trunk/content/doc/odata4/tutorials/readep/tutorial_readep.mdtext (original)
+++ olingo/site/trunk/content/doc/odata4/tutorials/readep/tutorial_readep.mdtext Thu Jul 9 06:14:07 2015
@@ -792,7 +792,7 @@ In the next tutorial ([Part 3: Write](/d
* Tutorial OData V4 service part 1: [Read Entity Collection](/doc/odata4/tutorials/read/tutorial_read.html) | [sample project](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.asc)).
* Tutorial OData V4 service part 2: [Read Entity, Read Property (this page)](/doc/odata4/tutorials/readep/tutorial_readep.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.asc))
- * Tutorial OData V4 service part 3: [Write (Create, Update, Delete Entity)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
+ * Tutorial OData V4 service part 3: [Write (Create, Update, Delete Entity)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
OData specification: <http://odata.org/>
Modified: olingo/site/trunk/content/doc/odata4/tutorials/write/tutorial_write.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata4/tutorials/write/tutorial_write.mdtext?rev=1689991&r1=1689990&r2=1689991&view=diff
==============================================================================
--- olingo/site/trunk/content/doc/odata4/tutorials/write/tutorial_write.mdtext (original)
+++ olingo/site/trunk/content/doc/odata4/tutorials/write/tutorial_write.mdtext Thu Jul 9 06:14:07 2015
@@ -405,10 +405,12 @@ In the next tutorial (Part 4: Navigation
# 6. Links
+### Tutorials
* Tutorial OData V4 service part 1: [Read Entity Collection](/doc/odata4/tutorials/read/tutorial_read.html) | [sample project](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Read.zip.asc)).
* Tutorial OData V4 service part 2: [Read Entity, Read Property](/doc/odata4/tutorials/readep/tutorial_readep.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_ReadEp.zip.asc))
- * Tutorial OData V4 service part 3: [Write (Create, Update, Delete Entity) (this page)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
+ * Tutorial OData V4 service part 3: [Write (Create, Update, Delete Entity) (this page)](/doc/odata4/tutorials/write/tutorial_write.html) | [sample project zip](http://www.apache.org/dyn/closer.cgi/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip) ([md5](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.md5), [sha512](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.sha512), [pgp](https://dist.apache.org/repos/dist/release/olingo/odata4/Tutorials/DemoService_Tutorial_Write.zip.asc))
* Tutorial OData V4 service part 4: [Navigation - (To Be Announced)](...)
-OData specification: <http://odata.org/>
-Olingo Javadoc: [http://olingo.apache.org/javadoc/odata4/index.html](/javadoc/odata4/index.html)
+### Further reading
+ * OData specification: <http://odata.org/>
+ * Olingo Javadoc: [http://olingo.apache.org/javadoc/odata4/index.html](/javadoc/odata4/index.html)