You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by il...@apache.org on 2014/05/07 12:39:37 UTC

[4/5] git commit: [OLINGO-264] Atom and JSON (de)serializers now dealing with instance annotations

[OLINGO-264] Atom and JSON (de)serializers now dealing with instance annotations


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/ec9e8cab
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/ec9e8cab
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/ec9e8cab

Branch: refs/heads/master
Commit: ec9e8cabf1e9a91134cbf791c797e8545f1bce7b
Parents: 936e19c
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Tue May 6 16:44:14 2014 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Tue May 6 16:44:14 2014 +0200

----------------------------------------------------------------------
 .../apache/olingo/client/core/v4/JSONTest.java  |  1 +
 .../client/core/v3/PersonDetails_0_Person.json  |  2 +-
 .../apache/olingo/client/core/v4/annotated.json | 62 +++++++++++++
 .../apache/olingo/client/core/v4/annotated.xml  | 76 ++++++++++++++++
 .../apache/olingo/commons/api/Constants.java    |  2 +
 .../olingo/commons/api/data/Annotation.java     | 10 +--
 .../olingo/commons/api/data/Property.java       | 10 +--
 .../olingo/commons/api/data/Valuable.java       | 30 +++++++
 .../commons/core/data/AbstractAtomDealer.java   |  4 +
 .../core/data/AbstractJsonDeserializer.java     | 92 +++++++++++++------
 .../core/data/AbstractJsonSerializer.java       | 25 ++++--
 .../commons/core/data/AbstractProperty.java     |  2 +
 .../commons/core/data/AnnotationImpl.java       | 63 +++++++++++++
 .../commons/core/data/AtomDeserializer.java     | 94 +++++++++++++++-----
 .../commons/core/data/AtomSerializer.java       | 43 +++++++++
 .../core/data/JSONEntityDeserializer.java       | 55 ++++++++----
 .../commons/core/data/JSONEntitySerializer.java |  7 +-
 .../core/data/JSONEntitySetDeserializer.java    | 18 ++++
 .../core/data/JSONEntitySetSerializer.java      |  5 ++
 .../core/data/JSONPropertyDeserializer.java     | 19 ++++
 .../core/data/JSONPropertySerializer.java       | 11 ++-
 21 files changed, 531 insertions(+), 100 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java
index 65d85e2..39e7a1e 100644
--- a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java
+++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/JSONTest.java
@@ -166,6 +166,7 @@ public class JSONTest extends AbstractTest {
     entity("Advertisements_f89dee73-af9f-4cd4-b330-db93c25ff3c7", getODataPubFormat());
     entity("entityReference", getODataPubFormat());
     entity("entity.withcomplexnavigation", getODataPubFormat());
+    entity("annotated", getODataPubFormat());
   }
 
   protected void property(final String filename, final ODataFormat format) throws Exception {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json
----------------------------------------------------------------------
diff --git a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json
index 61f0208..2d66e23 100644
--- a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json
+++ b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v3/PersonDetails_0_Person.json
@@ -1 +1 @@
-{"odata.metadata":"http://services.odata.org/V3/OData/OData.svc/$metadata#PersonDetails/@Element","odata.type":"ODataDemo.PersonDetail","odata.id":"http://services.odata.org/V3/OData/OData.svc/PersonDetails(0)","odata.editLink":"PersonDetails(0)","Person@odata.navigationLinkUrl":"PersonDetails(0)/Person","Person@odata.associationLinkUrl":"PersonDetails(0)/$links/Person","Person":{"odata.type":"ODataDemo.Person","odata.id":"http://services.odata.org/V3/OData/OData.svc/Persons(0)","odata.editLink":"Persons(0)","PersonDetail@odata.navigationLinkUrl":"Persons(0)/PersonDetail","PersonDetail@odata.associationLinkUrl":"Persons(0)/$links/PersonDetail","ID":0,"Name":"Paula Wilson"},"PersonID":0,"Age@odata.type":"Edm.Byte","Age":21,"Gender":false,"Phone":"(505) 555-5939","Address":{"odata.type":"ODataDemo.Address","Street":"2817 Milton Dr.","City":"Albuquerque","State":"NM","ZipCode":"87110","Country":"USA"},"Photo@odata.mediaEditLink":"PersonDetails(0)/Photo","Photo@odata.mediaETag":"\"nCP1T
 f4Uax96eYIWjvoC/6ZflG8=\""}
+{"odata.metadata":"http://services.odata.org/V3/OData/OData.svc/$metadata#PersonDetails/@Element","odata.type":"ODataDemo.PersonDetail","odata.id":"http://services.odata.org/V3/OData/OData.svc/PersonDetails(0)","odata.editLink":"PersonDetails(0)","Person@odata.navigationLinkUrl":"PersonDetails(0)/Person","Person@odata.associationLinkUrl":"PersonDetails(0)/$links/Person","Person":{"odata.type":"ODataDemo.Person","odata.id":"http://services.odata.org/V3/OData/OData.svc/Persons(0)","odata.editLink":"Persons(0)","PersonDetail@odata.navigationLinkUrl":"Persons(0)/PersonDetail","PersonDetail@odata.associationLinkUrl":"Persons(0)/$links/PersonDetail","ID":0,"Name":"Paula Wilson"},"PersonID":0,"Age@odata.type":"Edm.Byte","Age":21,"Gender":false,"Phone":"(505) 555-5939","Address":{"odata.type":"ODataDemo.Address","Street":"2817 Milton Dr.","City":"Albuquerque","State":"NM","ZipCode":"87110","Country":"USA"},"Photo@odata.mediaEditLink":"PersonDetails(0)/Photo","Photo@odata.mediaEtag":"\"nCP1T
 f4Uax96eYIWjvoC/6ZflG8=\""}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.json
----------------------------------------------------------------------
diff --git a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.json b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.json
new file mode 100644
index 0000000..a69884b
--- /dev/null
+++ b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.json
@@ -0,0 +1,62 @@
+{
+  "@odata.context": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/$metadata#Customers/$entity",
+  "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.Customer",
+  "@odata.id": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)",
+  "@odata.editLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)",
+  "@com.contoso.display.highlight": true,
+  "@com.contoso.PersonalInfo.PhoneNumbers": ["(203)555-1718", "(203)555-1719"],
+  "PersonID": 1,
+  "FirstName": "Bob",
+  "LastName@com.contoso.display.style": {
+    "@odata.type": "#com.contoso.display.styleType",
+    "title": true,
+    "order": 1
+  },
+  "LastName": "Cat",
+  "MiddleName": null,
+  "HomeAddress": {
+    "@odata.type": "#Microsoft.Test.OData.Services.ODataWCFService.HomeAddress",
+    "Street": "1 Microsoft Way",
+    "City": "London",
+    "PostalCode": "98052",
+    "FamilyName": "Cats"
+  },
+  "Home@odata.type": "#GeographyPoint",
+  "Home": {
+    "type": "Point",
+    "coordinates": [23.1, 32.1],
+    "crs": {
+      "type": "name",
+      "properties": {
+        "name": "EPSG:4326"
+      }
+    }
+  },
+  "Numbers@odata.type": "#Collection(String)",
+  "Numbers": ["111-111-1111", "0-12", "3-10", "bca", "ayz"],
+  "Emails@odata.type": "#Collection(String)",
+  "Emails": ["abc@abc.com"],
+  "City": "London",
+  "Birthday@odata.type": "#DateTimeOffset",
+  "Birthday": "1957-04-03T00:00:00Z",
+  "TimeBetweenLastTwoOrders@odata.type": "#Duration",
+  "TimeBetweenLastTwoOrders": "PT0.0000001S",
+  "Parent@odata.associationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Parent/$ref",
+  "Parent@odata.navigationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Parent",
+  "Orders@com.contoso.display.style": {
+    "@odata.type": "#com.contoso.display.styleType",
+    "order": 2
+  },
+  "Orders@odata.associationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Orders/$ref",
+  "Orders@odata.navigationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Orders",
+  "Company@odata.associationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Company/$ref",
+  "Company@odata.navigationLink": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Company",
+  "#Microsoft.Test.OData.Services.ODataWCFService.ResetAddress": {
+    "title": "Microsoft.Test.OData.Services.ODataWCFService.ResetAddress",
+    "target": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Microsoft.Test.OData.Services.ODataWCFService.ResetAddress"
+  },
+  "#Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress": {
+    "title": "Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress",
+    "target": "http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Microsoft.Test.OData.Services.ODataWCFService.GetHomeAddress"
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.xml
----------------------------------------------------------------------
diff --git a/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.xml b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.xml
new file mode 100644
index 0000000..851f960
--- /dev/null
+++ b/lib/client-core/src/test/resources/org/apache/olingo/client/core/v4/annotated.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+    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.
+
+-->
+<entry xml:base="http://odatae2etest.azurewebsites.net/javatest/DefaultService/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:context="http://odatae2etest.azurewebsites.net/javatest/DefaultService/$metadata#Products/$entity">
+  <id>http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)</id>
+  <category term="#Microsoft.Test.OData.Services.ODataWCFService.Customer" scheme="http://docs.oasis-open.org/odata/ns/scheme"/>
+  <link rel="edit" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)"/>
+  <link rel="http://docs.oasis-open.org/odata/ns/related/Parent" type="application/atom+xml;type=entry" title="Parent" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Parent"/>
+  <link rel="http://docs.oasis-open.org/odata/ns/related/Orders" type="application/atom+xml;type=feed" title="Orders" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Orders">
+    <m:annotation term="com.contoso.display.style" m:type="#com.contoso.display.styleType">
+      <d:order m:type="Int32">2</d:order>
+    </m:annotation>
+  </link>
+  <link rel="http://docs.oasis-open.org/odata/ns/related/Company" type="application/atom+xml;type=entry" title="Company" href="http://odatae2etest.azurewebsites.net/javatest/DefaultService/Customers(PersonID=1)/Company"/>
+  <title/>
+  <updated>2014-03-31T09:35:14Z</updated>
+  <author>
+    <name/>
+  </author>
+  <content type="application/xml">
+    <m:properties>
+      <d:PersonID m:type="Int32">1</d:PersonID>
+      <d:FirstName>Bob</d:FirstName>
+      <d:LastName>Cat</d:LastName>
+      <m:annotation term="com.contoso.display.style" target="LastName" m:type="#com.contoso.display.styleType">
+        <d:title m:type="Boolean">true</d:title>
+        <d:order m:type="Int32">1</d:order>
+      </m:annotation>      
+      <d:MiddleName m:null="true"/>
+      <d:HomeAddress m:type="#Microsoft.Test.OData.Services.ODataWCFService.HomeAddress">
+        <d:Street>1 Microsoft Way</d:Street>
+        <d:City>London</d:City>
+        <d:PostalCode>98052</d:PostalCode>
+        <d:FamilyName>Cats</d:FamilyName>
+      </d:HomeAddress>
+      <d:Home m:type="GeographyPoint">
+        <gml:Point gml:srsName="http://www.opengis.net/def/crs/EPSG/0/4326">
+          <gml:pos>32.1 23.1</gml:pos>
+        </gml:Point>
+      </d:Home>
+      <d:Numbers m:type="#Collection(String)">
+        <m:element>111-111-1111</m:element>
+      </d:Numbers>
+      <d:Emails m:type="#Collection(String)">
+        <m:element>abc@abc.com</m:element>
+      </d:Emails>
+      <d:City>London</d:City>
+      <d:Birthday m:type="DateTimeOffset">1957-04-03T00:00:00Z</d:Birthday>
+      <d:TimeBetweenLastTwoOrders m:type="Duration">PT0.0000001S</d:TimeBetweenLastTwoOrders>
+    </m:properties>
+  </content>
+  <m:annotation term="com.contoso.display.highlight" m:type="Boolean">true</m:annotation>
+  <m:annotation term="com.contoso.PersonalInfo.PhoneNumbers" m:type="#Collection(String)">
+    <m:element>(203)555-1718</m:element>
+    <m:element>(203)555-1719</m:element>
+  </m:annotation>  
+</entry>
+

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java
----------------------------------------------------------------------
diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java
index 17a65eb..c0612af 100644
--- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java
+++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/Constants.java
@@ -152,6 +152,8 @@ public interface Constants {
 
   public static final String ATTR_RELATIONSHIP = "relationship";
 
+  public static final String ANNOTATION = "annotation";
+
   // JSON stuff
   public final static String JSON_METADATA = "odata.metadata";
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java
----------------------------------------------------------------------
diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java
index 96db646..33f393f 100644
--- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java
+++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Annotation.java
@@ -21,17 +21,9 @@ package org.apache.olingo.commons.api.data;
 /**
  * Represents an instance annotation.
  */
-public interface Annotation {
+public interface Annotation extends Valuable {
 
   String getTerm();
 
   void setTerm(String term);
-
-  String getType();
-
-  void setType(String type);
-
-  Value getValue();
-
-  void setValue(Value value);
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java
----------------------------------------------------------------------
diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java
index 3f1ca03..36c6b74 100644
--- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java
+++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Property.java
@@ -18,17 +18,9 @@
  */
 package org.apache.olingo.commons.api.data;
 
-public interface Property extends Annotatable {
+public interface Property extends Valuable, Annotatable {
 
   String getName();
 
   void setName(String name);
-
-  String getType();
-
-  void setType(String type);
-
-  Value getValue();
-
-  void setValue(Value value);
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Valuable.java
----------------------------------------------------------------------
diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Valuable.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Valuable.java
new file mode 100644
index 0000000..11a4bf0
--- /dev/null
+++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/Valuable.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+package org.apache.olingo.commons.api.data;
+
+public interface Valuable {
+
+  String getType();
+
+  void setType(String type);
+
+  Value getValue();
+
+  void setValue(Value value);
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java
index 2f3ebb3..9c3fec2 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractAtomDealer.java
@@ -54,6 +54,8 @@ abstract class AbstractAtomDealer {
 
   protected final QName nextQName;
 
+  protected final QName annotationQName;
+
   protected final QName contextQName;
 
   protected final QName entryRefQName;
@@ -98,6 +100,8 @@ abstract class AbstractAtomDealer {
             new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_DATASERVICES), Constants.ELEM_URI);
     this.nextQName =
             new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_DATASERVICES), Constants.NEXT_LINK_REL);
+    this.annotationQName =
+            new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.ANNOTATION);
     this.contextQName =
             new QName(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.CONTEXT);
     this.entryRefQName =

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java
index 82f8ae0..f94ff68 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonDeserializer.java
@@ -27,14 +27,21 @@ import java.io.IOException;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotatable;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.CollectionValue;
 import org.apache.olingo.commons.api.data.ComplexValue;
-import org.apache.olingo.commons.api.data.ResWrap;
 import org.apache.olingo.commons.api.data.Linked;
+import org.apache.olingo.commons.api.data.Property;
+import org.apache.olingo.commons.api.data.ResWrap;
+import org.apache.olingo.commons.api.data.Valuable;
 import org.apache.olingo.commons.api.data.Value;
 import org.apache.olingo.commons.api.domain.ODataLinkType;
 import org.apache.olingo.commons.api.domain.ODataPropertyType;
@@ -44,6 +51,8 @@ import org.apache.olingo.commons.core.edm.EdmTypeInfo;
 
 abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResWrap<T>> {
 
+  protected final Pattern CUSTOM_ANNOTATION = Pattern.compile("(.+)@(.+)\\.(.+)");
+
   private JSONGeoValueDeserializer geoDeserializer;
 
   private JSONGeoValueDeserializer getGeoDeserializer() {
@@ -203,6 +212,48 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW
     return new SimpleEntry<ODataPropertyType, EdmTypeInfo>(type, typeInfo);
   }
 
+  protected void populate(final Annotatable annotatable, final List<Property> properties,
+          final ObjectNode tree, final ObjectCodec codec) throws IOException {
+
+    String type = null;
+    Annotation annotation = null;
+    for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) {
+      final Map.Entry<String, JsonNode> field = itor.next();
+      final Matcher customAnnotation = CUSTOM_ANNOTATION.matcher(field.getKey());
+
+      if (field.getKey().charAt(0) == '@') {
+        final Annotation entityAnnot = new AnnotationImpl();
+        entityAnnot.setTerm(field.getKey().substring(1));
+
+        value(entityAnnot, field.getValue(), codec);
+        if (annotatable != null) {
+          annotatable.getAnnotations().add(entityAnnot);
+        }
+      } else if (type == null && field.getKey().endsWith(getJSONAnnotation(jsonType))) {
+        type = field.getValue().asText();
+      } else if (annotation == null && customAnnotation.matches() && !"odata".equals(customAnnotation.group(2))) {
+        annotation = new AnnotationImpl();
+        annotation.setTerm(customAnnotation.group(2) + "." + customAnnotation.group(3));
+        value(annotation, field.getValue(), codec);
+      } else {
+        final JSONPropertyImpl property = new JSONPropertyImpl();
+        property.setName(field.getKey());
+        property.setType(type == null
+                ? null
+                : new EdmTypeInfo.Builder().setTypeExpression(type).build().internal());
+        type = null;
+
+        value(property, field.getValue(), codec);
+        properties.add(property);
+
+        if (annotation != null) {
+          property.getAnnotations().add(annotation);
+          annotation = null;
+        }
+      }
+    }
+  }
+
   private Value fromPrimitive(final JsonNode node, final EdmTypeInfo typeInfo) {
     final Value value;
 
@@ -234,22 +285,7 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW
       node.remove(toRemove);
     }
 
-    String type = null;
-    for (final Iterator<Map.Entry<String, JsonNode>> itor = node.fields(); itor.hasNext();) {
-      final Map.Entry<String, JsonNode> field = itor.next();
-
-      if (type == null && field.getKey().endsWith(getJSONAnnotation(jsonType))) {
-        type = field.getValue().asText();
-      } else {
-        final JSONPropertyImpl property = new JSONPropertyImpl();
-        property.setName(field.getKey());
-        property.setType(type);
-        type = null;
-
-        value(property, field.getValue(), codec);
-        value.get().add(property);
-      }
-    }
+    populate(value.asLinkedComplex(), value.get(), node, codec);
 
     return value;
   }
@@ -283,12 +319,12 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW
     return value;
   }
 
-  protected void value(final JSONPropertyImpl property, final JsonNode node, final ObjectCodec codec)
+  protected void value(final Valuable valuable, final JsonNode node, final ObjectCodec codec)
           throws IOException {
 
-    EdmTypeInfo typeInfo = StringUtils.isBlank(property.getType())
+    EdmTypeInfo typeInfo = StringUtils.isBlank(valuable.getType())
             ? null
-            : new EdmTypeInfo.Builder().setTypeExpression(property.getType()).build();
+            : new EdmTypeInfo.Builder().setTypeExpression(valuable.getType()).build();
 
     final Map.Entry<ODataPropertyType, EdmTypeInfo> guessed = guessPropertyType(node);
     if (typeInfo == null) {
@@ -307,31 +343,31 @@ abstract class AbstractJsonDeserializer<T> extends ODataJacksonDeserializer<ResW
 
     switch (propType) {
       case COLLECTION:
-        property.setValue(fromCollection(node.elements(), typeInfo, codec));
+        valuable.setValue(fromCollection(node.elements(), typeInfo, codec));
         break;
 
       case COMPLEX:
         if (node.has(jsonType)) {
-          property.setType(node.get(jsonType).asText());
+          valuable.setType(node.get(jsonType).asText());
           ((ObjectNode) node).remove(jsonType);
         }
-        property.setValue(fromComplex((ObjectNode) node, codec));
+        valuable.setValue(fromComplex((ObjectNode) node, codec));
         break;
 
       case ENUM:
-        property.setValue(new EnumValueImpl(node.asText()));
+        valuable.setValue(new EnumValueImpl(node.asText()));
         break;
 
       case PRIMITIVE:
-        if (property.getType() == null && typeInfo != null) {
-          property.setType(typeInfo.getFullQualifiedName().toString());
+        if (valuable.getType() == null && typeInfo != null) {
+          valuable.setType(typeInfo.getFullQualifiedName().toString());
         }
-        property.setValue(fromPrimitive(node, typeInfo));
+        valuable.setValue(fromPrimitive(node, typeInfo));
         break;
 
       case EMPTY:
       default:
-        property.setValue(new PrimitiveValueImpl(StringUtils.EMPTY));
+        valuable.setValue(new PrimitiveValueImpl(StringUtils.EMPTY));
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java
index 9efc67c..942516c 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractJsonSerializer.java
@@ -29,12 +29,15 @@ import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.math.NumberUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotatable;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.CollectionValue;
 import org.apache.olingo.commons.api.data.Entity;
 import org.apache.olingo.commons.api.data.Link;
 import org.apache.olingo.commons.api.data.Linked;
 import org.apache.olingo.commons.api.data.PrimitiveValue;
 import org.apache.olingo.commons.api.data.Property;
+import org.apache.olingo.commons.api.data.Valuable;
 import org.apache.olingo.commons.api.data.Value;
 import org.apache.olingo.commons.api.domain.ODataLinkType;
 import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
@@ -62,6 +65,10 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> {
   protected void clientLinks(final Linked linked, final JsonGenerator jgen) throws IOException {
     final Map<String, List<String>> entitySetLinks = new HashMap<String, List<String>>();
     for (Link link : linked.getNavigationLinks()) {
+      for (Annotation annotation : link.getAnnotations()) {
+        valuable(jgen, annotation, link.getTitle() + "@" + annotation.getTerm());
+      }
+
       ODataLinkType type = null;
       try {
         type = ODataLinkType.fromString(version, link.getRel(), link.getType());
@@ -185,7 +192,7 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> {
     } else if (value.isComplex()) {
       jgen.writeStartObject();
       for (Property property : value.asComplex().get()) {
-        property(jgen, property, property.getName());
+        valuable(jgen, property, property.getName());
       }
       if (value.isLinkedComplex()) {
         links(value.asLinkedComplex(), jgen);
@@ -194,12 +201,10 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> {
     }
   }
 
-  protected void property(final JsonGenerator jgen, final Property property, final String name) throws IOException {
+  protected void valuable(final JsonGenerator jgen, final Valuable valuable, final String name) throws IOException {
     if (serverMode && !Constants.VALUE.equals(name)) {
-      String type = property.getType();
-      if (StringUtils.isBlank(type)
-              && property.getValue().isPrimitive() || property.getValue().isNull()) {
-
+      String type = valuable.getType();
+      if (StringUtils.isBlank(type) && valuable.getValue().isPrimitive() || valuable.getValue().isNull()) {
         type = EdmPrimitiveTypeKind.String.getFullQualifiedName().toString();
       }
       if (StringUtils.isNotBlank(type)) {
@@ -209,7 +214,13 @@ abstract class AbstractJsonSerializer<T> extends ODataJacksonSerializer<T> {
       }
     }
 
+    if (valuable instanceof Annotatable) {
+      for (Annotation annotation : ((Annotatable) valuable).getAnnotations()) {
+        valuable(jgen, annotation, name + "@" + annotation.getTerm());
+      }
+    }
+
     jgen.writeFieldName(name);
-    value(jgen, property.getType(), property.getValue());
+    value(jgen, valuable.getType(), valuable.getValue());
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java
index 3f8da74..2363c0b 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AbstractProperty.java
@@ -23,6 +23,8 @@ import org.apache.olingo.commons.api.data.Value;
 
 public abstract class AbstractProperty extends AbstractAnnotatedObject implements Property {
 
+  private static final long serialVersionUID = -7175704800169997060L;
+
   private String name;
 
   private String type;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AnnotationImpl.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AnnotationImpl.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AnnotationImpl.java
new file mode 100644
index 0000000..e6a277e
--- /dev/null
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AnnotationImpl.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+package org.apache.olingo.commons.core.data;
+
+import org.apache.olingo.commons.api.data.Annotation;
+import org.apache.olingo.commons.api.data.Value;
+
+public class AnnotationImpl extends AbstractAnnotatedObject implements Annotation {
+
+  private static final long serialVersionUID = -2532246000091187020L;
+
+  private String term;
+
+  private String type;
+
+  private Value value;
+
+  @Override
+  public String getTerm() {
+    return term;
+  }
+
+  @Override
+  public void setTerm(final String term) {
+    this.term = term;
+  }
+
+  @Override
+  public String getType() {
+    return type;
+  }
+
+  @Override
+  public void setType(final String type) {
+    this.type = type;
+  }
+
+  @Override
+  public Value getValue() {
+    return value;
+  }
+
+  @Override
+  public void setValue(final Value value) {
+    this.value = value;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java
index a5171ba..27379da 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomDeserializer.java
@@ -23,6 +23,10 @@ import org.apache.olingo.commons.core.data.v4.AtomDeltaImpl;
 import java.io.InputStream;
 import java.net.URI;
 import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import javax.xml.namespace.QName;
 import javax.xml.stream.XMLEventReader;
 import javax.xml.stream.XMLInputFactory;
@@ -32,10 +36,13 @@ import javax.xml.stream.events.StartElement;
 import javax.xml.stream.events.XMLEvent;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.CollectionValue;
 import org.apache.olingo.commons.api.data.DeletedEntity.Reason;
 import org.apache.olingo.commons.api.data.EntitySet;
+import org.apache.olingo.commons.api.data.Property;
 import org.apache.olingo.commons.api.data.ResWrap;
+import org.apache.olingo.commons.api.data.Valuable;
 import org.apache.olingo.commons.api.data.Value;
 import org.apache.olingo.commons.api.domain.ODataOperation;
 import org.apache.olingo.commons.api.domain.ODataPropertyType;
@@ -242,6 +249,14 @@ public class AtomDeserializer extends AbstractAtomDealer {
       property.setName(start.getName().getLocalPart());
     }
 
+    valuable(property, reader, start);
+
+    return property;
+  }
+
+  private void valuable(final Valuable valuable, final XMLEventReader reader, final StartElement start)
+          throws XMLStreamException {
+
     final Attribute nullAttr = start.getAttributeByName(this.nullQName);
 
     Value value;
@@ -254,7 +269,7 @@ public class AtomDeserializer extends AbstractAtomDealer {
               : new EdmTypeInfo.Builder().setTypeExpression(typeAttrValue).build();
 
       if (typeInfo != null) {
-        property.setType(typeInfo.internal());
+        valuable.setType(typeInfo.internal());
       }
 
       final ODataPropertyType propType = typeInfo == null
@@ -277,7 +292,7 @@ public class AtomDeserializer extends AbstractAtomDealer {
         case PRIMITIVE:
           // No type specified? Defaults to Edm.String          
           if (typeInfo == null) {
-            property.setType(EdmPrimitiveTypeKind.String.getFullQualifiedName().toString());
+            valuable.setType(EdmPrimitiveTypeKind.String.getFullQualifiedName().toString());
           }
           value = fromPrimitive(reader, start, typeInfo);
           break;
@@ -290,9 +305,7 @@ public class AtomDeserializer extends AbstractAtomDealer {
       value = new NullValueImpl();
     }
 
-    property.setValue(value);
-
-    return property;
+    valuable.setValue(value);
   }
 
   private ResWrap<AtomPropertyImpl> property(final InputStream input) throws XMLStreamException {
@@ -344,23 +357,27 @@ public class AtomDeserializer extends AbstractAtomDealer {
     while (reader.hasNext() && !foundEndElement) {
       final XMLEvent event = reader.nextEvent();
 
-      if (event.isStartElement() && inlineQName.equals(event.asStartElement().getName())) {
-        StartElement inline = null;
-        while (reader.hasNext() && inline == null) {
-          final XMLEvent innerEvent = reader.peek();
-          if (innerEvent.isCharacters() && innerEvent.asCharacters().isWhiteSpace()) {
-            reader.nextEvent();
-          } else if (innerEvent.isStartElement()) {
-            inline = innerEvent.asStartElement();
-          }
-        }
-        if (inline != null) {
-          if (Constants.QNAME_ATOM_ELEM_ENTRY.equals(inline.getName())) {
-            link.setInlineEntity(entity(reader, inline));
+      if (event.isStartElement()) {
+        if (inlineQName.equals(event.asStartElement().getName())) {
+          StartElement inline = null;
+          while (reader.hasNext() && inline == null) {
+            final XMLEvent innerEvent = reader.peek();
+            if (innerEvent.isCharacters() && innerEvent.asCharacters().isWhiteSpace()) {
+              reader.nextEvent();
+            } else if (innerEvent.isStartElement()) {
+              inline = innerEvent.asStartElement();
+            }
           }
-          if (Constants.QNAME_ATOM_ELEM_FEED.equals(inline.getName())) {
-            link.setInlineEntitySet(entitySet(reader, inline));
+          if (inline != null) {
+            if (Constants.QNAME_ATOM_ELEM_ENTRY.equals(inline.getName())) {
+              link.setInlineEntity(entity(reader, inline));
+            }
+            if (Constants.QNAME_ATOM_ELEM_FEED.equals(inline.getName())) {
+              link.setInlineEntitySet(entitySet(reader, inline));
+            }
           }
+        } else if (annotationQName.equals(event.asStartElement().getName())) {
+          link.getAnnotations().add(annotation(reader, event.asStartElement()));
         }
       }
 
@@ -502,18 +519,47 @@ public class AtomDeserializer extends AbstractAtomDealer {
 
   private void properties(final XMLEventReader reader, final StartElement start, final AtomEntityImpl entity)
           throws XMLStreamException {
+
+    final Map<String, List<Annotation>> annotations = new HashMap<String, List<Annotation>>();
+
     boolean foundEndProperties = false;
     while (reader.hasNext() && !foundEndProperties) {
       final XMLEvent event = reader.nextEvent();
 
       if (event.isStartElement()) {
-        entity.getProperties().add(property(reader, event.asStartElement()));
+        if (annotationQName.equals(event.asStartElement().getName())) {
+          final String target = event.asStartElement().
+                  getAttributeByName(QName.valueOf(Constants.ATTR_TARGET)).getValue();
+          if (!annotations.containsKey(target)) {
+            annotations.put(target, new ArrayList<Annotation>());
+          }
+          annotations.get(target).add(annotation(reader, event.asStartElement()));
+        } else {
+          entity.getProperties().add(property(reader, event.asStartElement()));
+        }
       }
 
       if (event.isEndElement() && start.getName().equals(event.asEndElement().getName())) {
         foundEndProperties = true;
       }
     }
+
+    for (Property property : entity.getProperties()) {
+      if (annotations.containsKey(property.getName())) {
+        property.getAnnotations().addAll(annotations.get(property.getName()));
+      }
+    }
+  }
+
+  private Annotation annotation(final XMLEventReader reader, final StartElement start)
+          throws XMLStreamException {
+
+    final Annotation annotation = new AnnotationImpl();
+
+    annotation.setTerm(start.getAttributeByName(QName.valueOf(Constants.ATOM_ATTR_TERM)).getValue());
+    valuable(annotation, reader, start);
+
+    return annotation;
   }
 
   private AtomEntityImpl entityRef(final StartElement start) throws XMLStreamException {
@@ -636,6 +682,8 @@ public class AtomDeserializer extends AbstractAtomDealer {
             }
           } else if (propertiesQName.equals(event.asStartElement().getName())) {
             properties(reader, event.asStartElement(), entity);
+          } else if (annotationQName.equals(event.asStartElement().getName())) {
+            entity.getAnnotations().add(annotation(reader, event.asStartElement()));
           }
         }
 
@@ -643,8 +691,6 @@ public class AtomDeserializer extends AbstractAtomDealer {
           foundEndEntry = true;
         }
       }
-
-      return entity;
     } else {
       entity = null;
     }
@@ -719,6 +765,8 @@ public class AtomDeserializer extends AbstractAtomDealer {
           entitySet.getEntities().add(entity(reader, event.asStartElement()));
         } else if (entryRefQName.equals(event.asStartElement().getName())) {
           entitySet.getEntities().add(entityRef(event.asStartElement()));
+        } else if (annotationQName.equals(event.asStartElement().getName())) {
+          entitySet.getAnnotations().add(annotation(reader, event.asStartElement()));
         }
       }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java
index 0fdab42..0f9750a 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/AtomSerializer.java
@@ -28,6 +28,7 @@ import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.CollectionValue;
 import org.apache.olingo.commons.api.data.ResWrap;
 import org.apache.olingo.commons.api.data.Entity;
@@ -124,6 +125,10 @@ public class AtomSerializer extends AbstractAtomDealer {
     }
 
     writer.writeEndElement();
+
+    for (Annotation annotation : property.getAnnotations()) {
+      annotation(writer, annotation, property.getName());
+    }
   }
 
   private void property(final XMLStreamWriter writer, final Property property) throws XMLStreamException {
@@ -185,6 +190,10 @@ public class AtomSerializer extends AbstractAtomDealer {
         writer.writeEndElement();
       }
 
+      for (Annotation annotation : link.getAnnotations()) {
+        annotation(writer, annotation, null);
+      }
+
       writer.writeEndElement();
     }
   }
@@ -211,6 +220,36 @@ public class AtomSerializer extends AbstractAtomDealer {
     }
   }
 
+  private void annotation(final XMLStreamWriter writer, final Annotation annotation, final String target)
+          throws XMLStreamException {
+
+    writer.writeStartElement(Constants.PREFIX_METADATA, Constants.ANNOTATION,
+            version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA));
+
+    writer.writeAttribute(Constants.ATOM_ATTR_TERM, annotation.getTerm());
+
+    if (target != null) {
+      writer.writeAttribute(Constants.ATTR_TARGET, target);
+    }
+
+    if (StringUtils.isNotBlank(annotation.getType())) {
+      final EdmTypeInfo typeInfo = new EdmTypeInfo.Builder().setTypeExpression(annotation.getType()).build();
+      if (!EdmPrimitiveTypeKind.String.getFullQualifiedName().toString().equals(typeInfo.internal())) {
+        writer.writeAttribute(Constants.PREFIX_METADATA, version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA),
+                Constants.ATTR_TYPE, typeInfo.external(version));
+      }
+    }
+
+    if (annotation.getValue().isNull()) {
+      writer.writeAttribute(Constants.PREFIX_METADATA, version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA),
+              Constants.ATTR_NULL, Boolean.TRUE.toString());
+    } else {
+      value(writer, annotation.getValue());
+    }
+
+    writer.writeEndElement();
+  }
+
   private void entity(final XMLStreamWriter writer, final Entity entity) throws XMLStreamException {
     if (entity.getBaseURI() != null) {
       writer.writeAttribute(XMLConstants.XML_NS_URI, Constants.ATTR_XML_BASE, entity.getBaseURI().toASCIIString());
@@ -284,6 +323,10 @@ public class AtomSerializer extends AbstractAtomDealer {
       writer.writeEndElement();
     }
     writer.writeEndElement();
+
+    for (Annotation annotation : entity.getAnnotations()) {
+      annotation(writer, annotation, null);
+    }
   }
 
   private void entityRef(final XMLStreamWriter writer, final Entity entity) throws XMLStreamException {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java
index 4fd7ace..567fadb 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntityDeserializer.java
@@ -26,12 +26,17 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.IOException;
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.ResWrap;
 import org.apache.olingo.commons.api.data.Link;
 import org.apache.olingo.commons.api.domain.ODataLinkType;
@@ -106,7 +111,6 @@ public class JSONEntityDeserializer extends AbstractJsonDeserializer<JSONEntityI
 
     if (tree.hasNonNull(jsonEditLink)) {
       final LinkImpl link = new LinkImpl();
-      // Server mode
       if (serverMode) {
         link.setRel(Constants.EDIT_LINK_REL);
       }
@@ -134,8 +138,11 @@ public class JSONEntityDeserializer extends AbstractJsonDeserializer<JSONEntityI
     }
 
     final Set<String> toRemove = new HashSet<String>();
+
+    final Map<String, List<Annotation>> annotations = new HashMap<String, List<Annotation>>();
     for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) {
       final Map.Entry<String, JsonNode> field = itor.next();
+      final Matcher customAnnotation = CUSTOM_ANNOTATION.matcher(field.getKey());
 
       links(field, entity, toRemove, tree, parser.getCodec());
       if (field.getKey().endsWith(getJSONAnnotation(jsonMediaEditLink))) {
@@ -172,29 +179,39 @@ public class JSONEntityDeserializer extends AbstractJsonDeserializer<JSONEntityI
         entity.getOperations().add(operation);
 
         toRemove.add(field.getKey());
+      } else if (customAnnotation.matches() && !"odata".equals(customAnnotation.group(2))) {
+        final Annotation annotation = new AnnotationImpl();
+        annotation.setTerm(customAnnotation.group(2) + "." + customAnnotation.group(3));
+        value(annotation, field.getValue(), parser.getCodec());
+
+        if (!annotations.containsKey(customAnnotation.group(1))) {
+          annotations.put(customAnnotation.group(1), new ArrayList<Annotation>());
+        }
+        annotations.get(customAnnotation.group(1)).add(annotation);
       }
     }
-    tree.remove(toRemove);
 
-    String type = null;
-    for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) {
-      final Map.Entry<String, JsonNode> field = itor.next();
-
-      if (type == null && field.getKey().endsWith(getJSONAnnotation(jsonType))) {
-        type = field.getValue().asText();
-      } else {
-        final JSONPropertyImpl property = new JSONPropertyImpl();
-        property.setName(field.getKey());
-        property.setType(type == null
-                ? null
-                : new EdmTypeInfo.Builder().setTypeExpression(type).build().internal());
-        type = null;
-
-        value(property, field.getValue(), parser.getCodec());
-        entity.getProperties().add(property);
+    for (Link link : entity.getNavigationLinks()) {
+      if (annotations.containsKey(link.getTitle())) {
+        link.getAnnotations().addAll(annotations.get(link.getTitle()));
+        for (Annotation annotation : annotations.get(link.getTitle())) {
+          toRemove.add(link.getTitle() + "@" + annotation.getTerm());
+        }
       }
     }
-    
+    for (Link link : entity.getMediaEditLinks()) {
+      if (annotations.containsKey(link.getTitle())) {
+        link.getAnnotations().addAll(annotations.get(link.getTitle()));
+        for (Annotation annotation : annotations.get(link.getTitle())) {
+          toRemove.add(link.getTitle() + "@" + annotation.getTerm());
+        }
+      }
+    }
+
+    tree.remove(toRemove);
+
+    populate(entity, entity.getProperties(), tree, parser.getCodec());
+
     return new ResWrap<JSONEntityImpl>(contextURL, metadataETag, entity);
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java
index 0439783..cb0280d 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySerializer.java
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.net.URI;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.ResWrap;
 import org.apache.olingo.commons.api.data.Entity;
 import org.apache.olingo.commons.api.data.Link;
@@ -78,8 +79,12 @@ public class JSONEntitySerializer extends AbstractJsonSerializer<JSONEntityImpl>
       jgen.writeStringField(version.getJSONMap().get(ODataServiceVersion.JSON_ID), entity.getId());
     }
 
+    for (Annotation annotation : entity.getAnnotations()) {
+      valuable(jgen, annotation, "@" + annotation.getTerm());
+    }
+
     for (Property property : entity.getProperties()) {
-      property(jgen, property, property.getName());
+      valuable(jgen, property, property.getName());
     }
 
     if (serverMode && entity.getEditLink() != null && StringUtils.isNotBlank(entity.getEditLink().getHref())) {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java
index df5d40a..241dcbc 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetDeserializer.java
@@ -27,8 +27,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.IOException;
 import java.net.URI;
 import java.util.Iterator;
+import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.ResWrap;
 
 /**
@@ -74,12 +76,15 @@ public class JSONEntitySetDeserializer extends AbstractJsonDeserializer<JSONEnti
 
     if (tree.hasNonNull(jsonCount)) {
       entitySet.setCount(tree.get(jsonCount).asInt());
+      tree.remove(jsonCount);
     }
     if (tree.hasNonNull(jsonNextLink)) {
       entitySet.setNext(URI.create(tree.get(jsonNextLink).textValue()));
+      tree.remove(jsonNextLink);
     }
     if (tree.hasNonNull(jsonDeltaLink)) {
       entitySet.setDeltaLink(URI.create(tree.get(jsonDeltaLink).textValue()));
+      tree.remove(jsonDeltaLink);
     }
 
     if (tree.hasNonNull(Constants.VALUE)) {
@@ -89,6 +94,19 @@ public class JSONEntitySetDeserializer extends AbstractJsonDeserializer<JSONEnti
                         new TypeReference<JSONEntityImpl>() {
                         }).getPayload());
       }
+      tree.remove(Constants.VALUE);
+    }
+
+    // any remaining entry is supposed to be an annotation or is ignored
+    for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) {
+      final Map.Entry<String, JsonNode> field = itor.next();
+      if (field.getKey().charAt(0) == '@') {
+        final Annotation annotation = new AnnotationImpl();
+        annotation.setTerm(field.getKey().substring(1));
+
+        value(annotation, field.getValue(), parser.getCodec());
+        entitySet.getAnnotations().add(annotation);
+      }
     }
 
     return new ResWrap<JSONEntitySetImpl>(contextURL, metadataETag, entitySet);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java
index f3195ab..206e385 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONEntitySetSerializer.java
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.net.URI;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.ResWrap;
 import org.apache.olingo.commons.api.data.Entity;
 import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
@@ -78,6 +79,10 @@ public class JSONEntitySetSerializer extends AbstractJsonSerializer<JSONEntitySe
       }
     }
 
+    for (Annotation annotation : entitySet.getAnnotations()) {
+      valuable(jgen, annotation, "@" + annotation.getTerm());
+    }
+
     jgen.writeArrayFieldStart(Constants.VALUE);
     for (Entity entity : entitySet.getEntities()) {
       jgen.writeObject(entity);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java
index 74ae1d4..ea2a9df 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertyDeserializer.java
@@ -21,11 +21,15 @@ package org.apache.olingo.commons.core.data;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.IOException;
 import java.net.URI;
+import java.util.Iterator;
+import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.ResWrap;
 import org.apache.olingo.commons.core.edm.EdmTypeInfo;
 
@@ -68,14 +72,29 @@ public class JSONPropertyDeserializer extends AbstractJsonDeserializer<JSONPrope
 
     if (tree.has(jsonType)) {
       property.setType(new EdmTypeInfo.Builder().setTypeExpression(tree.get(jsonType).textValue()).build().internal());
+      tree.remove(jsonType);
     }
 
     if (tree.has(Constants.JSON_NULL) && tree.get(Constants.JSON_NULL).asBoolean()) {
       property.setValue(new NullValueImpl());
+      tree.remove(Constants.JSON_NULL);
     }
 
     if (property.getValue() == null) {
       value(property, tree.has(Constants.VALUE) ? tree.get(Constants.VALUE) : tree, parser.getCodec());
+      tree.remove(Constants.VALUE);
+    }
+
+    // any remaining entry is supposed to be an annotation or is ignored
+    for (final Iterator<Map.Entry<String, JsonNode>> itor = tree.fields(); itor.hasNext();) {
+      final Map.Entry<String, JsonNode> field = itor.next();
+      if (field.getKey().charAt(0) == '@') {
+        final Annotation annotation = new AnnotationImpl();
+        annotation.setTerm(field.getKey().substring(1));
+
+        value(annotation, field.getValue(), parser.getCodec());
+        property.getAnnotations().add(annotation);
+      }
     }
 
     return new ResWrap<JSONPropertyImpl>(contextURL, metadataETag, property);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ec9e8cab/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java
index 74e8cf0..1a7b908 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/data/JSONPropertySerializer.java
@@ -25,6 +25,7 @@ import java.io.IOException;
 import java.net.URI;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.data.Annotation;
 import org.apache.olingo.commons.api.data.ResWrap;
 import org.apache.olingo.commons.api.data.Property;
 import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
@@ -40,7 +41,7 @@ public class JSONPropertySerializer extends AbstractJsonSerializer<JSONPropertyI
   @Override
   protected void doSerialize(final JSONPropertyImpl property, final JsonGenerator jgen,
           final SerializerProvider provider) throws IOException, JsonProcessingException {
-    
+
     doContainerSerialize(new ResWrap<JSONPropertyImpl>((URI) null, null, property), jgen, provider);
   }
 
@@ -64,6 +65,10 @@ public class JSONPropertySerializer extends AbstractJsonSerializer<JSONPropertyI
               new EdmTypeInfo.Builder().setTypeExpression(property.getType()).build().external(version));
     }
 
+    for (Annotation annotation : property.getAnnotations()) {
+      valuable(jgen, annotation, "@" + annotation.getTerm());
+    }
+
     if (property.getValue().isNull()) {
       jgen.writeBooleanField(Constants.JSON_NULL, true);
     } else if (property.getValue().isPrimitive()) {
@@ -76,10 +81,10 @@ public class JSONPropertySerializer extends AbstractJsonSerializer<JSONPropertyI
     } else if (property.getValue().isEnum()) {
       jgen.writeStringField(Constants.VALUE, property.getValue().asEnum().get());
     } else if (property.getValue().isGeospatial() || property.getValue().isCollection()) {
-      property(jgen, property, Constants.VALUE);
+      valuable(jgen, property, Constants.VALUE);
     } else if (property.getValue().isComplex()) {
       for (Property cproperty : property.getValue().asComplex().get()) {
-        property(jgen, cproperty, cproperty.getName());
+        valuable(jgen, cproperty, cproperty.getName());
       }
     }