You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by fm...@apache.org on 2013/07/26 13:22:20 UTC

[15/51] [partial] initial commit

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java
new file mode 100644
index 0000000..1fc2d90
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java
@@ -0,0 +1,527 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.olingo.odata2.api.ODataCallback;
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmCustomizableFeedMappings;
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.edm.EdmFacets;
+import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
+import org.apache.olingo.odata2.api.edm.EdmMultiplicity;
+import org.apache.olingo.odata2.api.edm.EdmNavigationProperty;
+import org.apache.olingo.odata2.api.edm.EdmSimpleType;
+import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
+import org.apache.olingo.odata2.api.edm.EdmTargetPath;
+import org.apache.olingo.odata2.api.edm.EdmType;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent;
+import org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent;
+import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackContext;
+import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackResult;
+import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackContext;
+import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackResult;
+import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
+import org.apache.olingo.odata2.core.commons.ContentType;
+import org.apache.olingo.odata2.core.commons.Encoder;
+import org.apache.olingo.odata2.core.edm.EdmDateTimeOffset;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityPropertyInfo;
+import org.apache.olingo.odata2.core.ep.aggregator.NavigationPropertyInfo;
+import org.apache.olingo.odata2.core.ep.util.FormatXml;
+
+/**
+ * Serializes an ATOM entry.
+ * @author SAP AG
+ */
+public class AtomEntryEntityProducer {
+
+  private String etag;
+  private String location;
+  private final EntityProviderWriteProperties properties;
+
+  public AtomEntryEntityProducer(final EntityProviderWriteProperties properties) throws EntityProviderException {
+    this.properties = properties == null ? EntityProviderWriteProperties.serviceRoot(null).build() : properties;
+  }
+
+  public void append(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data, final boolean isRootElement, final boolean isFeedPart) throws EntityProviderException {
+    try {
+      writer.writeStartElement(FormatXml.ATOM_ENTRY);
+
+      if (isRootElement) {
+        writer.writeDefaultNamespace(Edm.NAMESPACE_ATOM_2005);
+        writer.writeNamespace(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08);
+        writer.writeNamespace(Edm.PREFIX_D, Edm.NAMESPACE_D_2007_08);
+      }
+      if (!isFeedPart) {
+        writer.writeAttribute(Edm.PREFIX_XML, Edm.NAMESPACE_XML_1998, FormatXml.XML_BASE, properties.getServiceRoot().toASCIIString());
+      }
+
+      etag = createETag(eia, data);
+      if (etag != null) {
+        writer.writeAttribute(Edm.NAMESPACE_M_2007_08, FormatXml.M_ETAG, etag);
+      }
+
+      // write all atom infos (mandatory and optional)
+      appendAtomMandatoryParts(writer, eia, data);
+      appendAtomOptionalParts(writer, eia, data);
+
+      if (eia.getEntityType().hasStream()) {
+        // write all links
+        appendAtomEditLink(writer, eia, data);
+        appendAtomContentLink(writer, eia, data, properties.getMediaResourceMimeType());
+        appendAtomNavigationLinks(writer, eia, data);
+        // write properties/content
+        appendCustomProperties(writer, eia, data);
+        appendAtomContentPart(writer, eia, data, properties.getMediaResourceMimeType());
+        appendProperties(writer, eia, data);
+      } else {
+        // write all links
+        appendAtomEditLink(writer, eia, data);
+        appendAtomNavigationLinks(writer, eia, data);
+        // write properties/content
+        appendCustomProperties(writer, eia, data);
+        writer.writeStartElement(FormatXml.ATOM_CONTENT);
+        writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_XML.toString());
+        appendProperties(writer, eia, data);
+        writer.writeEndElement();
+      }
+
+      writer.writeEndElement();
+
+      writer.flush();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (EdmException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (URISyntaxException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendCustomProperties(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException {
+    List<String> noneSyndicationTargetPaths = eia.getNoneSyndicationTargetPathNames();
+    for (String tpName : noneSyndicationTargetPaths) {
+      EntityPropertyInfo info = eia.getTargetPathInfo(tpName);
+      final String name = info.getName();
+      XmlPropertyEntityProducer aps = new XmlPropertyEntityProducer();
+      aps.appendCustomProperty(writer, name, info, data.get(name));
+    }
+  }
+
+  protected static String createETag(final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException {
+    try {
+      String etag = null;
+
+      Collection<EntityPropertyInfo> propertyInfos = eia.getETagPropertyInfos();
+      for (EntityPropertyInfo propertyInfo : propertyInfos) {
+        EdmType edmType = propertyInfo.getType();
+        if (edmType instanceof EdmSimpleType) {
+          EdmSimpleType edmSimpleType = (EdmSimpleType) edmType;
+          if (etag == null) {
+            etag = edmSimpleType.valueToString(data.get(propertyInfo.getName()), EdmLiteralKind.DEFAULT, propertyInfo.getFacets());
+          } else {
+            etag = etag + Edm.DELIMITER + edmSimpleType.valueToString(data.get(propertyInfo.getName()), EdmLiteralKind.DEFAULT, propertyInfo.getFacets());
+          }
+        }
+      }
+
+      if (etag != null) {
+        etag = "W/\"" + etag + "\"";
+      }
+
+      return etag;
+    } catch (EdmSimpleTypeException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendAtomNavigationLinks(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException, EdmException, URISyntaxException {
+    for (String name : eia.getSelectedNavigationPropertyNames()) {
+      NavigationPropertyInfo info = eia.getNavigationPropertyInfo(name);
+      boolean isFeed = (info.getMultiplicity() == EdmMultiplicity.MANY);
+      String self = createSelfLink(eia, data, info.getName());
+      appendAtomNavigationLink(writer, self, info.getName(), isFeed, eia, data);
+    }
+  }
+
+  private void appendAtomNavigationLink(final XMLStreamWriter writer, final String self, final String navigationPropertyName, final boolean isFeed, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException, EdmException, URISyntaxException {
+    try {
+      writer.writeStartElement(FormatXml.ATOM_LINK);
+      writer.writeAttribute(FormatXml.ATOM_HREF, self);
+      writer.writeAttribute(FormatXml.ATOM_REL, Edm.NAMESPACE_REL_2007_08 + navigationPropertyName);
+      writer.writeAttribute(FormatXml.ATOM_TITLE, navigationPropertyName);
+      if (isFeed) {
+        writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_FEED.toString());
+        appendInlineFeed(writer, navigationPropertyName, eia, data, self);
+      } else {
+        writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_ENTRY.toString());
+        appendInlineEntry(writer, navigationPropertyName, eia, data);
+      }
+
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendInlineFeed(final XMLStreamWriter writer, final String navigationPropertyName, final EntityInfoAggregator eia, final Map<String, Object> data, final String self) throws EntityProviderException, XMLStreamException, EdmException, URISyntaxException {
+
+    if (eia.getExpandedNavigationPropertyNames().contains(navigationPropertyName)) {
+      if (properties.getCallbacks() != null && properties.getCallbacks().containsKey(navigationPropertyName)) {
+        writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_INLINE);
+
+        EdmNavigationProperty navProp = (EdmNavigationProperty) eia.getEntityType().getProperty(navigationPropertyName);
+        WriteFeedCallbackContext context = new WriteFeedCallbackContext();
+        context.setSourceEntitySet(eia.getEntitySet());
+        context.setNavigationProperty(navProp);
+        context.setEntryData(data);
+        ExpandSelectTreeNode subNode = properties.getExpandSelectTree().getLinks().get(navigationPropertyName);
+        context.setCurrentExpandSelectTreeNode(subNode);
+        context.setSelfLink(new URI(self));
+
+        ODataCallback callback = properties.getCallbacks().get(navigationPropertyName);
+        if (callback == null) {
+          throw new EntityProviderException(EntityProviderException.EXPANDNOTSUPPORTED);
+        }
+        WriteFeedCallbackResult result = null;
+        try {
+          result = ((OnWriteFeedContent) callback).retrieveFeedResult(context);
+        } catch (ODataApplicationException e) {
+          throw new EntityProviderException(EntityProviderException.COMMON, e);
+        }
+        List<Map<String, Object>> inlineData = result.getFeedData();
+        if (inlineData == null) {
+          inlineData = new ArrayList<Map<String, Object>>();
+        }
+
+        EntityProviderWriteProperties inlineProperties = result.getInlineProperties();
+        EdmEntitySet inlineEntitySet = eia.getEntitySet().getRelatedEntitySet(navProp);
+        AtomFeedProducer inlineFeedProducer = new AtomFeedProducer(inlineProperties);
+        EntityInfoAggregator inlineEia = EntityInfoAggregator.create(inlineEntitySet, inlineProperties.getExpandSelectTree());
+        inlineFeedProducer.append(writer, inlineEia, inlineData, true);
+
+        writer.writeEndElement();
+      }
+    }
+  }
+
+  private void appendInlineEntry(final XMLStreamWriter writer, final String navigationPropertyName, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException, XMLStreamException, EdmException {
+
+    if (eia.getExpandedNavigationPropertyNames().contains(navigationPropertyName)) {
+      if (properties.getCallbacks() != null && properties.getCallbacks().containsKey(navigationPropertyName)) {
+        writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_INLINE);
+
+        EdmNavigationProperty navProp = (EdmNavigationProperty) eia.getEntityType().getProperty(navigationPropertyName);
+        WriteEntryCallbackContext context = new WriteEntryCallbackContext();
+        context.setSourceEntitySet(eia.getEntitySet());
+        context.setNavigationProperty(navProp);
+        context.setEntryData(data);
+        ExpandSelectTreeNode subNode = properties.getExpandSelectTree().getLinks().get(navigationPropertyName);
+        context.setCurrentExpandSelectTreeNode(subNode);
+
+        ODataCallback callback = properties.getCallbacks().get(navigationPropertyName);
+        if (callback == null) {
+          throw new EntityProviderException(EntityProviderException.EXPANDNOTSUPPORTED);
+        }
+        WriteEntryCallbackResult result = null;
+        try {
+          result = ((OnWriteEntryContent) callback).retrieveEntryResult(context);
+        } catch (ODataApplicationException e) {
+          throw new EntityProviderException(EntityProviderException.COMMON, e);
+        }
+        Map<String, Object> inlineData = result.getEntryData();
+        if (inlineData != null && !inlineData.isEmpty()) {
+          EntityProviderWriteProperties inlineProperties = result.getInlineProperties();
+          EdmEntitySet inlineEntitySet = eia.getEntitySet().getRelatedEntitySet(navProp);
+          AtomEntryEntityProducer inlineProducer = new AtomEntryEntityProducer(inlineProperties);
+          EntityInfoAggregator inlineEia = EntityInfoAggregator.create(inlineEntitySet, inlineProperties.getExpandSelectTree());
+          inlineProducer.append(writer, inlineEia, inlineData, false, false);
+        }
+
+        writer.writeEndElement();
+      }
+    }
+
+  }
+
+  private void appendAtomEditLink(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException {
+    try {
+      String self = createSelfLink(eia, data, null);
+
+      writer.writeStartElement(FormatXml.ATOM_LINK);
+      writer.writeAttribute(FormatXml.ATOM_HREF, self);
+      writer.writeAttribute(FormatXml.ATOM_REL, Edm.LINK_REL_EDIT);
+      writer.writeAttribute(FormatXml.ATOM_TITLE, eia.getEntityType().getName());
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (EdmException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendAtomContentLink(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data, String mediaResourceMimeType) throws EntityProviderException {
+    try {
+      String self = createSelfLink(eia, data, "$value");
+
+      if (mediaResourceMimeType == null) {
+        mediaResourceMimeType = ContentType.APPLICATION_OCTET_STREAM.toString();
+      }
+
+      writer.writeStartElement(FormatXml.ATOM_LINK);
+      writer.writeAttribute(FormatXml.ATOM_HREF, self);
+      writer.writeAttribute(FormatXml.ATOM_REL, Edm.LINK_REL_EDIT_MEDIA);
+      writer.writeAttribute(FormatXml.ATOM_TYPE, mediaResourceMimeType);
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendAtomContentPart(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data, String mediaResourceMimeType) throws EntityProviderException {
+    try {
+      String self = createSelfLink(eia, data, "$value");
+
+      if (mediaResourceMimeType == null) {
+        mediaResourceMimeType = ContentType.APPLICATION_OCTET_STREAM.toString();
+      }
+
+      writer.writeStartElement(FormatXml.ATOM_CONTENT);
+      writer.writeAttribute(FormatXml.ATOM_TYPE, mediaResourceMimeType);
+      writer.writeAttribute(FormatXml.ATOM_SRC, self);
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendAtomMandatoryParts(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException {
+    try {
+      writer.writeStartElement(FormatXml.ATOM_ID);
+      location = properties.getServiceRoot().toASCIIString() + createSelfLink(eia, data, null);
+      writer.writeCharacters(location);
+      writer.writeEndElement();
+
+      writer.writeStartElement(FormatXml.ATOM_TITLE);
+      writer.writeAttribute(FormatXml.ATOM_TYPE, FormatXml.ATOM_TEXT);
+      EntityPropertyInfo titleInfo = eia.getTargetPathInfo(EdmTargetPath.SYNDICATION_TITLE);
+      if (titleInfo != null) {
+        EdmSimpleType st = (EdmSimpleType) titleInfo.getType();
+        Object object = data.get(titleInfo.getName());
+        String title = st.valueToString(object, EdmLiteralKind.DEFAULT, titleInfo.getFacets());
+        if (title != null) {
+          writer.writeCharacters(title);
+        }
+      } else {
+        writer.writeCharacters(eia.getEntitySetName());
+      }
+      writer.writeEndElement();
+
+      writer.writeStartElement(FormatXml.ATOM_UPDATED);
+
+      writer.writeCharacters(getUpdatedString(eia, data));
+
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (EdmSimpleTypeException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  String getUpdatedString(final EntityInfoAggregator eia, final Map<String, Object> data) throws EdmSimpleTypeException {
+    Object updateDate = null;
+    EdmFacets updateFacets = null;
+    EntityPropertyInfo updatedInfo = eia.getTargetPathInfo(EdmTargetPath.SYNDICATION_UPDATED);
+    if (updatedInfo != null) {
+      updateDate = data.get(updatedInfo.getName());
+      if (updateDate != null) {
+        updateFacets = updatedInfo.getFacets();
+      }
+    }
+    if (updateDate == null) {
+      updateDate = new Date();
+    }
+    String valueToString = EdmDateTimeOffset.getInstance().valueToString(updateDate, EdmLiteralKind.DEFAULT, updateFacets);
+    return valueToString;
+  }
+
+  private String getTargetPathValue(final EntityInfoAggregator eia, final String targetPath, final Map<String, Object> data) throws EntityProviderException {
+    try {
+      EntityPropertyInfo info = eia.getTargetPathInfo(targetPath);
+      if (info != null) {
+        EdmSimpleType type = (EdmSimpleType) info.getType();
+        Object value = data.get(info.getName());
+        return type.valueToString(value, EdmLiteralKind.DEFAULT, info.getFacets());
+      }
+      return null;
+    } catch (EdmSimpleTypeException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendAtomOptionalParts(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException {
+    try {
+      String authorEmail = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_AUTHOREMAIL, data);
+      String authorName = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_AUTHORNAME, data);
+      String authorUri = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_AUTHORURI, data);
+      if (authorEmail != null || authorName != null || authorUri != null) {
+        writer.writeStartElement(FormatXml.ATOM_AUTHOR);
+        appendAtomOptionalPart(writer, FormatXml.ATOM_AUTHOR_NAME, authorName, false);
+        appendAtomOptionalPart(writer, FormatXml.ATOM_AUTHOR_EMAIL, authorEmail, false);
+        appendAtomOptionalPart(writer, FormatXml.ATOM_AUTHOR_URI, authorUri, false);
+        writer.writeEndElement();
+      }
+
+      String summary = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_SUMMARY, data);
+      appendAtomOptionalPart(writer, FormatXml.ATOM_SUMMARY, summary, true);
+
+      String contributorName = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_CONTRIBUTORNAME, data);
+      String contributorEmail = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_CONTRIBUTOREMAIL, data);
+      String contributorUri = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_CONTRIBUTORURI, data);
+      if (contributorEmail != null || contributorName != null || contributorUri != null) {
+        writer.writeStartElement(FormatXml.ATOM_CONTRIBUTOR);
+        appendAtomOptionalPart(writer, FormatXml.ATOM_CONTRIBUTOR_NAME, contributorName, false);
+        appendAtomOptionalPart(writer, FormatXml.ATOM_CONTRIBUTOR_EMAIL, contributorEmail, false);
+        appendAtomOptionalPart(writer, FormatXml.ATOM_CONTRIBUTOR_URI, contributorUri, false);
+        writer.writeEndElement();
+      }
+
+      String rights = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_RIGHTS, data);
+      appendAtomOptionalPart(writer, FormatXml.ATOM_RIGHTS, rights, true);
+      String published = getTargetPathValue(eia, EdmTargetPath.SYNDICATION_PUBLISHED, data);
+      appendAtomOptionalPart(writer, FormatXml.ATOM_PUBLISHED, published, false);
+
+      String term = eia.getEntityType().getNamespace() + Edm.DELIMITER + eia.getEntityType().getName();
+      writer.writeStartElement(FormatXml.ATOM_CATEGORY);
+      writer.writeAttribute(FormatXml.ATOM_CATEGORY_TERM, term);
+      writer.writeAttribute(FormatXml.ATOM_CATEGORY_SCHEME, Edm.NAMESPACE_SCHEME_2007_08);
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (EdmException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendAtomOptionalPart(final XMLStreamWriter writer, final String name, final String value, final boolean writeType) throws EntityProviderException {
+    try {
+      if (value != null) {
+        writer.writeStartElement(name);
+        if (writeType) {
+          writer.writeAttribute(FormatXml.ATOM_TYPE, FormatXml.ATOM_TEXT);
+        }
+        writer.writeCharacters(value);
+        writer.writeEndElement();
+      }
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  static String createSelfLink(final EntityInfoAggregator eia, final Map<String, Object> data, final String extension) throws EntityProviderException {
+    StringBuilder sb = new StringBuilder();
+    if (!eia.isDefaultEntityContainer()) {
+      sb.append(Encoder.encode(eia.getEntityContainerName())).append(Edm.DELIMITER);
+    }
+    sb.append(Encoder.encode(eia.getEntitySetName()));
+
+    sb.append("(").append(createEntryKey(eia, data)).append(")").append(extension == null ? "" : ("/" + extension));
+    return sb.toString();
+  }
+
+  private static String createEntryKey(final EntityInfoAggregator entityInfo, final Map<String, Object> data) throws EntityProviderException {
+    final List<EntityPropertyInfo> keyPropertyInfos = entityInfo.getKeyPropertyInfos();
+
+    StringBuilder keys = new StringBuilder();
+    for (final EntityPropertyInfo keyPropertyInfo : keyPropertyInfos) {
+      if (keys.length() > 0) {
+        keys.append(',');
+      }
+
+      final String name = keyPropertyInfo.getName();
+      if (keyPropertyInfos.size() > 1) {
+        keys.append(Encoder.encode(name)).append('=');
+      }
+
+      final EdmSimpleType type = (EdmSimpleType) keyPropertyInfo.getType();
+      try {
+        keys.append(Encoder.encode(type.valueToString(data.get(name), EdmLiteralKind.URI, keyPropertyInfo.getFacets())));
+      } catch (final EdmSimpleTypeException e) {
+        throw new EntityProviderException(EntityProviderException.COMMON, e);
+      }
+    }
+
+    return keys.toString();
+  }
+
+  private void appendProperties(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException {
+    try {
+      List<String> propertyNames = eia.getSelectedPropertyNames();
+      if (!propertyNames.isEmpty()) {
+        writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_PROPERTIES);
+
+        for (String propertyName : propertyNames) {
+          EntityPropertyInfo propertyInfo = eia.getPropertyInfo(propertyName);
+
+          if (isNotMappedViaCustomMapping(propertyInfo)) {
+            Object value = data.get(propertyName);
+            XmlPropertyEntityProducer aps = new XmlPropertyEntityProducer();
+            aps.append(writer, propertyInfo.getName(), propertyInfo, value);
+          }
+        }
+
+        writer.writeEndElement();
+      }
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private boolean isNotMappedViaCustomMapping(final EntityPropertyInfo propertyInfo) {
+    EdmCustomizableFeedMappings customMapping = propertyInfo.getCustomMapping();
+    if (customMapping != null && customMapping.isFcKeepInContent() != null) {
+      return customMapping.isFcKeepInContent().booleanValue();
+    }
+    return true;
+  }
+
+  public String getETag() {
+    return etag;
+  }
+
+  public String getLocation() {
+    return location;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducer.java
new file mode 100644
index 0000000..9049792
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducer.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.olingo.odata2.api.commons.InlineCount;
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmFacets;
+import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
+import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.api.ep.callback.TombstoneCallback;
+import org.apache.olingo.odata2.api.ep.callback.TombstoneCallbackResult;
+import org.apache.olingo.odata2.core.commons.Encoder;
+import org.apache.olingo.odata2.core.edm.EdmDateTimeOffset;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.util.FormatXml;
+
+/**
+ * Serializes an ATOM feed.
+ * @author SAP AG
+ */
+public class AtomFeedProducer {
+
+  private final EntityProviderWriteProperties properties;
+
+  public AtomFeedProducer(final EntityProviderWriteProperties properties) {
+    this.properties = properties == null ? EntityProviderWriteProperties.serviceRoot(null).build() : properties;
+  }
+
+  public void append(final XMLStreamWriter writer, final EntityInfoAggregator eia, final List<Map<String, Object>> data, final boolean isInline) throws EntityProviderException {
+    try {
+      writer.writeStartElement(FormatXml.ATOM_FEED);
+      TombstoneCallback callback = null;
+      if (!isInline) {
+        writer.writeDefaultNamespace(Edm.NAMESPACE_ATOM_2005);
+        writer.writeNamespace(Edm.PREFIX_M, Edm.NAMESPACE_M_2007_08);
+        writer.writeNamespace(Edm.PREFIX_D, Edm.NAMESPACE_D_2007_08);
+        callback = getTombstoneCallback();
+        if (callback != null) {
+          writer.writeNamespace(TombstoneCallback.PREFIX_TOMBSTONE, TombstoneCallback.NAMESPACE_TOMBSTONE);
+        }
+      }
+      writer.writeAttribute(Edm.PREFIX_XML, Edm.NAMESPACE_XML_1998, FormatXml.XML_BASE, properties.getServiceRoot().toASCIIString());
+
+      // write all atom infos (mandatory and optional)
+      appendAtomMandatoryParts(writer, eia);
+      appendAtomSelfLink(writer, eia);
+      if (properties.getInlineCountType() == InlineCount.ALLPAGES) {
+        appendInlineCount(writer, properties.getInlineCount());
+      }
+
+      appendEntries(writer, eia, data);
+
+      if (callback != null) {
+        appendDeletedEntries(writer, eia, callback);
+      }
+
+      if (properties.getNextLink() != null) {
+        appendNextLink(writer, properties.getNextLink());
+      }
+
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private TombstoneCallback getTombstoneCallback() {
+    if (properties.getCallbacks() != null && properties.getCallbacks().containsKey(TombstoneCallback.CALLBACK_KEY_TOMBSTONE)) {
+      TombstoneCallback callback = (TombstoneCallback) properties.getCallbacks().get(TombstoneCallback.CALLBACK_KEY_TOMBSTONE);
+      return callback;
+    } else {
+      return null;
+    }
+  }
+
+  private void appendDeletedEntries(final XMLStreamWriter writer, final EntityInfoAggregator eia, final TombstoneCallback callback) throws EntityProviderException {
+    TombstoneCallbackResult callbackResult = callback.getTombstoneCallbackResult();
+    List<Map<String, Object>> tombstoneData = callbackResult.getDeletedEntriesData();
+    if (tombstoneData != null) {
+      TombstoneProducer tombstoneProducer = new TombstoneProducer();
+      tombstoneProducer.appendTombstones(writer, eia, properties, tombstoneData);
+    }
+
+    String deltaLink = callbackResult.getDeltaLink();
+    if (deltaLink != null) {
+      try {
+        writer.writeStartElement(FormatXml.ATOM_LINK);
+        writer.writeAttribute(FormatXml.ATOM_REL, FormatXml.ATOM_DELTA_LINK);
+        writer.writeAttribute(FormatXml.ATOM_HREF, deltaLink);
+        writer.writeEndElement();
+      } catch (XMLStreamException e) {
+        throw new EntityProviderException(EntityProviderException.COMMON, e);
+      }
+    }
+  }
+
+  private void appendNextLink(final XMLStreamWriter writer, final String nextLink) throws EntityProviderException {
+    try {
+      writer.writeStartElement(FormatXml.ATOM_LINK);
+      writer.writeAttribute(FormatXml.ATOM_HREF, nextLink);
+      writer.writeAttribute(FormatXml.ATOM_REL, FormatXml.ATOM_NEXT_LINK);
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendEntries(final XMLStreamWriter writer, final EntityInfoAggregator eia, final List<Map<String, Object>> data) throws EntityProviderException {
+    AtomEntryEntityProducer entryProvider = new AtomEntryEntityProducer(properties);
+    for (Map<String, Object> singleEntryData : data) {
+      entryProvider.append(writer, eia, singleEntryData, false, true);
+    }
+  }
+
+  private void appendInlineCount(final XMLStreamWriter writer, final Integer inlineCount) throws EntityProviderException {
+    if (inlineCount == null || inlineCount < 0) {
+      throw new EntityProviderException(EntityProviderException.INLINECOUNT_INVALID);
+    }
+    try {
+      writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_COUNT);
+      writer.writeCharacters(String.valueOf(inlineCount));
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendAtomSelfLink(final XMLStreamWriter writer, final EntityInfoAggregator eia) throws EntityProviderException {
+
+    URI self = properties.getSelfLink();
+    String selfLink = "";
+    if (self == null) {
+      selfLink = createSelfLink(eia);
+    } else {
+      selfLink = self.toASCIIString();
+    }
+    try {
+      writer.writeStartElement(FormatXml.ATOM_LINK);
+      writer.writeAttribute(FormatXml.ATOM_HREF, selfLink);
+      writer.writeAttribute(FormatXml.ATOM_REL, Edm.LINK_REL_SELF);
+      writer.writeAttribute(FormatXml.ATOM_TITLE, eia.getEntitySetName());
+      writer.writeEndElement();
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private String createSelfLink(final EntityInfoAggregator eia) throws EntityProviderException {
+    StringBuilder sb = new StringBuilder();
+    if (!eia.isDefaultEntityContainer()) {
+      final String entityContainerName = Encoder.encode(eia.getEntityContainerName());
+      sb.append(entityContainerName).append(Edm.DELIMITER);
+    }
+    final String entitySetName = Encoder.encode(eia.getEntitySetName());
+    sb.append(entitySetName);
+    return sb.toString();
+  }
+
+  private void appendAtomMandatoryParts(final XMLStreamWriter writer, final EntityInfoAggregator eia) throws EntityProviderException {
+    try {
+      writer.writeStartElement(FormatXml.ATOM_ID);
+      writer.writeCharacters(createAtomId(eia));
+      writer.writeEndElement();
+
+      writer.writeStartElement(FormatXml.ATOM_TITLE);
+      writer.writeAttribute(FormatXml.M_TYPE, FormatXml.ATOM_TEXT);
+      writer.writeCharacters(eia.getEntitySetName());
+      writer.writeEndElement();
+
+      writer.writeStartElement(FormatXml.ATOM_UPDATED);
+
+      Object updateDate = null;
+      EdmFacets updateFacets = null;
+      updateDate = new Date();
+      writer.writeCharacters(EdmDateTimeOffset.getInstance().valueToString(updateDate, EdmLiteralKind.DEFAULT, updateFacets));
+      writer.writeEndElement();
+
+      writer.writeStartElement(FormatXml.ATOM_AUTHOR);
+      writer.writeStartElement(FormatXml.ATOM_AUTHOR_NAME);
+      writer.writeEndElement();
+      writer.writeEndElement();
+
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (EdmSimpleTypeException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private String createAtomId(final EntityInfoAggregator eia) throws EntityProviderException {
+    return properties.getServiceRoot() + createSelfLink(eia);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomServiceDocumentProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomServiceDocumentProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomServiceDocumentProducer.java
new file mode 100644
index 0000000..a38bb2f
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomServiceDocumentProducer.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.Writer;
+import java.util.List;
+
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmEntitySetInfo;
+import org.apache.olingo.odata2.api.edm.EdmServiceMetadata;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.exception.ODataException;
+import org.apache.olingo.odata2.core.commons.ContentType;
+import org.apache.olingo.odata2.core.ep.util.FormatXml;
+
+/**
+ * Writes the  OData service document in XML.
+ * @author SAP AG
+ */
+public class AtomServiceDocumentProducer {
+
+  private static final String DEFAULT_CHARSET = ContentType.CHARSET_UTF_8;
+  private static final String XML_VERSION = "1.0";
+  private final Edm edm;
+  private final String serviceRoot;
+
+  public AtomServiceDocumentProducer(final Edm edm, final String serviceRoot) {
+    this.edm = edm;
+    this.serviceRoot = serviceRoot;
+  }
+
+  public void writeServiceDocument(final Writer writer) throws EntityProviderException {
+
+    EdmServiceMetadata serviceMetadata = edm.getServiceMetadata();
+
+    try {
+      XMLStreamWriter xmlStreamWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
+
+      xmlStreamWriter.writeStartDocument(DEFAULT_CHARSET, XML_VERSION);
+      xmlStreamWriter.setPrefix(Edm.PREFIX_XML, Edm.NAMESPACE_XML_1998);
+      xmlStreamWriter.setPrefix(Edm.PREFIX_ATOM, Edm.NAMESPACE_ATOM_2005);
+      xmlStreamWriter.setDefaultNamespace(Edm.NAMESPACE_APP_2007);
+
+      xmlStreamWriter.writeStartElement(FormatXml.APP_SERVICE);
+      xmlStreamWriter.writeAttribute(Edm.PREFIX_XML, Edm.NAMESPACE_XML_1998, FormatXml.XML_BASE, serviceRoot);
+      xmlStreamWriter.writeDefaultNamespace(Edm.NAMESPACE_APP_2007);
+      xmlStreamWriter.writeNamespace(Edm.PREFIX_ATOM, Edm.NAMESPACE_ATOM_2005);
+
+      xmlStreamWriter.writeStartElement(FormatXml.APP_WORKSPACE);
+      xmlStreamWriter.writeStartElement(Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_TITLE);
+      xmlStreamWriter.writeCharacters(FormatXml.ATOM_TITLE_DEFAULT);
+      xmlStreamWriter.writeEndElement();
+
+      List<EdmEntitySetInfo> entitySetInfos = serviceMetadata.getEntitySetInfos();
+      for (EdmEntitySetInfo info : entitySetInfos) {
+        xmlStreamWriter.writeStartElement(FormatXml.APP_COLLECTION);
+        xmlStreamWriter.writeAttribute(FormatXml.ATOM_HREF, info.getEntitySetUri().toASCIIString());
+        xmlStreamWriter.writeStartElement(Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_TITLE);
+        xmlStreamWriter.writeCharacters(info.getEntitySetName());
+        xmlStreamWriter.writeEndElement();
+        xmlStreamWriter.writeEndElement();
+      }
+
+      //      Collection<Schema> schemas = edmProvider.getSchemas();
+      //      if (schemas != null) {
+      //        for (Schema schema : schemas) {
+      //          Collection<EntityContainer> entityContainers = schema.getEntityContainers();
+      //          if (entityContainers != null) {
+      //            for (EntityContainer entityContainer : entityContainers) {
+      //              Collection<EntitySet> entitySets = entityContainer.getEntitySets();
+      //              for (EntitySet entitySet : entitySets) {
+      //                xmlStreamWriter.writeStartElement(FormatXml.APP_COLLECTION);
+      //                if (entityContainer.isDefaultEntityContainer()) {
+      //                  xmlStreamWriter.writeAttribute(FormatXml.ATOM_HREF, entitySet.getName());
+      //                } else {
+      //                  xmlStreamWriter.writeAttribute(FormatXml.ATOM_HREF, entityContainer.getName() + Edm.DELIMITER + entitySet.getName());
+      //                }
+      //                xmlStreamWriter.writeStartElement(Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_TITLE);
+      //                xmlStreamWriter.writeCharacters(entitySet.getName());
+      //                xmlStreamWriter.writeEndElement();
+      //                xmlStreamWriter.writeEndElement();
+      //              }
+      //            }
+      //          }
+      //        }
+      //      }
+
+      xmlStreamWriter.writeEndElement();
+      xmlStreamWriter.writeEndElement();
+      xmlStreamWriter.writeEndDocument();
+
+      xmlStreamWriter.flush();
+    } catch (FactoryConfigurationError e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (ODataException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java
new file mode 100644
index 0000000..532ea36
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityPropertyInfo;
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Provider for writing a collection of simple-type or complex-type instances in JSON.
+ * @author SAP AG
+ */
+public class JsonCollectionEntityProducer {
+
+  public void append(final Writer writer, final EntityPropertyInfo propertyInfo, final List<?> data) throws EntityProviderException {
+    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
+
+    try {
+      jsonStreamWriter.beginObject()
+          .name(FormatJson.D)
+          .beginObject();
+
+      jsonStreamWriter.name(FormatJson.METADATA)
+          .beginObject()
+          .namedStringValueRaw(FormatJson.TYPE,
+              "Collection(" + propertyInfo.getType().getNamespace() + Edm.DELIMITER + propertyInfo.getType().getName() + ")")
+          .endObject()
+          .separator();
+
+      jsonStreamWriter.name(FormatJson.RESULTS)
+          .beginArray();
+      boolean first = true;
+      for (final Object item : data) {
+        if (first) {
+          first = false;
+        } else {
+          jsonStreamWriter.separator();
+        }
+        JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, propertyInfo, item);
+      }
+      jsonStreamWriter.endArray();
+
+      jsonStreamWriter.endObject()
+          .endObject();
+    } catch (final IOException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    } catch (final EdmException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java
new file mode 100644
index 0000000..40a0e33
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.ODataCallback;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmEntityType;
+import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.edm.EdmMultiplicity;
+import org.apache.olingo.odata2.api.edm.EdmNavigationProperty;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent;
+import org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent;
+import org.apache.olingo.odata2.api.ep.callback.WriteCallbackContext;
+import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackContext;
+import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackResult;
+import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackContext;
+import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackResult;
+import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.core.commons.Encoder;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Producer for writing an entity in JSON, also usable for function imports
+ * returning a single instance of an entity type.
+ * @author SAP AG
+ */
+public class JsonEntryEntityProducer {
+
+  private final EntityProviderWriteProperties properties;
+  private String eTag;
+  private String location;
+  private JsonStreamWriter jsonStreamWriter;
+
+  public JsonEntryEntityProducer(final EntityProviderWriteProperties properties) throws EntityProviderException {
+    this.properties = properties == null ? EntityProviderWriteProperties.serviceRoot(null).build() : properties;
+  }
+
+  public void append(final Writer writer, final EntityInfoAggregator entityInfo, final Map<String, Object> data, final boolean isRootElement) throws EntityProviderException {
+    final EdmEntityType type = entityInfo.getEntityType();
+
+    try {
+      jsonStreamWriter = new JsonStreamWriter(writer);
+      if (isRootElement) {
+        jsonStreamWriter.beginObject().name(FormatJson.D);
+      }
+
+      jsonStreamWriter.beginObject();
+
+      jsonStreamWriter.name(FormatJson.METADATA)
+          .beginObject();
+      final String self = AtomEntryEntityProducer.createSelfLink(entityInfo, data, null);
+      location = (properties.getServiceRoot() == null ? "" : properties.getServiceRoot().toASCIIString()) + self;
+      jsonStreamWriter.namedStringValue(FormatJson.ID, location).separator()
+          .namedStringValue(FormatJson.URI, location).separator()
+          .namedStringValueRaw(FormatJson.TYPE,
+              type.getNamespace() + Edm.DELIMITER + type.getName());
+      eTag = AtomEntryEntityProducer.createETag(entityInfo, data);
+      if (eTag != null) {
+        jsonStreamWriter.separator()
+            .namedStringValue(FormatJson.ETAG, eTag);
+      }
+      if (type.hasStream()) {
+        jsonStreamWriter.separator()
+            .namedStringValueRaw(FormatJson.CONTENT_TYPE,
+                properties.getMediaResourceMimeType() == null ?
+                    type.getMapping() == null || type.getMapping().getMimeType() == null || data.get(type.getMapping().getMimeType()) == null ?
+                        HttpContentType.APPLICATION_OCTET_STREAM : data.get(type.getMapping().getMimeType()).toString() :
+                    properties.getMediaResourceMimeType())
+            .separator()
+            .namedStringValue(FormatJson.MEDIA_SRC, self + "/$value").separator()
+            .namedStringValue(FormatJson.EDIT_MEDIA, location + "/$value");
+      }
+      jsonStreamWriter.endObject();
+
+      for (final String propertyName : type.getPropertyNames()) {
+        if (entityInfo.getSelectedPropertyNames().contains(propertyName)) {
+          jsonStreamWriter.separator()
+              .name(propertyName);
+          JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, entityInfo.getPropertyInfo(propertyName), data.get(propertyName));
+        }
+      }
+
+      for (final String navigationPropertyName : type.getNavigationPropertyNames()) {
+        if (entityInfo.getSelectedNavigationPropertyNames().contains(navigationPropertyName)) {
+          jsonStreamWriter.separator()
+              .name(navigationPropertyName);
+          if (entityInfo.getExpandedNavigationPropertyNames().contains(navigationPropertyName)) {
+            if (properties.getCallbacks() != null && properties.getCallbacks().containsKey(navigationPropertyName)) {
+              final EdmNavigationProperty navigationProperty = (EdmNavigationProperty) type.getProperty(navigationPropertyName);
+              final boolean isFeed = navigationProperty.getMultiplicity() == EdmMultiplicity.MANY;
+              final EdmEntitySet entitySet = entityInfo.getEntitySet();
+              final EdmEntitySet inlineEntitySet = entitySet.getRelatedEntitySet(navigationProperty);
+
+              WriteCallbackContext context = isFeed ? new WriteFeedCallbackContext() : new WriteEntryCallbackContext();
+              context.setSourceEntitySet(entitySet);
+              context.setNavigationProperty(navigationProperty);
+              context.setEntryData(data);
+              context.setCurrentExpandSelectTreeNode(properties.getExpandSelectTree().getLinks().get(navigationPropertyName));
+
+              ODataCallback callback = properties.getCallbacks().get(navigationPropertyName);
+              if (callback == null) {
+                throw new EntityProviderException(EntityProviderException.EXPANDNOTSUPPORTED);
+              }
+              try {
+                if (isFeed) {
+                  final WriteFeedCallbackResult result = ((OnWriteFeedContent) callback).retrieveFeedResult((WriteFeedCallbackContext) context);
+                  List<Map<String, Object>> inlineData = result.getFeedData();
+                  if (inlineData == null) {
+                    inlineData = new ArrayList<Map<String, Object>>();
+                  }
+                  final EntityProviderWriteProperties inlineProperties = result.getInlineProperties();
+                  final EntityInfoAggregator inlineEntityInfo = EntityInfoAggregator.create(inlineEntitySet, inlineProperties.getExpandSelectTree());
+                  new JsonFeedEntityProducer(inlineProperties).append(writer, inlineEntityInfo, inlineData, false);
+
+                } else {
+                  final WriteEntryCallbackResult result = ((OnWriteEntryContent) callback).retrieveEntryResult((WriteEntryCallbackContext) context);
+                  Map<String, Object> inlineData = result.getEntryData();
+                  if (inlineData != null && !inlineData.isEmpty()) {
+                    final EntityProviderWriteProperties inlineProperties = result.getInlineProperties();
+                    final EntityInfoAggregator inlineEntityInfo = EntityInfoAggregator.create(inlineEntitySet, inlineProperties.getExpandSelectTree());
+                    new JsonEntryEntityProducer(inlineProperties).append(writer, inlineEntityInfo, inlineData, false);
+                  } else {
+                    jsonStreamWriter.unquotedValue("null");
+                  }
+                }
+              } catch (final ODataApplicationException e) {
+                throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+              }
+            } else {
+              writeDeferredUri(navigationPropertyName);
+            }
+          } else {
+            writeDeferredUri(navigationPropertyName);
+          }
+        }
+      }
+
+      jsonStreamWriter.endObject();
+
+      if (isRootElement) {
+        jsonStreamWriter.endObject();
+      }
+
+      writer.flush();
+
+    } catch (final IOException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    } catch (final EdmException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    }
+  }
+
+  private void writeDeferredUri(final String navigationPropertyName) throws IOException {
+    jsonStreamWriter.beginObject()
+        .name(FormatJson.DEFERRED);
+    JsonLinkEntityProducer.appendUri(jsonStreamWriter, location + "/" + Encoder.encode(navigationPropertyName));
+    jsonStreamWriter.endObject();
+  }
+
+  public String getETag() {
+    return eTag;
+  }
+
+  public String getLocation() {
+    return location;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonErrorDocumentProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonErrorDocumentProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonErrorDocumentProducer.java
new file mode 100644
index 0000000..a65d997
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonErrorDocumentProducer.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Locale;
+
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * @author SAP AG
+ */
+public class JsonErrorDocumentProducer {
+
+  public void writeErrorDocument(final Writer writer, final String errorCode, final String message, final Locale locale, final String innerError) throws IOException {
+    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
+
+    jsonStreamWriter.beginObject()
+        .name(FormatJson.ERROR)
+        .beginObject()
+        .namedStringValue(FormatJson.CODE, errorCode).separator()
+        .name(FormatJson.MESSAGE)
+        .beginObject()
+        .namedStringValueRaw(FormatJson.LANG,
+            locale == null || locale.getLanguage() == null ? null :
+                locale.getLanguage() + (locale.getCountry() == null || locale.getCountry().isEmpty() ? "" : ("-" + locale.getCountry())))
+        .separator()
+        .namedStringValue(FormatJson.VALUE, message)
+        .endObject();
+    if (innerError != null) {
+      jsonStreamWriter.separator()
+          .namedStringValue(FormatJson.INNER_ERROR, innerError);
+    }
+    jsonStreamWriter.endObject()
+        .endObject();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducer.java
new file mode 100644
index 0000000..e406fe2
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducer.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.commons.InlineCount;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Producer for writing an entity collection (a feed) in JSON.
+ * @author SAP AG
+ */
+public class JsonFeedEntityProducer {
+
+  private final EntityProviderWriteProperties properties;
+
+  public JsonFeedEntityProducer(final EntityProviderWriteProperties properties) throws EntityProviderException {
+    this.properties = properties == null ? EntityProviderWriteProperties.serviceRoot(null).build() : properties;
+  }
+
+  public void append(final Writer writer, final EntityInfoAggregator entityInfo, final List<Map<String, Object>> data, final boolean isRootElement) throws EntityProviderException {
+    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
+
+    try {
+      jsonStreamWriter.beginObject();
+
+      if (isRootElement) {
+        jsonStreamWriter.name(FormatJson.D)
+            .beginObject();
+      }
+
+      if (properties.getInlineCountType() == InlineCount.ALLPAGES) {
+        final int inlineCount = properties.getInlineCount() == null ? 0 : properties.getInlineCount();
+        jsonStreamWriter.namedStringValueRaw(FormatJson.COUNT, String.valueOf(inlineCount)).separator();
+      }
+
+      jsonStreamWriter.name(FormatJson.RESULTS)
+          .beginArray();
+      JsonEntryEntityProducer entryProducer = new JsonEntryEntityProducer(properties);
+      boolean first = true;
+      for (final Map<String, Object> entryData : data) {
+        if (first) {
+          first = false;
+        } else {
+          jsonStreamWriter.separator();
+        }
+        entryProducer.append(writer, entityInfo, entryData, false);
+      }
+      jsonStreamWriter.endArray();
+
+      // Write "next" link.
+      // To be compatible with other implementations out there, the link is
+      // written directly after "__next" and not as "{"uri":"next link"}",
+      // deviating from the OData 2.0 specification.
+      if (properties.getNextLink() != null) {
+        jsonStreamWriter.separator()
+            .namedStringValue(FormatJson.NEXT, properties.getNextLink());
+      }
+
+      if (isRootElement) {
+        jsonStreamWriter.endObject();
+      }
+
+      jsonStreamWriter.endObject();
+    } catch (final IOException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinkEntityProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinkEntityProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinkEntityProducer.java
new file mode 100644
index 0000000..5edbe00
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinkEntityProducer.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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Producer for writing a link in JSON.
+ * @author SAP AG
+ */
+public class JsonLinkEntityProducer {
+
+  private final EntityProviderWriteProperties properties;
+
+  public JsonLinkEntityProducer(final EntityProviderWriteProperties properties) throws EntityProviderException {
+    this.properties = properties == null ? EntityProviderWriteProperties.serviceRoot(null).build() : properties;
+  }
+
+  public void append(final Writer writer, final EntityInfoAggregator entityInfo, final Map<String, Object> data) throws EntityProviderException {
+    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
+
+    final String uri = (properties.getServiceRoot() == null ? "" : properties.getServiceRoot().toASCIIString())
+        + AtomEntryEntityProducer.createSelfLink(entityInfo, data, null);
+    try {
+      jsonStreamWriter.beginObject()
+          .name(FormatJson.D);
+      appendUri(jsonStreamWriter, uri);
+      jsonStreamWriter.endObject();
+    } catch (final IOException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    }
+  }
+
+  protected static void appendUri(final JsonStreamWriter jsonStreamWriter, final String uri) throws IOException {
+    jsonStreamWriter.beginObject()
+        .namedStringValue(FormatJson.URI, uri)
+        .endObject();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinksEntityProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinksEntityProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinksEntityProducer.java
new file mode 100644
index 0000000..1c2aa75
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonLinksEntityProducer.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.commons.InlineCount;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Producer for writing a link collection in JSON.
+ * @author SAP AG
+ */
+public class JsonLinksEntityProducer {
+
+  private final EntityProviderWriteProperties properties;
+
+  public JsonLinksEntityProducer(final EntityProviderWriteProperties properties) throws EntityProviderException {
+    this.properties = properties == null ? EntityProviderWriteProperties.serviceRoot(null).build() : properties;
+  }
+
+  public void append(final Writer writer, final EntityInfoAggregator entityInfo, final List<Map<String, Object>> data) throws EntityProviderException {
+    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
+
+    try {
+      jsonStreamWriter.beginObject()
+          .name(FormatJson.D);
+
+      if (properties.getInlineCountType() == InlineCount.ALLPAGES) {
+        final int inlineCount = properties.getInlineCount() == null ? 0 : properties.getInlineCount();
+        jsonStreamWriter.beginObject()
+            .namedStringValueRaw(FormatJson.COUNT, String.valueOf(inlineCount)).separator()
+            .name(FormatJson.RESULTS);
+      }
+
+      jsonStreamWriter.beginArray();
+      final String serviceRoot = properties.getServiceRoot().toASCIIString();
+      boolean first = true;
+      for (final Map<String, Object> entryData : data) {
+        if (first) {
+          first = false;
+        } else {
+          jsonStreamWriter.separator();
+        }
+        JsonLinkEntityProducer.appendUri(jsonStreamWriter,
+            (serviceRoot == null ? "" : serviceRoot) + AtomEntryEntityProducer.createSelfLink(entityInfo, entryData, null));
+      }
+      jsonStreamWriter.endArray();
+
+      if (properties.getInlineCountType() == InlineCount.ALLPAGES) {
+        jsonStreamWriter.endObject();
+      }
+
+      jsonStreamWriter.endObject();
+    } catch (final IOException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java
new file mode 100644
index 0000000..81ec29f
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
+import org.apache.olingo.odata2.api.edm.EdmSimpleType;
+import org.apache.olingo.odata2.api.edm.EdmSimpleTypeKind;
+import org.apache.olingo.odata2.api.edm.EdmType;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityComplexPropertyInfo;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityPropertyInfo;
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Producer for writing a single simple or complex property in JSON, also usable
+ * for function imports returning a single instance of a simple or complex type.
+ * @author SAP AG
+ */
+public class JsonPropertyEntityProducer {
+
+  public void append(final Writer writer, final EntityPropertyInfo propertyInfo, final Object value) throws EntityProviderException {
+    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
+
+    try {
+      jsonStreamWriter.beginObject()
+          .name(FormatJson.D)
+          .beginObject();
+
+      jsonStreamWriter.name(propertyInfo.getName());
+      appendPropertyValue(jsonStreamWriter, propertyInfo.isComplex() ? (EntityComplexPropertyInfo) propertyInfo : propertyInfo, value);
+
+      jsonStreamWriter.endObject()
+          .endObject();
+    } catch (final IOException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    } catch (final EdmException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    }
+  }
+
+  protected static void appendPropertyValue(final JsonStreamWriter jsonStreamWriter, final EntityPropertyInfo propertyInfo, final Object value) throws IOException, EdmException, EntityProviderException {
+    if (propertyInfo.isComplex()) {
+      if (value == null || value instanceof Map<?, ?>) {
+        jsonStreamWriter.beginObject();
+        appendPropertyMetadata(jsonStreamWriter, propertyInfo.getType());
+        for (final EntityPropertyInfo childPropertyInfo : ((EntityComplexPropertyInfo) propertyInfo).getPropertyInfos()) {
+          jsonStreamWriter.separator();
+          final String name = childPropertyInfo.getName();
+          jsonStreamWriter.name(name);
+          appendPropertyValue(jsonStreamWriter, childPropertyInfo, value == null ? null : ((Map<?, ?>) value).get(name));
+        }
+        jsonStreamWriter.endObject();
+      } else {
+        throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT.addContent("A complex property must have a Map as data"));
+      }
+    } else {
+      final EdmSimpleType type = (EdmSimpleType) propertyInfo.getType();
+      final Object contentValue = value instanceof Map ? ((Map<?, ?>) value).get(propertyInfo.getName()) : value;
+      final String valueAsString = type.valueToString(contentValue, EdmLiteralKind.JSON, propertyInfo.getFacets());
+      switch (EdmSimpleTypeKind.valueOf(type.getName())) {
+      case String:
+        jsonStreamWriter.stringValue(valueAsString);
+        break;
+      case Boolean:
+      case Byte:
+      case SByte:
+      case Int16:
+      case Int32:
+        jsonStreamWriter.unquotedValue(valueAsString);
+        break;
+      case DateTime:
+      case DateTimeOffset:
+        // Although JSON escaping is (and should be) done in the JSON
+        // serializer, we backslash-escape the forward slash here explicitly
+        // because it is not required to escape it in JSON but in OData.
+        jsonStreamWriter.stringValueRaw(valueAsString == null ? null : valueAsString.replace("/", "\\/"));
+        break;
+      default:
+        jsonStreamWriter.stringValueRaw(valueAsString);
+        break;
+      }
+    }
+  }
+
+  protected static void appendPropertyMetadata(final JsonStreamWriter jsonStreamWriter, final EdmType type) throws IOException, EdmException {
+    jsonStreamWriter.name(FormatJson.METADATA)
+        .beginObject()
+        .namedStringValueRaw(FormatJson.TYPE, type.getNamespace() + Edm.DELIMITER + type.getName())
+        .endObject();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonServiceDocumentProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonServiceDocumentProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonServiceDocumentProducer.java
new file mode 100644
index 0000000..3dce13c
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonServiceDocumentProducer.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmEntitySetInfo;
+import org.apache.olingo.odata2.api.edm.EdmServiceMetadata;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.exception.ODataException;
+import org.apache.olingo.odata2.core.ep.util.FormatJson;
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Writes the  OData service document in JSON.
+ * @author SAP AG
+ */
+public class JsonServiceDocumentProducer {
+
+  public static void writeServiceDocument(final Writer writer, final Edm edm) throws EntityProviderException {
+    final EdmServiceMetadata serviceMetadata = edm.getServiceMetadata();
+
+    JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
+    try {
+      jsonStreamWriter.beginObject()
+          .name(FormatJson.D)
+          .beginObject()
+          .name(FormatJson.ENTITY_SETS)
+          .beginArray();
+
+      boolean first = true;
+      for (EdmEntitySetInfo info : serviceMetadata.getEntitySetInfos()) {
+        if (first) {
+          first = false;
+        } else {
+          jsonStreamWriter.separator();
+        }
+        jsonStreamWriter.stringValue(info.getEntitySetUri().toASCIIString());
+      }
+
+      jsonStreamWriter.endArray()
+          .endObject()
+          .endObject();
+    } catch (final IOException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    } catch (final ODataException e) {
+      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
+    }
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/ff2b0a0e/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/TombstoneProducer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/TombstoneProducer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/TombstoneProducer.java
new file mode 100644
index 0000000..23f7ad7
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/TombstoneProducer.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * 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.odata2.core.ep.producer;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.apache.olingo.odata2.api.edm.EdmFacets;
+import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
+import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
+import org.apache.olingo.odata2.api.edm.EdmTargetPath;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.api.ep.callback.TombstoneCallback;
+import org.apache.olingo.odata2.core.edm.EdmDateTimeOffset;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityPropertyInfo;
+import org.apache.olingo.odata2.core.ep.util.FormatXml;
+
+/**
+ * @author SAP AG
+ */
+public class TombstoneProducer {
+
+  private String defaultDateString;
+
+  /**
+   * Appends tombstones to an already started feed.
+   * If the list is empty no elements will be appended.
+   * @param writer         same as in feed
+   * @param eia            same as in feed
+   * @param properties     same as in feed
+   * @param deletedEntries data to be appended
+   * @throws EntityProviderException
+   */
+  public void appendTombstones(final XMLStreamWriter writer, final EntityInfoAggregator eia, final EntityProviderWriteProperties properties, final List<Map<String, Object>> deletedEntries) throws EntityProviderException {
+    try {
+      for (Map<String, Object> deletedEntry : deletedEntries) {
+        writer.writeStartElement(TombstoneCallback.NAMESPACE_TOMBSTONE, FormatXml.ATOM_TOMBSTONE_DELETED_ENTRY);
+
+        appendRefAttribute(writer, eia, properties, deletedEntry);
+        appendWhenAttribute(writer, eia, deletedEntry);
+
+        writer.writeEndElement();
+      }
+    } catch (XMLStreamException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    } catch (EdmSimpleTypeException e) {
+      throw new EntityProviderException(EntityProviderException.COMMON, e);
+    }
+  }
+
+  private void appendWhenAttribute(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> deletedEntry) throws XMLStreamException, EdmSimpleTypeException {
+    Object updateDate = null;
+    EntityPropertyInfo updatedInfo = eia.getTargetPathInfo(EdmTargetPath.SYNDICATION_UPDATED);
+    if (updatedInfo != null) {
+      updateDate = deletedEntry.get(updatedInfo.getName());
+    }
+
+    if (updateDate == null) {
+      appendDefaultWhenAttribute(writer);
+    } else {
+      appendCustomWhenAttribute(writer, updateDate, updatedInfo);
+    }
+  }
+
+  private void appendCustomWhenAttribute(final XMLStreamWriter writer, final Object updateDate, final EntityPropertyInfo updatedInfo) throws XMLStreamException, EdmSimpleTypeException {
+    EdmFacets updateFacets = updatedInfo.getFacets();
+    writer.writeAttribute(FormatXml.ATOM_TOMBSTONE_WHEN, EdmDateTimeOffset.getInstance().valueToString(updateDate, EdmLiteralKind.DEFAULT, updateFacets));
+  }
+
+  private void appendRefAttribute(final XMLStreamWriter writer, final EntityInfoAggregator eia, final EntityProviderWriteProperties properties, final Map<String, Object> deletedEntry) throws XMLStreamException, EntityProviderException {
+    String ref = properties.getServiceRoot().toASCIIString() + AtomEntryEntityProducer.createSelfLink(eia, deletedEntry, null);
+    writer.writeAttribute(FormatXml.ATOM_TOMBSTONE_REF, ref);
+  }
+
+  private void appendDefaultWhenAttribute(final XMLStreamWriter writer) throws XMLStreamException, EdmSimpleTypeException {
+    if (defaultDateString == null) {
+      Object defaultDate = new Date();
+      defaultDateString = EdmDateTimeOffset.getInstance().valueToString(defaultDate, EdmLiteralKind.DEFAULT, null);
+    }
+    writer.writeAttribute(FormatXml.ATOM_TOMBSTONE_WHEN, defaultDateString);
+
+  }
+}