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 2014/09/16 14:33:57 UTC

svn commit: r1625261 [3/8] - in /olingo/site/trunk: content/ content/doc/odata2/ content/doc/odata2/tutorials/ content/doc/odata4/ content/doc/odata4/tutorials/ templates/

Added: olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_AdvancedRead_FilterVisitor.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_AdvancedRead_FilterVisitor.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_AdvancedRead_FilterVisitor.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_AdvancedRead_FilterVisitor.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,382 @@
+Title:
+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.
+
+# Implementation of Filter Visitor (JDBC)
+
+
+### How To Guide for implementing a filter tree transformation into a JDBC where clause
+The query option $filter can be used to apply a filter query to the result set. This tutorial will be about consuming and working with the filter tree which an application will get from the OData Java library by implementing a transformation of the filter expression into a JDBC where clause. The example explained here will be kept simple to show the mechanism of the visitor pattern. Security problem which occur when using user input (e.g. the filter string of the URI) inside a where clause will be pointed out but not solved for this tutorial. Knowledge about the visitor pattern is not necessary but helpful. If you want to read further please refer to the further information chapter at the end of this tutorial.
+
+### Examples
+##### Simple example
+If a filter expression is parsed by the OData library it will be transformed into a filter tree. A simple tree for the expression ‘a’ eq ‘b’ would look like this:
+
+![Picture:Simple Filter Expression](/img/FilterExpressionSimple.png)
+
+To visit a filter tree we have to implement the interface `org.apache.olingo.odata2.api.uri.expression.ExpressionVisitor`. For this simple example we will only need the following methods:
+
+- `visitFilter(…)`
+- `visitBinary(…)` 
+- `visitLiteral(…)` 
+
+These methods will be called if the `accept(…)` method of the filter expression is called. The visitor will always start with the far left of the tree. First visitLiteral is called for 'a' and 'b'  before the `visitBinary()` and finally the `visitFilter()` is called.
+
+Now lets have a look at the implementation of the `visitLiteral()`:
+
+    @Override
+    public Object visitLiteral(LiteralExpression literal, EdmLiteral edmLiteral) {
+      if(EdmSimpleTypeKind.String.getEdmSimpleTypeInstance().equals(edmLiteral.getType())) {
+        // we have to be carefull with strings due to sql injection
+        // TODO: Prevent sql injection via escaping
+        return "'" + edmLiteral.getLiteral() + "'";
+      } else {
+        return "'" + edmLiteral.getLiteral() + "'";
+      }
+    }
+
+The signature for this method contains the literal expression as an object as well as the edmLiteral representation. In this case the literal would be "a" without the '. Inside this method we just return the literal. Since this literal is coming from the URL it represents user input. In the case of the type Edm.String we would have to escape the String to make sure no SQL Injection is possible.
+
+Next the method visitBinary is called:
+
+
+    @Override
+      public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide) {    
+        //Transform the OData filter operator into an equivalent sql operator
+        String sqlOperator = "";
+        switch (operator) {
+        case EQ:
+          sqlOperator = "=";
+          break;
+        case NE:
+          sqlOperator = "<>";
+          break;
+        case OR:
+          sqlOperator = "OR";
+          break;
+        case AND:
+          sqlOperator = "AND";
+          break;
+        case GE:
+          sqlOperator = ">=";
+          break;
+        case GT:
+          sqlOperator = ">";
+          break;
+        case LE:
+          sqlOperator = "<=";
+          break;
+        case LT:
+          sqlOperator = "<";
+          break;
+        default:
+          //Other operators are not supported for SQL Statements
+          throw new UnsupportetOperatorException("Unsupported operator: " + operator.toUriLiteral());
+        }  
+        //return the binary statement
+        return leftSide + " " + sqlOperator + " " + rightSide;
+      }
+
+The signature for this method contains the binaryExpression as an object as well as the left and right Side of this binary expression. In this example the left side would be "a" and the right side would be "b" which we got from the visitLiteral() method. In between we set the operator in its SQL syntax.
+
+Finally the `visitFilter()` method is called:
+
+      @Override
+      public Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression) {
+          return "WHERE " + expression;
+      }
+
+Here we just append the WHERE at the beginning and give the whole thing back to the caller of the `accept()` method.
+
+A simple test can show how the expression is transformed. As one can see the `UriParser` can parse a filter expression if necessary. Usually the application will already have the filter expression.
+
+
+    @Test
+    public void printExpression() throws Exception {
+        FilterExpression expression = UriParser.parseFilter(null, null, "'a' eq 'b'");
+        String whereClause = (String) expression.accept(new JdbcStringVisitor());
+        System.out.println("Raw: " + rawExpression + " ------> Whereclause: " + whereClause);
+        System.out.println();
+      }
+
+The output will be:
+
+
+    Raw: 'a' eq 'b' ------> Whereclause: 'a' = 'b'
+
+The implementation right now can only transform literals which will not be sufficient if you want to address a property. If an expression contains properties like "EmployeeId" we have to implement the method `visitProperty()`.
+
+    @Override
+    public Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmProperty) {
+      if (edmProperty == null) {
+        //If a property is not found it wont be represented in the database thus we have to throw an exception
+        throw new PropertyNotFoundException("Could not find Property: " + uriLiteral);
+      } else {
+        //It is also possible to use the mapping of the edmProperty if the name differs from the databasename
+        try {
+          return edmProperty.getName();
+        } catch (EdmException e) {
+          throw new PropertyNotFoundException(e);
+        }
+      }
+    }
+
+This method has an `edmProperty` in its signature which is set if the `uriLiteral` matches a property in the `EntityType`. Now we could transform an expression like `"EmployeeId eq '1'" or "EmployeeId eq '1' and ManagerId eq '2'"`. To get a validation if the property exists we have to give an `EntityType` on which the filter will be applied. In this case we use the `EntityType` "Employee".
+
+    @Test
+    public void printExpressionWithProperty() throws Exception {
+      //Use a mocked edmProvider for this tutorial
+      TestEdmProvider provider = new TestEdmProvider();
+      Edm edm = RuntimeDelegate.createEdm(provider);
+      EdmEntityType entityType = edm.getEntityType(TestEdmProvider.NAMESPACE_1, TestEdmProvider.ENTITY_TYPE_1_1.getName());
+
+      String rawExpression = "EmployeeId eq '1'";
+      FilterExpression expression = UriParser.parseFilter (null, entityType, rawExpression);
+      String whereClause = (String) expression.accept(new JdbcSimpleStringVisitor());
+      System.out.println("Raw: " + rawExpression + " ------> Whereclause: " + whereClause);
+      System.out.println();
+    }
+
+The output will be:
+
+
+    Raw: EmployeeId eq '1' ------> Whereclause: WHERE EmployeeId = '1'
+
+Test in the sources: JdbcSimpleStringVisitorTest.class
+
+##### Advanced Example
+The implementation shown in the previous chapter can transform simple expressions. But if the expression gets more complex like `"'a' eq 'b' or ('c' eq 'd' and 'b' eq 'd')"` it won´t produce a correct where clause. The following test shows this.
+
+    @Test
+    public void compareSimpleAndAdvancedVisitor() throws Exception{
+      String rawExpression = "'a' eq 'b' or ('c' eq 'd' and 'b' eq 'd')";
+      FilterExpression expression = UriParser.parseFilter(null, null, rawExpression);
+
+      String whereClauseSimple = (String) expression.accept(new JdbcSimpleStringVisitor());
+      String whereClauseAdvanced = (String) expression.accept(new JdbcAdvancedStringVisitor());
+      System.out.println("Simple: " + whereClauseSimple + " ------> Advanced: " + whereClauseAdvanced);
+    }
+
+The output will be:
+
+
+    Simple: WHERE 'a' = 'b' OR 'c' = 'd' AND 'b' = 'd' ------> Advanced: WHERE 'a' = 'b' OR ('c' = 'd' AND 'b' = 'd')
+
+In the simple implementation the brackets will not be transformed. To fix this the method `visitBinary()` has to be enhanced.
+
+    @Override
+    public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator, Object leftSide, Object rightSide) {
+      String actualLeftSide = leftSide.toString();
+      String actualRightSide = rightSide.toString();
+      if (leftSide instanceof Expression) {
+        //If something is lower in the tree and is of the type AND or OR it needs brackets to show the higher priority
+        if (BinaryOperator.AND.equals(((Expression) leftSide).getOperator()) || BinaryOperator.OR.equals(((Expression) leftSide).getOperator())) {
+        actualLeftSide = "(" + leftSide + ")";
+        }
+      }
+      if (rightSide instanceof Expression) {
+        //If something is lower in the tree and is of the type AND or OR it needs brackets to show the higher priority
+        if (BinaryOperator.AND.equals(((Expression) rightSide).getOperator()) || BinaryOperator.OR.equals(((Expression) rightSide).getOperator())) {
+        actualRightSide = "(" + rightSide + ")";
+        }
+      }
+      //Transform the OData filter operator into an equivalent sql operator
+      String sqlOperator = "";
+      switch (operator) {
+      case EQ:
+        sqlOperator = "=";
+        break;
+      case NE:
+        sqlOperator = "<>";
+        break;
+      case OR:
+        sqlOperator = "OR";
+        break;
+      case AND:
+        sqlOperator = "AND";
+        break;
+      case GE:
+        sqlOperator = ">=";
+        break;
+      case GT:
+        sqlOperator = ">";
+        break;
+      case LE:
+        sqlOperator = "<=";
+        break;
+      case LT:
+        sqlOperator = "<";
+        break;
+      default:
+        //Other operators are not supported for SQL  Statements
+        throw new UnsupportetOperatorException("Unsupported operator: " + operator.toUriLiteral());
+      }
+
+      //return the binary statement
+      return new Expression(actualLeftSide + " " + sqlOperator + " " + actualRightSide, operator);
+    }
+
+Since simple strings cannot show this complexity a new private class "Expression" was introduced. The signature of all `visit()` methods accept any kind of object so we can mix in the new class. Now we only have to check if one side of the tree is of type String or Expression.
+
+Test in the sources: JdbcAdvancedStringVisitorTest.class
+
+##### Example with prepared Statements
+Since string concatenation is very vulnerable against SQL Injection a best practice is to use prepared statements. This can be a tough challenge in this case because not only the value of `EmployeeId` is supplied in the filter expression but the field EmployeeId and the operator as well. Prepared Statements don´t allow statements like `"WHERE ? ? ?"` thus we have to find a way to prepare the prepared statements in advance which can be very complex as the following example will show: The filter expression `"EmployeeId eq '1' and ManagerId eq '2'"` is the same as `"ManagerId eq '2' and EmployeeId eq '1'"` but the prepared statement will always look like `"…. WHERE EmployeeId = ? and ManagerId = ?"`.
+
+This tutorial will not solve this problem. Instead it will show a first idea on how to implement such a prepared statement visitor. Again the Where clause will be created using string concatenation. But this time we will replace literals with a "?". These questionmarks can be set in a prepared statement. The methods `visitLiteral` and `visitProperty` will just return their value while the `visitBinary` will contain the logic.
+
+VisitLiteral:
+
+    @Override
+    public Object visitLiteral(final LiteralExpression literal, final EdmLiteral edmLiteral) {
+      //Sql Injection is not possible anymore since we are using prepared statements. Thus we can just give back the edmLiteral content
+      return edmLiteral.getLiteral();
+    }
+
+VisitProperty:
+
+    @Override
+    public Object visitProperty(final PropertyExpression propertyExpression, final String uriLiteral, final EdmTyped edmProperty) {
+      if (edmProperty == null) {
+        //If a property is not found it wont be represented in the database thus we have to throw an exception
+        throw new PropertyNotFoundException("Could not find Property: " + uriLiteral);
+      } else {
+        //To distinguish between literals and properties we give back the whole edmProperty in this case
+        return edmProperty;
+      }
+    }
+
+
+In addition we will use the following class as a container for the where clause and the parameters:
+
+    public class Expression {
+      private String preparedWhere;
+      private List<Object> parameters;
+      private BinaryOperator operator;
+
+      public Expression(final BinaryOperator operator) {
+        preparedWhere = "";
+        parameters = new ArrayList<Object>();
+        this.operator = operator;
+      }
+
+      public void addParameter(final Object parameter) {
+      parameters.add(parameter);
+      }
+
+      public void setPrepeparedWhere(final String where) {
+        preparedWhere = where;
+      }
+
+      public List<Object> getParameters() {
+        return parameters;
+      }
+
+      public BinaryOperator getOperator() {
+        return operator;
+      }
+
+      @Override
+      public String toString() {
+        return preparedWhere;
+      }
+    }
+
+Finally the visitBinary method:
+
+    @Override
+    public Object visitBinary(final BinaryExpression binaryExpression, final BinaryOperator operator, final Object leftSide, final Object rightSide) {
+      //Transform the OData filter operator into an equivalent sql operator
+      String sqlOperator = "";
+      switch (operator) {
+      case EQ:
+        sqlOperator = "=";
+        break;
+      case NE:
+        sqlOperator = "<>";
+        break;
+      case OR:
+        sqlOperator = "OR";
+        break;
+      case AND:
+        sqlOperator = "AND";
+        break;
+      case GE:
+        sqlOperator = ">=";
+        break;
+      case GT:
+        sqlOperator = ">";
+        break;
+      case LE:
+        sqlOperator = "<=";
+        break;
+      case LT:
+        sqlOperator = "<";
+        break;
+      default:
+        //Other operators are not supported for SQL Statements
+        throw new UnsupportetOperatorException("Unsupported operator: " + operator.toUriLiteral());
+      }
+      //The idea is to check if the left side is of type property. If this is the case we append the property name and the operator to the where clause
+      if (leftSide instanceof EdmTyped && rightSide instanceof String) {
+        Expression expression = new Expression(operator);
+        try {
+          expression.setPrepeparedWhere(((EdmTyped)   leftSide).getName() + " " + sqlOperator + " ?");
+        } catch (EdmException e) {
+          throw new RuntimeException("EdmException occured");
+        }
+        expression.addParameter(rightSide);
+        return expression;
+      } else if (leftSide instanceof Expression && rightSide instanceof Expression) {
+        Expression returnExpression = new Expression(operator);
+        Expression leftSideExpression = (Expression) leftSide;
+        if (BinaryOperator.AND.equals    (leftSideExpression.getOperator()) || BinaryOperator.OR.equals(leftSideExpression.getOperator())) {
+        leftSideExpression.setPrepeparedWhere("(" + leftSideExpression.toString() + ")");
+        }
+        Expression rightSideExpression = (Expression) rightSide;
+        if (BinaryOperator.AND.equals(rightSideExpression.getOperator()) || BinaryOperator.OR.equals(rightSideExpression.getOperator())) {
+        rightSideExpression.setPrepeparedWhere("(" + rightSideExpression.toString() + ")");
+        }
+        returnExpression.setPrepeparedWhere(leftSideExpression.toString() + " " + sqlOperator + " " + rightSideExpression.toString());
+
+        for (Object parameter : leftSideExpression.getParameters()) {
+        returnExpression.addParameter(parameter);
+        }
+        for (Object parameter : rightSideExpression.getParameters()) {
+          returnExpression.addParameter(parameter);
+        }
+        return returnExpression;
+      } else {
+        throw new RuntimeException("Not right format");
+      }
+    }
+
+
+This implementation will work if the filter supplies a property on the left side and a literal on the right side. The output of this new implementation could look like this:
+
+    Raw: EmployeeId eq '1' or (ManagerId eq '2' and TeamId eq '3') ------> Whereclause: EmployeeId = ? OR (ManagerId = ? AND TeamId = ?)
+    1
+    2
+    3
+
+
+Test in the sources: JdbcPreparedStatementVisitorTest.class
+
+### Further Information
+Documentation about how to create such a filter expression can be found under [http://www.odata.org](http://www.odata.org "External Link") in the OData protocol specification.
+
+Visitor pattern: [http://en.wikipedia.org/wiki/Visitor_pattern](http://en.wikipedia.org/wiki/Visitor_pattern "External Link")
\ No newline at end of file

Added: olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_Advanced_Service_Resolution.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_Advanced_Service_Resolution.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_Advanced_Service_Resolution.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_Advanced_Service_Resolution.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,82 @@
+Title:
+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.
+
+# Service Resolution 
+
+An OData service usually starts with "/" which delivers the service document while all preceding path segments belong to the servlet and servlet mapping of a web application.
+
+Some service providers would like to get control over the path hierarchy. This is called service resolution. Actually it means an OData path can start at any hierarchy path level behind the service mapping path elements.
+
+An uri schema with and without service resolution is shown here: 
+
+![Picture: Service Resolution](/img/service-resolution-url.png)
+
+A service init parameter (`org.apache.olingo.odata2.path.split`) can be set for servlet configuration (see web.xml) and define how many path segments are not interpreted as OData path segments. In the example the split value is 2 and the resulting service resolution uses two path segments. The first one is for <namespace> and the secound for <service>. 
+
+    <?xmlversion="1.0"encoding="UTF-8"?>
+    <web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns="http://java.sun.com/xml/ns/javaee"xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+       id="WebApp_ID"version="2.5">
+       <display-name>MyApp</display-name>
+       <welcome-file-list>
+             <welcome-file>index.jsp</welcome-file>
+       </welcome-file-list>
+       <servlet>
+             <servlet-name>ODataServlet</servlet-name>
+             <servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
+             <init-param>
+                   <param-name>javax.ws.rs.Application</param-name>
+                    <param-value>org.apache.olingo.odata2.core.ODataApplication</param-value>
+             </init-param>
+             <init-param>
+                    <param-name>org.apache.olingo.odata2.service.factory</param-name>
+                    <param-value>org.apache.olingo.odata2.ref.processor.ScenarioServiceFactory</param-value>
+             </init-param>
+             <init-param>
+                    <param-name>org.apache.olingo.odata2.path.split</param-name>
+                    <param-value>2</param-value>
+             </init-param>
+             <load-on-startup>1</load-on-startup>
+       </servlet>
+       <servlet-mapping>
+             <servlet-name>ODataServlet</servlet-name>
+             <url-pattern>/odata.svc/*</url-pattern>
+       </servlet-mapping>
+    </web-app>
+
+A processor implementation (e.g. `ODataSingleProcessor`) does have access to an `ODataContext` object which will deliver a `ODataUriInfo` object. From this class a processor implementation can access the service resolution information which is as following: 
+
+- URI: *http://localhost:8080/odata.svc/[namespace]/[system]/Room('1')/Size/$value*  
+- preceding path segments:	*[namespace], [system]*  
+- OData path segments:  *Room('1'), Size, $value*  
+- base URI:  *http://localhost:8080/odata.svc/[namespace]/[system]/*
+
+**Sample Code**
+
+    public interface ODataContext {
+      ODataService getService() throws ODataException;
+      ODataUriInfo getUriInfo() throws ODataException;
+    }
+    
+    public interface ODataUriInfo {
+      List<ODataPathSegment> getPrecedingPathSegmentList();
+      List<ODataPathSegment> getODataPathSegmentList();
+      URI getBaseUri();
+    }
+      

Added: olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicRead_EDM.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicRead_EDM.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicRead_EDM.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicRead_EDM.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,42 @@
+Title:
+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 use EDMX source as EDM Provider within an OData Service 
+
+
+## How To Guide for the using an EDM Parser
+
+The EDM Parser is designed to parse the metadata document.
+To make the parser accessible from the API, we have to implement the method `readMetadata` from interface `org.apache.olingo.odata2.api.ep.EntityProviderInterface`:
+
+     @Override
+     public Edm readMetadata(final InputStream inputStream, final boolean validate) throws EntityProviderException {
+       EdmProvider provider = new EdmxProvider().parse(inputStream, validate);
+       return new EdmImplProv(provider);
+      }
+
+The signature contains the `InputStream` that represents a data stream read from a file and flag validate. If validate is set to true, the structure of the metadata will be checked according to the CSDL after parsing. For example: it will be validated that each `EntityType` defines a key element or derives from a `BaseType` that for its part defines a key.
+
+To start the parsing we have to follow the next steps:
+
+- create an object of the class `EdmxProvider`. This class derives from `EdmProvider` and provides implementations for all of its abstract methods 
+- invoke the method `parse(InputStream, boolean)` of this object 
+
+Returned is an EDM object that contains the complete information from parsed metadata.
+
+   

Added: olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicWrite.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicWrite.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicWrite.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/Olingo_Tutorial_BasicWrite.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,141 @@
+Title:
+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.
+
+# Write Scenario 
+### How To Guide for building a Sample OData service with the OData Library (Java)
+
+
+This How To Guide shows how to create, update and delete an entry via your Data Provider (`ODataSingleProcessor`).
+
+### Prerequisites
+This tutorial is based on the [Read Scenario](/doc/odata2/tutorials/basicread.html) - OData Library (Java) tutorial.  
+### Implementing create, update and delete entry at the single processor
+
+##### Create entry
+- You already created the `MyODataSingleProcessor` in the basic tutorial 
+- Implement `MyODataSingleProcessor.createEntity(PostUriInfo uriInfo, InputStream content, String requestContentType, String contentType) throws ODataException` by overriding the corresponding method of the `ODataSingleProcessor` 
+  
+**Sample Code**
+
+    
+    @Override
+    public ODataResponse createEntity(PostUriInfo uriInfo, InputStream content, 
+    String requestContentType, String contentType) throws ODataException {
+      //No support for creating and linking a new entry
+      if (uriInfo.getNavigationSegments().size() > 0) {
+      throw new ODataNotImplementedException();
+      }
+
+      //No support for media resources
+      if (uriInfo.getStartEntitySet().getEntityType().hasStream()) {
+      throw new ODataNotImplementedException();
+      }
+
+      EntityProviderReadProperties properties = EntityProviderReadProperties.init().mergeSemantic(false).build();
+
+      ODataEntry entry = EntityProvider.readEntry(requestContentType, uriInfo.getStartEntitySet(), content, properties);
+      //if something goes wrong in deserialization this is managed via the ExceptionMapper
+      //no need for an application to do exception handling here an convert the exceptions in HTTP exceptions
+
+      Map<String, Object> data = entry.getProperties();
+      //now one can use the data to create the entry in the backend ...
+      //retrieve the key value after creation, if the key is generated by the server
+
+      //update the data accordingly
+      data.put("Id", Integer.valueOf(887788675));
+
+      //serialize the entry, Location header is set by OData Library
+      return EntityProvider.writeEntry(contentType, uriInfo.getStartEntitySet(), entry.getProperties(), EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).build());
+    }
+
+
+##### Update entry
+- You already created the `MyODataSingleProcessor` in the basic tutorial 
+- Implement `MyODataSingleProcessor.updateEntity(PutMergePatchUriInfo uriInfo, InputStream content, String requestContentType, boolean merge, String contentType) throws ODataException` by overriding the corresponding method of the `ODataSingleProcessor` 
+
+**Sample Code**
+
+    @Override
+    public ODataResponse updateEntity(PutMergePatchUriInfo uriInfo, InputStream content, String requestContentType, boolean merge, String contentType) throws ODataException {
+    EntityProviderReadProperties properties = EntityProviderReadProperties.init().mergeSemantic(false).build();
+
+      ODataEntry entry = EntityProvider.readEntry(requestContentType, uriInfo.getTargetEntitySet(), content, properties);
+      //if something goes wrong in deserialization this is managed via the ExceptionMapper,
+      //no need for an application to do exception handling here an convert the exceptions in HTTP exceptions
+
+      Map<String, Object> data = entry.getProperties();
+
+      if ("Cars".equals(uriInfo.getTargetEntitySet().getName())) {
+      int key = getKeyValue(uriInfo.getKeyPredicates().get(0));
+
+        //if there is no entry with this key available, one should return "404 Not Found"
+        //return ODataResponse.status(HttpStatusCodes.NOT_FOUND).build();
+
+        //now one can use the data to create the entry in the backend ...
+        String model = (String) data.get("Model");
+        //...
+      } else if ("Manufacturers".equals(uriInfo.getTargetEntitySet().getName())) {
+      int key = getKeyValue(uriInfo.getKeyPredicates().get(0));
+      //now one can use the data to create the entry in the backend ...
+      }
+
+      //we can return Status Code 204 No Content because the URI Parsing already guarantees that
+      //a) only valid URIs are dispatched (also checked against the metadata)
+      //b) 404 Not Found is already returned above, when the entry does not exist 
+    return ODataResponse.status(HttpStatusCodes.NO_CONTENT).build();
+    }
+
+##### Delete entry
+- You already created the `MyODataSingleProcessor` in the basic tutorial 
+- Implement `MyODataSingleProcessor.deleteEntity(DeleteUriInfo uriInfo, String contentType) throws ODataException` by overriding the corresponding method of the `ODataSingleProcessor` 
+
+**Sample Code**
+
+    @Override
+    public ODataResponse deleteEntity(DeleteUriInfo uriInfo, String contentType) throws ODataException {
+      if ("Cars".equals(uriInfo.getTargetEntitySet().getName())) {
+      int key = getKeyValue(uriInfo.getKeyPredicates().get(0));
+
+        //if there is no entry with this key available, one should return "404 Not Found"
+        //return ODataResponse.status(HttpStatusCodes.NOT_FOUND).build();
+
+        //now one can delete the entry with this particular key in the backend...
+
+      } else if ("Manufacturers".equals(uriInfo.getTargetEntitySet().getName())) {
+      int key = getKeyValue(uriInfo.getKeyPredicates().get(0));
+      //now one can delete the entry with this particular key in the backend...
+      }
+
+      //we can return Status Code 204 No Content because the URI Parsing already guarantees that
+      //a) only valid URIs are dispatched (also checked against the metadata)
+      //b) 404 Not Found is already returned above, when the entry does not exist 
+      return ODataResponse.status(HttpStatusCodes.NO_CONTENT).build();
+    }
+
+### Test your Service
+
+After the implementation of the MyODataSingleProcessor the web application can be tested.
+
+- Build your project `mvn clean install` 
+- In Eclipse, run the Web Application via Run As -> Run on Server 
+ - Create a new Car 
+ - Update an existing Car 
+ - Delete a Car 
+ - Create a new Manufacturer (this is a Media Link Entry), we expect the response 501 Not Implemented
+ - Update a Manufacturer (this is a Media Link Entry) 
+     
\ No newline at end of file

Added: olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-adv_read_expand.zip
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-adv_read_expand.zip?rev=1625261&view=auto
==============================================================================
Binary file - no diff available.

Propchange: olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-adv_read_expand.zip
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-adv_read_mediaresource.zip
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-adv_read_mediaresource.zip?rev=1625261&view=auto
==============================================================================
Binary file - no diff available.

Propchange: olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-adv_read_mediaresource.zip
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-basic_read.zip
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-basic_read.zip?rev=1625261&view=auto
==============================================================================
Binary file - no diff available.

Propchange: olingo/site/trunk/content/doc/odata2/tutorials/apache-olingo-tutorial-basic_read.zip
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: olingo/site/trunk/content/doc/odata2/tutorials/basicread.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/basicread.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/basicread.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/basicread.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,650 @@
+Title:
+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.
+
+# Read Scenario
+
+---
+
+### How To Guide for building a Sample OData service with the OData Library (Java)
+
+This How To Guide prerequisites a Project Setup (Git, Maven, etc.) and then shows how to develop an OData Service and make the same available.
+It shows in addition how to implement the Model Provider to expose the Entity Data Model (EDM) and the Data Provider to expose the runtime data.
+The implementation of the Data Provider (ODataSingleProcessor) illustrates how to expose a single entry, a feed and how to follow associations.
+
+### Prerequisites 
+
+[Project Setup](../project-setup.html) is successfully done and the project is ready to start in your `$ODATA_PROJECT_HOME`.
+
+### Implement your OData Service 
+
+##### Shortcut 
+
+As a shortcut you can download the [Olingo Tutorial 'Basic-Read' Project](apache-olingo-tutorial-basic_read.zip).
+
+### Deployment Descriptor 
+
+##### Sample Code      
+ 
+    :::xml
+    <?xml version="1.0" encoding="UTF-8"?>
+    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+        id="WebApp_ID" version="2.5">
+        <display-name>org.apache.olingo.odata2.sample</display-name>
+        <servlet>
+            <servlet-name>MyODataSampleServlet</servlet-name>
+            <servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
+            <init-param>
+                <param-name>javax.ws.rs.Application</param-name>
+                <param-value>org.apache.olingo.odata2.core.rest.app.ODataApplication</param-value>
+            </init-param>
+            <init-param>
+                <param-name>org.apache.olingo.odata2.service.factory</param-name>
+                <param-value>org.apache.olingo.odata2.sample.service.MyServiceFactory</param-value>
+            </init-param>
+            <load-on-startup>1</load-on-startup>
+        </servlet>
+        <servlet-mapping>
+            <servlet-name>MyODataSampleServlet</servlet-name>
+            <url-pattern>/MyODataSample.svc/*</url-pattern>
+        </servlet-mapping>
+    </web-app>
+
+  - Start the command line tool, go to folder *$ODATA_PROJECT_HOME\org.apache.olingo.odata2.sample.cars* and enter
+`mvn clean install` to build your projects 
+
+  - The deployment Descriptor contains two `<init-param>` elements which define the OData Application `org.apache.olingo.odata2.core.rest.app.ODataApplication` and your Service Factory `org.apache.olingo.odata2.sample.service.MyServiceFactory`. The OData Application is implemented in the OData Library (Core) and registers a root locator and an exception mapper. The root locator looks up your registered Service Factory to get access to the Entity Data Model Provider and the OData Processor which provides the runtime data. In addition the root locator looks up a parameter `org.apache.olingo.odata2.path.split` (not present in the deployment descriptor above) which indicates how many path segments are reserved for the OData Service via an Integer value (default is 0, which means that the OData Service name corresponds to the defined `url-pattern`). 
+
+### Implement the OData Service Factory
+  - Create a new source folder *src/main/java* in the eclipse project 
+  - Create a new package `org.apache.olingo.odata2.sample.service` in the source folder
+  - Create a class `MyServiceFactory` which extends `org.apache.olingo.odata2.api.ODataServiceFactory` in the new package and contains the following implementation 
+
+##### Sample Code
+
+
+
+    :::java
+    package org.apache.olingo.odata2.sample.service;
+    
+    import org.apache.olingo.odata2.api.ODataService;
+    import org.apache.olingo.odata2.api.ODataServiceFactory;
+    import org.apache.olingo.odata2.api.edm.provider.EdmProvider;
+    import org.apache.olingo.odata2.api.exception.ODataException;
+    import org.apache.olingo.odata2.api.processor.ODataContext;
+    import org.apache.olingo.odata2.api.processor.ODataSingleProcessor;
+    
+    public class MyServiceFactory extends ODataServiceFactory {
+    
+      @Override
+      public ODataService createService(ODataContext ctx) throws ODataException {
+    
+    EdmProvider edmProvider = new MyEdmProvider();
+    ODataSingleProcessor singleProcessor = new MyODataSingleProcessor();
+    
+    return createODataSingleProcessorService(edmProvider, singleProcessor);
+      }
+    }
+
+
+
+- In order to make your coding able to compile you have to create Java classes for 
+ `MyEdmProvider` which extends `org.apache.olingo.odata2.api.edm.provider.EdmProvider` and 
+ `MyODataSingleProcessor` which extends `org.apache.olingo.odata2.api.processor.ODataSingleProcessor` 
+- After these steps compile your project with `mvn clean install` 
+
+### Implement the Entity Data Model Provider
+In this paragraph you will implement the `MyEdmProvider` class by overriding all methods of `org.apache.olingo.odata2.api.edm.provider.EdmProvider`.
+
+- You will implement the following Entity Data Model.
+
+![alt text][1]
+ 
+- As we have a static model we define constants for all top level elements of the schema (declared in the `MyEdmProvider` class).
+
+##### Sample Code
+
+    :::java 
+      static final String ENTITY_SET_NAME_MANUFACTURERS = "Manufacturers";
+      static final String ENTITY_SET_NAME_CARS = "Cars";
+      static final String ENTITY_NAME_MANUFACTURER = "Manufacturer";
+      static final String ENTITY_NAME_CAR = "Car";
+    
+      private static final String NAMESPACE = "org.apache.olingo.odata2.ODataCars";
+    
+      private static final FullQualifiedName ENTITY_TYPE_1_1 = new FullQualifiedName(NAMESPACE, ENTITY_NAME_CAR);
+      private static final FullQualifiedName ENTITY_TYPE_1_2 = new FullQualifiedName(NAMESPACE, ENTITY_NAME_MANUFACTURER);
+    
+      private static final FullQualifiedName COMPLEX_TYPE = new FullQualifiedName(NAMESPACE, "Address");
+    
+      private static final FullQualifiedName ASSOCIATION_CAR_MANUFACTURER = new FullQualifiedName(NAMESPACE, "Car_Manufacturer_Manufacturer_Cars");
+    
+      private static final String ROLE_1_1 = "Car_Manufacturer";
+      private static final String ROLE_1_2 = "Manufacturer_Cars";
+    
+      private static final String ENTITY_CONTAINER = "ODataCarsEntityContainer";
+    
+      private static final String ASSOCIATION_SET = "Cars_Manufacturers";
+    
+- Implement `MyEdmProvider.getSchemas`. This method is used to retrieve the complete structural information in order to build the metadata document and the service document. The implementation makes use of other getter methods of this class for simplicity reasons. If a very performant way of building the whole structural information was required, other implementation strategies could be used. 
+
+##### Sample Code
+
+    :::java
+    public List<Schema> getSchemas() throws ODataException {
+    List<Schema> schemas = new ArrayList<Schema>();
+    
+    Schema schema = new Schema();
+    schema.setNamespace(NAMESPACE);
+    
+    List<EntityType> entityTypes = new ArrayList<EntityType>();
+    entityTypes.add(getEntityType(ENTITY_TYPE_1_1));
+    entityTypes.add(getEntityType(ENTITY_TYPE_1_2));
+    schema.setEntityTypes(entityTypes);
+    
+    List<ComplexType> complexTypes = new ArrayList<ComplexType>();
+    complexTypes.add(getComplexType(COMPLEX_TYPE));
+    schema.setComplexTypes(complexTypes);
+    
+    List<Association> associations = new ArrayList<Association>();
+    associations.add(getAssociation(ASSOCIATION_CAR_MANUFACTURER));
+    schema.setAssociations(associations);
+    
+    List<EntityContainer> entityContainers = new ArrayList<EntityContainer>();
+    EntityContainer entityContainer = new EntityContainer();
+    entityContainer.setName(ENTITY_CONTAINER).setDefaultEntityContainer(true);
+    
+    List<EntitySet> entitySets = new ArrayList<EntitySet>();
+    entitySets.add(getEntitySet(ENTITY_CONTAINER, ENTITY_SET_NAME_CARS));
+    entitySets.add(getEntitySet(ENTITY_CONTAINER, ENTITY_SET_NAME_MANUFACTURERS));
+    entityContainer.setEntitySets(entitySets);
+    
+    List<AssociationSet> associationSets = new ArrayList<AssociationSet>();
+    associationSets.add(getAssociationSet(ENTITY_CONTAINER, ASSOCIATION_CAR_MANUFACTURER, ENTITY_SET_NAME_MANUFACTURERS, ROLE_1_2));
+    entityContainer.setAssociationSets(associationSets);
+    
+    entityContainers.add(entityContainer);
+    schema.setEntityContainers(entityContainers);
+    
+    schemas.add(schema);
+    
+    return schemas;
+    }
+
+- `MyEdmProvider.getEntityType(FullQualifiedName edmFQName)` returns an Entity Type according to the full qualified name specified. The Entity Type holds all information about its structure like simple properties, complex properties, navigation properties and the definition of its key property (or properties). 
+
+##### Sample Code
+
+    :::java
+    @Override
+      public EntityType getEntityType(FullQualifiedName edmFQName) throws ODataException {
+    if (NAMESPACE.equals(edmFQName.getNamespace())) {
+
+      if (ENTITY_TYPE_1_1.getName().equals(edmFQName.getName())) {
+
+        //Properties
+        List<Property> properties = new ArrayList<Property>();
+        properties.add(new SimpleProperty().setName("Id").setType(EdmSimpleTypeKind.Int32).setFacets(new Facets().setNullable(false)));
+        properties.add(new SimpleProperty().setName("Model").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setNullable(false).setMaxLength(100).setDefaultValue("Hugo"))
+            .setCustomizableFeedMappings(new CustomizableFeedMappings().setFcTargetPath(EdmTargetPath.SYNDICATION_TITLE)));
+        properties.add(new SimpleProperty().setName("ManufacturerId").setType(EdmSimpleTypeKind.Int32));
+        properties.add(new SimpleProperty().setName("Price").setType(EdmSimpleTypeKind.Decimal));
+        properties.add(new SimpleProperty().setName("Currency").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setMaxLength(3)));
+        properties.add(new SimpleProperty().setName("ModelYear").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setMaxLength(4)));
+        properties.add(new SimpleProperty().setName("Updated").setType(EdmSimpleTypeKind.DateTime)
+            .setFacets(new Facets().setNullable(false).setConcurrencyMode(EdmConcurrencyMode.Fixed))
+            .setCustomizableFeedMappings(new CustomizableFeedMappings().setFcTargetPath(EdmTargetPath.SYNDICATION_UPDATED)));
+        properties.add(new SimpleProperty().setName("ImagePath").setType(EdmSimpleTypeKind.String));
+
+        //Navigation Properties
+        List<NavigationProperty> navigationProperties = new ArrayList<NavigationProperty>();
+        navigationProperties.add(new NavigationProperty().setName("Manufacturer")
+            .setRelationship(ASSOCIATION_CAR_MANUFACTURER).setFromRole(ROLE_1_1).setToRole(ROLE_1_2));
+
+        //Key
+        List<PropertyRef> keyProperties = new ArrayList<PropertyRef>();
+        keyProperties.add(new PropertyRef().setName("Id"));
+        Key key = new Key().setKeys(keyProperties);
+
+        return new EntityType().setName(ENTITY_TYPE_1_1.getName())
+            .setProperties(properties)
+            .setKey(key)
+            .setNavigationProperties(navigationProperties);
+
+      } else if (ENTITY_TYPE_1_2.getName().equals(edmFQName.getName())) {
+
+        //Properties
+        List<Property> properties = new ArrayList<Property>();
+        properties.add(new SimpleProperty().setName("Id").setType(EdmSimpleTypeKind.Int32).setFacets(new Facets().setNullable(false)));
+        properties.add(new SimpleProperty().setName("Name").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setNullable(false).setMaxLength(100))
+            .setCustomizableFeedMappings(new CustomizableFeedMappings().setFcTargetPath(EdmTargetPath.SYNDICATION_TITLE)));
+        properties.add(new ComplexProperty().setName("Address").setType(new FullQualifiedName(NAMESPACE, "Address")));
+        properties.add(new SimpleProperty().setName("Updated").setType(EdmSimpleTypeKind.DateTime)
+            .setFacets(new Facets().setNullable(false).setConcurrencyMode(EdmConcurrencyMode.Fixed))
+            .setCustomizableFeedMappings(new CustomizableFeedMappings().setFcTargetPath(EdmTargetPath.SYNDICATION_UPDATED)));
+
+        //Navigation Properties
+        List<NavigationProperty> navigationProperties = new ArrayList<NavigationProperty>();
+        navigationProperties.add(new NavigationProperty().setName("Cars")
+            .setRelationship(ASSOCIATION_CAR_MANUFACTURER).setFromRole(ROLE_1_2).setToRole(ROLE_1_1));
+
+        //Key
+        List<PropertyRef> keyProperties = new ArrayList<PropertyRef>();
+        keyProperties.add(new PropertyRef().setName("Id"));
+        Key key = new Key().setKeys(keyProperties);
+
+        return new EntityType().setName(ENTITY_TYPE_1_2.getName())
+            .setProperties(properties)
+            .setHasStream(true)
+            .setKey(key)
+            .setNavigationProperties(navigationProperties);
+      }
+    }
+
+    return null;
+    }
+
+- `MyEdmProvider.getComplexType(FullQualifiedName edmFQName)` 
+
+##### Sample Code
+
+
+    :::java
+    public ComplexType getComplexType(FullQualifiedName edmFQName) throws ODataException {
+    if (NAMESPACE.equals(edmFQName.getNamespace())) {
+      if (COMPLEX_TYPE.getName().equals(edmFQName.getName())) {
+        List<Property> properties = new ArrayList<Property>();
+        properties.add(new SimpleProperty().setName("Street").setType(EdmSimpleTypeKind.String));
+        properties.add(new SimpleProperty().setName("City").setType(EdmSimpleTypeKind.String));
+        properties.add(new SimpleProperty().setName("ZipCode").setType(EdmSimpleTypeKind.String));
+        properties.add(new SimpleProperty().setName("Country").setType(EdmSimpleTypeKind.String));
+        return new ComplexType().setName(COMPLEX_TYPE.getName()).setProperties(properties);
+      }
+    }
+
+    return null;
+
+    }
+  
+
+  
+
+
+- `MyEdmProvider.getAssociation(FullQualifiedName edmFQName)` 
+  
+##### Sample Code
+
+
+    :::java
+    public Association getAssociation(FullQualifiedName edmFQName) throws ODataException {
+    if (NAMESPACE.equals(edmFQName.getNamespace())) {
+      if (ASSOCIATION_CAR_MANUFACTURER.getName().equals(edmFQName.getName())) {
+        return new Association().setName(ASSOCIATION_CAR_MANUFACTURER.getName())
+            .setEnd1(new AssociationEnd().setType(ENTITY_TYPE_1_1).setRole(ROLE_1_1).setMultiplicity(EdmMultiplicity.MANY))
+            .setEnd2(new AssociationEnd().setType(ENTITY_TYPE_1_2).setRole(ROLE_1_2).setMultiplicity(EdmMultiplicity.ONE));
+      }
+    }
+    return null;
+    }
+
+
+- `MyEdmProvider.getEntityContainerInfo(String name)` 
+  
+##### Sample Code
+
+    :::java
+    public EntityContainerInfo getEntityContainerInfo(String name) throws ODataException {
+    if (name == null || "ODataCarsEntityContainer".equals(name)) {
+      return new EntityContainerInfo().setName("ODataCarsEntityContainer").setDefaultEntityContainer(true);
+    }
+
+    return null;
+    }
+
+
+- `MyEdmProvider.getEntitySet(String entityContainer, String name)`
+
+##### Sample Code
+
+    :::java
+    public EntitySet getEntitySet(String entityContainer, String name) throws ODataException {
+    if (ENTITY_CONTAINER.equals(entityContainer)) {
+      if (ENTITY_SET_NAME_CARS.equals(name)) {
+        return new EntitySet().setName(name).setEntityType(ENTITY_TYPE_1_1);
+      } else if (ENTITY_SET_NAME_MANUFACTURERS.equals(name)) {
+        return new EntitySet().setName(name).setEntityType(ENTITY_TYPE_1_2);
+      }
+    }
+    return null;
+    }
+
+
+- `MyEdmProvider.getAssociationSet(String entityContainer, FullQualifiedName association, String sourceEntitySetName, String sourceEntitySetRole)`
+
+##### Sample Code
+
+ 
+    :::java
+    public AssociationSet getAssociationSet(String entityContainer, FullQualifiedName association, String sourceEntitySetName, String sourceEntitySetRole) throws ODataException {
+    if (ENTITY_CONTAINER.equals(entityContainer)) {
+      if (ASSOCIATION_CAR_MANUFACTURER.equals(association)) {
+        return new AssociationSet().setName(ASSOCIATION_SET)
+            .setAssociation(ASSOCIATION_CAR_MANUFACTURER)
+            .setEnd1(new AssociationSetEnd().setRole(ROLE_1_2).setEntitySet(ENTITY_SET_NAME_MANUFACTURERS))
+            .setEnd2(new AssociationSetEnd().setRole(ROLE_1_1).setEntitySet(ENTITY_SET_NAME_CARS));
+      }
+    }
+    return null;
+    }
+
+#### Conclusion
+
+After the implementation of the Edm Provider the web application can be executed to show the Service Document and the Metadata Document.
+
+- Build your project `mvn clean install` 
+- Deploy the Web Application to the server. 
+ - Show the Service Document: http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/
+ - Show the Metadata Document: http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/$metadata 
+
+### Implement the OData Processor which provides the runtime data
+
+You already created the `MyODataSingleProcessor` class which we now extend with some needed imports and a reference to a DataStore which contains our data (and will be implemented in the next step).
+
+##### Sample Code
+ 
+    :::java
+    package org.apache.olingo.odata2.sample.service;
+    
+    import static org.apache.olingo.odata2.sample.service.MyEdmProvider.ENTITY_SET_NAME_CARS;
+    import static org.apache.olingo.odata2.sample.service.MyEdmProvider.ENTITY_SET_NAME_MANUFACTURERS;
+    
+    import org.apache.olingo.odata2.api.processor.ODataSingleProcessor;
+    
+    public class MyODataSingleProcessor extends ODataSingleProcessor {
+      private DataStore dataStore = new DataStore();
+    }
+
+
+- As next steps we will implement the read access to the Car and Manufacturer entries and the read access to the Cars and Manufacturers feed. As we need some basis for sample data we create a very simple DataStore which contains the data as well as access methods to serve the required data: 
+
+##### Sample Code
+
+
+    :::java
+    package org.apache.olingo.odata2.sample.service;
+    
+    import java.util.ArrayList;
+    import java.util.Calendar;
+    import java.util.HashMap;
+    import java.util.List;
+    import java.util.Map;
+    import java.util.TimeZone;
+    
+    public class DataStore {
+    
+      //Data accessors
+      public Map<String, Object> getCar(int id) {
+    Map<String, Object> data = null;
+
+    Calendar updated = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+    
+    switch (id) {
+    case 1:
+      updated.set(2012, 11, 11, 11, 11, 11);
+      data = createCar(1, "F1 W03", 1, 189189.43, "EUR", "2012", updated, "file://imagePath/w03");
+      break;
+
+    case 2:
+      updated.set(2013, 11, 11, 11, 11, 11);
+      data = createCar(2, "F1 W04", 1, 199999.99, "EUR", "2013", updated, "file://imagePath/w04");
+      break;
+
+    case 3:
+      updated.set(2012, 12, 12, 12, 12, 12);
+      data = createCar(3, "F2012", 2, 137285.33, "EUR", "2012", updated, "http://pathToImage/f2012");
+      break;
+
+    case 4:
+      updated.set(2013, 12, 12, 12, 12, 12);
+      data = createCar(4, "F2013", 2, 145285.00, "EUR", "2013", updated, "http://pathToImage/f2013");
+      break;
+
+    case 5:
+      updated.set(2011, 11, 11, 11, 11, 11);
+      data = createCar(5, "F1 W02", 1, 167189.00, "EUR", "2011", updated, "file://imagePath/wXX");
+      break;
+
+    default:
+      break;
+    }
+    
+    return data;
+      }
+    
+      
+      private Map<String, Object> createCar(int carId, String model, int manufacturerId, double price, String currency, String modelYear, Calendar updated, String imagePath) {
+    Map<String, Object> data = new HashMap<String, Object>();
+    
+    data.put("Id", carId);
+    data.put("Model", model);
+    data.put("ManufacturerId", manufacturerId);
+    data.put("Price", price);
+    data.put("Currency", currency);
+    data.put("ModelYear", modelYear);
+    data.put("Updated", updated);
+    data.put("ImagePath", imagePath);
+    
+    return data;
+      }
+      
+      public Map<String, Object> getManufacturer(int id) {
+    Map<String, Object> data = null;
+    Calendar date = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+    
+    switch (id) {
+    case 1:
+      Map<String, Object> addressStar = createAddress("Star Street 137", "Stuttgart", "70173", "Germany");
+      date.set(1954, 7, 4);
+      data = createManufacturer(1, "Star Powered Racing", addressStar, date);
+      break;
+      
+    case 2:
+      Map<String, Object> addressHorse = createAddress("Horse Street 1", "Maranello", "41053", "Italy");
+      date.set(1929, 11, 16);
+      data = createManufacturer(2, "Horse Powered Racing", addressHorse, date);
+      break;
+      
+    default:
+      break;
+    }
+    
+    return data;
+      }
+    
+      private Map<String, Object> createManufacturer(int id, String name, Map<String, Object> address, Calendar updated) {
+    Map<String, Object> data = new HashMap<String, Object>();
+    data.put("Id", id);
+    data.put("Name", name);
+    data.put("Address", address);
+    data.put("Updated", updated);
+    return data;
+      }
+      
+      private Map<String, Object> createAddress(String street, String city, String zipCode, String country) {
+    Map<String, Object> address = new HashMap<String, Object>();
+    address.put("Street", street);
+    address.put("City", city);
+    address.put("ZipCode", zipCode);
+    address.put("Country", country);
+    return address;
+      }
+    
+    
+      public List<Map<String, Object>> getCars() {
+    List<Map<String, Object>> cars = new ArrayList<Map<String, Object>>();
+    cars.add(getCar(1));
+    cars.add(getCar(2));
+    cars.add(getCar(3));
+    cars.add(getCar(4));
+    cars.add(getCar(5));
+    return cars;
+      }
+      
+      public List<Map<String, Object>> getManufacturers() {
+    List<Map<String, Object>> manufacturers = new ArrayList<Map<String, Object>>();
+    manufacturers.add(getManufacturer(1));
+    manufacturers.add(getManufacturer(2));
+    return manufacturers;
+      }
+    
+
+      public List<Map<String, Object>> getCarsFor(int manufacturerId) {
+    List<Map<String, Object>> cars = getCars();
+    List<Map<String, Object>> carsForManufacturer = new ArrayList<Map<String,Object>>();
+    
+    for (Map<String,Object> car: cars) {
+      if(Integer.valueOf(manufacturerId).equals(car.get("ManufacturerId"))) {
+        carsForManufacturer.add(car);
+      }
+    }
+    
+    return carsForManufacturer;
+      }
+      
+      public Map<String, Object> getManufacturerFor(int carId) {
+    Map<String, Object> car = getCar(carId);
+    if(car != null) {
+      Object manufacturerId = car.get("ManufacturerId");
+      if(manufacturerId != null) {
+        return getManufacturer((Integer) manufacturerId);
+      }
+    }
+    return null;
+      }
+    }
+
+
+
+
+
+
+- Implement `MyODataSingleProcessor.readEntity(GetEntityUriInfo uriParserResultInfo)` by overriding the corresponding method of the ODataSingleProcessor
+
+##### Sample Code
+ 
+    :::java
+      public ODataResponse readEntity(GetEntityUriInfo uriInfo, String contentType) throws ODataException {
+    
+    if (uriInfo.getNavigationSegments().size() == 0) {
+      EdmEntitySet entitySet = uriInfo.getStartEntitySet();
+
+      if (ENTITY_SET_NAME_CARS.equals(entitySet.getName())) {
+        int id = getKeyValue(uriInfo.getKeyPredicates().get(0));
+        Map<String, Object> data = dataStore.getCar(id);
+        
+        if (data != null) {
+          URI serviceRoot = getContext().getPathInfo().getServiceRoot();
+          ODataEntityProviderPropertiesBuilder propertiesBuilder = EntityProviderWriteProperties.serviceRoot(serviceRoot);
+          
+          return EntityProvider.writeEntry(contentType, entitySet, data, propertiesBuilder.build());
+        }
+      } else if (ENTITY_SET_NAME_MANUFACTURERS.equals(entitySet.getName())) {
+        int id = getKeyValue(uriInfo.getKeyPredicates().get(0));
+        Map<String, Object> data = dataStore.getManufacturer(id);
+        
+        if (data != null) {
+          URI serviceRoot = getContext().getPathInfo().getServiceRoot();
+          ODataEntityProviderPropertiesBuilder propertiesBuilder = EntityProviderWriteProperties.serviceRoot(serviceRoot);
+  
+          return EntityProvider.writeEntry(contentType, entitySet, data, propertiesBuilder.build());
+        }
+      }
+
+      throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
+
+    } else if (uriInfo.getNavigationSegments().size() == 1) {
+      //navigation first level, simplified example for illustration purposes only
+      EdmEntitySet entitySet = uriInfo.getTargetEntitySet();
+      if (ENTITY_SET_NAME_MANUFACTURERS.equals(entitySet.getName())) {
+        int carKey = getKeyValue(uriInfo.getKeyPredicates().get(0));
+        return EntityProvider.writeEntry(contentType, uriInfo.getTargetEntitySet(), dataStore.getManufacturer(carKey), EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).build());
+      }
+
+      throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
+    }
+
+    throw new ODataNotImplementedException();
+      }
+    Implement MyODataSingleProcessor.readEntitySet(GetEntitySetUriInfo uriParserResultInfo) by overriding the corresponding method of the ODataSingleProcessor 
+      public ODataResponse readEntitySet(GetEntitySetUriInfo uriInfo, String contentType) throws ODataException {
+    
+    EdmEntitySet entitySet;
+
+    if (uriInfo.getNavigationSegments().size() == 0) {
+      entitySet = uriInfo.getStartEntitySet();
+
+      if (ENTITY_SET_NAME_CARS.equals(entitySet.getName())) {
+        return EntityProvider.writeFeed(contentType, entitySet, dataStore.getCars(), EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).build());
+      } else if (ENTITY_SET_NAME_MANUFACTURERS.equals(entitySet.getName())) {
+        return EntityProvider.writeFeed(contentType, entitySet, dataStore.getManufacturers(), EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).build());
+      }
+
+      throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
+
+    } else if (uriInfo.getNavigationSegments().size() == 1) {
+      //navigation first level, simplified example for illustration purposes only
+      entitySet = uriInfo.getTargetEntitySet();
+
+      if (ENTITY_SET_NAME_CARS.equals(entitySet.getName())) {
+        int manufacturerKey = getKeyValue(uriInfo.getKeyPredicates().get(0));
+
+        List<Map<String, Object>> cars = new ArrayList<Map<String, Object>>();
+        cars.add(dataStore.getCar(manufacturerKey));
+
+        return EntityProvider.writeFeed(contentType, entitySet, cars, EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).build());
+      }
+
+      throw new ODataNotFoundException(ODataNotFoundException.ENTITY);
+    }
+
+    throw new ODataNotImplementedException();
+      }
+
+And add the small method to get the key value of a `KeyPredicate`:
+    
+      private int getKeyValue(KeyPredicate key) throws ODataException {
+    EdmProperty property = key.getProperty();
+    EdmSimpleType type = (EdmSimpleType) property.getType();
+    return type.valueOfString(key.getLiteral(), EdmLiteralKind.DEFAULT, property.getFacets(), Integer.class);
+      }
+
+
+After the implementation of the `MyODataSingleProcessor` the web application can be tested.
+
+- Build your project. Remember? `mvn clean install`
+- Deploy the web application on your local server
+- Show the Manufacturers: [http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Manufacturers](http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Manufacturers)
+- Show one Manufacturer: [http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Manufacturers(1)](http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Manufacturers(1))
+- Show the Cars: [http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Cars](http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Cars)
+- Show one Car: [http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Cars(2)](http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Cars(2))
+- Show the related Manufacturer of a Car: [http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Cars(2)/Manufacturer](http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Cars(2)/Manufacturer)
+- Show the related Cars of a Manufacturer: [http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Manufacturers(1)/Cars](http://localhost:8080/olingo.odata2.sample.cars.web/MyODataSample.svc/Manufacturers(1)/Cars)
+
+
+  [1]: /img/ODataCarsModel.JPG
\ No newline at end of file

Added: olingo/site/trunk/content/doc/odata2/tutorials/batchClientApi.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/batchClientApi.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/batchClientApi.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/batchClientApi.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,86 @@
+Title:
+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.
+
+Batch Request construction
+--------------------------
+
+**Query Request construction**
+
+A BatchQueryPart is a representation of a single retrieve request. You can use the following methods in order to fill out a request:
+
+  - method(String) 
+  - uri(String) 
+  - contentId(String) 
+  - headers(List<String>)
+
+<pre><code>
+BatchQueryPart request = BatchQueryPart.method("GET").uri("$metadata").build();
+</pre></code>
+**Note:** The valid method value is GET.
+
+**ChangeSet construction**
+A BatchChangeSetPart is a representation of a single change request. You can use the following methods in order to fill out a change request:
+
+  - method(String)
+  - uri(String)
+  - headers(List<String>)
+  - contentId(String)
+  - body(String)
+
+<pre><code>Map<String, String> changeSetHeaders = new HashMap<String, String>();
+changeSetHeaders.put("content-type", "application/json;odata=verbose");
+BatchChangeSetPart changeRequest = BatchChangeSetPart.method("PUT")
+.uri("Employees('2')/EmployeeName")
+.headers(changeSetHeaders)
+.body("{\"EmployeeName\":\"Frederic Fall MODIFIED\"}")
+.build();
+...
+</pre></code>
+**Note:** The valid method values are POST, PUT, DELETE or MERGE.
+
+The change request has to become a part of a changeSet. For that you need to create a changeSet object and to attach the change request to this object.
+
+    ...
+    BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+    changeSet.add(changeRequest);
+
+**Batch request payload construction**
+After you collected all created parts, you can call the method writeBatchRequestBody(..) provided by EntityProvider
+
+    ...
+    List<BatchPart> batchParts = new ArrayList<BatchPart>();
+    batchParts.add(request);
+    batchParts.add(changeSet);
+     
+    InputStream payload = EntityProvider.writeBatchRequest(batchParts, BOUNDARY);
+
+The second parameter BOUNDARY is necessary information for the construction of the batch request payload. It is the value of the boundary parameter, that is set in Content-Type header of the batch request.
+
+**Batch Response interpretation**
+Interpretation of the batch response payload
+You receive a list of single response by calling EntityProvider.parseBatchResponse(..)
+
+    List<BatchSingleResponse> responses = EntityProvider.parseBatchResponse(responseBody, contentType);
+    for (BatchSingleResponse response : responses) {
+          response.getStatusCode());
+          response.getStatusInfo());
+          response.getHeader(HttpHeaders.CONTENT_TYPE);
+          response.getBody();
+          response.getContentId();
+    }
+

Added: olingo/site/trunk/content/doc/odata2/tutorials/debug.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/debug.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/debug.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/debug.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,233 @@
+Title:
+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.
+
+# Debug Support and Error Handling
+
+---
+
+### OData Error Conditions
+
+OData exposes error conditions as HTTP responses with error status code (4xx and 5xx) and it is in the responsibility of a client to handle this situations. For more details see 
+[OData Error Conditions](http://www.odata.org/documentation/odata-v2-documentation/operations/#13_Error_Conditions).
+
+In OData the format for error messages is described in [OData-Atom](http://www.odata.org/developers/protocols/atom-format) and [Odata-JSON](http://www.odata.org/documentation/json-format). Apache Olingo OData2 has implemented this this format for error message.
+
+### Debug Support
+
+The OData V2 error message format is limited to the HTTP status codes and gives indicators for client errors (status code 4xx) and server errors (5xx). For development and support this is not sufficient because of more technical information is needed for doing a deep error analysis.
+
+Apache Olingo has implemented a feature to return more error information (stack traces, object states, runtime measurements …) within a response to ease the development and support use case. 
+
+For productive uses cases this feature is by default off. The following explains how to enable the feature and gives options to a service implementation to switch if on and off e.g. role based or by configuration.
+
+##### DebugCallback
+
+The debug feature can be enabled by the following callback implementation:
+
+	public class MyDebugCallback implements ODataDebugCallback {
+	  @Override
+	  public boolean isDebugEnabled() {  
+	  	boolean isDebug = …; // true|configuration|user role check
+	    return isDebug; 
+	  }
+	}
+
+##### Register DebugCallback
+
+In your service factory (`ODataServiceFactory`) implement the following method to register the callback:
+
+	:::java
+	public <T extends ODataCallback> T getCallback(final Class<? extends ODataCallback> callbackInterface) { 
+	  T callback
+	
+	  if (callbackInterface.isAssignableFrom(MyDebugCallback.class)) {
+	    callback = (T) new MyDebugCallback();
+	  } else {
+	    callback = (T) super.getCallback(callbackInterface);
+	  }
+	
+	  return callback;
+	}
+
+If this is in place then the url query option odata-debug=json will return detailed error information in json format for each request.
+
+##### Query for Debug Information
+
+** JSON Debug View**
+
+Request url: http://localhost:8080/olingo-odata2-ref-web/ReferenceScenario.svc/?odata-debug=json
+
+Response:
+
+	:::json
+	{
+	  "body": "<?xml version='1.0' encoding='utf-8'?><service xml:base=\"http://localhost:8080/olingo-odata2-ref-web/ReferenceScenario.svc/\" xmlns=\"http://www.w3.org/2007/app\" xmlns:atom=\"http://www.w3.org/2005/Atom\"><workspace><atom:title>Default</atom:title><collection href=\"Employees\"><atom:title>Employees</atom:title></collection><collection href=\"Teams\"><atom:title>Teams</atom:title></collection><collection href=\"Rooms\"><atom:title>Rooms</atom:title></collection><collection href=\"Managers\"><atom:title>Managers</atom:title></collection><collection href=\"Buildings\"><atom:title>Buildings</atom:title></collection><collection href=\"Container2.Photos\"><atom:title>Photos</atom:title></collection></workspace></service>",
+	  "request": {
+	    "method": "GET",
+	    "uri": "https://localhost:8080/olingo-odata2-ref-web/ReferenceScenario.svc/?odata-debug=json",
+	    "headers": {
+	      "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+	      "accept-encoding": "gzip, deflate",
+	      "accept-language": "de-de,de;q=0.8,en-us;q=0.5,en;q=0.3",
+	      "connection": "keep-alive",
+	      "Content-Type": null,
+	      "cookie": "JSESSIONID=C6A2403F354B61B1E645744FABCB7FB6C6BC5DA41BC841823647CF2DFF001556; BIGipServerlocalhost=3543090186.26911.0000",
+	      "host": "localhost:8080",
+	      "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0",
+	      "x-forwarded-for": "173.12.210.11"
+	    }
+	  },
+	  "response": {
+	    "status": {
+	      "code": 200,
+	      "info": "OK"
+	    },
+	    "headers": {
+	      "DataServiceVersion": "1.0",
+	      "Content-Type": "application/xml;charset=utf-8"
+	    }
+	  },
+	  "runtime": [{
+	      "class": "ODataRequestHandler",
+	      "method": "handle",
+	      "duration": 1911,
+	      "memory": 191,
+	      "children": [
+	        {
+	          "class": "UriParserImpl",
+	          "method": "parse",
+	          "duration": 66,
+	          "memory": 0,
+	          "children": {}
+	        },
+	        {
+	          "class": "Dispatcher",
+	          "method": "dispatch",
+	          "duration": 1029,
+	          "memory": 95,
+	          "children": {}
+	        }
+	      ]
+	    }]
+	}
+
+** HTML Debug View**
+
+Request url: http://localhost:8080/olingo-odata2-ref-web-incubating/ReferenceScenario.svc/?odata-debug=html
+to get a self-contained HTML document with all information that is in the JSON
+output but can be viewed conveniently in a browser.
+
+##### Custom Debug Output
+
+Starting with release 1.2, it is possible to create custom debug output.
+The complete formatting of the debug-support information can be implemented
+in a callback method.
+
+Add to the already existing `getCallback` method before the line with `else` a condition check whether the given `ODataCallback` is a `DebugWrapperCallback`.
+
+	:::java
+	} else if (callbackInterface.isAssignableFrom(ODataDebugResponseWrapperCallback.class)) {
+	  callback = (T) new DebugWrapperCallback();
+	}
+
+which then results in following method
+
+	:::java
+	public <T extends ODataCallback> T getCallback(final Class<? extends ODataCallback> callbackInterface) { 
+	  T callback
+	
+	  if (callbackInterface.isAssignableFrom(MyDebugCallback.class)) {
+	    callback = (T) new MyDebugCallback();
+	  } else if (callbackInterface.isAssignableFrom(ODataDebugResponseWrapperCallback.class)) {
+	    callback = (T) new DebugWrapperCallback();
+	  } else {
+	    callback = (T) super.getCallback(callbackInterface);
+	  }
+	
+	  return callback;
+	}
+
+
+and implement the callback class
+
+	:::java
+	private final class DebugWrapperCallback implements ODataDebugResponseWrapperCallback {
+	  @Override
+	  public ODataResponse handle(final ODataContext context, final ODataRequest request, final ODataResponse response,
+	      final UriInfo uriInfo, final Exception exception) {
+	    if ("true".equals(request.getQueryParameters().get("my-debug"))) {
+	      return DebugResponseWrapper.handle(context, request, response, uriInfo, exception);
+	    } else {
+	      return response;
+	    }
+	  }
+	}
+
+
+where `DebugResponseWrapper` is a class you have to implement which does
+the real work.
+
+**Please note** that this callback is not called if the built-in debug output
+is requested as described above.
+
+### Log and Trace Support
+
+Apache Olingo has no dependencies to any specific log and trace api (e.g. slf4j, log4j …) and with that it does not trace anything by default. This is to keep the library independent from a specific api so that it can be used on any JEE compliant platform independent from which l&t api is offered there.
+
+Anyhow log and trace is required for productive use and the following recommendations are given:
+
+##### Servlet Filter
+
+For tracing the http traffic (request url, query parameter, http headers, response code …) to and from the server it is recommended to implement a servlet filter on top of the service. This is completely independent from the Apache Olingo OData library and has no restrictions.
+
+##### Service Processor Implementation
+
+To trace OData activities (read/write activities) at the server it is recommended to do that within a custom processor implementation. 
+
+##### Error Callback
+
+Because of OData requires to handle error situations someone can hook into the handling and trace information there.  
+
+Simply implement another `ODataCallback` interface and register it within a `ODataServiceFactory`.
+
+Callback:
+
+	public class MyErrorCallback implements ODataErrorCallback {
+	  @Override
+	  public ODataResponse handleError(ODataErrorContext context) throws ODataApplicationException { 
+	    LOGGER.severe(context.getException().getClass().getName() + ":" + context.getMessage()); 
+            return EntityProvider.writeErrorDocument(context); 
+	  }
+	}
+
+Register Callback:
+
+	public <T extends ODataCallback> T getCallback(final Class<? extends ODataCallback> callbackInterface) { 
+	  T callback
+	
+	  if (callbackInterface.isAssignableFrom(MyDebugCallback.class)) {
+	    callback = (T) new MyDebugCallback();
+	  } else if (callbackInterface.isAssignableFrom(MyErrorCallback.class)) {
+	    callback = (T) new MyErrorCallback();
+	  } else {
+	    callback = (T) super.getCallback(callbackInterface);
+	  }
+	
+	  return callback;
+	}
+	

Added: olingo/site/trunk/content/doc/odata2/tutorials/delta.mdtext
URL: http://svn.apache.org/viewvc/olingo/site/trunk/content/doc/odata2/tutorials/delta.mdtext?rev=1625261&view=auto
==============================================================================
--- olingo/site/trunk/content/doc/odata2/tutorials/delta.mdtext (added)
+++ olingo/site/trunk/content/doc/odata2/tutorials/delta.mdtext Tue Sep 16 12:33:56 2014
@@ -0,0 +1,141 @@
+Title:
+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.
+
+# Delta Responses
+
+Delta responses is a feature on top of OData 2.0 for requesting changes. The feature is defined in OData 4.0 and this is a preliminary and lightweight implementation close to the OData 4.0 specification [(see here)](http://docs.oasis-open.org/odata/odata/v4.0/cos01/part1-protocol/odata-v4.0-cos01-part1-protocol.html#_Toc372793707).
+
+Because of delta responses are not defined in OData 2.0 this feature is optional.
+
+Features:
+
+* Delta Links (for Atom and Json)
+* Tombstones [RFC6721](http://tools.ietf.org/html/rfc6721) for deleted entries in Atom format
+* Deleted Entries in Json as a lightweight implementation of [Delta Responses](http://docs.oasis-open.org/odata/odata-json-format/v4.0/cos01/odata-json-format-v4.0-cos01.html#_Toc372793080) 
+
+### Use Case
+
+A client requests a (paged) feed. A server can add a delta link on the last feed page. Using the delta link returns only changed data of the feed to the client. Changed data are feed entries with changed properties or deleted entries.
+
+### Implementation
+
+A server has to implement the `TombstoneCallback` interface:
+
+    public interface TombstoneCallback extends ODataCallback {
+    ...
+    }
+
+Basically the implementation of this interface has to carry information about deleted data and the delta link which is realized as custom query option:
+
+    http://host:80/service/Rooms?!deltatoken=1234
+
+Finally the following code has to go into a `ODataSingleProcessor` implementation:
+
+    /**
+     * @see EntitySetProcessor
+     */
+    @Override
+    public ODataResponse readEntitySet(final GetEntitySetUriInfo uriInfo, final String contentType) throws ODataException {
+   
+      [...]   
+
+      Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>();
+      callbacks.put(TombstoneCallback.CALLBACK_KEY_TOMBSTONE, tombstoneCallback);
+
+      EntityProviderWriteProperties properties = EntityProviderWriteProperties.serviceRoot(new URI(BASE_URI)).callbacks(callbacks).build();
+
+      final ODataResponse response = new JsonEntityProvider().writeFeed(entitySet, roomsData, properties);
+
+      [...]   
+
+	  return response;
+	}
+	
+##### Json Response
+
+This is an example for a Json response:
+
+        [...]
+         {
+            "__metadata":{
+               "id":"http://host:80/service/Rooms('2')",
+               "uri":"http://host:80/service/Rooms('2')",
+               "type":"RefScenario.Room",
+               "etag":"W/\"2\""
+            },
+            "Id":"2",
+            "Name":null,
+            "Seats":66,
+            "Version":2,
+            "nr_Employees":{
+               "__deferred":{
+                  "uri":"http://host:80/service/Rooms('2')/nr_Employees"
+               }
+            },
+            "nr_Building":{
+               "__deferred":{
+                  "uri":"http://host:80/service/Rooms('2')/nr_Building"
+               }
+            }
+         },
+         {
+            "@odata.context":"$metadata#Rooms/$deletedEntity",
+            "id":"http://host:80/service/Rooms('3')"
+         },
+         {
+            "@odata.context":"$metadata#Rooms/$deletedEntity",
+            "id":"http://host:80/service/Rooms('4')"
+         },
+         {
+            "@odata.context":"$metadata#Rooms/$deletedEntity",
+            "id":"http://host:80/service/Rooms('5')"
+         },
+
+      ],
+      "__delta":"http://host:80/service/Rooms?!deltatoken=1234"
+      }
+    }
+    
+##### Atom Response
+
+This is an example of an Atom delta response (tombstones, RFC6721)
+
+    <feed ...>
+    
+    [..]
+    
+    <entry m:etag="W/&quot;3&quot;">
+        <id>http://host:80/service/Rooms('2')</id>
+        <title type="text">Rooms</title>
+        <updated>2014-01-14T18:11:06.681+01:00</updated>
+        <category term="RefScenario.Room" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"/>
+        <link href="Rooms('2')" rel="edit" title="Room"/>
+        <link href="Rooms('2')/nr_Employees" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees" title="nr_Employees" type="application/atom+xml;type=feed"/>
+        <link href="Rooms('2')/nr_Building" rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building" title="nr_Building" type="application/atom+xml;type=entry"/>
+        <content type="application/xml">
+        <m:properties>
+            <d:Id>2</d:Id>
+            <d:Name>Neu Schwanstein2</d:Name>
+            <d:Seats>20</d:Seats>
+            <d:Version>3</d:Version>
+        </m:properties>
+        </content>
+    </entry>
+    <at:deleted-entry ref="http://host:80/service/Rooms('2')" when="2014-01-14T18:11:06.682+01:00"/>
+    <link rel="delta" href="http://host:80/service/Rooms?!deltatoken=1234"/>
+    </feed>