http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/4261deb7/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlEntryDeserializer.java
----------------------------------------------------------------------
diff --git 
a/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlEntryDeserializer.java
 
b/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlEntryDeserializer.java
new file mode 100644
index 0000000..06bae41
--- /dev/null
+++ 
b/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlEntryDeserializer.java
@@ -0,0 +1,596 @@
+/*******************************************************************************
+ * 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.client.core.ep.deserializer;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmException;
+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.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.entry.ODataEntry;
+import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.client.api.ep.DeserializerProperties;
+import 
org.apache.olingo.odata2.client.api.ep.callback.OnDeserializeInlineContent;
+import org.apache.olingo.odata2.core.commons.ContentType;
+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.EntityTypeMapping;
+import org.apache.olingo.odata2.core.ep.entry.EntryMetadataImpl;
+import org.apache.olingo.odata2.core.ep.entry.MediaMetadataImpl;
+import org.apache.olingo.odata2.core.ep.entry.ODataEntryImpl;
+import org.apache.olingo.odata2.core.ep.feed.FeedMetadataImpl;
+import org.apache.olingo.odata2.core.ep.feed.ODataDeltaFeedImpl;
+import org.apache.olingo.odata2.core.ep.util.FormatXml;
+
+/**
+ * Atom/XML format reader/consumer for entries.
+ * 
+ * {@link XmlEntryDeserializer} instance can be reused for several
+ * {@link #readEntry(XMLStreamReader, EntityInfoAggregator, 
EntityProviderReadProperties)} calls
+ * but be aware that the instance and their <code>readEntry*</code> methods 
are <b>NOT THREAD SAFE</b>.
+ * 
+ */
+public class XmlEntryDeserializer {
+
+  private ODataEntryImpl readEntryResult;
+  private Map<String, Object> properties;
+  private MediaMetadataImpl mediaMetadata;
+  private EntryMetadataImpl entryMetadata;
+  private EntityTypeMapping typeMappings;
+  private String currentHandledStartTagName;
+
+  /**
+   * Deserializes payload entry
+   * @param reader
+   * @param eia
+   * @param readProperties
+   * @param isInline
+   * @return ODataEntry
+   * @throws EntityProviderException
+   */
+  public ODataEntry readEntry(final XMLStreamReader reader, final 
EntityInfoAggregator eia,
+      final DeserializerProperties readProperties, final boolean isInline) 
throws EntityProviderException {
+    try {
+      initialize(readProperties);
+
+      if (isInline) {
+        setETag(reader);
+      }
+
+      while (reader.hasNext() && !isEntryEndTag(reader)) {
+        reader.nextTag();
+        if (reader.isStartElement()) {
+          handleStartedTag(reader, eia, readProperties);
+        }
+      }
+
+      return readEntryResult;
+    } catch (XMLStreamException e) {
+      throw new 
EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
+          .getSimpleName()), e);
+    } catch (EdmException e) {
+      throw new 
EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
+          .getSimpleName()), e);
+    }
+  }
+
+  private boolean isEntryEndTag(final XMLStreamReader reader) {
+    return reader.isEndElement()
+        && Edm.NAMESPACE_ATOM_2005.equals(reader.getNamespaceURI())
+        && FormatXml.ATOM_ENTRY.equals(reader.getLocalName());
+  }
+
+  /**
+   * Initializes the {@link XmlEntryDeserializer} to be ready for reading an 
entry.
+   * @param readProperties
+   * @throws EntityProviderException
+   */
+  private void initialize(final DeserializerProperties readProperties) throws 
EntityProviderException {
+    properties = new HashMap<String, Object>();
+    mediaMetadata = new MediaMetadataImpl();
+    entryMetadata = new EntryMetadataImpl();
+    
+    readEntryResult = new ODataEntryImpl(properties, mediaMetadata, 
entryMetadata, null);
+    typeMappings = EntityTypeMapping.create(readProperties.getTypeMappings());
+  }
+
+  private void handleStartedTag(final XMLStreamReader reader, final 
EntityInfoAggregator eia,
+      final DeserializerProperties readProperties)
+      throws EntityProviderException, XMLStreamException, EdmException {
+
+    currentHandledStartTagName = reader.getLocalName();
+
+    if (FormatXml.ATOM_ID.equals(currentHandledStartTagName)) {
+      readId(reader);
+    } else if (FormatXml.ATOM_ENTRY.equals(currentHandledStartTagName)) {
+      readEntry(reader);
+    } else if (FormatXml.ATOM_LINK.equals(currentHandledStartTagName)) {
+      readLink(reader, eia, readProperties);
+    } else if (FormatXml.ATOM_CONTENT.equals(currentHandledStartTagName)) {
+      readContent(reader, eia, readProperties);
+    } else if (FormatXml.M_PROPERTIES.equals(currentHandledStartTagName)) {
+      readProperties(reader, eia, readProperties);
+    } else {
+      readCustomElement(reader, currentHandledStartTagName, eia, 
readProperties);
+    }
+  }
+
+  private void readCustomElement(final XMLStreamReader reader, final String 
tagName, //NOSONAR
+      final EntityInfoAggregator eia,
+      final DeserializerProperties readProperties)
+      throws EdmException, EntityProviderException, XMLStreamException { 
//NOSONAR
+    EntityPropertyInfo targetPathInfo = eia.getTargetPathInfo(tagName);
+    NamespaceContext nsctx = reader.getNamespaceContext();
+
+    boolean skipTag = true;
+    if (!Edm.NAMESPACE_ATOM_2005.equals(reader.getName().getNamespaceURI())) {
+
+      if (targetPathInfo != null) {
+        final String customPrefix = 
targetPathInfo.getCustomMapping().getFcNsPrefix();
+        final String customNamespaceURI = 
targetPathInfo.getCustomMapping().getFcNsUri();
+
+        if (customPrefix != null && customNamespaceURI != null) {
+          String xmlPrefix = nsctx.getPrefix(customNamespaceURI);
+          String xmlNamespaceUri = reader.getNamespaceURI(customPrefix);
+
+          if (customNamespaceURI.equals(xmlNamespaceUri) && 
customPrefix.equals(xmlPrefix)) { //NOSONAR
+            skipTag = false;
+            reader.require(XMLStreamConstants.START_ELEMENT, 
customNamespaceURI, tagName);
+            final String text = reader.getElementText();
+            reader.require(XMLStreamConstants.END_ELEMENT, customNamespaceURI, 
tagName);
+
+            final EntityPropertyInfo propertyInfo = 
getValidatedPropertyInfo(eia, tagName);
+            final Class<?> typeMapping = 
typeMappings.getMappingClass(propertyInfo.getName());
+            final EdmSimpleType type = (EdmSimpleType) propertyInfo.getType();
+            final Object value = type.valueOfString(text, 
EdmLiteralKind.DEFAULT,
+                readProperties == null || readProperties.isValidatingFacets() 
? propertyInfo.getFacets() : null,
+                typeMapping == null ? type.getDefaultType() : typeMapping);
+            properties.put(tagName, value);
+          }
+        }
+      } else {
+        throw new 
EntityProviderException(EntityProviderException.INVALID_PROPERTY.addContent(tagName));
+      }
+    }
+
+    if (skipTag) {
+      skipStartedTag(reader);
+    }
+  }
+
+  /**
+   * Skip the tag to which the {@link XMLStreamReader} currently points.
+   * Therefore it is read until an end element tag with current local name is 
found.
+   * 
+   * @param reader
+   * @throws XMLStreamException
+   */
+  private void skipStartedTag(final XMLStreamReader reader) throws 
XMLStreamException {
+    final String name = reader.getLocalName();
+    int read = 1;
+    while (read > 0 && reader.hasNext()) {
+      reader.next();
+      if (reader.hasName() && name.equals(reader.getLocalName())) {
+        if (reader.isEndElement()) {
+          read--;
+        } else if (reader.isStartElement()) {
+          read++;
+        }
+      }
+    }
+  }
+
+  private void readEntry(final XMLStreamReader reader) throws 
XMLStreamException {
+    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, 
FormatXml.ATOM_ENTRY);
+    setETag(reader);
+  }
+
+  private void setETag(final XMLStreamReader reader) {
+    final String etag = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, 
FormatXml.M_ETAG);
+    entryMetadata.setEtag(etag);
+  }
+
+  /**
+   * 
+   * @param reader
+   * @param eia
+   * @param readProperties
+   * @throws EntityProviderException
+   * @throws XMLStreamException
+   * @throws EdmException
+   */
+  private void readLink(final XMLStreamReader reader, final 
EntityInfoAggregator eia,
+      final DeserializerProperties readProperties) throws 
EntityProviderException, XMLStreamException,
+      EdmException {
+    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, 
FormatXml.ATOM_LINK);
+
+    final String rel = reader.getAttributeValue(null, FormatXml.ATOM_REL);
+    final String uri = reader.getAttributeValue(null, FormatXml.ATOM_HREF);
+    final String type = reader.getAttributeValue(null, FormatXml.ATOM_TYPE);
+    final String etag = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, 
FormatXml.M_ETAG);
+
+    // read to next tag to check if <link> contains any further tags
+    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, 
FormatXml.ATOM_LINK);
+    reader.nextTag();
+
+    if (rel == null || uri == null) {
+      throw new 
EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE.addContent(
+          FormatXml.ATOM_HREF + "' and/or '" + 
FormatXml.ATOM_REL).addContent(FormatXml.ATOM_LINK));
+    } else if (rel.startsWith(Edm.NAMESPACE_REL_2007_08)) {
+      final String navigationPropertyName = 
rel.substring(Edm.NAMESPACE_REL_2007_08.length());
+      entryMetadata.putAssociationUri(navigationPropertyName, uri);
+    } else if (rel.equals(Edm.LINK_REL_EDIT_MEDIA)) {
+      mediaMetadata.setEditLink(uri);
+      mediaMetadata.setEtag(etag);
+    }
+
+    if (!reader.isEndElement() && rel != null && 
rel.startsWith(Edm.NAMESPACE_REL_2007_08)) {
+      readInlineContent(reader, eia, readProperties, type, rel);
+    }
+  }
+
+  /**
+   * Inline content was found and {@link XMLStreamReader} already points to 
<m:inline> tag.
+   * 
+   * @param reader
+   * @param eia
+   * @param readProperties
+   * @param atomLinkType the atom <code>type</code> attribute value of the 
<code>link</code> tag
+   * @param atomLinkRel the atom <code>rel</code> attribute value of the 
<code>link</code> tag
+   * @throws XMLStreamException
+   * @throws EntityProviderException
+   * @throws EdmException
+   */
+  private void readInlineContent(final XMLStreamReader reader, final 
EntityInfoAggregator eia,
+      final DeserializerProperties readProperties,
+      final String atomLinkType, final String atomLinkRel)
+      throws XMLStreamException, EntityProviderException, EdmException {
+
+    //
+    String navigationPropertyName = 
atomLinkRel.substring(Edm.NAMESPACE_REL_2007_08.length());
+
+    EdmNavigationProperty navigationProperty =
+        (EdmNavigationProperty) 
eia.getEntityType().getProperty(navigationPropertyName);
+    EdmEntitySet entitySet = 
eia.getEntitySet().getRelatedEntitySet(navigationProperty);
+    EntityInfoAggregator inlineEia = EntityInfoAggregator.create(entitySet);
+
+    final DeserializerProperties inlineProperties = 
createInlineProperties(readProperties, navigationProperty);
+
+    // validations
+    boolean isFeed = isInlineFeedValidated(reader, atomLinkType, 
navigationProperty);
+
+    List<ODataEntry> inlineEntries = new ArrayList<ODataEntry>();
+
+    while (!(reader.isEndElement() && 
Edm.NAMESPACE_M_2007_08.equals(reader.getNamespaceURI()) && FormatXml.M_INLINE
+        .equals(reader.getLocalName()))) {
+
+      if (reader.isStartElement() && 
Edm.NAMESPACE_ATOM_2005.equals(reader.getNamespaceURI())
+          && FormatXml.ATOM_ENTRY.equals(reader.getLocalName())) {
+        XmlEntryDeserializer xec = new XmlEntryDeserializer();
+        ODataEntry inlineEntry = xec.readEntry(reader, inlineEia, 
inlineProperties, true);
+        inlineEntries.add(inlineEntry);
+      }
+      // next tag
+      reader.next();
+    }
+
+    updateReadProperties(navigationPropertyName, isFeed, inlineEntries);
+
+    reader.require(XMLStreamConstants.END_ELEMENT, Edm.NAMESPACE_M_2007_08, 
FormatXml.M_INLINE);
+  }
+
+  /**
+   * Updates the read properties ({@link #properties}) for this {@link 
ReadEntryResult} ({@link #readEntryResult}).
+   * 
+   * @param readProperties
+   * @param navigationPropertyName
+   * @param navigationProperty
+   * @param isFeed
+   * @param inlineEntries
+   * @throws EntityProviderException
+   */
+  private void updateReadProperties(final String navigationPropertyName,
+      final boolean isFeed, 
+      final List<ODataEntry> inlineEntries) {
+    Object entry = extractODataEntity(isFeed, inlineEntries);
+    readEntryResult.setContainsInlineEntry(true);
+    properties.put(navigationPropertyName, entry);
+   
+  }
+
+  
+
+  /**
+   * Get a list of {@link ODataEntry}, an empty list, a single {@link 
ODataEntry} or <code>NULL</code> based on
+   * <code>isFeed</code> value and <code>inlineEntries</code> content.
+   * 
+   * @param isFeed
+   * @param inlineEntries
+   * @return
+   */
+  private Object extractODataEntity(final boolean isFeed, final 
List<ODataEntry> inlineEntries) {
+    if (isFeed) {
+      return new ODataDeltaFeedImpl(inlineEntries, new FeedMetadataImpl());
+    } else if (!inlineEntries.isEmpty()) {
+      return inlineEntries.get(0);
+    }
+    return null;
+  }
+
+
+  /**
+   * Create {@link EntityProviderReadProperties} which can be used for reading 
of inline properties/entrys of navigation
+   * links within
+   * this current read entry.
+   * 
+   * @param readProperties
+   * @param navigationProperty
+   * @return
+   * @throws EntityProviderException
+   */
+  private DeserializerProperties createInlineProperties
+  (final DeserializerProperties readProperties,
+      final EdmNavigationProperty navigationProperty) throws 
EntityProviderException {
+     final OnDeserializeInlineContent callback = readProperties.getCallback();
+
+     DeserializerProperties currentReadProperties = 
DeserializerProperties.initFrom(readProperties).build();
+    if (callback == null) {
+      return currentReadProperties;
+    } else {
+      try {
+        return callback.receiveReadProperties(currentReadProperties, 
navigationProperty);
+      } catch (ODataApplicationException e) {
+        throw new 
EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
+            .getSimpleName()), e);
+      }
+    }
+  }
+
+  /**
+   * <p>
+   * Inline content was found and {@link XMLStreamReader} already points to 
<code><m:inline> tag</code>.
+   * <br/>
+   * <b>ATTENTION</b>: If {@link XMLStreamReader} does not point to the 
<code><m:inline> tag</code> an exception is
+   * thrown.
+   * </p>
+   * <p>
+   * Check whether it is an inline <code>Feed</code> or <code>Entry</code> and 
validate that...
+   * <ul>
+   * <li>...{@link FormatXml#M_INLINE} tag is correctly set.</li>
+   * <li>...based on {@link EdmMultiplicity} of {@link EdmNavigationProperty} 
all tags are correctly set.</li>
+   * <li>...{@link FormatXml#ATOM_TYPE} tag is correctly set and according to 
{@link FormatXml#ATOM_ENTRY} or
+   * {@link FormatXml#ATOM_FEED} to following tags are available.</li>
+   * </ul>
+   * 
+   * For the case that one of above validations fail an {@link 
EntityProviderException} is thrown.
+   * If validation was successful <code>true</code> is returned for 
<code>Feed</code> and <code>false</code> for
+   * <code>Entry</code>
+   * multiplicity.
+   * </p>
+   * 
+   * @param reader xml content reader which already points to <code><m:inline> 
tag</code>
+   * @param eia all necessary information about the entity
+   * @param type the atom type attribute value of the <code>link</code> tag
+   * @param navigationProperty the navigation property name of the entity
+   * @return <code>true</code> for <code>Feed</code> and <code>false</code> 
for <code>Entry</code>
+   * @throws EntityProviderException is thrown if at least one validation 
fails.
+   * @throws EdmException if edm access fails
+   */
+  private boolean isInlineFeedValidated(final XMLStreamReader reader,
+      final String type, final EdmNavigationProperty navigationProperty) 
throws EntityProviderException, EdmException {
+    boolean isFeed = false;
+    try {
+      reader.require(XMLStreamConstants.START_ELEMENT, 
Edm.NAMESPACE_M_2007_08, FormatXml.M_INLINE);
+      //
+      ContentType cType = ContentType.parse(type);
+      if (cType == null) {
+        throw new 
EntityProviderException(EntityProviderException.INVALID_INLINE_CONTENT.addContent("xml
 data"));
+      }
+      EdmMultiplicity navigationMultiplicity = 
navigationProperty.getMultiplicity();
+
+      switch (navigationMultiplicity) {
+      case MANY:
+        validateFeedTags(reader, cType);
+        isFeed = true;
+        break;
+      case ONE:
+      case ZERO_TO_ONE:
+        validateEntryTags(reader, cType);
+        break;
+      }
+    } catch (XMLStreamException e) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_INLINE_CONTENT.addContent("xml
 data"), e);
+    }
+    return isFeed;
+  }
+
+  private void validateEntryTags(final XMLStreamReader reader, final 
ContentType cType) throws XMLStreamException,
+      EntityProviderException {
+    if 
(FormatXml.ATOM_ENTRY.equals(cType.getParameters().get(FormatXml.ATOM_TYPE))) {
+      int next = reader.nextTag();
+      if (XMLStreamConstants.START_ELEMENT == next) {
+        reader.require(XMLStreamConstants.START_ELEMENT, 
Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_ENTRY);
+      } else {
+        reader.require(XMLStreamConstants.END_ELEMENT, 
Edm.NAMESPACE_M_2007_08, FormatXml.M_INLINE);
+      }
+    } else {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_INLINE_CONTENT.addContent("entry"));
+    }
+  }
+
+  private void validateFeedTags(final XMLStreamReader reader, final 
ContentType cType) throws XMLStreamException,
+      EntityProviderException {
+    if 
(FormatXml.ATOM_FEED.equals(cType.getParameters().get(FormatXml.ATOM_TYPE))) {
+      int next = reader.nextTag();
+      if (XMLStreamConstants.START_ELEMENT == next) {
+        reader.require(XMLStreamConstants.START_ELEMENT, 
Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_FEED);
+      } else {
+        reader.require(XMLStreamConstants.END_ELEMENT, 
Edm.NAMESPACE_M_2007_08, FormatXml.M_INLINE);
+      }
+    } else {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_INLINE_CONTENT.addContent("feed"));
+    }
+  }
+
+  private void readContent(final XMLStreamReader reader, final 
EntityInfoAggregator eia,
+      final DeserializerProperties readProperties)
+      throws EntityProviderException, XMLStreamException, EdmException {
+    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, 
FormatXml.ATOM_CONTENT);
+
+    final String contentType = reader.getAttributeValue(null, 
FormatXml.ATOM_TYPE);
+    final String sourceLink = reader.getAttributeValue(null, 
FormatXml.ATOM_SRC);
+
+    reader.nextTag();
+
+    if (reader.isStartElement() && 
reader.getLocalName().equals(FormatXml.M_PROPERTIES)) {
+      readProperties(reader, eia, readProperties);
+    } else if (reader.isEndElement()) {
+      reader.require(XMLStreamConstants.END_ELEMENT, Edm.NAMESPACE_ATOM_2005, 
FormatXml.ATOM_CONTENT);
+    } else {
+      throw new EntityProviderException(EntityProviderException.INVALID_STATE
+          .addContent("Expected closing 'content' or starting 'properties' but 
found '"
+              + reader.getLocalName() + "'."));
+    }
+
+    mediaMetadata.setContentType(contentType);
+    mediaMetadata.setSourceLink(sourceLink);
+  }
+
+  private void readId(final XMLStreamReader reader) throws 
EntityProviderException, XMLStreamException {
+    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, 
FormatXml.ATOM_ID);
+    entryMetadata.setId(reader.getElementText());
+    reader.require(XMLStreamConstants.END_ELEMENT, Edm.NAMESPACE_ATOM_2005, 
FormatXml.ATOM_ID);
+  }
+
+  private void readProperties(final XMLStreamReader reader, final 
EntityInfoAggregator entitySet, //NOSONAR
+      final DeserializerProperties readProperties)
+      throws XMLStreamException, EdmException, EntityProviderException { 
+    // validate namespace
+    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_M_2007_08, 
FormatXml.M_PROPERTIES);
+    if (entitySet.getEntityType().hasStream()) {
+      // external properties
+      checkCurrentHandledStartTag(FormatXml.M_PROPERTIES);
+    } else {
+      // inline properties
+      checkCurrentHandledStartTag(FormatXml.ATOM_CONTENT);
+    }
+
+    EntityPropertyInfo property;
+    XmlPropertyDeserializer xpc = new XmlPropertyDeserializer();
+
+    String closeTag = null;
+    boolean run = true;
+    reader.next();
+
+    while (run) {
+      if (reader.isStartElement() && closeTag == null) {
+        closeTag = reader.getLocalName();
+        if (isEdmNamespaceProperty(reader)) {
+          if (properties.containsKey(closeTag)) {
+            throw new 
EntityProviderException(EntityProviderException.DOUBLE_PROPERTY.addContent(closeTag));
+          }
+          property = getValidatedPropertyInfo(entitySet, closeTag);
+          final Object value = xpc.readStartedElement(reader, closeTag, 
property, typeMappings, readProperties);
+          properties.put(closeTag, value);
+          closeTag = null;
+        }
+      } else if (reader.isEndElement()) {
+        if (reader.getLocalName().equals(closeTag)) {
+          closeTag = null;
+        } else if (Edm.NAMESPACE_M_2007_08.equals(reader.getNamespaceURI())
+            && FormatXml.M_PROPERTIES.equals(reader.getLocalName())) {
+          run = false;
+        }
+      }
+      reader.next();
+    }
+  }
+
+  /**
+   * Check if the {@link #currentHandledStartTagName} is the same as the 
<code>expectedTagName</code>.
+   * If tag name is not as expected or if {@link #currentHandledStartTagName} 
is not set an
+   * {@link EntityProviderException} is thrown.
+   * 
+   * @param expectedTagName expected name for {@link 
#currentHandledStartTagName}
+   * @throws EntityProviderException if tag name is not as expected or if 
{@link #currentHandledStartTagName} is
+   * <code>NULL</code>.
+   */
+  private void checkCurrentHandledStartTag(final String expectedTagName) 
throws EntityProviderException {
+    if (currentHandledStartTagName == null) {
+      throw new EntityProviderException(EntityProviderException.INVALID_STATE
+          .addContent("No current handled start tag name set."));
+    } else if (!currentHandledStartTagName.equals(expectedTagName)) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_PARENT_TAG.addContent(expectedTagName)
+          .addContent(currentHandledStartTagName));
+    }
+  }
+
+  /**
+   * Checks if property of currently read tag in {@link XMLStreamReader} is 
defined in
+   * <code>edm properties namespace</code> {@value Edm#NAMESPACE_D_2007_08}.
+   * 
+   * If no namespace uri definition is found for namespace prefix of property 
(<code>tag</code>) an exception is thrown.
+   * 
+   * @param reader {@link XMLStreamReader} with position at to checked tag
+   * @return <code>true</code> if property is in <code>edm properties 
namespace</code>, otherwise <code>false</code>.
+   * @throws EntityProviderException If no namespace uri definition is found 
for namespace prefix of property
+   * (<code>tag</code>).
+   */
+  private boolean isEdmNamespaceProperty(final XMLStreamReader reader) throws 
EntityProviderException {
+    final String nsUri = reader.getNamespaceURI();
+    if (nsUri == null) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(reader.getLocalName()));
+    } else {
+      return Edm.NAMESPACE_D_2007_08.equals(nsUri);
+    }
+  }
+
+  /**
+   * Get validated {@link EntityPropertyInfo} for property with given 
<code>name</code>.
+   * If validation fails an {@link EntityProviderException} is thrown.
+   * 
+   * Currently this is the case if no {@link EntityPropertyInfo} if found for 
given <code>name</code>.
+   * 
+   * @param entitySet
+   * @param name
+   * @return valid {@link EntityPropertyInfo} (which is never 
<code>NULL</code>).
+   * @throws EntityProviderException
+   */
+  private EntityPropertyInfo getValidatedPropertyInfo(final 
EntityInfoAggregator entitySet, final String name)
+      throws EntityProviderException {
+    EntityPropertyInfo info = entitySet.getPropertyInfo(name);
+    if (info == null) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_PROPERTY.addContent(name));
+    }
+    return info;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/4261deb7/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlErrorDocumentDeserializer.java
----------------------------------------------------------------------
diff --git 
a/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlErrorDocumentDeserializer.java
 
b/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlErrorDocumentDeserializer.java
new file mode 100644
index 0000000..dd22aa5
--- /dev/null
+++ 
b/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlErrorDocumentDeserializer.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * 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.client.core.ep.deserializer;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.processor.ODataErrorContext;
+import org.apache.olingo.odata2.core.commons.ContentType;
+import org.apache.olingo.odata2.core.commons.XmlHelper;
+import org.apache.olingo.odata2.core.ep.util.FormatXml;
+
+/**
+ * Consuming (read / deserialization) for OData error document in XML format.
+ */
+public class XmlErrorDocumentDeserializer {
+  /**
+   * Map containing language code (language - country) to Locale mapping
+   * based on Locale.getAvailableLocales()
+   * */
+  private static final Map<String, Locale> AVAILABLE_LOCALES = new 
HashMap<String, Locale>();
+  static {
+    Locale[] locales = Locale.getAvailableLocales();
+    for (Locale l : locales) {
+      AVAILABLE_LOCALES.put(l.getLanguage() + "-" + l.getCountry(), l);
+    }
+  }
+
+  /**
+   * Deserialize / read OData error document in ODataErrorContext.
+   * 
+   * @param errorDocument OData error document in XML format
+   * @return created ODataErrorContext based on input stream content.
+   * @throws EntityProviderException if an exception during read / 
deserialization occurs.
+   */
+  public ODataErrorContext readError(final InputStream errorDocument) throws 
EntityProviderException {
+    XMLStreamReader reader = null;
+    EntityProviderException cachedException = null;
+
+    try {
+      reader = XmlHelper.createStreamReader(errorDocument);
+      return parserError(reader);
+    } catch (XMLStreamException e) {
+      cachedException = new 
EntityProviderException(EntityProviderException.INVALID_STATE.addContent(
+          e.getMessage()), e);
+      throw cachedException;
+    } catch (EntityProviderException e) {
+      cachedException = e;
+      throw cachedException;
+    } finally {//NOPMD  - suppressed
+      if (reader != null) {
+        try {
+          reader.close();
+        } catch (XMLStreamException e) {
+          if (cachedException != null) {
+            throw cachedException; //NOSONAR
+          } else {
+            throw new EntityProviderException( //NOSONAR
+                EntityProviderException.EXCEPTION_OCCURRED.addContent(
+                    e.getClass().getSimpleName()), e); 
+          }
+        }
+      }
+    }
+  }
+
+  private ODataErrorContext parserError(final XMLStreamReader reader)
+      throws XMLStreamException, EntityProviderException {
+    // read xml tag
+    reader.require(XMLStreamConstants.START_DOCUMENT, null, null);
+    reader.nextTag();
+
+    // read error tag
+    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_M_2007_08, 
FormatXml.M_ERROR);
+
+    // read error data
+    boolean codeFound = false;
+    boolean messageFound = false;
+    ODataErrorContext errorContext = new ODataErrorContext();
+    while (notFinished(reader)) {
+      reader.nextTag();
+      if (reader.isStartElement()) {
+        String name = reader.getLocalName();
+        if (FormatXml.M_CODE.equals(name)) {
+          codeFound = true;
+          handleCode(reader, errorContext);
+        } else if (FormatXml.M_MESSAGE.equals(name)) {
+          messageFound = true;
+          handleMessage(reader, errorContext);
+        } else if (FormatXml.M_INNER_ERROR.equals(name)) {
+          handleInnerError(reader, errorContext);
+        } else {
+          throw new EntityProviderException(
+              EntityProviderException.INVALID_CONTENT.addContent(name, 
FormatXml.M_ERROR));
+        }
+      }
+    }
+    validate(codeFound, messageFound);
+
+    
errorContext.setContentType(ContentType.APPLICATION_XML.toContentTypeString());
+    return errorContext;
+  }
+
+  private void validate(final boolean codeFound, final boolean messageFound) 
throws EntityProviderException {
+    if (!codeFound) {
+      throw new EntityProviderException(
+          EntityProviderException.MISSING_PROPERTY.addContent("Mandatory 
'code' property not found.'"));
+    } else if (!messageFound) {
+      throw new EntityProviderException(
+          EntityProviderException.MISSING_PROPERTY.addContent("Mandatory 
'message' property not found.'"));
+    }
+  }
+
+  private boolean notFinished(final XMLStreamReader reader) throws 
XMLStreamException {
+    return notFinished(reader, FormatXml.M_ERROR);
+  }
+
+  private boolean notFinished(final XMLStreamReader reader, final String 
tagName) throws XMLStreamException {
+    boolean finished = reader.isEndElement() && 
tagName.equals(reader.getLocalName());
+    return !finished && reader.hasNext();
+  }
+
+  private void handleInnerError(final XMLStreamReader reader, final 
ODataErrorContext errorContext)
+      throws XMLStreamException {
+
+    StringBuilder sb = new StringBuilder();
+    while (notFinished(reader, FormatXml.M_INNER_ERROR)) {
+      if (reader.hasName() && 
!FormatXml.M_INNER_ERROR.equals(reader.getLocalName())) {
+        sb.append("<");
+        if (reader.isEndElement()) {
+          sb.append("/");
+        }
+        sb.append(reader.getLocalName()).append(">");
+      } else if (reader.isCharacters()) {
+        sb.append(reader.getText());
+      }
+      reader.next();
+    }
+
+    errorContext.setInnerError(sb.toString());
+  }
+
+  private void handleMessage(final XMLStreamReader reader, final 
ODataErrorContext errorContext)
+      throws XMLStreamException {
+    String lang = reader.getAttributeValue(Edm.NAMESPACE_XML_1998, 
FormatXml.XML_LANG);
+    errorContext.setLocale(getLocale(lang));
+    String message = reader.getElementText();
+    errorContext.setMessage(message);
+  }
+
+  private void handleCode(final XMLStreamReader reader, final 
ODataErrorContext errorContext)
+      throws XMLStreamException {
+    String code = reader.getElementText();
+    errorContext.setErrorCode(code);
+  }
+
+  private Locale getLocale(final String langValue) {
+    return AVAILABLE_LOCALES.get(langValue);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/4261deb7/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlFeedDeserializer.java
----------------------------------------------------------------------
diff --git 
a/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlFeedDeserializer.java
 
b/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlFeedDeserializer.java
new file mode 100644
index 0000000..f47d303
--- /dev/null
+++ 
b/odata2-lib/odata-client-core/src/main/java/org/apache/olingo/odata2/client/core/ep/deserializer/XmlFeedDeserializer.java
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ * 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.client.core.ep.deserializer;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.apache.olingo.odata2.api.edm.Edm;
+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.entry.DeletedEntryMetadata;
+import org.apache.olingo.odata2.api.ep.entry.ODataEntry;
+import org.apache.olingo.odata2.api.ep.feed.ODataDeltaFeed;
+import org.apache.olingo.odata2.client.api.ep.DeserializerProperties;
+import org.apache.olingo.odata2.core.edm.EdmDateTimeOffset;
+import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
+import org.apache.olingo.odata2.core.ep.entry.DeletedEntryMetadataImpl;
+import org.apache.olingo.odata2.core.ep.feed.FeedMetadataImpl;
+import org.apache.olingo.odata2.core.ep.feed.ODataDeltaFeedImpl;
+import org.apache.olingo.odata2.core.ep.util.FormatXml;
+
+/**
+ * Atom/XML format reader/consumer for feeds.
+ * 
+ * {@link XmlFeedDeserializer} instance use
+ * {@link XmlEntryDeserializer#readEntry(XMLStreamReader, 
EntityInfoAggregator, EntityProviderReadProperties)} for
+ * read/consume of several entries.
+ * 
+ * 
+ */
+public class XmlFeedDeserializer {
+
+  /**
+   * 
+   * @param reader
+   * @param eia
+   * @param readProperties
+   * @return {@link ODataDeltaFeed} object
+   * @throws EntityProviderException
+   */
+  public ODataDeltaFeed readFeed(final XMLStreamReader reader, final 
EntityInfoAggregator eia,
+      final DeserializerProperties readProperties) throws 
EntityProviderException {
+    try {
+      // read xml tag
+      reader.require(XMLStreamConstants.START_DOCUMENT, null, null);
+      reader.nextTag();
+
+      // read feed tag
+      reader.require(XMLStreamConstants.START_ELEMENT, 
Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_FEED);
+      Map<String, String> foundPrefix2NamespaceUri = 
extractNamespacesFromTag(reader);
+      
foundPrefix2NamespaceUri.putAll(readProperties.getValidatedPrefixNamespaceUris());
+      checkAllMandatoryNamespacesAvailable(foundPrefix2NamespaceUri);
+      DeserializerProperties entryReadProperties =
+          DeserializerProperties.initFrom(readProperties)
+          .addValidatedPrefixes(foundPrefix2NamespaceUri).build();
+
+      // read feed data (metadata and entries)
+      return readFeedData(reader, eia, entryReadProperties);
+    } catch (XMLStreamException e) {
+      throw new 
EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
+          .getSimpleName()), e);
+    }
+  }
+
+  /**
+   * Read all feed specific data (like <code>inline count</code> and 
<code>next link</code>) as well as all feed entries
+   * (<code>entry</code>) and delta feed extensions (tombstones).
+   * 
+   * @param reader xml stream reader with xml content to be read
+   * @param eia entity infos for validation and mapping
+   * @param entryReadProperties properties which are used for read of feed.
+   * @return all feed specific data (like <code>inline count</code> and 
<code>next link</code>) as well as all feed
+   * entries (<code>entry</code>).
+   * @throws XMLStreamException if malformed xml is read in stream
+   * @throws EntityProviderException if xml contains invalid data (based on 
odata specification and edm definition)
+   */
+  private ODataDeltaFeed readFeedData(final XMLStreamReader reader, final 
EntityInfoAggregator eia,
+      final DeserializerProperties entryReadProperties) throws 
XMLStreamException, EntityProviderException {
+    FeedMetadataImpl metadata = new FeedMetadataImpl();
+    XmlEntryDeserializer xec = new XmlEntryDeserializer();
+    List<ODataEntry> results = new ArrayList<ODataEntry>();
+    List<DeletedEntryMetadata> deletedEntries = new 
ArrayList<DeletedEntryMetadata>();
+
+    while (reader.hasNext() && !isFeedEndTag(reader)) {
+      if (FormatXml.ATOM_ENTRY.equals(reader.getLocalName())) {
+        ODataEntry entry = xec.readEntry(reader, eia, entryReadProperties, 
true);
+        results.add(entry);
+      } else if 
(FormatXml.ATOM_TOMBSTONE_DELETED_ENTRY.equals(reader.getLocalName())) {
+        reader.require(XMLStreamConstants.START_ELEMENT, 
FormatXml.ATOM_TOMBSTONE_NAMESPACE,
+            FormatXml.ATOM_TOMBSTONE_DELETED_ENTRY);
+
+        DeletedEntryMetadataImpl deletedEntryMetadata = 
readDeletedEntryMetadata(reader);
+        deletedEntries.add(deletedEntryMetadata);
+        reader.next();
+      } else if (FormatXml.M_COUNT.equals(reader.getLocalName())) {
+        reader.require(XMLStreamConstants.START_ELEMENT, 
Edm.NAMESPACE_M_2007_08, FormatXml.M_COUNT);
+        readInlineCount(reader, metadata);
+      } else if (FormatXml.ATOM_LINK.equals(reader.getLocalName())) {
+        reader.require(XMLStreamConstants.START_ELEMENT, 
Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_LINK);
+
+        final String rel = reader.getAttributeValue(null, FormatXml.ATOM_REL);
+        if (FormatXml.ATOM_NEXT_LINK.equals(rel)) {
+          final String uri = reader.getAttributeValue(null, 
FormatXml.ATOM_HREF);
+          metadata.setNextLink(uri);
+        } else if (FormatXml.ATOM_DELTA_LINK.equals(rel)) {
+          final String uri = reader.getAttributeValue(null, 
FormatXml.ATOM_HREF);
+          metadata.setDeltaLink(uri);
+        }
+        reader.next();
+      } else {
+        reader.next();
+      }
+      readTillNextStartTag(reader);
+    }
+    return new ODataDeltaFeedImpl(results, metadata, deletedEntries);
+  }
+
+  private DeletedEntryMetadataImpl readDeletedEntryMetadata(final 
XMLStreamReader reader)
+      throws EntityProviderException {
+    try {
+      DeletedEntryMetadataImpl deletedEntryMetadata = new 
DeletedEntryMetadataImpl();
+
+      String uri = reader.getAttributeValue(null, 
FormatXml.ATOM_TOMBSTONE_REF);
+      String whenStr = reader.getAttributeValue(null, 
FormatXml.ATOM_TOMBSTONE_WHEN);
+      Date when;
+      when = EdmDateTimeOffset.getInstance().valueOfString(whenStr, 
EdmLiteralKind.DEFAULT, null,
+          Date.class);
+
+      deletedEntryMetadata.setUri(uri);
+      deletedEntryMetadata.setWhen(when);
+      return deletedEntryMetadata;
+    } catch (EdmSimpleTypeException e) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_DELETED_ENTRY_METADATA, 
e);
+    }
+  }
+
+  private void readInlineCount(final XMLStreamReader reader, final 
FeedMetadataImpl metadata)
+      throws XMLStreamException,
+      EntityProviderException {
+    String inlineCountString = reader.getElementText();
+    if (inlineCountString != null) {
+      try {
+        int inlineCountNumber = Integer.parseInt(inlineCountString);
+        if (inlineCountNumber >= 0) {
+          metadata.setInlineCount(inlineCountNumber);
+        } else {
+          throw new 
EntityProviderException(EntityProviderException.INLINECOUNT_INVALID
+              .addContent(inlineCountNumber));
+        }
+      } catch (NumberFormatException e) {
+        throw new 
EntityProviderException(EntityProviderException.INLINECOUNT_INVALID.addContent(""),
 e);
+      }
+    }
+  }
+
+  private void readTillNextStartTag(final XMLStreamReader reader) throws 
XMLStreamException {
+    while (reader.hasNext() && !reader.isStartElement()) {
+      reader.next();
+    }
+  }
+
+  private boolean isFeedEndTag(final XMLStreamReader reader) {
+    return reader.isEndElement()
+        && Edm.NAMESPACE_ATOM_2005.equals(reader.getNamespaceURI())
+        && FormatXml.ATOM_FEED.equals(reader.getLocalName());
+  }
+
+  /**
+   * Maps all all found namespaces of current xml tag into a map.
+   * 
+   * @param reader xml reader with current position at a xml tag
+   * @return map with all found namespaces of current xml tag
+   */
+  private Map<String, String> extractNamespacesFromTag(final XMLStreamReader 
reader) {
+    // collect namespaces
+    Map<String, String> foundPrefix2NamespaceUri = new HashMap<String, 
String>();
+    int namespaceCount = reader.getNamespaceCount();
+    for (int i = 0; i < namespaceCount; i++) {
+      String namespacePrefix = reader.getNamespacePrefix(i);
+      String namespaceUri = reader.getNamespaceURI(i);
+
+      foundPrefix2NamespaceUri.put(namespacePrefix, namespaceUri);
+    }
+    return foundPrefix2NamespaceUri;
+  }
+
+  /**
+   * 
+   * @param foundPrefix2NamespaceUri
+   * @throws EntityProviderException
+   */
+  private void checkAllMandatoryNamespacesAvailable(final Map<String, String> 
foundPrefix2NamespaceUri)
+      throws EntityProviderException {
+    if (!foundPrefix2NamespaceUri.containsValue(Edm.NAMESPACE_D_2007_08)) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_D_2007_08));
+    } else if 
(!foundPrefix2NamespaceUri.containsValue(Edm.NAMESPACE_M_2007_08)) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_M_2007_08));
+    } else if 
(!foundPrefix2NamespaceUri.containsValue(Edm.NAMESPACE_ATOM_2005)) {
+      throw new 
EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_ATOM_2005));
+    }
+  }
+}

Reply via email to