You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by jh...@apache.org on 2013/08/23 16:29:02 UTC

[1/4] Batch Support, Validation in Deserialization

Updated Branches:
  refs/heads/master a13366112 -> 45c27419c


http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumerTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumerTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumerTest.java
index dd99c52..8672744 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumerTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumerTest.java
@@ -19,6 +19,7 @@
 package org.apache.olingo.odata2.core.ep.consumer;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -33,11 +34,7 @@ import java.util.TimeZone;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.junit.Assert;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.mockito.Mockito;
-
+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.EdmException;
@@ -60,6 +57,10 @@ import org.apache.olingo.odata2.api.exception.ODataMessageException;
 import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
 import org.apache.olingo.odata2.testutil.helper.StringHelper;
 import org.apache.olingo.odata2.testutil.mock.MockFacade;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
 
 /**
  *  
@@ -232,6 +233,13 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
       "  </m:properties>" +
       "</entry>";
 
+  public XmlEntityConsumerTest() {
+    // CHECKSTYLE:OFF:Regexp
+    System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); //NOSONAR
+    System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); //NOSONAR
+    // CHECKSTYLE:ON
+  }
+
   private static class EmployeeCallback implements OnReadInlineContent {
     List<ODataEntry> employees;
 
@@ -1366,12 +1374,10 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
   }
 
   /**
-   * Missing _d_ namespace at mandatory property/tag (_Version_) results in an exception.
-   * 
+   * Missing _d_ namespace at non-nullable property/tag (_Version_) is allowed.
    * @throws Exception
    */
-  @Test(expected = EntityProviderException.class)
-  public void validationOfNamespacesMissingD_NamespaceAtRequiredTag() throws Exception {
+  public void validationOfNamespacesMissingD_NamespaceAtNonNullableTag() throws Exception {
     String roomWithValidNamespaces =
         "<?xml version='1.0' encoding='UTF-8'?>" +
             "<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xml:base=\"http://localhost:19000/test/\" m:etag=\"W/&quot;1&quot;\">" +
@@ -1387,9 +1393,14 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
             "  </content>" +
             "</entry>";
 
-    EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    final EdmProperty property = (EdmProperty) entitySet.getEntityType().getProperty("Version");
+    EdmFacets facets = property.getFacets();
+    Mockito.when(facets.isNullable()).thenReturn(false);
+
     InputStream reqContent = createContentAsStream(roomWithValidNamespaces);
-    readAndExpectException(entitySet, reqContent, false, EntityProviderException.MISSING_PROPERTY.addContent("Version"));
+    final ODataEntry result = new XmlEntityConsumer().readEntry(entitySet, reqContent, EntityProviderReadProperties.init().mergeSemantic(false).build());
+    assertNotNull(result);
   }
 
   private void readAndExpectException(final EdmEntitySet entitySet, final InputStream reqContent, final MessageReference messageReference) throws ODataMessageException {
@@ -1512,7 +1523,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("Employees('1')/$value", properties.get("ImageUrl"));
   }
@@ -1557,7 +1568,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("Employees('1')/$value", properties.get("ImageUrl"));
   }
@@ -1591,7 +1602,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
@@ -1626,7 +1637,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
@@ -1639,7 +1650,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
   @SuppressWarnings("unchecked")
   public void testReadEntryMissingKeyProperty() throws Exception {
     // prepare
-    EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees");
+    final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees");
     InputStream contentBody = createContentAsStream(EMPLOYEE_1_XML.replace("<d:EmployeeId>1</d:EmployeeId>", ""));
 
     // execute
@@ -1651,7 +1662,6 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals(8, properties.size());
 
     assertNull(properties.get("EmployeeId"));
-    //    assertEquals("1", properties.get("EmployeeId"));
     assertEquals("Walter Winter", properties.get("EmployeeName"));
     assertEquals("1", properties.get("ManagerId"));
     assertEquals("1", properties.get("RoomId"));
@@ -1665,41 +1675,28 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
 
-  @Test(expected = EntityProviderException.class)
-  public void testReadEntryMissingProperty() throws Exception {
-    // prepare
-    EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees");
-    EdmProperty property = (EdmProperty) entitySet.getEntityType().getProperty("Age");
-    EdmFacets facets = Mockito.mock(EdmFacets.class);
-    Mockito.when(facets.isNullable()).thenReturn(false);
-    Mockito.when(property.getFacets()).thenReturn(facets);
-    String content = EMPLOYEE_1_XML.replace("<d:Age>52</d:Age>", "");
+  @Test
+  public void readEntryNullProperty() throws Exception {
+    final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees");
+    final String content = EMPLOYEE_1_XML.replace("<d:EntryDate>1999-01-01T00:00:00</d:EntryDate>",
+        "<d:EntryDate m:null='true' />");
     InputStream contentBody = createContentAsStream(content);
 
-    // execute
-    try {
-      XmlEntityConsumer xec = new XmlEntityConsumer();
-      ODataEntry result = xec.readEntry(entitySet, contentBody, EntityProviderReadProperties.init().mergeSemantic(false).build());
+    final ODataEntry result = new XmlEntityConsumer().readEntry(entitySet, contentBody, EntityProviderReadProperties.init().mergeSemantic(true).build());
 
-      // verify - not necessary because of thrown exception - but kept to prevent eclipse warning about unused variables
-      Map<String, Object> properties = result.getProperties();
-      assertEquals(9, properties.size());
-    } catch (EntityProviderException e) {
-      // do some assertions...
-      assertEquals(EntityProviderException.MISSING_PROPERTY.getKey(), e.getMessageReference().getKey());
-      assertEquals("Age", e.getMessageReference().getContent().get(0));
-      // ...and then re-throw
-      throw e;
-    }
+    final Map<String, Object> properties = result.getProperties();
+    assertEquals(9, properties.size());
+    assertTrue(properties.containsKey("EntryDate"));
+    assertNull(properties.get("EntryDate"));
   }
 
   @Test(expected = EntityProviderException.class)
-  public void testReadEntryTooManyValues() throws Exception {
+  public void readEntryTooManyValues() throws Exception {
     // prepare
     EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees");
     String content = EMPLOYEE_1_XML.replace("<d:Age>52</d:Age>", "<d:Age>52</d:Age><d:SomeUnknownTag>SomeUnknownValue</d:SomeUnknownTag>");
@@ -1707,12 +1704,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
 
     // execute
     try {
-      XmlEntityConsumer xec = new XmlEntityConsumer();
-      ODataEntry result = xec.readEntry(entitySet, contentBody, EntityProviderReadProperties.init().mergeSemantic(false).build());
-
-      // verify - not necessary because of thrown exception - but kept to prevent eclipse warning about unused variables
-      Map<String, Object> properties = result.getProperties();
-      assertEquals(9, properties.size());
+      new XmlEntityConsumer().readEntry(entitySet, contentBody, EntityProviderReadProperties.init().mergeSemantic(false).build());
     } catch (EntityProviderException e) {
       // do some assertions...
       assertEquals(EntityProviderException.INVALID_PROPERTY.getKey(), e.getMessageReference().getKey());
@@ -1755,7 +1747,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("69124", city.get("PostalCode"));
     assertEquals("Heidelberg", city.get("CityName"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
@@ -1827,7 +1819,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
@@ -1859,7 +1851,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
@@ -1891,7 +1883,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
@@ -1961,7 +1953,7 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
     assertEquals("69124", city.get("PostalCode"));
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Short.valueOf("52"), properties.get("Age"));
-    assertEquals(Long.valueOf(915148800000l), properties.get("EntryDate"));
+    assertEquals(Long.valueOf(915148800000L), properties.get("EntryDate"));
     assertEquals("/male_1_WinterW.jpg", properties.get("ImageUrl"));
   }
 
@@ -2013,7 +2005,30 @@ public class XmlEntityConsumerTest extends AbstractConsumerTest {
   }
 
   @Test
-  public void testReadEntryRooms() throws Exception {
+  public void readIncompleteEntry() throws Exception {
+    final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    InputStream reqContent = createContentAsStream(ROOM_1_XML);
+    final ODataEntry result = new XmlEntityConsumer().readEntry(entitySet, reqContent, EntityProviderReadProperties.init().build());
+
+    final EntryMetadata entryMetadata = result.getMetadata();
+    assertEquals("http://localhost:19000/test/Rooms('1')", entryMetadata.getId());
+    assertEquals("W/\"1\"", entryMetadata.getEtag());
+    assertNull(entryMetadata.getUri());
+
+    final MediaMetadata mediaMetadata = result.getMediaMetadata();
+    assertEquals(HttpContentType.APPLICATION_XML, mediaMetadata.getContentType());
+    assertNull(mediaMetadata.getSourceLink());
+    assertNull(mediaMetadata.getEditLink());
+    assertNull(mediaMetadata.getEtag());
+
+    final Map<String, Object> properties = result.getProperties();
+    assertEquals(1, properties.size());
+    assertEquals("1", properties.get("Id"));
+    assertFalse(properties.containsKey("Seats"));
+  }
+
+  @Test
+  public void readIncompleteEntryMerge() throws Exception {
     XmlEntityConsumer xec = new XmlEntityConsumer();
 
     EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlLinkConsumerTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlLinkConsumerTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlLinkConsumerTest.java
index d0256a1..84d3416 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlLinkConsumerTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlLinkConsumerTest.java
@@ -47,6 +47,13 @@ public class XmlLinkConsumerTest extends AbstractConsumerTest {
   private static final String SINGLE_LINK =
       "<uri xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">" + SERVICE_ROOT + "Employees('6')</uri>";
 
+  public XmlLinkConsumerTest() {
+    // CHECKSTYLE:OFF:Regexp
+    System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); //NOSONAR
+    System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); //NOSONAR
+    // CHECKSTYLE:ON
+  }
+
   @Test
   public void readLink() throws Exception {
     XMLStreamReader reader = createReaderForTest(SINGLE_LINK, true);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumerTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumerTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumerTest.java
index 805f29e..ec12dc8 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumerTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumerTest.java
@@ -19,30 +19,27 @@
 package org.apache.olingo.odata2.core.ep.consumer;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import java.util.Arrays;
+import java.util.Calendar;
 import java.util.HashMap;
 import java.util.Map;
 
 import javax.xml.stream.XMLStreamReader;
 
-import junit.framework.Assert;
-
-import org.junit.Test;
-
 import org.apache.olingo.odata2.api.edm.Edm;
 import org.apache.olingo.odata2.api.edm.EdmComplexType;
-import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.edm.EdmFacets;
 import org.apache.olingo.odata2.api.edm.EdmProperty;
 import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
 import org.apache.olingo.odata2.api.edm.EdmSimpleTypeKind;
-import org.apache.olingo.odata2.api.edm.EdmStructuralType;
-import org.apache.olingo.odata2.api.edm.EdmTypeKind;
 import org.apache.olingo.odata2.api.ep.EntityProviderException;
 import org.apache.olingo.odata2.testutil.mock.MockFacade;
+import org.junit.Test;
 
 /**
  *  
@@ -50,7 +47,7 @@ import org.apache.olingo.odata2.testutil.mock.MockFacade;
 public class XmlPropertyConsumerTest extends AbstractConsumerTest {
 
   @Test
-  public void testReadIntegerProperty() throws Exception {
+  public void readIntegerProperty() throws Exception {
     String xml = "<Age xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">67</Age>";
     XMLStreamReader reader = createReaderForTest(xml, true);
     final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
@@ -61,7 +58,7 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
   }
 
   @Test
-  public void testReadIntegerPropertyAsLong() throws Exception {
+  public void readIntegerPropertyAsLong() throws Exception {
     String xml = "<Age xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">67</Age>";
     XMLStreamReader reader = createReaderForTest(xml, true);
     final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
@@ -73,7 +70,7 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
   }
 
   @Test
-  public void testReadIntegerPropertyWithNullMapping() throws Exception {
+  public void readIntegerPropertyWithNullMapping() throws Exception {
     String xml = "<Age xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">67</Age>";
     XMLStreamReader reader = createReaderForTest(xml, true);
     final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
@@ -85,7 +82,7 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
   }
 
   @Test
-  public void testReadIntegerPropertyWithEmptyMapping() throws Exception {
+  public void readIntegerPropertyWithEmptyMapping() throws Exception {
     String xml = "<Age xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">67</Age>";
     XMLStreamReader reader = createReaderForTest(xml, true);
     final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
@@ -97,7 +94,7 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
   }
 
   @Test
-  public void testReadStringProperty() throws Exception {
+  public void readStringProperty() throws Exception {
     String xml = "<EmployeeName xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">Max Mustermann</EmployeeName>";
     XMLStreamReader reader = createReaderForTest(xml, true);
     final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("EmployeeName");
@@ -108,29 +105,77 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
   }
 
   @Test
-  public void testReadStringNullProperty() throws Exception {
-    String xml = "<EntryDate xmlns=\"" + Edm.NAMESPACE_D_2007_08
+  public void readStringPropertyEmpty() throws Exception {
+    final String xml = "<EmployeeName xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\" />";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("EmployeeName");
+
+    final Map<String, Object> resultMap = new XmlPropertyConsumer().readProperty(reader, property, false);
+
+    assertTrue(resultMap.containsKey("EmployeeName"));
+    assertEquals("", resultMap.get("EmployeeName"));
+  }
+
+  @Test
+  public void readStringPropertyNull() throws Exception {
+    final String xml = "<EntryDate xmlns=\"" + Edm.NAMESPACE_D_2007_08
         + "\" m:null=\"true\" xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" />";
     XMLStreamReader reader = createReaderForTest(xml, true);
     final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("EntryDate");
 
-    Map<String, Object> resultMap = new XmlPropertyConsumer().readProperty(reader, property, false);
+    final Map<String, Object> resultMap = new XmlPropertyConsumer().readProperty(reader, property, false);
 
+    assertTrue(resultMap.containsKey("EntryDate"));
     assertNull(resultMap.get("EntryDate"));
   }
 
+  @Test
+  public void readStringPropertyNullFalse() throws Exception {
+    final String xml = "<EntryDate xmlns=\"" + Edm.NAMESPACE_D_2007_08
+        + "\" m:null=\"false\" xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\">1970-01-02T00:00:00</EntryDate>";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("EntryDate");
+
+    final Map<String, Object> resultMap = new XmlPropertyConsumer().readProperty(reader, property, false);
+
+    assertEquals(86400000L, ((Calendar) resultMap.get("EntryDate")).getTimeInMillis());
+  }
+
   @Test(expected = EntityProviderException.class)
   public void invalidSimplePropertyName() throws Exception {
-    String xml = "<Invalid xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">67</Invalid>";
+    final String xml = "<Invalid xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\">67</Invalid>";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
+
+    new XmlPropertyConsumer().readProperty(reader, property, false);
+  }
+
+  @Test(expected = EntityProviderException.class)
+  public void invalidNullAttribute() throws Exception {
+    final String xml = "<Age xmlns=\"" + Edm.NAMESPACE_D_2007_08
+        + "\" m:null=\"wrong\" xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" />";
     XMLStreamReader reader = createReaderForTest(xml, true);
     final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
 
     new XmlPropertyConsumer().readProperty(reader, property, false);
   }
 
+  @Test(expected = EntityProviderException.class)
+  public void nullValueNotAllowed() throws Exception {
+    final String xml = "<Age xmlns=\"" + Edm.NAMESPACE_D_2007_08
+        + "\" m:null=\"true\" xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" />";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
+    EdmFacets facets = mock(EdmFacets.class);
+    when(facets.isNullable()).thenReturn(false);
+    when(property.getFacets()).thenReturn(facets);
+
+    new XmlPropertyConsumer().readProperty(reader, property, false);
+  }
+
   @Test
   @SuppressWarnings("unchecked")
-  public void testReadComplexProperty() throws Exception {
+  public void readComplexProperty() throws Exception {
     String xml =
         "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\""
             + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" m:type=\"RefScenario.c_Location\">" +
@@ -154,7 +199,7 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
 
   @Test
   @SuppressWarnings("unchecked")
-  public void testReadComplexPropertyWithLineBreaks() throws Exception {
+  public void readComplexPropertyWithLineBreaks() throws Exception {
     String xml =
         "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\""
             + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" m:type=\"RefScenario.c_Location\">" +
@@ -180,7 +225,7 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
   }
 
   @Test(expected = EntityProviderException.class)
-  public void testReadComplexPropertyInvalidMapping() throws Exception {
+  public void readComplexPropertyInvalidMapping() throws Exception {
     String xml =
         "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\""
             + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" type=\"RefScenario.c_Location\">" +
@@ -196,19 +241,16 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
     try {
       Map<String, Object> resultMap = new XmlPropertyConsumer().readProperty(reader, property, false,
           createTypeMappings("Location", createTypeMappings("City", createTypeMappings("PostalCode", Integer.class))));
-
-      //      Map<String, Object> resultMap = xpc.readProperty(reader, property, false, 
-      //          createTypeMappings("PostalCode", Integer.class));
-      Assert.assertNotNull(resultMap);
+      assertNotNull(resultMap);
     } catch (EntityProviderException e) {
-      Assert.assertTrue(e.getCause() instanceof EdmSimpleTypeException);
+      assertTrue(e.getCause() instanceof EdmSimpleTypeException);
       throw e;
     }
   }
 
   @Test
   @SuppressWarnings("unchecked")
-  public void testReadComplexPropertyWithMappings() throws Exception {
+  public void readComplexPropertyWithMappings() throws Exception {
     String xml =
         "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\""
             + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" m:type=\"RefScenario.c_Location\">" +
@@ -220,30 +262,11 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
             "</Location>";
     XMLStreamReader reader = createReaderForTest(xml, true);
 
-    final EdmComplexType locationComplexType = mock(EdmComplexType.class);
-    when(locationComplexType.getKind()).thenReturn(EdmTypeKind.COMPLEX);
-    when(locationComplexType.getName()).thenReturn("c_Location");
-    when(locationComplexType.getNamespace()).thenReturn("RefScenario");
-    when(locationComplexType.getPropertyNames()).thenReturn(Arrays.asList("City", "Country"));
-
-    final EdmProperty locationComplexProperty = mock(EdmProperty.class);
-    when(locationComplexProperty.getType()).thenReturn(locationComplexType);
-    when(locationComplexProperty.getName()).thenReturn("Location");
-    createProperty("Country", EdmSimpleTypeKind.String, locationComplexType);
-
-    final EdmComplexType cityComplexType = mock(EdmComplexType.class);
-    when(cityComplexType.getKind()).thenReturn(EdmTypeKind.COMPLEX);
-    when(cityComplexType.getName()).thenReturn("c_City");
-    when(cityComplexType.getNamespace()).thenReturn("RefScenario");
-    when(cityComplexType.getPropertyNames()).thenReturn(Arrays.asList("PostalCode", "CityName"));
-
-    final EdmProperty cityProperty = mock(EdmProperty.class);
-    when(cityProperty.getType()).thenReturn(cityComplexType);
-    when(cityProperty.getName()).thenReturn("City");
-    when(locationComplexType.getProperty("City")).thenReturn(cityProperty);
-
-    createProperty("PostalCode", EdmSimpleTypeKind.Int32, cityComplexType);
-    createProperty("CityName", EdmSimpleTypeKind.String, cityComplexType);
+    EdmProperty locationComplexProperty = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Location");
+    EdmProperty cityProperty = (EdmProperty) ((EdmComplexType) locationComplexProperty.getType()).getProperty("City");
+    EdmProperty postalCodeProperty = (EdmProperty) ((EdmComplexType) cityProperty.getType()).getProperty("PostalCode");
+    // Change the type of the PostalCode property to one that allows different Java types.
+    when(postalCodeProperty.getType()).thenReturn(EdmSimpleTypeKind.Int32.getEdmSimpleTypeInstance());
 
     // Execute test
     Map<String, Object> typeMappings =
@@ -262,7 +285,7 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
 
   @SuppressWarnings("unchecked")
   @Test
-  public void testReadComplexPropertyWithNamespace() throws Exception {
+  public void readComplexPropertyWithNamespace() throws Exception {
     String xml =
         "<d:Location m:type=\"RefScenario.c_Location\" " +
             "    xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\"" +
@@ -378,11 +401,55 @@ public class XmlPropertyConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", cityMap.get("CityName"));
   }
 
-  private static EdmProperty createProperty(final String name, final EdmSimpleTypeKind kind, final EdmStructuralType entityType) throws EdmException {
-    final EdmProperty property = mock(EdmProperty.class);
-    when(property.getType()).thenReturn(kind.getEdmSimpleTypeInstance());
-    when(property.getName()).thenReturn(name);
-    when(entityType.getProperty(name)).thenReturn(property);
-    return property;
+  @Test
+  public void complexPropertyNull() throws Exception {
+    String xml = "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08
+        + "\" m:null=\"true\" xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" />";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Location");
+
+    final Map<String, Object> resultMap = new XmlPropertyConsumer().readProperty(reader, property, false);
+
+    assertTrue(resultMap.containsKey("Location"));
+    assertNull(resultMap.get("Location"));
+  }
+
+  @Test(expected = EntityProviderException.class)
+  public void complexPropertyNullValueNotAllowed() throws Exception {
+    final String xml = "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08
+        + "\" m:null=\"true\" xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\" />";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Location");
+    EdmFacets facets = mock(EdmFacets.class);
+    when(facets.isNullable()).thenReturn(false);
+    when(property.getFacets()).thenReturn(facets);
+
+    new XmlPropertyConsumer().readProperty(reader, property, false);
+  }
+
+  @Test(expected = EntityProviderException.class)
+  public void complexPropertyNullWithContent() throws Exception {
+    String xml = "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08
+        + "\" m:null=\"true\" xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\">"
+        + "<City><PostalCode/><CityName/></City><Country>Germany</Country>"
+        + "</Location>";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Location");
+
+    new XmlPropertyConsumer().readProperty(reader, property, false);
+  }
+
+  @Test
+  public void complexPropertyEmpty() throws Exception {
+    final String xml = "<Location xmlns=\"" + Edm.NAMESPACE_D_2007_08 + "\" />";
+    XMLStreamReader reader = createReaderForTest(xml, true);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Location");
+
+    final Map<String, Object> resultMap = new XmlPropertyConsumer().readProperty(reader, property, false);
+
+    assertNotNull(resultMap.get("Location"));
+    @SuppressWarnings("unchecked")
+    final Map<String, Object> innerMap = (Map<String, Object>) resultMap.get("Location");
+    assertTrue(innerMap.isEmpty());
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorServiceTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorServiceTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorServiceTest.java
index 0bf7e82..17cd8fe 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorServiceTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorServiceTest.java
@@ -175,6 +175,6 @@ public class ODataSingleProcessorServiceTest extends BaseTest {
   @Test
   public void defaultSupportedContentTypesForBatch() throws Exception {
     List<String> types = service.getSupportedContentTypes(BatchProcessor.class);
-    assertTrue(types.contains(ContentType.MULTIPART_MIXED.toContentTypeString()));
+    assertTrue(types.contains(ContentType.WILDCARD.toContentTypeString()));
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestAbapCompatibility.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestAbapCompatibility.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestAbapCompatibility.java
index 7df85c6..018dfc2 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestAbapCompatibility.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestAbapCompatibility.java
@@ -235,8 +235,7 @@ public class TestAbapCompatibility extends TestBase {
   }
 
   @Test
-  public void abapTestFilterParser() //copy of ABAP method test_filter_parser
-  {
+  public void abapTestFilterParser() { //copy of ABAP method test_filter_parser 
     //lcl_helper=>veri_expression( iv_expression = 'W/X' iv_expected = '{W/X}' ).
     GetPTF("W/X").aSerialized("{W/X}");
 

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestParserExceptions.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestParserExceptions.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestParserExceptions.java
index edf2550..683861c 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestParserExceptions.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/uri/expression/TestParserExceptions.java
@@ -240,16 +240,14 @@ public class TestParserExceptions extends TestBase {
   }
 
   @Test
-  public void testPMvalidateBinaryOperator() /*PM = Parsermethod*/
-  {
+  public void testPMvalidateBinaryOperator() {/*PM = Parsermethod*/
     //http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter=123 add 'abc'
     //-->Operator 'add' incompatible with operand types 'System.Int32' and 'System.String' at position 4.
     GetPTF("123 add 'abc'").aExType(ExpressionParserException.class).aExKey(ExpressionParserException.INVALID_TYPES_FOR_BINARY_OPERATOR).aExMsgText("Operator \"add\" incompatible with operand types \"System.Uint7\" and \"Edm.String\" at position 5 in \"123 add 'abc'\".");
   }
 
   @Test
-  public void testPMvalidateMethodTypes() /*PM = Parsermethod*/
-  {
+  public void testPMvalidateMethodTypes() /*PM = Parsermethod*/{
     //CASE 1
     //http://services.odata.org/Northwind/Northwind.svc/Products/?$filter=year(327686)
     //-->No applicable function found for 'year' at position 0 with the specified arguments. The functions considered are: year(System.DateTime).
@@ -258,8 +256,8 @@ public class TestParserExceptions extends TestBase {
   }
 
   @Test
-  public void testPMparseFilterString() /*PM = Parsermethod*/
-  {
+  public void testPMparseFilterString() { /*PM = Parsermethod*/
+
     //http://services.odata.org/Northwind/Northwind.svc/Products(1)/Supplier?$filter='123
     //-->Unterminated string literal at position 4 in ''123'.
     GetPTF("'123").aExType(ExpressionParserException.class).aExKey(ExpressionParserException.TOKEN_UNDETERMINATED_STRING).aExMsgText("Unterminated string literal at position 1 in \"'123\".");

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/resources/batchResponse.batch
----------------------------------------------------------------------
diff --git a/odata-core/src/test/resources/batchResponse.batch b/odata-core/src/test/resources/batchResponse.batch
new file mode 100644
index 0000000..ff65bb1
--- /dev/null
+++ b/odata-core/src/test/resources/batchResponse.batch
@@ -0,0 +1,39 @@
+--batch_123
+Content-Type: multipart/mixed;boundary=changeset_12ks93js84d
+
+--changeset_12ks93js84d
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-Id: 1
+
+HTTP/1.1 204 No Content
+
+
+--changeset_12ks93js84d
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+
+HTTP/1.1 201 Created
+DataServiceVersion: 2.0
+Content-Id: 2
+Location: http://localhost:19000/ClientBatchTest/Employees('7')
+Content-Type: application/json
+Content-Length: 918
+
+{"d":{"__metadata":{"id":"http://localhost:19000/ClientBatchTest/Employees('7')","uri":"http://localhost:19000/ClientBatchTest/Employees('7')","type":"RefScenario.Employee","content_type":"application/octet-stream","media_src":"Employees('7')/$value","edit_media":"http://localhost:19000/ClientBatchTest/Employees('7')/$value"},"EmployeeId":"7","EmployeeName":"Employee 7","ManagerId":null,"RoomId":null,"TeamId":null,"Location":{"__metadata":{"type":"RefScenario.c_Location"},"City":{"__metadata":{"type":"RefScenario.c_City"},"PostalCode":null,"CityName":null},"Country":null},"Age":0,"EntryDate":null,"ImageUrl":null,"ne_Manager":{"__deferred":{"uri":"http://localhost:19000/ClientBatchTest/Employees('7')/ne_Manager"}},"ne_Team":{"__deferred":{"uri":"http://localhost:19000/ClientBatchTest/Employees('7')/ne_Team"}},"ne_Room":{"__deferred":{"uri":"http://localhost:19000/ClientBatchTest/Employees('7')/ne_Room"}}}}
+
+--changeset_12ks93js84d--
+
+--batch_123
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 2
+
+HTTP/1.1 200 OK
+DataServiceVersion: 2.0
+Content-Type: text/plain;charset=utf-8
+Content-length: 13
+
+Frederic Fall
+
+--batch_123--
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/BasicBatchTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/BasicBatchTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/BasicBatchTest.java
index 8d7b846..3cfcffb 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/BasicBatchTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/BasicBatchTest.java
@@ -36,11 +36,9 @@ import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
-import org.junit.Test;
-
 import org.apache.olingo.odata2.api.ODataService;
 import org.apache.olingo.odata2.api.batch.BatchHandler;
-import org.apache.olingo.odata2.api.batch.BatchPart;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart;
 import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
 import org.apache.olingo.odata2.api.edm.Edm;
@@ -60,6 +58,7 @@ import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 import org.apache.olingo.odata2.core.processor.ODataSingleProcessorService;
 import org.apache.olingo.odata2.testutil.helper.StringHelper;
 import org.apache.olingo.odata2.testutil.mock.MockFacade;
+import org.junit.Test;
 
 /**
  *  
@@ -141,8 +140,8 @@ public class BasicBatchTest extends AbstractBasicTest {
         pathInfo.setServiceRoot(new URI("http://localhost:19000/odata"));
 
         EntityProviderBatchProperties batchProperties = EntityProviderBatchProperties.init().pathInfo(pathInfo).build();
-        List<BatchPart> batchParts = EntityProvider.parseBatchRequest(requestContentType, content, batchProperties);
-        for (BatchPart batchPart : batchParts) {
+        List<BatchRequestPart> batchParts = EntityProvider.parseBatchRequest(requestContentType, content, batchProperties);
+        for (BatchRequestPart batchPart : batchParts) {
           batchResponseParts.add(handler.handleBatchPart(batchPart));
         }
         batchResponse = EntityProvider.writeBatchResponse(batchResponseParts);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-fit/src/test/java/org/apache/olingo/odata2/fit/client/ClientBatchTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/client/ClientBatchTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/client/ClientBatchTest.java
new file mode 100644
index 0000000..61eb4b2
--- /dev/null
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/client/ClientBatchTest.java
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ * 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.fit.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSet;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
+import org.apache.olingo.odata2.api.client.batch.BatchPart;
+import org.apache.olingo.odata2.api.client.batch.BatchQueryPart;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.api.ep.EntityProvider;
+import org.apache.olingo.odata2.fit.ref.AbstractRefTest;
+import org.apache.olingo.odata2.testutil.helper.StringHelper;
+import org.junit.Test;
+
+public class ClientBatchTest extends AbstractRefTest {
+  private static final String PUT = "PUT";
+  private static final String POST = "POST";
+  private static final String GET = "GET";
+  private static final String BOUNDARY = "batch_123";
+
+  @Test
+  public void testSimpleBatch() throws Exception {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+    BatchPart request = BatchQueryPart.method(GET).uri("$metadata").build();
+    batch.add(request);
+
+    InputStream body = EntityProvider.writeBatchRequest(batch, BOUNDARY);
+    String batchRequestBody = StringHelper.inputStreamToString(body, true);
+    checkMimeHeaders(batchRequestBody);
+    checkBoundaryDelimiters(batchRequestBody);
+    assertTrue(batchRequestBody.contains("GET $metadata HTTP/1.1"));
+
+    HttpResponse batchResponse = execute(batchRequestBody);
+    InputStream responseBody = batchResponse.getEntity().getContent();
+    String contentType = batchResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
+    List<BatchSingleResponse> responses = EntityProvider.parseBatchResponse(responseBody, contentType);
+    for (BatchSingleResponse response : responses) {
+      assertEquals("200", response.getStatusCode());
+      assertEquals("OK", response.getStatusInfo());
+      assertTrue(response.getBody().contains("<edmx:Edmx Version=\"1.0\""));
+      assertEquals("application/xml;charset=utf-8", response.getHeader(HttpHeaders.CONTENT_TYPE));
+      assertNotNull(response.getHeader(HttpHeaders.CONTENT_LENGTH));
+    }
+  }
+
+  @Test
+  public void testChangeSetBatch() throws Exception {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+
+    BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+    Map<String, String> changeSetHeaders = new HashMap<String, String>();
+    changeSetHeaders.put("content-type", "application/json;odata=verbose");
+
+    BatchChangeSetPart changeRequest = BatchChangeSetPart.method(PUT)
+        .uri("Employees('2')/EmployeeName")
+        .body("{\"EmployeeName\":\"Frederic Fall MODIFIED\"}")
+        .headers(changeSetHeaders)
+        .build();
+    changeSet.add(changeRequest);
+    batch.add(changeSet);
+
+    BatchPart request = BatchQueryPart.method(GET)
+        .uri("Employees('2')/EmployeeName/$value")
+        .build();
+    batch.add(request);
+
+    InputStream body = EntityProvider.writeBatchRequest(batch, BOUNDARY);
+    String bodyAsString = StringHelper.inputStreamToString(body, true);
+    checkMimeHeaders(bodyAsString);
+    checkBoundaryDelimiters(bodyAsString);
+
+    assertTrue(bodyAsString.contains("PUT Employees('2')/EmployeeName HTTP/1.1"));
+    assertTrue(bodyAsString.contains("GET Employees('2')/EmployeeName/$value HTTP/1.1"));
+    assertTrue(bodyAsString.contains("content-type: application/json;odata=verbose"));
+
+    HttpResponse batchResponse = execute(bodyAsString);
+    InputStream responseBody = batchResponse.getEntity().getContent();
+    String contentType = batchResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
+    List<BatchSingleResponse> responses = EntityProvider.parseBatchResponse(responseBody, contentType);
+    for (BatchSingleResponse response : responses) {
+      if ("204".equals(response.getStatusCode())) {
+        assertEquals("No Content", response.getStatusInfo());
+      } else if ("200".equals(response.getStatusCode())) {
+        assertEquals("OK", response.getStatusInfo());
+        assertTrue(response.getBody().contains("Frederic Fall MODIFIED"));
+      } else {
+        fail();
+      }
+    }
+
+  }
+
+  @Test
+  public void testContentIdReferencing() throws Exception {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+    BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+    Map<String, String> changeSetHeaders = new HashMap<String, String>();
+    changeSetHeaders.put("content-type", "application/octet-stream");
+    changeSetHeaders.put("Accept", "application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1");
+    BatchChangeSetPart changeRequest = BatchChangeSetPart.method(POST)
+        .uri("Employees")
+        .contentId("1")
+        .body("gAAAAgABwESAAMAAAABAAEA")
+        .headers(changeSetHeaders)
+        .build();
+    changeSet.add(changeRequest);
+
+    changeSetHeaders = new HashMap<String, String>();
+    changeSetHeaders.put("content-type", "application/json;odata=verbose");
+    BatchChangeSetPart changeRequest2 = BatchChangeSetPart.method(PUT)
+        .uri("$1/EmployeeName")
+        .contentId("2")
+        .body("{\"EmployeeName\":\"Frederic Fall MODIFIED\"}")
+        .headers(changeSetHeaders)
+        .build();
+    changeSet.add(changeRequest2);
+    batch.add(changeSet);
+
+    Map<String, String> getRequestHeaders = new HashMap<String, String>();
+    getRequestHeaders.put("content-id", "3");
+    BatchPart request = BatchQueryPart.method(GET)
+        .uri("Employees('7')/EmployeeName")
+        .contentId("3")
+        .headers(getRequestHeaders).build();
+    batch.add(request);
+
+    InputStream body = EntityProvider.writeBatchRequest(batch, BOUNDARY);
+    String bodyAsString = StringHelper.inputStreamToString(body, true);
+    checkMimeHeaders(bodyAsString);
+    checkBoundaryDelimiters(bodyAsString);
+    assertTrue(bodyAsString.contains("POST Employees HTTP/1.1"));
+    assertTrue(bodyAsString.contains("PUT $1/EmployeeName"));
+    assertTrue(bodyAsString.contains("GET Employees('7')/EmployeeName HTTP/1.1"));
+
+    HttpResponse batchResponse = execute(bodyAsString);
+    InputStream responseBody = batchResponse.getEntity().getContent();
+    String contentType = batchResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
+    List<BatchSingleResponse> responses = EntityProvider.parseBatchResponse(responseBody, contentType);
+    for (BatchSingleResponse response : responses) {
+      if ("1".equals(response.getContentId())) {
+        assertEquals("201", response.getStatusCode());
+        assertEquals("Created", response.getStatusInfo());
+      } else if ("2".equals(response.getContentId())) {
+        assertEquals("204", response.getStatusCode());
+        assertEquals("No Content", response.getStatusInfo());
+      } else if ("3".equals(response.getContentId())) {
+        assertEquals("200", response.getStatusCode());
+        assertEquals("OK", response.getStatusInfo());
+      } else {
+        fail();
+      }
+    }
+  }
+
+  @Test
+  public void testErrorBatch() throws Exception {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+    BatchPart request = BatchQueryPart.method(GET)
+        .uri("nonsense")
+        .build();
+    batch.add(request);
+
+    InputStream body = EntityProvider.writeBatchRequest(batch, BOUNDARY);
+    String bodyAsString = StringHelper.inputStreamToString(body, true);
+    checkMimeHeaders(bodyAsString);
+    checkBoundaryDelimiters(bodyAsString);
+
+    assertTrue(bodyAsString.contains("GET nonsense HTTP/1.1"));
+    HttpResponse batchResponse = execute(bodyAsString);
+    InputStream responseBody = batchResponse.getEntity().getContent();
+    String contentType = batchResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
+    List<BatchSingleResponse> responses = EntityProvider.parseBatchResponse(responseBody, contentType);
+    for (BatchSingleResponse response : responses) {
+      assertEquals("404", response.getStatusCode());
+      assertEquals("Not Found", response.getStatusInfo());
+    }
+  }
+
+  private HttpResponse execute(final String body) throws Exception {
+    final HttpPost post = new HttpPost(URI.create(getEndpoint().toString() + "$batch"));
+
+    post.setHeader("Content-Type", "multipart/mixed;boundary=" + BOUNDARY);
+    HttpEntity entity = new StringEntity(body);
+    post.setEntity(entity);
+    HttpResponse response = getHttpClient().execute(post);
+
+    assertNotNull(response);
+    assertEquals(202, response.getStatusLine().getStatusCode());
+
+    return response;
+  }
+
+  private void checkMimeHeaders(final String requestBody) {
+    assertTrue(requestBody.contains("Content-Type: application/http"));
+    assertTrue(requestBody.contains("Content-Transfer-Encoding: binary"));
+  }
+
+  private void checkBoundaryDelimiters(final String requestBody) {
+    assertTrue(requestBody.contains("--" + BOUNDARY));
+    assertTrue(requestBody.contains("--" + BOUNDARY + "--"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/BatchTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/BatchTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/BatchTest.java
index 1614b5c..845c4e5 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/BatchTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/BatchTest.java
@@ -91,7 +91,7 @@ public class BatchTest extends AbstractRefTest {
     assertNotNull(response);
     assertEquals(202, response.getStatusLine().getStatusCode());
 
-    String responseBody = StringHelper.inputStreamToString(response.getEntity().getContent());
+    String responseBody = StringHelper.inputStreamToString(response.getEntity().getContent(), true);
     return responseBody;
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java
index 770372f..2f9be76 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java
@@ -22,10 +22,10 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
 import org.apache.http.HttpResponse;
-import org.junit.Test;
-
 import org.apache.olingo.odata2.api.commons.HttpContentType;
 import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
+import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
+import org.junit.Test;
 
 /**
  * Tests employing the reference scenario changing entities in JSON format.
@@ -46,6 +46,14 @@ public class EntryJsonChangeTest extends AbstractRefTest {
   }
 
   @Test
+  public void createEntryMinimal() throws Exception {
+    final String requestBody = "{\"Id\":\"99\"}";
+    final HttpResponse response = postUri("Teams()", requestBody, HttpContentType.APPLICATION_JSON, HttpStatusCodes.CREATED);
+    assertFalse(getBody(response).isEmpty());
+    checkUri("Teams('4')?$format=json");
+  }
+
+  @Test
   public void createEntryWithNavigation() throws Exception {
     final String requestBody = "{\"Id\":\"199\",\"Name\":\"Room 199\"}";
     final HttpResponse response = postUri("Buildings('1')/nb_Rooms()", requestBody, HttpContentType.APPLICATION_JSON, HttpStatusCodes.CREATED);
@@ -97,6 +105,7 @@ public class EntryJsonChangeTest extends AbstractRefTest {
     final String requestBody = "{\"Id\":\"99\",\"Name\":\"Building 4\",\"Image\":null,"
         + "\"nb_Rooms\":[{\"Id\":\"201\",\"Name\":\"Room 201\",\"Seats\":1,\"Version\":1,"
         + "\"nr_Employees\":[{\"EmployeeId\":\"99\",\"EmployeeName\":\"Ms X\",\"Age\":22,"
+        + "\"Location\":{\"City\":{\"PostalCode\":null,\"CityName\":null},\"Country\":null},"
         + "\"EntryDate\":\"\\/Date(1424242424242)\\/\","
         + "\"ne_Manager\":{\"__deferred\":{\"uri\":\"" + getEndpoint() + "Managers('1')\"}}}]}]}";
     final HttpResponse response = postUri("Buildings()", requestBody, HttpContentType.APPLICATION_JSON, HttpStatusCodes.CREATED);
@@ -113,8 +122,22 @@ public class EntryJsonChangeTest extends AbstractRefTest {
         + "\"RoomId\":\"1\",\"TeamId\":\"1\","
         + "\"Location\":{\"City\":{\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},"
         + "              \"Country\":\"Germany\"},"
-        + "\"Age\":52,\"EntryDate\":null,\"ImageUrl\":\"http://some.host:80/image.url\"}";
+        + "\"EntryDate\":null,\"ImageUrl\":\"http://some.host:80/image.url\"}";
     putUri("Employees('2')", requestBody, HttpContentType.APPLICATION_JSON, HttpStatusCodes.NO_CONTENT);
     assertEquals("Mister X", getBody(callUri("Employees('2')/EmployeeName/$value")));
+    assertEquals("0", getBody(callUri("Employees('2')/Age/$value")));
+    assertEquals("{\"d\":{\"EntryDate\":null}}", getBody(callUri("Employees('2')/EntryDate?$format=json")));
+  }
+
+  @Test
+  public void patchEntry() throws Exception {
+    final String requestBody = "{\"Location\":"
+        + "{\"City\":{\"PostalCode\":\"69124\",\"CityName\":\"" + CITY_1_NAME + "\"},"
+        + " \"Country\":\"Germany\"},\"EntryDate\":null}";
+    callUri(ODataHttpMethod.PATCH, "Employees('2')", null, null, requestBody, HttpContentType.APPLICATION_JSON, HttpStatusCodes.NO_CONTENT);
+    assertEquals(CITY_1_NAME, getBody(callUri("Employees('2')/Location/City/CityName/$value")));
+    assertEquals("{\"d\":{\"EntryDate\":null}}", getBody(callUri("Employees('2')/EntryDate?$format=json")));
+    assertEquals(EMPLOYEE_2_NAME, getBody(callUri("Employees('2')/EmployeeName/$value")));
+    assertEquals(EMPLOYEE_2_AGE, getBody(callUri("Employees('2')/Age/$value")));
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonCreateInlineTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonCreateInlineTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonCreateInlineTest.java
index 7609d00..6dea2f0 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonCreateInlineTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonCreateInlineTest.java
@@ -288,6 +288,8 @@ public class EntryJsonCreateInlineTest extends AbstractRefJsonTest {
         + "\"RoomId\": \"1\","
         + "\"TeamId\": \"1\","
         + "\"Age\": 52,"
+        + "\"Location\":{\"City\":{\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},"
+        + "              \"Country\":\"Germany\"},"
         + "\"ImageUrl\": \"" + getEndpoint() + "Employees('1')/$value\"}]},"
         + "\"nr_Building\":{\"__deferred\":{\"uri\":\"" + getEndpoint() + "Rooms('1')/nr_Building\"}}}}";
 

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryXmlChangeTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryXmlChangeTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryXmlChangeTest.java
index d2407ba..752feb2 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryXmlChangeTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryXmlChangeTest.java
@@ -19,12 +19,12 @@
 package org.apache.olingo.odata2.fit.ref;
 
 import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo;
+import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 
 import org.apache.http.HttpResponse;
-import org.custommonkey.xmlunit.XMLAssert;
 import org.junit.Test;
 
 import org.apache.olingo.odata2.api.commons.HttpContentType;
@@ -89,9 +89,9 @@ public class EntryXmlChangeTest extends AbstractRefXmlTest {
 
   @Test
   public void createWithLargeProperty() throws Exception {
-    String largeTeamName = StringHelper.generateData(888888);
+    final String largeTeamName = StringHelper.generateData(888888);
     // Create an entry for a type that has no media resource.
-    String requestBody = getBody(callUri("Teams('1')"))
+    final String requestBody = getBody(callUri("Teams('1')"))
         .replace("'1'", "'9'")
         .replace("Id>1", "Id>9")
         .replace("Team 1", largeTeamName)
@@ -105,6 +105,19 @@ public class EntryXmlChangeTest extends AbstractRefXmlTest {
   }
 
   @Test
+  public void createMinimal() throws Exception {
+    final String requestBody = "<entry xmlns=\"" + Edm.NAMESPACE_ATOM_2005 + "\"" + "\n"
+        + "       xmlns:d=\"" + Edm.NAMESPACE_D_2007_08 + "\"" + "\n"
+        + "       xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\">" + "\n"
+        + "  <content><m:properties><d:Id>99</d:Id></m:properties></content>" + "\n"
+        + "</entry>";
+
+    final HttpResponse response = postUri("Teams()", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.CREATED);
+    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
+    assertEquals(getEndpoint() + "Teams('4')", response.getFirstHeader(HttpHeaders.LOCATION).getValue());
+  }
+
+  @Test
   public void createInvalidXml() throws Exception {
     getBody(callUri("Employees('7')", HttpStatusCodes.NOT_FOUND));
 
@@ -207,24 +220,30 @@ public class EntryXmlChangeTest extends AbstractRefXmlTest {
 
   @Test
   public void update() throws Exception {
-    final String requestBody = getBody(callUri("Employees('1')"))
-        .replace("'1'", "'9'")
-        .replace("EmployeeId>1", "EmployeeId>9")
-        .replace(EMPLOYEE_1_NAME, "Mister X")
+    final String requestBody = getBody(callUri("Employees('2')"))
+        .replace("'2'", "'9'")
+        .replace("EmployeeId>2", "EmployeeId>9")
+        .replace(EMPLOYEE_2_NAME, "Mister X")
+        .replace("<d:Age>" + EMPLOYEE_2_AGE + "</d:Age>", "")
+        .replace(">2003-07-01T00:00:00", " m:null='true'>")
         .replaceAll("<link.+?/>", "");
-    final HttpResponse response = callUri(ODataHttpMethod.PUT, "Employees('2')", null, null, requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.NO_CONTENT);
+    final HttpResponse response = callUri(ODataHttpMethod.PUT, "Employees('1')", null, null, requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.NO_CONTENT);
     assertFalse(response.containsHeader(HttpHeaders.LOCATION));
-    assertXpathEvaluatesTo("Mister X", "/atom:entry/m:properties/d:EmployeeName", getBody(callUri("Employees('2')")));
+    final String body = getBody(callUri("Employees('1')"));
+    assertXpathEvaluatesTo("Mister X", "/atom:entry/m:properties/d:EmployeeName", body);
+    assertXpathEvaluatesTo("0", "/atom:entry/m:properties/d:Age", body);
+    assertXpathEvaluatesTo("true", "/atom:entry/m:properties/d:EntryDate/@m:null", body);
   }
 
   @Test
   public void updateUnknownProperty() throws Exception {
-    final String requestBody = getBody(callUri("Employees('1')"))
-        .replace("<d:Age>52</d:Age>", "<d:Age>33</d:Age><d:SomeUnknownTag>SomeUnknownValue</d:SomeUnknownTag>");
+    final String requestBody = getBody(callUri("Employees('2')"))
+        .replace("<d:Age>" + EMPLOYEE_2_AGE + "</d:Age>",
+            "<d:Age>33</d:Age><d:SomeUnknownTag>SomeUnknownValue</d:SomeUnknownTag>");
 
-    putUri("Employees('1')", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.BAD_REQUEST);
+    putUri("Employees('2')", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.BAD_REQUEST);
     // check nothing has changed
-    assertXpathEvaluatesTo("52", "/atom:entry/m:properties/d:Age", getBody(callUri("Employees('1')")));
+    assertXpathEvaluatesTo(EMPLOYEE_2_AGE, "/atom:entry/m:properties/d:Age", getBody(callUri("Employees('2')")));
   }
 
   @Test
@@ -254,7 +273,9 @@ public class EntryXmlChangeTest extends AbstractRefXmlTest {
         + "  </m:properties>" + "\n"
         + "</entry>";
     callUri(ODataHttpMethod.PATCH, "Employees('2')", null, null, requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.NO_CONTENT);
-    assertXpathEvaluatesTo(CITY_1_NAME, "/atom:entry/m:properties/d:Location/d:City/d:CityName", getBody(callUri("Employees('2')")));
+    final String body = getBody(callUri("Employees('2')"));
+    assertXpathEvaluatesTo(CITY_1_NAME, "/atom:entry/m:properties/d:Location/d:City/d:CityName", body);
+    assertXpathEvaluatesTo(EMPLOYEE_2_AGE, "/atom:entry/m:properties/d:Age", body);
 
     requestBody = "<entry xmlns=\"" + Edm.NAMESPACE_ATOM_2005 + "\">" + "\n"
         + "  <content xmlns:d=\"" + Edm.NAMESPACE_D_2007_08 + "\"" + "\n"
@@ -272,9 +293,9 @@ public class EntryXmlChangeTest extends AbstractRefXmlTest {
   public void delete() throws Exception {
     final String uri = "Employees('2')";
     deleteUriOk(uri);
-    final String requestBody = getBody(callUri(uri, HttpStatusCodes.NOT_FOUND));
 
-    XMLAssert.assertXpathExists("/m:error", requestBody);
+    final String requestBody = getBody(callUri(uri, HttpStatusCodes.NOT_FOUND));
+    assertXpathExists("/m:error", requestBody);
     assertXpathEvaluatesTo("Requested entity could not be found.", "/m:error/m:message", requestBody);
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-ref/src/main/java/org/apache/olingo/odata2/ref/edm/ScenarioEdmProvider.java
----------------------------------------------------------------------
diff --git a/odata-ref/src/main/java/org/apache/olingo/odata2/ref/edm/ScenarioEdmProvider.java b/odata-ref/src/main/java/org/apache/olingo/odata2/ref/edm/ScenarioEdmProvider.java
index 70509d0..650125e 100644
--- a/odata-ref/src/main/java/org/apache/olingo/odata2/ref/edm/ScenarioEdmProvider.java
+++ b/odata-ref/src/main/java/org/apache/olingo/odata2/ref/edm/ScenarioEdmProvider.java
@@ -326,7 +326,7 @@ public class ScenarioEdmProvider extends EdmProvider {
     if (NAMESPACE_1.equals(edmFQName.getNamespace())) {
       if (COMPLEX_TYPE_1.getName().equals(edmFQName.getName())) {
         List<Property> properties = new ArrayList<Property>();
-        properties.add(new ComplexProperty().setName("City").setType(COMPLEX_TYPE_2));
+        properties.add(new ComplexProperty().setName("City").setType(COMPLEX_TYPE_2).setFacets(new Facets().setNullable(false)));
         properties.add(new SimpleProperty().setName("Country").setType(EdmSimpleTypeKind.String));
         return new ComplexType().setName(COMPLEX_TYPE_1.getName()).setProperties(properties);
 

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-ref/src/main/java/org/apache/olingo/odata2/ref/processor/ListsProcessor.java
----------------------------------------------------------------------
diff --git a/odata-ref/src/main/java/org/apache/olingo/odata2/ref/processor/ListsProcessor.java b/odata-ref/src/main/java/org/apache/olingo/odata2/ref/processor/ListsProcessor.java
index f93b26d..8534381 100644
--- a/odata-ref/src/main/java/org/apache/olingo/odata2/ref/processor/ListsProcessor.java
+++ b/odata-ref/src/main/java/org/apache/olingo/odata2/ref/processor/ListsProcessor.java
@@ -33,7 +33,7 @@ import java.util.Map;
 
 import org.apache.olingo.odata2.api.ODataCallback;
 import org.apache.olingo.odata2.api.batch.BatchHandler;
-import org.apache.olingo.odata2.api.batch.BatchPart;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart;
 import org.apache.olingo.odata2.api.commons.HttpContentType;
 import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
@@ -348,12 +348,12 @@ public class ListsProcessor extends ODataSingleProcessor {
 
     } else {
       final EntityProviderReadProperties properties = EntityProviderReadProperties.init()
-          .mergeSemantic(true)
+          .mergeSemantic(false)
           .addTypeMappings(getStructuralTypeTypeMap(data, entityType))
           .build();
       final ODataEntry entryValues = parseEntry(entitySet, content, requestContentType, properties);
 
-      setStructuralTypeValuesFromMap(data, entityType, entryValues.getProperties(), true);
+      setStructuralTypeValuesFromMap(data, entityType, entryValues.getProperties(), false);
 
       dataSource.createData(entitySet, data);
 
@@ -1104,7 +1104,7 @@ public class ListsProcessor extends ODataSingleProcessor {
           final List<ODataEntry> relatedValueList = feed.getEntries();
           for (final ODataEntry relatedValues : relatedValueList) {
             Object relatedData = dataSource.newDataObject(relatedEntitySet);
-            setStructuralTypeValuesFromMap(relatedData, relatedEntityType, relatedValues.getProperties(), true);
+            setStructuralTypeValuesFromMap(relatedData, relatedEntityType, relatedValues.getProperties(), false);
             dataSource.createData(relatedEntitySet, relatedData);
             dataSource.writeRelation(entitySet, data, relatedEntitySet, getStructuralTypeValueMap(relatedData, relatedEntityType));
             createInlinedEntities(relatedEntitySet, relatedData, relatedValues);
@@ -1112,7 +1112,7 @@ public class ListsProcessor extends ODataSingleProcessor {
         } else if (relatedValue instanceof ODataEntry) {
           final ODataEntry relatedValueEntry = (ODataEntry) relatedValue;
           Object relatedData = dataSource.newDataObject(relatedEntitySet);
-          setStructuralTypeValuesFromMap(relatedData, relatedEntityType, relatedValueEntry.getProperties(), true);
+          setStructuralTypeValuesFromMap(relatedData, relatedEntityType, relatedValueEntry.getProperties(), false);
           dataSource.createData(relatedEntitySet, relatedData);
           dataSource.writeRelation(entitySet, data, relatedEntitySet, getStructuralTypeValueMap(relatedData, relatedEntityType));
           createInlinedEntities(relatedEntitySet, relatedData, relatedValueEntry);
@@ -1539,15 +1539,12 @@ public class ListsProcessor extends ODataSingleProcessor {
       if (type instanceof EdmEntityType && ((EdmEntityType) type).getKeyProperties().contains(property)) {
         continue;
       }
-      if (property.isSimple()) {
-        final Object value = valueMap.get(propertyName);
-        if (value != null || !merge) {
-          setPropertyValue(data, property, value);
-        }
-      } else {
-        @SuppressWarnings("unchecked")
-        final Map<String, Object> values = (Map<String, Object>) valueMap.get(propertyName);
-        if (values != null || !merge) {
+      if (!merge || valueMap.containsKey(propertyName)) {
+        if (property.isSimple()) {
+          setPropertyValue(data, property, valueMap.get(propertyName));
+        } else {
+          @SuppressWarnings("unchecked")
+          final Map<String, Object> values = (Map<String, Object>) valueMap.get(propertyName);
           setStructuralTypeValuesFromMap(getPropertyValue(data, property), (EdmStructuralType) property.getType(), values, merge);
         }
       }
@@ -1658,8 +1655,8 @@ public class ListsProcessor extends ODataSingleProcessor {
     List<BatchResponsePart> batchResponseParts = new ArrayList<BatchResponsePart>();
     PathInfo pathInfo = getContext().getPathInfo();
     EntityProviderBatchProperties batchProperties = EntityProviderBatchProperties.init().pathInfo(pathInfo).build();
-    List<BatchPart> batchParts = EntityProvider.parseBatchRequest(contentType, content, batchProperties);
-    for (BatchPart batchPart : batchParts) {
+    List<BatchRequestPart> batchParts = EntityProvider.parseBatchRequest(contentType, content, batchProperties);
+    for (BatchRequestPart batchPart : batchParts) {
       batchResponseParts.add(handler.handleBatchPart(batchPart));
     }
     batchResponse = EntityProvider.writeBatchResponse(batchResponseParts);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/helper/StringHelper.java
----------------------------------------------------------------------
diff --git a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/helper/StringHelper.java b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/helper/StringHelper.java
index cc2d3e0..bf2e24e 100644
--- a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/helper/StringHelper.java
+++ b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/helper/StringHelper.java
@@ -25,6 +25,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.util.Random;
 
 import org.apache.http.HttpEntity;
 
@@ -100,15 +101,15 @@ public class StringHelper {
   }
 
   /**
-   * Generate a string with given length containing random upper case characters ([A-Z]).
-   * 
-   * @param len length of to generated string
-   * @return random upper case characters ([A-Z]).
+   * Generates a string with given length containing random upper case characters ([A-Z]).
+   * @param len length of the generated string
+   * @return random upper case characters ([A-Z])
    */
   public static String generateData(final int len) {
+    Random random = new Random();
     StringBuilder b = new StringBuilder(len);
     for (int j = 0; j < len; j++) {
-      char c = (char) (Math.random() * 26 + 65);
+      final char c = (char) ('A' + random.nextInt('Z' - 'A' + 1));
       b.append(c);
     }
     return b.toString();

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java
----------------------------------------------------------------------
diff --git a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java
index 005bb13..9ffe44a 100644
--- a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java
+++ b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java
@@ -148,6 +148,7 @@ class EdmMock {
     EdmProperty roomVersion = createProperty("Version", EdmSimpleTypeKind.Int16, roomType);
     EdmFacets roomVersionFacets = mock(EdmFacets.class);
     when(roomVersionFacets.getConcurrencyMode()).thenReturn(EdmConcurrencyMode.Fixed);
+    when(roomVersionFacets.isNullable()).thenReturn(null);
     when(roomVersion.getFacets()).thenReturn(roomVersionFacets);
     when(roomType.getNavigationPropertyNames()).thenReturn(Arrays.asList("nr_Employees", "nr_Building"));
     createNavigationProperty("nr_Employees", EdmMultiplicity.MANY, roomEntitySet, employeeEntitySet);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TecEdmInfo.java
----------------------------------------------------------------------
diff --git a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TecEdmInfo.java b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TecEdmInfo.java
index acaf2ad..92733af 100644
--- a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TecEdmInfo.java
+++ b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TecEdmInfo.java
@@ -37,7 +37,10 @@ public class TecEdmInfo {
 
   public EdmEntityType getTypeEtAllTypes() {
     try {
-      return edm.getEntityContainer(TechnicalScenarioEdmProvider.ENTITY_CONTAINER_1).getEntitySet(TechnicalScenarioEdmProvider.ES_ALL_TYPES).getEntityType();
+      return edm
+          .getEntityContainer(TechnicalScenarioEdmProvider.ENTITY_CONTAINER_1)
+          .getEntitySet(TechnicalScenarioEdmProvider.ES_ALL_TYPES)
+          .getEntityType();
     } catch (final EdmException e) {
       fail("Error in test setup" + e.getLocalizedMessage());
     }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TechnicalScenarioEdmProvider.java
----------------------------------------------------------------------
diff --git a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TechnicalScenarioEdmProvider.java b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TechnicalScenarioEdmProvider.java
index 8708fd4..703a068 100644
--- a/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TechnicalScenarioEdmProvider.java
+++ b/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/TechnicalScenarioEdmProvider.java
@@ -80,13 +80,22 @@ public class TechnicalScenarioEdmProvider extends EdmProvider {
     final Schema schema = new Schema();
     schema.setNamespace(NAMESPACE_1);
 
-    schema.setEntityTypes(Arrays.asList(getEntityType(ET_KEY_IS_STRING), getEntityType(ET_KEY_IS_INTEGER), getEntityType(ET_COMPLEX_KEY), getEntityType(ET_ALL_TYPES)));
+    schema.setEntityTypes(Arrays.asList(
+        getEntityType(ET_KEY_IS_STRING),
+        getEntityType(ET_KEY_IS_INTEGER),
+        getEntityType(ET_COMPLEX_KEY),
+        getEntityType(ET_ALL_TYPES)));
 
     schema.setComplexTypes(Arrays.asList(getComplexType(CT_ALL_TYPES)));
 
     final EntityContainer entityContainer = new EntityContainer();
     entityContainer.setName(ENTITY_CONTAINER_1).setDefaultEntityContainer(true);
-    entityContainer.setEntitySets(Arrays.asList(getEntitySet(ENTITY_CONTAINER_1, ES_KEY_IS_STRING), getEntitySet(ENTITY_CONTAINER_1, ES_KEY_IS_INTEGER), getEntitySet(ENTITY_CONTAINER_1, ES_COMPLEX_KEY), getEntitySet(ENTITY_CONTAINER_1, ES_ALL_TYPES), getEntitySet(ENTITY_CONTAINER_1, ES_STRING_FACETS)));
+    entityContainer.setEntitySets(Arrays.asList(
+        getEntitySet(ENTITY_CONTAINER_1, ES_KEY_IS_STRING),
+        getEntitySet(ENTITY_CONTAINER_1, ES_KEY_IS_INTEGER),
+        getEntitySet(ENTITY_CONTAINER_1, ES_COMPLEX_KEY),
+        getEntitySet(ENTITY_CONTAINER_1, ES_ALL_TYPES),
+        getEntitySet(ENTITY_CONTAINER_1, ES_STRING_FACETS)));
 
     schema.setEntityContainers(Arrays.asList(entityContainer));
 
@@ -98,7 +107,9 @@ public class TechnicalScenarioEdmProvider extends EdmProvider {
     if (NAMESPACE_1.equals(edmFQName.getNamespace())) {
       if (ET_KEY_IS_STRING.getName().equals(edmFQName.getName())) {
         final List<Property> properties = new ArrayList<Property>();
-        properties.add(new SimpleProperty().setName("KeyString").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setNullable(false)));
+        properties.add(new SimpleProperty().setName("KeyString")
+            .setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setNullable(false)));
 
         final List<NavigationProperty> navigationProperties = new ArrayList<NavigationProperty>();
 
@@ -107,7 +118,9 @@ public class TechnicalScenarioEdmProvider extends EdmProvider {
         return new EntityType().setName(ET_KEY_IS_STRING.getName()).setProperties(properties).setNavigationProperties(navigationProperties).setKey(createKey("KeyString"));
       } else if (ET_KEY_IS_INTEGER.getName().equals(edmFQName.getName())) {
         final List<Property> properties = new ArrayList<Property>();
-        properties.add(new SimpleProperty().setName("KeyInteger").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setNullable(false)));
+        properties.add(new SimpleProperty().setName("KeyInteger")
+            .setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setNullable(false)));
 
         final List<NavigationProperty> navigationProperties = new ArrayList<NavigationProperty>();
         navigationProperties.add(new NavigationProperty().setName("navProperty").setFromRole(ROLE_2).setToRole(ROLE_1).setRelationship(ASSOCIATION_ET1_ET2));
@@ -116,8 +129,12 @@ public class TechnicalScenarioEdmProvider extends EdmProvider {
 
       } else if (ET_COMPLEX_KEY.getName().equals(edmFQName.getName())) {
         final List<Property> properties = new ArrayList<Property>();
-        properties.add(new SimpleProperty().setName("KeyString").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setNullable(false)));
-        properties.add(new SimpleProperty().setName("KeyInteger").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setNullable(false)));
+        properties.add(new SimpleProperty().setName("KeyString")
+            .setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setNullable(false)));
+        properties.add(new SimpleProperty().setName("KeyInteger")
+            .setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setNullable(false)));
 
         return new EntityType().setName(ET_COMPLEX_KEY.getName()).setProperties(properties).setKey(createKey("KeyInteger", "KeyString"));
       } else if (ET_ALL_TYPES.getName().equals(edmFQName.getName())) {
@@ -142,17 +159,23 @@ public class TechnicalScenarioEdmProvider extends EdmProvider {
       } else if (ET_STRING_FACETS.getName().equals(edmFQName.getName())) {
         final List<Property> properties = new ArrayList<Property>();
 
-        properties.add(new SimpleProperty().setName("StringDefaultValue").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setDefaultValue("defaultValue")));
+        properties.add(new SimpleProperty().setName("StringDefaultValue").setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setDefaultValue("defaultValue")));
 
-        properties.add(new SimpleProperty().setName("StringFixedLength").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setFixedLength(true)));
+        properties.add(new SimpleProperty().setName("StringFixedLength").setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setFixedLength(true)));
 
-        properties.add(new SimpleProperty().setName("StringMaxLength").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setMaxLength(15)));
+        properties.add(new SimpleProperty().setName("StringMaxLength").setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setMaxLength(15)));
 
-        properties.add(new SimpleProperty().setName("StringLength").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setMaxLength(15).setFixedLength(true)));
+        properties.add(new SimpleProperty().setName("StringLength").setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setMaxLength(15).setFixedLength(true)));
 
-        properties.add(new SimpleProperty().setName("StringNullable").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setNullable(false)));
+        properties.add(new SimpleProperty().setName("StringNullable").setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setNullable(false)));
 
-        properties.add(new SimpleProperty().setName("StringUnicode").setType(EdmSimpleTypeKind.String).setFacets(new Facets().setUnicode(false)));
+        properties.add(new SimpleProperty().setName("StringUnicode").setType(EdmSimpleTypeKind.String)
+            .setFacets(new Facets().setUnicode(false)));
 
         return new EntityType().setName(ET_STRING_FACETS.getName()).setProperties(properties);
       }


[2/4] Batch Support, Validation in Deserialization

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestParserTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestParserTest.java
index cb2f420..b0babf7 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestParserTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestParserTest.java
@@ -30,11 +30,8 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.List;
 
-import org.junit.BeforeClass;
-import org.junit.Test;
-
 import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.batch.BatchPart;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
 import org.apache.olingo.odata2.api.commons.HttpHeaders;
 import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
 import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
@@ -42,24 +39,27 @@ import org.apache.olingo.odata2.api.processor.ODataRequest;
 import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
 import org.apache.olingo.odata2.core.PathInfoImpl;
 import org.apache.olingo.odata2.testutil.helper.StringHelper;
+import org.junit.BeforeClass;
+import org.junit.Test;
 
 /**
  *  
  */
 public class BatchRequestParserTest {
 
+  private static final String LF = "\r\n";
   private static final String CONTENT_ID_REFERENCE = "NewEmployee";
   private static final String PUT_MIME_HEADER_CONTENT_ID = "BBB_MIMEPART1";
   private static final String PUT_REQUEST_HEADER_CONTENT_ID = "BBB_REQUEST1";
   private static final String SERVICE_ROOT = "http://localhost/odata/";
   private static EntityProviderBatchProperties batchProperties;
   private static final String contentType = "multipart/mixed;boundary=batch_8194-cf13-1f56";
-  private static final String MIME_HEADERS = "Content-Type: application/http" + "\r\n"
-      + "Content-Transfer-Encoding: binary" + "\r\n";
-  private static final String GET_REQUEST = MIME_HEADERS + "\n"
-      + "GET Employees('1')/EmployeeName HTTP/1.1" + "\n"
-      + "\n"
-      + "\n";
+  private static final String MIME_HEADERS = "Content-Type: application/http" + LF
+      + "Content-Transfer-Encoding: binary" + LF;
+  private static final String GET_REQUEST = MIME_HEADERS + LF
+      + "GET Employees('1')/EmployeeName HTTP/1.1" + LF
+      + LF
+      + LF;
 
   @BeforeClass
   public static void setProperties() throws URISyntaxException {
@@ -78,10 +78,10 @@ public class BatchRequestParserTest {
     }
 
     BatchRequestParser parser = new BatchRequestParser(contentType, batchProperties);
-    List<BatchPart> batchParts = parser.parse(in);
-    assertNotNull(batchParts);
-    assertEquals(false, batchParts.isEmpty());
-    for (BatchPart object : batchParts) {
+    List<BatchRequestPart> batchRequestParts = parser.parse(in);
+    assertNotNull(batchRequestParts);
+    assertEquals(false, batchRequestParts.isEmpty());
+    for (BatchRequestPart object : batchRequestParts) {
       if (!object.isChangeSet()) {
         assertEquals(1, object.getRequests().size());
         ODataRequest retrieveRequest = object.getRequests().get(0);
@@ -128,34 +128,34 @@ public class BatchRequestParserTest {
       throw new IOException("Requested file '" + fileName + "' was not found.");
     }
     String content = StringHelper.inputStreamToString(contentInputStream);
-    String batch = "\r\n"
-        + "--batch_8194-cf13-1f56" + "\r" + "\n"
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + "\r\n"
-        + "\r\n"
-        + "--changeset_f980-1cb6-94dd" + "\r\n"
-        + "content-type:     Application/http" + "\r\n"
-        + "content-transfer-encoding: Binary" + "\r\n"
-        + "Content-ID: 1" + "\r\n"
-        + "\r\n"
-        + "POST Employees HTTP/1.1" + "\r\n"
-        + "Content-length: 100000" + "\r\n"
-        + "Content-type: application/octet-stream" + "\r\n"
-        + "\r\n"
-        + content + "\r\n"
-        + "\r\n"
-        + "--changeset_f980-1cb6-94dd--" + "\r\n"
-        + "\r\n"
-        + "--batch_8194-cf13-1f56" + "\r\n"
+    String batch = LF
+        + "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + LF
+        + LF
+        + "--changeset_f980-1cb6-94dd" + LF
+        + "content-type:     Application/http" + LF
+        + "content-transfer-encoding: Binary" + LF
+        + "Content-ID: 1" + LF
+        + LF
+        + "POST Employees HTTP/1.1" + LF
+        + "Content-length: 100000" + LF
+        + "Content-type: application/octet-stream" + LF
+        + LF
+        + content + LF
+        + LF
+        + "--changeset_f980-1cb6-94dd--" + LF
+        + LF
+        + "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\r\n"
-        + "GET Employees?$filter=Age%20gt%2040 HTTP/1.1" + "\r\n"
-        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + "\r\n"
-        + "MaxDataServiceVersion: 2.0" + "\r\n"
-        + "\r\n"
-        + "\r\n"
+        + LF
+        + "GET Employees?$filter=Age%20gt%2040 HTTP/1.1" + LF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + LF
+        + "MaxDataServiceVersion: 2.0" + LF
+        + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
-    List<BatchPart> batchParts = parse(batch);
-    for (BatchPart object : batchParts) {
+    List<BatchRequestPart> BatchRequestParts = parse(batch);
+    for (BatchRequestPart object : BatchRequestParts) {
       if (!object.isChangeSet()) {
         assertEquals(1, object.getRequests().size());
         ODataRequest retrieveRequest = object.getRequests().get(0);
@@ -167,7 +167,7 @@ public class BatchRequestParserTest {
         for (ODataRequest request : requests) {
           assertEquals(ODataHttpMethod.POST, request.getMethod());
           assertEquals("100000", request.getRequestHeaderValue(HttpHeaders.CONTENT_LENGTH.toLowerCase()));
-          assertEquals("1", request.getRequestHeaderValue(BatchConstants.MIME_HEADER_CONTENT_ID.toLowerCase()));
+          assertEquals("1", request.getRequestHeaderValue(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase()));
           assertEquals("application/octet-stream", request.getContentType());
           InputStream body = request.getBody();
           assertEquals(content, StringHelper.inputStreamToString(body));
@@ -186,23 +186,23 @@ public class BatchRequestParserTest {
       throw new IOException("Requested file '" + fileName + "' was not found.");
     }
     StringHelper.inputStreamToString(contentInputStream);
-    String batch = "\r\n"
-        + "--batch_8194-cf13-1f56" + "\r" + "\n"
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + "\r\n"
-        + "\r\n"
-        + "--changeset_f980-1cb6-94dd" + "\r\n"
+    String batch = LF
+        + "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + LF
+        + LF
+        + "--changeset_f980-1cb6-94dd" + LF
         + MIME_HEADERS
-        + "\r\n"
-        + "POST Employees('2') HTTP/1.1" + "\r\n"
-        + "Content-Length: 100" + "\r\n"
-        + "Content-Type: application/octet-stream" + "\r\n"
-        + "\r\n"
-        + "\r\n"
-        + "--changeset_f980-1cb6-94dd--" + "\r\n"
-        + "\r\n"
+        + LF
+        + "POST Employees('2') HTTP/1.1" + LF
+        + "Content-Length: 100" + LF
+        + "Content-Type: application/octet-stream" + LF
+        + LF
+        + LF
+        + "--changeset_f980-1cb6-94dd--" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
-    List<BatchPart> batchParts = parse(batch);
-    for (BatchPart object : batchParts) {
+    List<BatchRequestPart> batchRequestParts = parse(batch);
+    for (BatchRequestPart object : batchRequestParts) {
       if (object.isChangeSet()) {
         List<ODataRequest> requests = object.getRequests();
         for (ODataRequest request : requests) {
@@ -219,21 +219,21 @@ public class BatchRequestParserTest {
   public void testBoundaryParameterWithQuotas() throws BatchException {
     String contentType = "multipart/mixed; boundary=\"batch_1.2+34:2j)0?\"";
 
-    String batch = "--batch_1.2+34:2j)0?" + "\n"
+    String batch = "--batch_1.2+34:2j)0?" + LF
         + GET_REQUEST
         + "--batch_1.2+34:2j)0?--";
     InputStream in = new ByteArrayInputStream(batch.getBytes());
     BatchRequestParser parser = new BatchRequestParser(contentType, batchProperties);
-    List<BatchPart> batchParts = parser.parse(in);
-    assertNotNull(batchParts);
-    assertEquals(false, batchParts.isEmpty());
+    List<BatchRequestPart> batchRequestParts = parser.parse(in);
+    assertNotNull(batchRequestParts);
+    assertEquals(false, batchRequestParts.isEmpty());
   }
 
   @Test(expected = BatchException.class)
   public void testBatchWithInvalidContentType() throws BatchException {
     String invalidContentType = "multipart;boundary=batch_1740-bb84-2f7f";
 
-    String batch = "--batch_1740-bb84-2f7f" + "\n"
+    String batch = "--batch_1740-bb84-2f7f" + LF
         + GET_REQUEST
         + "--batch_1740-bb84-2f7f--";
     InputStream in = new ByteArrayInputStream(batch.getBytes());
@@ -244,7 +244,7 @@ public class BatchRequestParserTest {
   @Test(expected = BatchException.class)
   public void testBatchWithoutBoundaryParameter() throws BatchException {
     String invalidContentType = "multipart/mixed";
-    String batch = "--batch_1740-bb84-2f7f" + "\n"
+    String batch = "--batch_1740-bb84-2f7f" + LF
         + GET_REQUEST
         + "--batch_1740-bb84-2f7f--";
     InputStream in = new ByteArrayInputStream(batch.getBytes());
@@ -255,7 +255,7 @@ public class BatchRequestParserTest {
   @Test(expected = BatchException.class)
   public void testBoundaryParameterWithoutQuota() throws BatchException {
     String invalidContentType = "multipart;boundary=batch_1740-bb:84-2f7f";
-    String batch = "--batch_1740-bb:84-2f7f" + "\n"
+    String batch = "--batch_1740-bb:84-2f7f" + LF
         + GET_REQUEST
         + "--batch_1740-bb:84-2f7f--";
     InputStream in = new ByteArrayInputStream(batch.getBytes());
@@ -265,7 +265,7 @@ public class BatchRequestParserTest {
 
   @Test(expected = BatchException.class)
   public void testWrongBoundaryString() throws BatchException {
-    String batch = "--batch_8194-cf13-1f5" + "\n"
+    String batch = "--batch_8194-cf13-1f5" + LF
         + GET_REQUEST
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
@@ -273,9 +273,9 @@ public class BatchRequestParserTest {
 
   @Test(expected = BatchException.class)
   public void testBoundaryWithoutHyphen() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + GET_REQUEST
-        + "batch_8194-cf13-1f56" + "\n"
+        + "batch_8194-cf13-1f56" + LF
         + GET_REQUEST
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
@@ -283,7 +283,7 @@ public class BatchRequestParserTest {
 
   @Test(expected = BatchException.class)
   public void testNoBoundaryString() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + GET_REQUEST
         //+ no boundary string
         + GET_REQUEST
@@ -293,161 +293,184 @@ public class BatchRequestParserTest {
 
   @Test(expected = BatchException.class)
   public void testBatchBoundaryEqualsChangeSetBoundary() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
-        + "Content-Type: multipart/mixed;boundary=batch_8194-cf13-1f56" + "\n"
-        + "\n"
-        + "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: multipart/mixed;boundary=batch_8194-cf13-1f56" + LF
+        + LF
+        + "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + "PUT Employees('2')/EmployeeName HTTP/1.1" + "\n"
-        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + "\n"
-        + "Content-Type: application/json;odata=verbose" + "\n"
-        + "MaxDataServiceVersion: 2.0" + "\n"
-        + "\n"
-        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + "\n"
-        + "\n"
+        + LF
+        + "PUT Employees('2')/EmployeeName HTTP/1.1" + LF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + LF
+        + "Content-Type: application/json;odata=verbose" + LF
+        + "MaxDataServiceVersion: 2.0" + LF
+        + LF
+        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testNoContentType() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
-        + "Content-Transfer-Encoding: binary" + "\n"
-        + "\n"
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + "\n"
-        + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + LF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testMimeHeaderContentType() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
-        + "Content-Type: text/plain" + "\n"
-        + "Content-Transfer-Encoding: binary" + "\n"
-        + "\n"
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + "\n"
-        + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: text/plain" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + LF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testMimeHeaderEncoding() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
-        + "Content-Type: application/http" + "\n"
-        + "Content-Transfer-Encoding: 8bit" + "\n"
-        + "\n"
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + "\n"
-        + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: 8bit" + LF
+        + LF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testMimeHeaderContentId() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "Content-ID: 1" + "\n"
-        + "\n"
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + "\n"
-        + "\n"
+        + "Content-ID: 1" + LF
+        + LF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testInvalidMethodForBatch() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + "POST Employees('1')/EmployeeName HTTP/1.1" + "\n"
-        + "\n"
+        + LF
+        + "POST Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testNoMethod() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + /*GET*/"Employees('1')/EmployeeName HTTP/1.1" + "\n"
-        + "\n"
+        + LF
+        + /*GET*/"Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testInvalidMethodForChangeset() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + "\n"
-        + "\n"
-        + "--changeset_f980-1cb6-94dd" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + LF
+        + LF
+        + "--changeset_f980-1cb6-94dd" + LF
         + MIME_HEADERS
-        + "\n"
-        + "GET Employees('2')/EmployeeName HTTP/1.1" + "\n"
-        + "Content-Type: application/json;odata=verbose" + "\n"
-        + "MaxDataServiceVersion: 2.0" + "\n"
-        + "\n"
+        + LF
+        + "GET Employees('2')/EmployeeName HTTP/1.1" + LF
+        + "Content-Type: application/json;odata=verbose" + LF
+        + "MaxDataServiceVersion: 2.0" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testInvalidChangeSetBoundary() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
-        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + "\n"
-        + "\n"
-        + "--changeset_f980-1cb6-94d"/*+"d"*/+ "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + LF
+        + LF
+        + "--changeset_f980-1cb6-94d"/*+"d"*/+ LF
         + MIME_HEADERS
-        + "\n"
-        + "POST Employees('2') HTTP/1.1" + "\n"
-        + "Content-Type: application/json;odata=verbose" + "\n"
-        + "MaxDataServiceVersion: 2.0" + "\n"
-        + "\n"
+        + LF
+        + "POST Employees('2') HTTP/1.1" + LF
+        + "Content-Type: application/json;odata=verbose" + LF
+        + "MaxDataServiceVersion: 2.0" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testNoCloseDelimiter() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + GET_REQUEST;
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testNoCloseDelimiter2() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + "\n"
-    /*--batch_8194-cf13-1f56--*/;
+        + LF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + LF;
+    parseInvalidBatchBody(batch);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidUri() throws BatchException {
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + MIME_HEADERS
+        + LF
+        + "GET http://localhost/aa/odata/Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
+        + LF
+        + "--batch_8194-cf13-1f56--";
+    parseInvalidBatchBody(batch);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testUriWithAbsolutePath() throws BatchException {
+    String batch = "--batch_8194-cf13-1f56" + LF
+        + MIME_HEADERS
+        + LF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + LF
+        + LF
+        + LF
+        + "--batch_8194-cf13-1f56--";
     parseInvalidBatchBody(batch);
   }
 
   @Test(expected = BatchException.class)
   public void testNoCloseDelimiter3() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n" + GET_REQUEST + "--batch_8194-cf13-1f56-"/*no hash*/;
+    String batch = "--batch_8194-cf13-1f56" + LF + GET_REQUEST + "--batch_8194-cf13-1f56-"/*no hash*/;
     parseInvalidBatchBody(batch);
   }
 
   @Test
   public void testAcceptHeaders() throws BatchException, URISyntaxException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + "GET Employees('2')/EmployeeName HTTP/1.1" + "\n"
-        + "Content-Length: 100000" + "\n"
-        + "Content-Type: application/json;odata=verbose" + "\n"
-        + "Accept: application/xml;q=0.3, application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.001" + "\n"
-        + "\n"
-        + "\n"
+        + LF
+        + "GET Employees('2')/EmployeeName HTTP/1.1" + LF
+        + "Content-Length: 100000" + LF
+        + "Content-Type: application/json;odata=verbose" + LF
+        + "Accept: application/xml;q=0.3, application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.001" + LF
+        + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
-    List<BatchPart> batchParts = parse(batch);
-    for (BatchPart multipart : batchParts) {
+    List<BatchRequestPart> batchRequestParts = parse(batch);
+    for (BatchRequestPart multipart : batchRequestParts) {
       if (!multipart.isChangeSet()) {
         assertEquals(1, multipart.getRequests().size());
         ODataRequest retrieveRequest = multipart.getRequests().get(0);
@@ -463,18 +486,18 @@ public class BatchRequestParserTest {
 
   @Test
   public void testAcceptHeaders2() throws BatchException, URISyntaxException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + "GET Employees('2')/EmployeeName HTTP/1.1" + "\n"
-        + "Content-Length: 100000" + "\n"
-        + "Content-Type: application/json;odata=verbose" + "\n"
-        + "Accept: */*;q=0.5, application/json;odata=verbose;q=1.0,application/atom+xml" + "\n"
-        + "\n"
-        + "\n"
+        + LF
+        + "GET Employees('2')/EmployeeName HTTP/1.1" + LF
+        + "Content-Length: 100000" + LF
+        + "Content-Type: application/json;odata=verbose" + LF
+        + "Accept: */*;q=0.5, application/json;odata=verbose;q=1.0,application/atom+xml" + LF
+        + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
-    List<BatchPart> batchParts = parse(batch);
-    for (BatchPart multipart : batchParts) {
+    List<BatchRequestPart> batchRequestParts = parse(batch);
+    for (BatchRequestPart multipart : batchRequestParts) {
       if (!multipart.isChangeSet()) {
         assertEquals(1, multipart.getRequests().size());
         ODataRequest retrieveRequest = multipart.getRequests().get(0);
@@ -491,18 +514,18 @@ public class BatchRequestParserTest {
 
   @Test
   public void testAcceptHeaders3() throws BatchException, URISyntaxException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + "GET Employees('2')/EmployeeName HTTP/1.1" + "\n"
-        + "Content-Length: 100000" + "\n"
-        + "Content-Type: application/json;odata=verbose" + "\n"
-        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + "\n"
-        + "\n"
-        + "\n"
+        + LF
+        + "GET Employees('2')/EmployeeName HTTP/1.1" + LF
+        + "Content-Length: 100000" + LF
+        + "Content-Type: application/json;odata=verbose" + LF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + LF
+        + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
-    List<BatchPart> batchParts = parse(batch);
-    for (BatchPart multipart : batchParts) {
+    List<BatchRequestPart> batchRequestParts = parse(batch);
+    for (BatchRequestPart multipart : batchRequestParts) {
       if (!multipart.isChangeSet()) {
         assertEquals(1, multipart.getRequests().size());
         ODataRequest retrieveRequest = multipart.getRequests().get(0);
@@ -521,53 +544,53 @@ public class BatchRequestParserTest {
 
   @Test
   public void testContentId() throws BatchException {
-    String batch = "--batch_8194-cf13-1f56" + "\n"
+    String batch = "--batch_8194-cf13-1f56" + LF
         + MIME_HEADERS
-        + "\n"
-        + "GET Employees HTTP/1.1" + "\n"
-        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + "\n"
-        + "Content-Id: BBB" + "\n"
-        + "\n" + "\n"
-        + "--batch_8194-cf13-1f56" + "\n"
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + "\n"
-        + "\n"
-        + "--changeset_f980-1cb6-94dd" + "\n"
+        + LF
+        + "GET Employees HTTP/1.1" + LF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + LF
+        + "Content-Id: BBB" + LF
+        + LF + LF
+        + "--batch_8194-cf13-1f56" + LF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + LF
+        + LF
+        + "--changeset_f980-1cb6-94dd" + LF
         + MIME_HEADERS
-        + "Content-Id: " + CONTENT_ID_REFERENCE + "\n"
-        + "\n"
-        + "POST Employees HTTP/1.1" + "\n"
-        + "Content-type: application/octet-stream" + "\n"
-        + "\n"
-        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + "\n"
-        + "\n"
-        + "--changeset_f980-1cb6-94dd" + "\n"
+        + "Content-Id: " + CONTENT_ID_REFERENCE + LF
+        + LF
+        + "POST Employees HTTP/1.1" + LF
+        + "Content-type: application/octet-stream" + LF
+        + LF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + LF
+        + LF
+        + "--changeset_f980-1cb6-94dd" + LF
         + MIME_HEADERS
-        + "Content-ID: " + PUT_MIME_HEADER_CONTENT_ID + "\n"
-        + "\n"
-        + "PUT $" + CONTENT_ID_REFERENCE + "/EmployeeName HTTP/1.1" + "\n"
-        + "Content-Type: application/json;odata=verbose" + "\n"
-        + "Content-Id:" + PUT_REQUEST_HEADER_CONTENT_ID + "\n"
-        + "\n"
-        + "{\"EmployeeName\":\"Peter Fall\"}" + "\n"
-        + "--changeset_f980-1cb6-94dd--" + "\n"
-        + "\n"
+        + "Content-ID: " + PUT_MIME_HEADER_CONTENT_ID + LF
+        + LF
+        + "PUT $" + CONTENT_ID_REFERENCE + "/EmployeeName HTTP/1.1" + LF
+        + "Content-Type: application/json;odata=verbose" + LF
+        + "Content-Id:" + PUT_REQUEST_HEADER_CONTENT_ID + LF
+        + LF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + LF
+        + "--changeset_f980-1cb6-94dd--" + LF
+        + LF
         + "--batch_8194-cf13-1f56--";
     InputStream in = new ByteArrayInputStream(batch.getBytes());
     BatchRequestParser parser = new BatchRequestParser(contentType, batchProperties);
-    List<BatchPart> batchParts = parser.parse(in);
-    assertNotNull(batchParts);
-    for (BatchPart multipart : batchParts) {
+    List<BatchRequestPart> batchRequestParts = parser.parse(in);
+    assertNotNull(batchRequestParts);
+    for (BatchRequestPart multipart : batchRequestParts) {
       if (!multipart.isChangeSet()) {
         assertEquals(1, multipart.getRequests().size());
         ODataRequest retrieveRequest = multipart.getRequests().get(0);
-        assertEquals("BBB", retrieveRequest.getRequestHeaderValue(BatchConstants.REQUEST_HEADER_CONTENT_ID.toLowerCase()));
+        assertEquals("BBB", retrieveRequest.getRequestHeaderValue(BatchHelper.REQUEST_HEADER_CONTENT_ID.toLowerCase()));
       } else {
         for (ODataRequest request : multipart.getRequests()) {
           if (ODataHttpMethod.POST.equals(request.getMethod())) {
-            assertEquals(CONTENT_ID_REFERENCE, request.getRequestHeaderValue(BatchConstants.MIME_HEADER_CONTENT_ID.toLowerCase()));
+            assertEquals(CONTENT_ID_REFERENCE, request.getRequestHeaderValue(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase()));
           } else if (ODataHttpMethod.PUT.equals(request.getMethod())) {
-            assertEquals(PUT_MIME_HEADER_CONTENT_ID, request.getRequestHeaderValue(BatchConstants.MIME_HEADER_CONTENT_ID.toLowerCase()));
-            assertEquals(PUT_REQUEST_HEADER_CONTENT_ID, request.getRequestHeaderValue(BatchConstants.REQUEST_HEADER_CONTENT_ID.toLowerCase()));
+            assertEquals(PUT_MIME_HEADER_CONTENT_ID, request.getRequestHeaderValue(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase()));
+            assertEquals(PUT_REQUEST_HEADER_CONTENT_ID, request.getRequestHeaderValue(BatchHelper.REQUEST_HEADER_CONTENT_ID.toLowerCase()));
             assertNull(request.getPathInfo().getRequestUri());
             assertEquals("$" + CONTENT_ID_REFERENCE, request.getPathInfo().getODataSegments().get(0).getPath());
           }
@@ -576,13 +599,13 @@ public class BatchRequestParserTest {
     }
   }
 
-  private List<BatchPart> parse(final String batch) throws BatchException {
+  private List<BatchRequestPart> parse(final String batch) throws BatchException {
     InputStream in = new ByteArrayInputStream(batch.getBytes());
     BatchRequestParser parser = new BatchRequestParser(contentType, batchProperties);
-    List<BatchPart> batchParts = parser.parse(in);
-    assertNotNull(batchParts);
-    assertEquals(false, batchParts.isEmpty());
-    return batchParts;
+    List<BatchRequestPart> batchRequestParts = parser.parse(in);
+    assertNotNull(batchRequestParts);
+    assertEquals(false, batchRequestParts.isEmpty());
+    return batchRequestParts;
   }
 
   private void parseInvalidBatchBody(final String batch) throws BatchException {

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestWriterTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestWriterTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestWriterTest.java
new file mode 100644
index 0000000..61e6709
--- /dev/null
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchRequestWriterTest.java
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * 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.batch;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSet;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
+import org.apache.olingo.odata2.api.client.batch.BatchPart;
+import org.apache.olingo.odata2.api.client.batch.BatchQueryPart;
+import org.apache.olingo.odata2.testutil.helper.StringHelper;
+import org.junit.Test;
+
+public class BatchRequestWriterTest {
+
+  private static final String POST = "POST";
+  private static final String GET = "GET";
+  private static final String PUT = "PUT";
+  private static final String BOUNDARY = "batch_123";
+
+  private void checkMimeHeaders(final String requestBody) {
+    assertTrue(requestBody.contains("Content-Type: application/http"));
+    assertTrue(requestBody.contains("Content-Transfer-Encoding: binary"));
+  }
+
+  @Test
+  public void testBatchQueryPart() throws BatchException, IOException {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+    Map<String, String> headers = new HashMap<String, String>();
+    headers.put("Accept", "application/json");
+    BatchPart request = BatchQueryPart.method(GET).uri("Employees").headers(headers).build();
+    batch.add(request);
+
+    BatchRequestWriter writer = new BatchRequestWriter();
+    InputStream batchRequest = writer.writeBatchRequest(batch, BOUNDARY);
+
+    String requestBody = StringHelper.inputStreamToString(batchRequest);
+    assertNotNull(batchRequest);
+    checkMimeHeaders(requestBody);
+
+    assertTrue(requestBody.contains("--batch_"));
+    assertTrue(requestBody.contains("GET Employees HTTP/1.1"));
+    checkHeaders(headers, requestBody);
+  }
+
+  @Test
+  public void testBatchChangeSet() throws IOException, BatchException {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+    Map<String, String> headers = new HashMap<String, String>();
+    headers.put("content-type", "application/json");
+    BatchChangeSetPart request = BatchChangeSetPart.method(PUT)
+        .uri("Employees('2')")
+        .body("{\"Возраст\":40}")
+        .headers(headers)
+        .contentId("111")
+        .build();
+    BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+    changeSet.add(request);
+    batch.add(changeSet);
+
+    BatchRequestWriter writer = new BatchRequestWriter();
+    InputStream batchRequest = writer.writeBatchRequest(batch, BOUNDARY);
+
+    String requestBody = StringHelper.inputStreamToString(batchRequest, true);
+    assertNotNull(batchRequest);
+    checkMimeHeaders(requestBody);
+    checkHeaders(headers, requestBody);
+
+    assertTrue(requestBody.contains("--batch_"));
+    assertTrue(requestBody.contains("--changeset_"));
+    assertTrue(requestBody.contains("PUT Employees('2') HTTP/1.1"));
+    assertTrue(requestBody.contains("{\"Возраст\":40}"));
+  }
+
+  @Test
+  public void testBatchWithGetAndPost() throws BatchException, IOException {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+    Map<String, String> headers = new HashMap<String, String>();
+    headers.put("Accept", "application/json");
+    BatchPart request = BatchQueryPart.method(GET).uri("Employees").headers(headers).contentId("000").build();
+    batch.add(request);
+
+    Map<String, String> changeSetHeaders = new HashMap<String, String>();
+    changeSetHeaders.put("content-type", "application/json");
+    String body = "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEA";
+    BatchChangeSetPart changeRequest = BatchChangeSetPart.method(POST)
+        .uri("Employees")
+        .body(body)
+        .headers(changeSetHeaders)
+        .contentId("111")
+        .build();
+    BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+    changeSet.add(changeRequest);
+    batch.add(changeSet);
+    BatchRequestWriter writer = new BatchRequestWriter();
+    InputStream batchRequest = writer.writeBatchRequest(batch, BOUNDARY);
+
+    String requestBody = StringHelper.inputStreamToString(batchRequest);
+    assertNotNull(batchRequest);
+    checkMimeHeaders(requestBody);
+
+    checkHeaders(headers, requestBody);
+    checkHeaders(changeSetHeaders, requestBody);
+    assertTrue(requestBody.contains("GET Employees HTTP/1.1"));
+    assertTrue(requestBody.contains("POST Employees HTTP/1.1"));
+    assertTrue(requestBody.contains(body));
+  }
+
+  @Test
+  public void testChangeSetWithContentIdReferencing() throws BatchException, IOException {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+
+    Map<String, String> changeSetHeaders = new HashMap<String, String>();
+    changeSetHeaders.put("content-type", "application/json");
+    String body = "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEA";
+    BatchChangeSetPart changeRequest = BatchChangeSetPart.method(POST)
+        .uri("Employees('2')")
+        .body(body)
+        .headers(changeSetHeaders)
+        .contentId("1")
+        .build();
+    BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+    changeSet.add(changeRequest);
+
+    changeSetHeaders = new HashMap<String, String>();
+    changeSetHeaders.put("content-type", "application/json;odata=verbose");
+    BatchChangeSetPart changeRequest2 = BatchChangeSetPart.method(PUT)
+        .uri("$/ManagerId")
+        .body("{\"ManagerId\":1}")
+        .headers(changeSetHeaders)
+        .contentId("2")
+        .build();
+    changeSet.add(changeRequest2);
+    batch.add(changeSet);
+
+    BatchRequestWriter writer = new BatchRequestWriter();
+    InputStream batchRequest = writer.writeBatchRequest(batch, BOUNDARY);
+
+    String requestBody = StringHelper.inputStreamToString(batchRequest);
+    assertNotNull(batchRequest);
+    checkMimeHeaders(requestBody);
+
+    assertTrue(requestBody.contains("POST Employees('2') HTTP/1.1"));
+    assertTrue(requestBody.contains("PUT $/ManagerId HTTP/1.1"));
+    assertTrue(requestBody.contains(BatchHelper.HTTP_CONTENT_ID + ": 1"));
+    assertTrue(requestBody.contains(BatchHelper.HTTP_CONTENT_ID + ": 2"));
+    assertTrue(requestBody.contains(body));
+
+  }
+
+  @Test
+  public void testBatchWithTwoChangeSets() throws BatchException, IOException {
+    List<BatchPart> batch = new ArrayList<BatchPart>();
+
+    Map<String, String> changeSetHeaders = new HashMap<String, String>();
+    changeSetHeaders.put("content-type", "application/json");
+    changeSetHeaders.put("content-Id", "111");
+    String body = "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEA";
+    BatchChangeSetPart changeRequest = BatchChangeSetPart.method(POST)
+        .uri("Employees")
+        .body(body)
+        .headers(changeSetHeaders)
+        .build();
+    BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+    changeSet.add(changeRequest);
+    batch.add(changeSet);
+
+    Map<String, String> changeSetHeaders2 = new HashMap<String, String>();
+    changeSetHeaders2.put("content-type", "application/json;odata=verbose");
+    changeSetHeaders2.put("content-Id", "222");
+    BatchChangeSetPart changeRequest2 = BatchChangeSetPart.method(PUT)
+        .uri("Employees('2')/ManagerId")
+        .body("{\"ManagerId\":1}")
+        .headers(changeSetHeaders2)
+        .build();
+    BatchChangeSet changeSet2 = BatchChangeSet.newBuilder().build();
+    changeSet2.add(changeRequest2);
+    batch.add(changeSet2);
+
+    BatchRequestWriter writer = new BatchRequestWriter();
+    InputStream batchRequest = writer.writeBatchRequest(batch, BOUNDARY);
+
+    String requestBody = StringHelper.inputStreamToString(batchRequest);
+    assertNotNull(batchRequest);
+    checkMimeHeaders(requestBody);
+
+    assertTrue(requestBody.contains("POST Employees HTTP/1.1"));
+    assertTrue(requestBody.contains("PUT Employees('2')/ManagerId HTTP/1.1"));
+
+    assertTrue(requestBody.contains(body));
+
+  }
+
+  private void checkHeaders(final Map<String, String> headers, final String requestBody) {
+    for (Map.Entry<String, String> header : headers.entrySet()) {
+      assertTrue(requestBody.contains(header.getKey() + ": " + header.getValue()));
+    }
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testBatchQueryPartWithInvalidMethod() throws BatchException, IOException {
+    BatchQueryPart.method(PUT).uri("Employees").build();
+
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testBatchChangeSetPartWithInvalidMethod() throws BatchException, IOException {
+    BatchChangeSetPart.method(GET).uri("Employees('2')").build();
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseParserTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseParserTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseParserTest.java
new file mode 100644
index 0000000..7936051
--- /dev/null
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseParserTest.java
@@ -0,0 +1,280 @@
+/*******************************************************************************
+ * 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.batch;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.junit.Test;
+
+public class BatchResponseParserTest {
+
+  private static final String LF = "\r\n";
+
+  @Test
+  public void testSimpleBatchResponse() throws BatchException {
+    String getResponse = "--batch_123" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + "Content-ID: 1" + LF
+        + LF
+        + "HTTP/1.1 200 OK" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + "Content-Type: text/plain;charset=utf-8" + LF
+        + "Content-length: 22" + LF
+        + LF
+        + "Frederic Fall MODIFIED" + LF
+        + LF
+        + "--batch_123--";
+
+    InputStream in = new ByteArrayInputStream(getResponse.getBytes());
+    BatchResponseParser parser = new BatchResponseParser("multipart/mixed;boundary=batch_123");
+    List<BatchSingleResponse> responses = parser.parse(in);
+    for (BatchSingleResponse response : responses) {
+      assertEquals("200", response.getStatusCode());
+      assertEquals("OK", response.getStatusInfo());
+      assertEquals("text/plain;charset=utf-8", response.getHeaders().get(HttpHeaders.CONTENT_TYPE));
+      assertEquals("22", response.getHeaders().get("Content-length"));
+      assertEquals("1", response.getContentId());
+    }
+  }
+
+  @Test
+  public void testBatchResponse() throws BatchException, IOException {
+    String fileName = "/batchResponse.batch";
+    InputStream in = ClassLoader.class.getResourceAsStream(fileName);
+    if (in == null) {
+      throw new IOException("Requested file '" + fileName + "' was not found.");
+    }
+    BatchResponseParser parser = new BatchResponseParser("multipart/mixed;boundary=batch_123");
+    List<BatchSingleResponse> responses = parser.parse(in);
+    for (BatchSingleResponse response : responses) {
+      if ("1".equals(response.getContentId())) {
+        assertEquals("204", response.getStatusCode());
+        assertEquals("No Content", response.getStatusInfo());
+      } else if ("3".equals(response.getContentId())) {
+        assertEquals("200", response.getStatusCode());
+        assertEquals("OK", response.getStatusInfo());
+      }
+    }
+  }
+
+  @Test
+  public void testResponseToChangeSet() throws BatchException {
+    String putResponse = "--batch_123" + LF
+        + "Content-Type: " + HttpContentType.MULTIPART_MIXED + ";boundary=changeset_12ks93js84d" + LF
+        + LF
+        + "--changeset_12ks93js84d" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + "Content-ID: 1" + LF
+        + LF
+        + "HTTP/1.1 204 No Content" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + LF
+        + LF
+        + "--changeset_12ks93js84d--" + LF
+        + LF
+        + "--batch_123--";
+
+    InputStream in = new ByteArrayInputStream(putResponse.getBytes());
+    BatchResponseParser parser = new BatchResponseParser("multipart/mixed;boundary=batch_123");
+    List<BatchSingleResponse> responses = parser.parse(in);
+    for (BatchSingleResponse response : responses) {
+      assertEquals("204", response.getStatusCode());
+      assertEquals("No Content", response.getStatusInfo());
+      assertEquals("1", response.getContentId());
+    }
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidMimeHeader() throws BatchException {
+    String putResponse = "--batch_123" + LF
+        + "Content-Type: " + HttpContentType.MULTIPART_MIXED + ";boundary=changeset_12ks93js84d" + LF
+        + LF
+        + "--changeset_12ks93js84d" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: 7bit" + LF // Content-Transfer-Encoding must be binary
+        + LF
+        + "HTTP/1.1 No Content" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + LF
+        + LF
+        + "--changeset_12ks93js84d--" + LF
+        + LF
+        + "--batch_123--";
+
+    parseInvalidBatchResponseBody(putResponse);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testMissingMimeHeader() throws BatchException {
+    String putResponse = "--batch_123" + LF
+        + "Content-Type: " + HttpContentType.MULTIPART_MIXED + ";boundary=changeset_12ks93js84d" + LF
+        + LF
+        + "--changeset_12ks93js84d" + LF
+        + LF
+        + "HTTP/1.1 No Content" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + LF
+        + LF
+        + "--changeset_12ks93js84d--" + LF
+        + LF
+        + "--batch_123--";
+
+    parseInvalidBatchResponseBody(putResponse);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidContentType() throws BatchException {
+    String putResponse = "--batch_123" + LF
+        + "Content-Type: " + HttpContentType.MULTIPART_MIXED + LF //Missing boundary parameter
+        + LF
+        + "--changeset_12ks93js84d" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + LF
+        + "HTTP/1.1 No Content" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + LF
+        + LF
+        + "--changeset_12ks93js84d--" + LF
+        + LF
+        + "--batch_123--";
+
+    parseInvalidBatchResponseBody(putResponse);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidStatusLine() throws BatchException {
+    String putResponse = "--batch_123" + LF
+        + "Content-Type: " + HttpContentType.MULTIPART_MIXED + ";boundary=changeset_12ks93js84d" + LF
+        + LF
+        + "--changeset_12ks93js84d" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + LF
+        + "HTTP/1.1 No Content" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + LF
+        + LF
+        + "--changeset_12ks93js84d--" + LF
+        + LF
+        + "--batch_123--";
+
+    parseInvalidBatchResponseBody(putResponse);
+
+  }
+
+  @Test(expected = BatchException.class)
+  public void testMissingCloseDelimiter() throws BatchException {
+    String putResponse = "--batch_123" + LF
+        + "Content-Type: " + HttpContentType.MULTIPART_MIXED + ";boundary=changeset_12ks93js84d" + LF
+        + LF
+        + "--changeset_12ks93js84d" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + LF
+        + "HTTP/1.1 204 No Content" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + LF
+        + LF
+        + "--changeset_12ks93js84d--" + LF
+        + LF;
+
+    parseInvalidBatchResponseBody(putResponse);
+
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidEnteredContentLength() throws BatchException {
+    String getResponse = "--batch_123" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + "Content-ID: 1" + LF
+        + LF
+        + "HTTP/1.1 200 OK" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + "Content-Type: text/plain;charset=utf-8" + LF
+        + "Content-length: 100" + LF
+        + LF
+        + "Frederic Fall" + LF
+        + LF
+        + "--batch_123--";
+
+    parseInvalidBatchResponseBody(getResponse);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidBoundary() throws BatchException {
+    String getResponse = "--batch_321" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + "Content-ID: 1" + LF
+        + LF
+        + "HTTP/1.1 200 OK" + LF
+        + "DataServiceVersion: 2.0" + LF
+        + "Content-Type: text/plain;charset=utf-8" + LF
+        + LF
+        + "Frederic Fall" + LF
+        + LF
+        + "--batch_123--";
+
+    parseInvalidBatchResponseBody(getResponse);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidBoundary2() throws BatchException {
+    String getResponse = "--batch_123" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + LF
+        + "HTTP/1.1 200 OK" + LF
+        + "Content-Type: text/plain;charset=utf-8" + LF
+        + "Content-Length: 13" + LF
+        + LF
+        + "Frederic Fall" + LF
+        + LF
+        + "batch_123" + LF
+        + "Content-Type: application/http" + LF
+        + "Content-Transfer-Encoding: binary" + LF
+        + LF
+        + "HTTP/1.1 200 OK" + LF
+        + "Content-Type: text/plain;charset=utf-8" + LF
+        + LF
+        + "Walter Winter" + LF
+        + LF
+        + "--batch_123--";
+    parseInvalidBatchResponseBody(getResponse);
+  }
+
+  private void parseInvalidBatchResponseBody(final String putResponse) throws BatchException {
+    InputStream in = new ByteArrayInputStream(putResponse.getBytes());
+    BatchResponseParser parser = new BatchResponseParser("multipart/mixed;boundary=batch_123");
+    parser.parse(in);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseWriterTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseWriterTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseWriterTest.java
index f236226..d63ab83 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseWriterTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/batch/BatchResponseWriterTest.java
@@ -125,8 +125,8 @@ public class BatchResponseWriterTest {
     ODataResponse response = ODataResponse.entity("Walter Winter")
         .status(HttpStatusCodes.OK)
         .contentHeader("application/json")
-        .header(BatchConstants.MIME_HEADER_CONTENT_ID, "mimeHeaderContentId123")
-        .header(BatchConstants.REQUEST_HEADER_CONTENT_ID, "requestHeaderContentId123")
+        .header(BatchHelper.MIME_HEADER_CONTENT_ID, "mimeHeaderContentId123")
+        .header(BatchHelper.REQUEST_HEADER_CONTENT_ID, "requestHeaderContentId123")
         .build();
     List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
     responses.add(response);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/parser/EdmParserTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/parser/EdmParserTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/parser/EdmParserTest.java
index 9860fef..da91133 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/parser/EdmParserTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/edm/parser/EdmParserTest.java
@@ -30,8 +30,6 @@ import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
 
-import org.junit.Test;
-
 import org.apache.olingo.odata2.api.edm.Edm;
 import org.apache.olingo.odata2.api.edm.EdmAction;
 import org.apache.olingo.odata2.api.edm.EdmConcurrencyMode;
@@ -61,10 +59,16 @@ import org.apache.olingo.odata2.api.edm.provider.SimpleProperty;
 import org.apache.olingo.odata2.api.ep.EntityProvider;
 import org.apache.olingo.odata2.api.ep.EntityProviderException;
 import org.apache.olingo.odata2.api.processor.ODataResponse;
+import org.apache.olingo.odata2.core.ep.AbstractXmlProducerTestHelper;
 import org.apache.olingo.odata2.testutil.helper.StringHelper;
 import org.apache.olingo.odata2.testutil.mock.EdmTestProvider;
+import org.junit.Test;
 
-public class EdmParserTest {
+public class EdmParserTest extends AbstractXmlProducerTestHelper {
+
+  public EdmParserTest(final StreamWriterImplType type) {
+    super(type);
+  }
 
   private static final String DEFAULT_VALUE = "Photo";
   private static final String FC_TARGET_PATH = "Содержание";
@@ -663,6 +667,7 @@ public class EdmParserTest {
         }
       }
       for (EntityType entityType : schema.getEntityTypes()) {
+        assertEquals(1, entityType.getAnnotationAttributes().size());
         AnnotationAttribute attr = entityType.getAnnotationAttributes().get(0);
         assertEquals("href", attr.getName());
         assertEquals("prefix1", attr.getPrefix());

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/AbstractXmlProducerTestHelper.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/AbstractXmlProducerTestHelper.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/AbstractXmlProducerTestHelper.java
index f86b658..56d7118 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/AbstractXmlProducerTestHelper.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/AbstractXmlProducerTestHelper.java
@@ -37,13 +37,16 @@ public abstract class AbstractXmlProducerTestHelper extends BaseTest {
   public AbstractXmlProducerTestHelper(final StreamWriterImplType type) {
     switch (type) {
     case WOODSTOCKIMPL:
+      System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory"); //NOSONAR
       System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory"); //NOSONAR
       break;
     case SUNINTERNALIMPL:
+      System.setProperty("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl"); //NOSONAR
       System.setProperty("javax.xml.stream.XMLOutputFactory", "com.sun.xml.internal.stream.XMLOutputFactoryImpl"); //NOSONAR
       break;
     default:
       System.setProperty("javax.xml.stream.XMLOutputFactory", "com.sun.xml.internal.stream.XMLOutputFactoryImpl"); //NOSONAR
+      System.setProperty("javax.xml.stream.XMLInputFactory", "com.sun.xml.internal.stream.XMLInputFactoryImpl"); //NOSONAR
       break;
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumerTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumerTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumerTest.java
index 23dcdda..37edd4d 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumerTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumerTest.java
@@ -21,6 +21,7 @@ package org.apache.olingo.odata2.core.ep.consumer;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.InputStream;
@@ -29,13 +30,13 @@ import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
 
-import org.junit.Test;
-
 import org.apache.olingo.odata2.api.edm.EdmEntitySet;
 import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderReadProperties;
 import org.apache.olingo.odata2.api.ep.entry.MediaMetadata;
 import org.apache.olingo.odata2.api.ep.entry.ODataEntry;
 import org.apache.olingo.odata2.testutil.mock.MockFacade;
+import org.junit.Test;
 
 /**
  *  
@@ -86,7 +87,7 @@ public class JsonEntryConsumerTest extends AbstractConsumerTest {
     assertEquals("Heidelberg", city.get("CityName"));
     assertEquals(Integer.valueOf(52), properties.get("Age"));
     Calendar entryDate = (Calendar) properties.get("EntryDate");
-    assertEquals(Long.valueOf(915148800000l), Long.valueOf(entryDate.getTimeInMillis()));
+    assertEquals(915148800000L, entryDate.getTimeInMillis());
     assertEquals(TimeZone.getTimeZone("GMT"), entryDate.getTimeZone());
     assertEquals("Employees('1')/$value", properties.get("ImageUrl"));
 
@@ -135,7 +136,7 @@ public class JsonEntryConsumerTest extends AbstractConsumerTest {
     assertNotNull(properties);
     assertEquals("1", properties.get("Id"));
     assertEquals("Building 1", properties.get("Name"));
-    assertEquals(null, properties.get("Image"));
+    assertNull(properties.get("Image"));
     assertNull(properties.get("nb_Rooms"));
 
     List<String> associationUris = result.getMetadata().getAssociationUris("nb_Rooms");
@@ -153,7 +154,7 @@ public class JsonEntryConsumerTest extends AbstractConsumerTest {
     assertNotNull(properties);
     assertEquals("1", properties.get("Id"));
     assertEquals("Building 1", properties.get("Name"));
-    assertEquals(null, properties.get("Image"));
+    assertNull(properties.get("Image"));
     assertNull(properties.get("nb_Rooms"));
 
     List<String> associationUris = result.getMetadata().getAssociationUris("nb_Rooms");
@@ -164,6 +165,41 @@ public class JsonEntryConsumerTest extends AbstractConsumerTest {
   }
 
   @Test
+  public void readMinimalEntry() throws Exception {
+    final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Teams");
+    final ODataEntry result = new JsonEntityConsumer().readEntry(entitySet, createContentAsStream("{\"Id\":\"99\"}"), DEFAULT_PROPERTIES);
+
+    final Map<String, Object> properties = result.getProperties();
+    assertNotNull(properties);
+    assertEquals(1, properties.size());
+    assertEquals("99", properties.get("Id"));
+
+    assertTrue(result.getMetadata().getAssociationUris("nt_Employees").isEmpty());
+    checkMediaDataInitial(result.getMediaMetadata());
+  }
+
+  @Test
+  public void readEntryWithNullProperty() throws Exception {
+    final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms");
+    final String content = "{\"Id\":\"99\",\"Seats\":null}";
+
+    for (final boolean merge : new boolean[] { false, true }) {
+      final ODataEntry result = new JsonEntityConsumer().readEntry(entitySet, createContentAsStream(content),
+          EntityProviderReadProperties.init().mergeSemantic(merge).build());
+
+      final Map<String, Object> properties = result.getProperties();
+      assertNotNull(properties);
+      assertEquals(2, properties.size());
+      assertEquals("99", properties.get("Id"));
+      assertTrue(properties.containsKey("Seats"));
+      assertNull(properties.get("Seats"));
+
+      assertTrue(result.getMetadata().getAssociationUris("nr_Employees").isEmpty());
+      checkMediaDataInitial(result.getMediaMetadata());
+    }
+  }
+
+  @Test
   public void readWithDoublePropertyOnTeam() throws Exception {
     //The file contains the name property two times
     try {

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumerTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumerTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumerTest.java
index 5cedb91..61ef209 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumerTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumerTest.java
@@ -36,10 +36,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
-import org.junit.Test;
-
-import com.google.gson.stream.JsonReader;
-import org.apache.olingo.odata2.api.edm.EdmComplexType;
+import org.apache.olingo.odata2.api.edm.EdmFacets;
 import org.apache.olingo.odata2.api.edm.EdmProperty;
 import org.apache.olingo.odata2.api.edm.EdmSimpleTypeKind;
 import org.apache.olingo.odata2.api.ep.EntityProviderException;
@@ -49,6 +46,9 @@ import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator;
 import org.apache.olingo.odata2.core.ep.aggregator.EntityPropertyInfo;
 import org.apache.olingo.odata2.testutil.fit.BaseTest;
 import org.apache.olingo.odata2.testutil.mock.MockFacade;
+import org.junit.Test;
+
+import com.google.gson.stream.JsonReader;
 
 /**
  *  
@@ -56,6 +56,18 @@ import org.apache.olingo.odata2.testutil.mock.MockFacade;
 public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
+  public void booleanSimpleProperty() throws Exception {
+    EdmProperty property = mock(EdmProperty.class);
+    when(property.getName()).thenReturn("Boolean");
+    when(property.isSimple()).thenReturn(true);
+    when(property.getType()).thenReturn(EdmSimpleTypeKind.Boolean.getEdmSimpleTypeInstance());
+
+    JsonReader reader = prepareReader("{\"Boolean\":true}");
+    final Map<String, Object> resultMap = execute(property, reader);
+    assertEquals(Boolean.TRUE, resultMap.get("Boolean"));
+  }
+
+  @Test
   public void allNumberSimplePropertyKinds() throws Exception {
     String simplePropertyJson = "{\"d\":{\"Age\":67}}";
     EdmProperty edmProperty = mock(EdmProperty.class);
@@ -143,15 +155,12 @@ public class JsonPropertyConsumerTest extends BaseTest {
     resultMap = execute(edmProperty, reader);
     assertEquals(UUID.fromString("aabbccdd-aabb-ccdd-eeff-aabbccddeeff"), resultMap.get("Name"));
     //Binary
-    byte[] binary = new byte[] { (byte) 0xAA, (byte) 0xBB, (byte) 0xCC };
     simplePropertyJson = "{\"d\":{\"Name\":\"qrvM\"}}";
     reader = prepareReader(simplePropertyJson);
     when(edmProperty.getType()).thenReturn(EdmSimpleTypeKind.Binary.getEdmSimpleTypeInstance());
     resultMap = execute(edmProperty, reader);
-    //TODO: check why byte is comming back insted of Byte object
-    //Byte[] expectedArray = new Byte[] { binary[0], binary[1], binary[2] };
-    byte[] actualArray = (byte[]) resultMap.get("Name");
-    assertTrue(Arrays.equals(binary, actualArray));
+    assertTrue(Arrays.equals(new byte[] { (byte) 0xAA, (byte) 0xBB, (byte) 0xCC },
+        (byte[]) resultMap.get("Name")));
     //Time
     simplePropertyJson = "{\"d\":{\"Name\":\"PT23H32M3S\"}}";
     reader = prepareReader(simplePropertyJson);
@@ -196,6 +205,26 @@ public class JsonPropertyConsumerTest extends BaseTest {
   }
 
   @Test
+  public void simplePropertyNull() throws Exception {
+    JsonReader reader = prepareReader("{\"Name\":null}");
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Room").getProperty("Name");
+    final Map<String, Object> resultMap = new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
+    assertTrue(resultMap.containsKey("Name"));
+    assertNull(resultMap.get("Name"));
+  }
+
+  @Test(expected = EntityProviderException.class)
+  public void simplePropertyNullValueNotAllowed() throws Exception {
+    JsonReader reader = prepareReader("{\"Age\":null}");
+    EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getEntityType("RefScenario", "Employee").getProperty("Age");
+    EdmFacets facets = mock(EdmFacets.class);
+    when(facets.isNullable()).thenReturn(false);
+    when(property.getFacets()).thenReturn(facets);
+
+    new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
+  }
+
+  @Test
   public void simplePropertyWithNullMappingStandalone() throws Exception {
     String simplePropertyJson = "{\"d\":{\"Age\":67}}";
     JsonReader reader = prepareReader(simplePropertyJson);
@@ -289,10 +318,9 @@ public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
   public void complexPropertyWithStringToStringMappingStandalone() throws Exception {
-    String simplePropertyJson = "{\"d\":{\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}}}";
-    JsonReader reader = prepareReader(simplePropertyJson);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
+    final String complexPropertyJson = "{\"d\":{\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}}}";
+    JsonReader reader = prepareReader(complexPropertyJson);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
 
     EntityProviderReadProperties readProperties = mock(EntityProviderReadProperties.class);
     Map<String, Object> innerMappings = new HashMap<String, Object>();
@@ -300,7 +328,7 @@ public class JsonPropertyConsumerTest extends BaseTest {
     Map<String, Object> typeMappings = new HashMap<String, Object>();
     typeMappings.put("City", innerMappings);
     when(readProperties.getTypeMappings()).thenReturn(typeMappings);
-    Map<String, Object> result = new JsonPropertyConsumer().readPropertyStandalone(reader, edmProperty, readProperties);
+    Map<String, Object> result = new JsonPropertyConsumer().readPropertyStandalone(reader, property, readProperties);
 
     assertEquals(1, result.size());
     @SuppressWarnings("unchecked")
@@ -311,8 +339,8 @@ public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
   public void deepComplexPropertyWithStringToStringMappingStandalone() throws Exception {
-    String simplePropertyJson = "{\"d\":{\"Location\":{\"__metadata\":{\"type\":\"RefScenario.c_Location\"},\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},\"Country\":\"Germany\"}}}";
-    JsonReader reader = prepareReader(simplePropertyJson);
+    final String complexPropertyJson = "{\"d\":{\"Location\":{\"__metadata\":{\"type\":\"RefScenario.c_Location\"},\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},\"Country\":\"Germany\"}}}";
+    JsonReader reader = prepareReader(complexPropertyJson);
     EdmProperty edmProperty = (EdmProperty) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location");
 
     EntityProviderReadProperties readProperties = mock(EntityProviderReadProperties.class);
@@ -340,11 +368,10 @@ public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
   public void complexPropertyOnOpenReader() throws Exception {
-    String simplePropertyJson = "{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}";
-    JsonReader reader = prepareReader(simplePropertyJson);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
-    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(edmProperty);
+    final String complexPropertyJson = "{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}";
+    JsonReader reader = prepareReader(complexPropertyJson);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
+    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(property);
 
     JsonPropertyConsumer jpc = new JsonPropertyConsumer();
     @SuppressWarnings("unchecked")
@@ -357,11 +384,10 @@ public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
   public void complexPropertyOnOpenReaderWithNoMetadata() throws Exception {
-    String simplePropertyJson = "{\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}";
-    JsonReader reader = prepareReader(simplePropertyJson);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
-    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(edmProperty);
+    final String complexPropertyJson = "{\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}";
+    JsonReader reader = prepareReader(complexPropertyJson);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
+    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(property);
 
     JsonPropertyConsumer jpc = new JsonPropertyConsumer();
     @SuppressWarnings("unchecked")
@@ -374,8 +400,8 @@ public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
   public void deepComplexPropertyOnOpenReader() throws Exception {
-    String simplePropertyJson = "{\"__metadata\":{\"type\":\"RefScenario.c_Location\"},\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},\"Country\":\"Germany\"}";
-    JsonReader reader = prepareReader(simplePropertyJson);
+    final String complexPropertyJson = "{\"__metadata\":{\"type\":\"RefScenario.c_Location\"},\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},\"Country\":\"Germany\"}";
+    JsonReader reader = prepareReader(complexPropertyJson);
     EdmProperty edmProperty = (EdmProperty) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location");
     EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(edmProperty);
 
@@ -404,12 +430,11 @@ public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
   public void complexPropertyStandalone() throws Exception {
-    String simplePropertyJson = "{\"d\":{\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}}}";
-    JsonReader reader = prepareReader(simplePropertyJson);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
+    final String complexPropertyJson = "{\"d\":{\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}}}";
+    JsonReader reader = prepareReader(complexPropertyJson);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
 
-    Map<String, Object> result = new JsonPropertyConsumer().readPropertyStandalone(reader, edmProperty, null);
+    final Map<String, Object> result = new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
 
     assertEquals(1, result.size());
     @SuppressWarnings("unchecked")
@@ -420,8 +445,8 @@ public class JsonPropertyConsumerTest extends BaseTest {
 
   @Test
   public void deepComplexPropertyStandalone() throws Exception {
-    String simplePropertyJson = "{\"d\":{\"Location\":{\"__metadata\":{\"type\":\"RefScenario.c_Location\"},\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},\"Country\":\"Germany\"}}}";
-    JsonReader reader = prepareReader(simplePropertyJson);
+    final String complexPropertyJson = "{\"d\":{\"Location\":{\"__metadata\":{\"type\":\"RefScenario.c_Location\"},\"City\":{\"__metadata\":{\"type\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"},\"Country\":\"Germany\"}}}";
+    JsonReader reader = prepareReader(complexPropertyJson);
     EdmProperty edmProperty = (EdmProperty) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location");
 
     JsonPropertyConsumer jpc = new JsonPropertyConsumer();
@@ -443,44 +468,66 @@ public class JsonPropertyConsumerTest extends BaseTest {
   public void complexPropertyWithInvalidChild() throws Exception {
     String cityProperty = "{\"d\":{\"City\":{\"Invalid\":\"69124\",\"CityName\":\"Heidelberg\"}}}";
     JsonReader reader = prepareReader(cityProperty);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
 
-    new JsonPropertyConsumer().readPropertyStandalone(reader, edmProperty, null);
+    new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
   }
 
   @Test(expected = EntityProviderException.class)
   public void complexPropertyWithInvalidName() throws Exception {
     String cityProperty = "{\"d\":{\"Invalid\":{\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}}}";
     JsonReader reader = prepareReader(cityProperty);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
 
-    new JsonPropertyConsumer().readPropertyStandalone(reader, edmProperty, null);
+    new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
+  }
+
+  @Test
+  public void complexPropertyNull() throws Exception {
+    final String locationProperty = "{\"Location\":null}";
+    JsonReader reader = prepareReader(locationProperty);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location");
+
+    final Map<String, Object> propertyData = new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
+    assertNotNull(propertyData);
+    assertEquals(1, propertyData.size());
+    assertTrue(propertyData.containsKey("Location"));
+    assertNull(propertyData.get("Location"));
+  }
+
+  @Test(expected = EntityProviderException.class)
+  public void complexPropertyNullValueNotAllowed() throws Exception {
+    final String locationProperty = "{\"Location\":null}";
+    JsonReader reader = prepareReader(locationProperty);
+    EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location");
+    EdmFacets facets = mock(EdmFacets.class);
+    when(facets.isNullable()).thenReturn(false);
+    when(property.getFacets()).thenReturn(facets);
+
+    new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
   }
 
   @Test
   public void complexPropertyEmpty() throws Exception {
-    String cityProperty = "{\"d\":{\"City\":null}}";
+    final String cityProperty = "{\"d\":{\"City\":{}}}";
     JsonReader reader = prepareReader(cityProperty);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
 
-    Map<String, Object> propertyData = new JsonPropertyConsumer().readPropertyStandalone(reader, edmProperty, null);
+    final Map<String, Object> propertyData = new JsonPropertyConsumer().readPropertyStandalone(reader, property, null);
     assertNotNull(propertyData);
     assertEquals(1, propertyData.size());
+    assertNotNull(propertyData.get("City"));
     @SuppressWarnings("unchecked")
-    Map<String, Object> innerResult = (Map<String, Object>) propertyData.get("City");
-    assertNull(innerResult);
+    final Map<String, Object> innerMap = (Map<String, Object>) propertyData.get("City");
+    assertTrue(innerMap.isEmpty());
   }
 
   @Test(expected = EntityProviderException.class)
   public void complexPropertyMetadataInvalidTag() throws Exception {
     String complexPropertyJson = "{\"__metadata\":{\"invalid\":\"RefScenario.c_City\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}";
     JsonReader reader = prepareReader(complexPropertyJson);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
-    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(edmProperty);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
+    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(property);
 
     new JsonPropertyConsumer().readPropertyValue(reader, entityPropertyInfo, null);
   }
@@ -489,9 +536,8 @@ public class JsonPropertyConsumerTest extends BaseTest {
   public void complexPropertyMetadataInvalidTypeContent() throws Exception {
     String complexPropertyJson = "{\"__metadata\":{\"type\":\"Invalid\"},\"PostalCode\":\"69124\",\"CityName\":\"Heidelberg\"}";
     JsonReader reader = prepareReader(complexPropertyJson);
-    EdmComplexType complexPropertyType = (EdmComplexType) MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Employees").getEntityType().getProperty("Location").getType();
-    EdmProperty edmProperty = (EdmProperty) complexPropertyType.getProperty("City");
-    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(edmProperty);
+    final EdmProperty property = (EdmProperty) MockFacade.getMockEdm().getComplexType("RefScenario", "c_Location").getProperty("City");
+    EntityComplexPropertyInfo entityPropertyInfo = (EntityComplexPropertyInfo) EntityInfoAggregator.create(property);
 
     new JsonPropertyConsumer().readPropertyValue(reader, entityPropertyInfo, null);
   }


[4/4] git commit: Batch Support, Validation in Deserialization

Posted by jh...@apache.org.
Batch Support, Validation in Deserialization

Change-Id: Ic263de081d06b71d395804b75a36c6e4b525d386


Project: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/commit/45c27419
Tree: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/tree/45c27419
Diff: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/diff/45c27419

Branch: refs/heads/master
Commit: 45c27419cfcf1024d8e57100e7408276a932598c
Parents: a133661
Author: Jens Huesken <jh...@apache.org>
Authored: Fri Aug 23 16:27:58 2013 +0200
Committer: Jens Huesken <jh...@apache.org>
Committed: Fri Aug 23 16:27:58 2013 +0200

----------------------------------------------------------------------
 .../olingo/odata2/api/batch/BatchException.java |   8 +-
 .../olingo/odata2/api/batch/BatchHandler.java   |   6 +-
 .../olingo/odata2/api/batch/BatchPart.java      |  43 --
 .../odata2/api/batch/BatchRequestPart.java      |  42 ++
 .../odata2/api/client/batch/BatchChangeSet.java |  61 +++
 .../api/client/batch/BatchChangeSetPart.java    | 110 +++++
 .../odata2/api/client/batch/BatchPart.java      |  27 ++
 .../odata2/api/client/batch/BatchQueryPart.java |  97 +++++
 .../api/client/batch/BatchSingleResponse.java   |  65 +++
 .../olingo/odata2/api/ep/EntityProvider.java    |  74 +++-
 .../api/ep/EntityProviderReadProperties.java    |  29 +-
 .../olingo/odata2/api/rt/RuntimeDelegate.java   |  21 +
 .../odata2/core/batch/BatchChangeSetImpl.java   |  49 +++
 .../core/batch/BatchChangeSetPartImpl.java      | 116 +++++
 .../odata2/core/batch/BatchConstants.java       |  36 --
 .../odata2/core/batch/BatchHandlerImpl.java     |  20 +-
 .../olingo/odata2/core/batch/BatchHelper.java   |  51 +++
 .../olingo/odata2/core/batch/BatchPartImpl.java |  58 ---
 .../odata2/core/batch/BatchQueryPartImpl.java   | 102 +++++
 .../odata2/core/batch/BatchRequestParser.java   | 294 +++++++------
 .../odata2/core/batch/BatchRequestPartImpl.java |  58 +++
 .../odata2/core/batch/BatchRequestWriter.java   | 120 ++++++
 .../odata2/core/batch/BatchResponseParser.java  | 354 +++++++++++++++
 .../odata2/core/batch/BatchResponseWriter.java  |  80 ++--
 .../core/batch/BatchSingleResponseImpl.java     |  90 ++++
 .../olingo/odata2/core/commons/Encoder.java     |   1 -
 .../olingo/odata2/core/edm/EdmBinary.java       |   2 +-
 .../olingo/odata2/core/edm/EdmDateTime.java     |   2 +-
 .../apache/olingo/odata2/core/edm/EdmTime.java  |  15 +-
 .../odata2/core/edm/parser/EdmParser.java       |  11 +-
 .../odata2/core/ep/JsonEntityProvider.java      |   9 +-
 .../odata2/core/ep/ProviderFacadeImpl.java      |  22 +-
 .../core/ep/aggregator/EntityPropertyInfo.java  |  20 +-
 .../core/ep/consumer/JsonEntityConsumer.java    |   4 +-
 .../core/ep/consumer/JsonEntryConsumer.java     |   4 +-
 .../core/ep/consumer/JsonPropertyConsumer.java  |   3 +
 .../core/ep/consumer/XmlEntityConsumer.java     |   6 +-
 .../core/ep/consumer/XmlEntryConsumer.java      |  35 +-
 .../core/ep/consumer/XmlPropertyConsumer.java   |  65 ++-
 .../processor/ODataSingleProcessorService.java  |   3 +-
 .../odata2/core/rt/RuntimeDelegateImpl.java     |  25 ++
 odata-core/src/main/resources/i18n.properties   |   4 +-
 .../core/batch/BatchRequestParserTest.java      | 427 ++++++++++---------
 .../core/batch/BatchRequestWriterTest.java      | 236 ++++++++++
 .../core/batch/BatchResponseParserTest.java     | 280 ++++++++++++
 .../core/batch/BatchResponseWriterTest.java     |   4 +-
 .../odata2/core/edm/parser/EdmParserTest.java   |  11 +-
 .../core/ep/AbstractXmlProducerTestHelper.java  |   3 +
 .../core/ep/consumer/JsonEntryConsumerTest.java |  46 +-
 .../ep/consumer/JsonPropertyConsumerTest.java   | 152 ++++---
 .../core/ep/consumer/XmlEntityConsumerTest.java | 123 +++---
 .../core/ep/consumer/XmlLinkConsumerTest.java   |   7 +
 .../ep/consumer/XmlPropertyConsumerTest.java    | 181 +++++---
 .../ODataSingleProcessorServiceTest.java        |   2 +-
 .../uri/expression/TestAbapCompatibility.java   |   3 +-
 .../uri/expression/TestParserExceptions.java    |  10 +-
 .../src/test/resources/batchResponse.batch      |  39 ++
 .../olingo/odata2/fit/basic/BasicBatchTest.java |   9 +-
 .../odata2/fit/client/ClientBatchTest.java      | 235 ++++++++++
 .../apache/olingo/odata2/fit/ref/BatchTest.java |   2 +-
 .../odata2/fit/ref/EntryJsonChangeTest.java     |  29 +-
 .../fit/ref/EntryJsonCreateInlineTest.java      |   2 +
 .../odata2/fit/ref/EntryXmlChangeTest.java      |  53 ++-
 .../odata2/ref/edm/ScenarioEdmProvider.java     |   2 +-
 .../odata2/ref/processor/ListsProcessor.java    |  29 +-
 .../odata2/testutil/helper/StringHelper.java    |  11 +-
 .../olingo/odata2/testutil/mock/EdmMock.java    |   1 +
 .../olingo/odata2/testutil/mock/TecEdmInfo.java |   5 +-
 .../mock/TechnicalScenarioEdmProvider.java      |  47 +-
 69 files changed, 3295 insertions(+), 896 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
index bbed22b..f5582d6 100644
--- a/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
@@ -49,7 +49,7 @@ public class BatchException extends ODataMessageException {
   /** INVALID_QUERY_PARAMETER requires no content value */
   public static final MessageReference INVALID_QUERY_PARAMETER = createMessageReference(BatchException.class, "INVALID_QUERY_PARAMETER");
 
-  /** INVALID_URI requires no content value */
+  /** INVALID_URI requires 1 content value('line number') */
   public static final MessageReference INVALID_URI = createMessageReference(BatchException.class, "INVALID_URI");
 
   /** INVALID_BOUNDARY requires 1 content value('line number') */
@@ -91,9 +91,15 @@ public class BatchException extends ODataMessageException {
   /** INVALID_REQUEST_LINE requires 2 content value ('request line', 'line number') */
   public static final MessageReference INVALID_REQUEST_LINE = createMessageReference(BatchException.class, "INVALID_REQUEST_LINE");
 
+  /** INVALID_STATUS_LINE requires 2 content value ('status line', 'line number') */
+  public static final MessageReference INVALID_STATUS_LINE = createMessageReference(BatchException.class, "INVALID_STATUS_LINE");
+
   /** TRUNCETED_BODY requires 1 content value ('line number') */
   public static final MessageReference TRUNCATED_BODY = createMessageReference(BatchException.class, "TRUNCATED_BODY");
 
+  /** UNSUPPORTED_ABSOLUTE_PATH requires 1 content value ('line number') */
+  public static final MessageReference UNSUPPORTED_ABSOLUTE_PATH = createMessageReference(BatchException.class, "UNSUPPORTED_ABSOLUTE_PATH");
+
   public BatchException(final MessageReference messageReference) {
     super(messageReference);
   }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchHandler.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchHandler.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchHandler.java
index 23584f3..1748e3e 100644
--- a/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchHandler.java
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchHandler.java
@@ -27,12 +27,12 @@ import org.apache.olingo.odata2.api.processor.ODataResponse;
  */
 public interface BatchHandler {
   /**
-   * <p>Handles the {@link BatchPart} in a way that it results in a corresponding {@link BatchResponsePart}.</p>
-   * @param batchPart the incoming batchPart
+   * <p>Handles the {@link BatchRequestPart} in a way that it results in a corresponding {@link BatchResponsePart}.</p>
+   * @param batchRequestPart the incoming MIME part
    * @return the corresponding result
    * @throws ODataException
    */
-  public BatchResponsePart handleBatchPart(BatchPart batchPart) throws ODataException;
+  public BatchResponsePart handleBatchPart(BatchRequestPart batchRequestPart) throws ODataException;
 
   /**
    * <p>Delegates a handling of the request {@link ODataRequest} to the request handler and provides ODataResponse {@link ODataResponse}.</p>

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchPart.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchPart.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchPart.java
deleted file mode 100644
index b7d8539..0000000
--- a/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchPart.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*******************************************************************************
- * 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.api.batch;
-
-import java.util.List;
-
-import org.apache.olingo.odata2.api.processor.ODataRequest;
-
-/**
- * A BatchPart
- * <p> BatchPart represents a distinct MIME part of a Batch Request body. It can be ChangeSet or Query Operation
- *  
- */
-public interface BatchPart {
-
-  /**
-   * Get the info if a BatchPart is a ChangeSet
-   * @return true or false
-   */
-  public boolean isChangeSet();
-
-  /**
-   * Get requests. If a BatchPart is a Query Operation, the list contains one request.
-   * @return a list of {@link ODataRequest}
-   */
-  public List<ODataRequest> getRequests();
-}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
new file mode 100644
index 0000000..a12f774
--- /dev/null
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.api.batch;
+
+import java.util.List;
+
+import org.apache.olingo.odata2.api.processor.ODataRequest;
+
+/**
+ * A BatchPart
+ * <p> BatchPart represents a distinct MIME part of a Batch Request body. It can be ChangeSet or Query Operation
+ */
+public interface BatchRequestPart {
+
+  /**
+   * Get the info if a BatchPart is a ChangeSet
+   * @return true or false
+   */
+  public boolean isChangeSet();
+
+  /**
+   * Get requests. If a BatchPart is a Query Operation, the list contains one request.
+   * @return a list of {@link ODataRequest}
+   */
+  public List<ODataRequest> getRequests();
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSet.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSet.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSet.java
new file mode 100644
index 0000000..13f7df0
--- /dev/null
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSet.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.api.client.batch;
+
+import java.util.List;
+
+import org.apache.olingo.odata2.api.rt.RuntimeDelegate;
+
+/**
+ * A BatchChangeSet
+ * <p> BatchChangeSet represents a Change Set, that consists of change requests
+ */
+public abstract class BatchChangeSet implements BatchPart {
+
+  /**
+   * Add a new change request to the ChangeSet
+   * @param BatchChangeSetPart {@link BatchChangeSetPart}
+   */
+  public abstract void add(BatchChangeSetPart request);
+
+  /**
+   * Get change requests 
+   * @return a list of {@link BatchChangeSetPart}
+   */
+  public abstract List<BatchChangeSetPart> getChangeSetParts();
+
+  /**
+   * Get new builder instance 
+   * @return {@link BatchChangeSetBuilder}
+   */
+  public static BatchChangeSetBuilder newBuilder() {
+    return BatchChangeSetBuilder.newInstance();
+  }
+
+  public static abstract class BatchChangeSetBuilder {
+
+    protected BatchChangeSetBuilder() {}
+
+    private static BatchChangeSetBuilder newInstance() {
+      return RuntimeDelegate.createBatchChangeSetBuilder();
+    }
+
+    public abstract BatchChangeSet build();
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSetPart.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSetPart.java
new file mode 100644
index 0000000..ba59edc
--- /dev/null
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchChangeSetPart.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * 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.api.client.batch;
+
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.rt.RuntimeDelegate;
+
+/**
+ * A BatchChangeSetPart
+ * <p> BatchChangeSetPart represents a change request within a Change Set
+ */
+public abstract class BatchChangeSetPart {
+
+  public abstract Map<String, String> getHeaders();
+
+  public abstract String getBody();
+
+  public abstract String getUri();
+
+  public abstract String getMethod();
+
+  public abstract String getContentId();
+
+  /**
+   * @param headers
+   * @return a new builder object
+   */
+  public static BatchChangeSetPartBuilder headers(final Map<String, String> headers) {
+    return newBuilder().headers(headers);
+  }
+
+  /**
+   * @param body a change request body
+   * @return a new builder object
+   */
+  public static BatchChangeSetPartBuilder body(final String body) {
+    return newBuilder().body(body);
+  }
+
+  /**
+   * @param uri should not be null
+   * @return a new builder object
+   */
+  public static BatchChangeSetPartBuilder uri(final String uri) {
+    return newBuilder().uri(uri);
+  }
+
+  /**
+   * @param method MUST be the PUT, POST, MERGE, DELETE or PATCH method
+   * @return a new builder object
+   */
+  public static BatchChangeSetPartBuilder method(final String method) {
+    return newBuilder().method(method);
+  }
+
+  /**
+   * @param contentId can be used to identify the different request within a the batch
+   * @return a new builder object
+   */
+  public static BatchChangeSetPartBuilder contentId(final String contentId) {
+    return newBuilder().contentId(contentId);
+  }
+
+  /**
+   * @return returns a new builder object
+   */
+  public static BatchChangeSetPartBuilder newBuilder() {
+    return BatchChangeSetPartBuilder.newInstance();
+  }
+
+  public static abstract class BatchChangeSetPartBuilder {
+
+    protected BatchChangeSetPartBuilder() {}
+
+    private static BatchChangeSetPartBuilder newInstance() {
+      return RuntimeDelegate.createBatchChangeSetPartBuilder();
+    }
+
+    public abstract BatchChangeSetPart build();
+
+    public abstract BatchChangeSetPartBuilder headers(Map<String, String> headers);
+
+    public abstract BatchChangeSetPartBuilder body(String body);
+
+    public abstract BatchChangeSetPartBuilder uri(String uri);
+
+    public abstract BatchChangeSetPartBuilder method(String method);
+
+    public abstract BatchChangeSetPartBuilder contentId(String contentId);
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchPart.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchPart.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchPart.java
new file mode 100644
index 0000000..6c6c7f0
--- /dev/null
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchPart.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * 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.api.client.batch;
+
+/**
+ * A BatchPartRequest
+ * <p> BatchPartRequest represents a distinct part of a Batch Request body. It can be a ChangeSet or Query Operation.
+ */
+public interface BatchPart {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchQueryPart.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchQueryPart.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchQueryPart.java
new file mode 100644
index 0000000..42bd28d
--- /dev/null
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchQueryPart.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * 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.api.client.batch;
+
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.rt.RuntimeDelegate;
+
+/**
+ * A BatchQueryPart
+ * <p>BatchQueryPart represents a single retrieve request
+ */
+public abstract class BatchQueryPart implements BatchPart {
+
+  public abstract Map<String, String> getHeaders();
+
+  public abstract String getUri();
+
+  public abstract String getMethod();
+
+  public abstract String getContentId();
+
+  /**
+   * @param headers
+   * @return a new builder object
+   */
+  public static BatchQueryPartBuilder headers(final Map<String, String> headers) {
+    return newBuilder().headers(headers);
+  }
+
+  /**
+   * @param uri should not be null
+   * @return a new builder object
+   */
+  public static BatchQueryPartBuilder uri(final String uri) {
+    return newBuilder().uri(uri);
+  }
+
+  /**
+   * @param method MUST be the HTTP GET method
+   * @return a new builder object
+   */
+  public static BatchQueryPartBuilder method(final String method) {
+    return newBuilder().method(method);
+  }
+
+  /**
+   * @param contentId can be used to identify the different request within a the batch
+   * @return a new builder object
+   */
+  public static BatchQueryPartBuilder contentId(final String contentId) {
+    return newBuilder().contentId(contentId);
+  }
+
+  /**
+   * @return returns a new builder object
+   */
+  public static BatchQueryPartBuilder newBuilder() {
+    return BatchQueryPartBuilder.newInstance();
+  }
+
+  public static abstract class BatchQueryPartBuilder {
+
+    protected BatchQueryPartBuilder() {}
+
+    private static BatchQueryPartBuilder newInstance() {
+      return RuntimeDelegate.createBatchQueryPartBuilder();
+    }
+
+    public abstract BatchQueryPart build();
+
+    public abstract BatchQueryPartBuilder headers(Map<String, String> headers);
+
+    public abstract BatchQueryPartBuilder uri(String uri);
+
+    public abstract BatchQueryPartBuilder method(String method);
+
+    public abstract BatchQueryPartBuilder contentId(String contentId);
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
new file mode 100644
index 0000000..4d99c7d
--- /dev/null
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * 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.api.client.batch;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+* A BatchSingleResponse
+* <p> BatchSingleResponse represents a single response of a Batch Response body. It can be a response to a change request of ChangeSet or a response to a retrieve request
+*/
+public interface BatchSingleResponse {
+  /**
+   * @return a result code of the attempt to understand and satisfy the request
+   */
+  public String getStatusCode();
+
+  /**
+   * @return a short textual description of the status code
+   */
+  public String getStatusInfo();
+
+  /**
+   * @return a value of the Content-Id header
+   */
+  public String getContentId();
+
+  /**
+   * @return a body part of a response message
+   */
+  public String getBody();
+
+  /**
+   * @return all available headers
+   */
+  public Map<String, String> getHeaders();
+
+  /**
+   * @param name HTTP response header name
+   * @return a header value or null if not set
+   */
+  public String getHeader(final String name);
+
+  /**
+   * @return a set of all available header names
+   */
+  public Set<String> getHeaderNames();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProvider.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProvider.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProvider.java
index 97cc26a..cb876aa 100644
--- a/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProvider.java
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProvider.java
@@ -23,8 +23,10 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.batch.BatchPart;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart;
+import org.apache.olingo.odata2.api.client.batch.BatchPart;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
 import org.apache.olingo.odata2.api.edm.Edm;
 import org.apache.olingo.odata2.api.edm.EdmEntitySet;
 import org.apache.olingo.odata2.api.edm.EdmFunctionImport;
@@ -220,13 +222,18 @@ public final class EntityProvider {
     ODataFeed readFeed(String contentType, EdmEntitySet entitySet, InputStream content, EntityProviderReadProperties properties) throws EntityProviderException;
 
     /**
-     * Read (de-serialize) data from <code>content</code> (as {@link InputStream}) in specified format (given as <code>contentType</code>)
-     * based on <code>entity data model</code> (given as {@link EdmEntitySet}) and provide this data as {@link ODataEntry}.
-     * 
-     * @param contentType format of content in the given input stream.
-     * @param entitySet entity data model for entity set to be read
-     * @param content data in form of an {@link InputStream} which contains the data in specified format
-     * @param properties additional properties necessary for reading content from {@link InputStream} into {@link Map}.
+     * Reads (de-serializes) data from <code>content</code> (as {@link InputStream})
+     * in specified format (given as <code>contentType</code>) based on
+     * <code>entity data model</code> (given as {@link EdmEntitySet})
+     * and provides this data as {@link ODataEntry}.
+     * Does not return complete entry data but only data present in the
+     * de-serialized content.
+     * @param contentType format of content in the given input stream
+     * @param entitySet   entity data model for entity set to be read
+     * @param content     data in form of an {@link InputStream} which
+     *                    contains the data in specified format
+     * @param properties  additional properties necessary for reading
+     *                    content from {@link InputStream} into {@link Map}.
      * @return entry as {@link ODataEntry}
      * @throws EntityProviderException if reading of data (de-serialization) fails
      */
@@ -331,7 +338,7 @@ public final class EntityProvider {
      * @return list of {@link BatchPart}
      * @throws BatchException  if parsing fails
      */
-    List<BatchPart> parseBatchRequest(String contentType, InputStream content, EntityProviderBatchProperties properties) throws BatchException;
+    List<BatchRequestPart> parseBatchRequest(String contentType, InputStream content, EntityProviderBatchProperties properties) throws BatchException;
 
     /**
      * Write responses of Batch Response Parts in Batch Response as {@link ODataResponse}.
@@ -342,6 +349,26 @@ public final class EntityProvider {
      * @throws BatchException 
      */
     ODataResponse writeBatchResponse(List<BatchResponsePart> batchResponseParts) throws BatchException;
+
+    /**
+     * Create Batch Request body as InputStream.
+     * 
+     * @param batchParts a list of BatchPartRequests {@link BatchPart}
+     * @param boundary 
+     * @return Batch Request as InputStream
+     */
+    InputStream writeBatchRequest(List<BatchPart> batchParts, String boundary);
+
+    /** 
+     * Parse Batch Response body (as {@link InputStream}) and provide a list of single responses as {@link BatchSingleResponse}
+     *
+     * @param content response body
+     * @param contentType format of content in the given input stream (incl. boundary parameter)
+     * @return list of {@link BatchSingleResponse}
+     * @throws BatchException 
+     */
+    List<BatchSingleResponse> parseBatchResponse(String contentType, InputStream content) throws BatchException;
+
   }
 
   /**
@@ -671,15 +698,15 @@ public final class EntityProvider {
   }
 
   /**
-   * Parse Batch Request body <code>inputStream</code> (as {@link InputStream}) and provide a list of Batch Parts as {@link BatchPart}
+   * Parse Batch Request body <code>inputStream</code> (as {@link InputStream}) and provide a list of Batch Request parts as {@link BatchRequestPart}
    * 
    * @param contentType format of content in the given input stream
    * @param content request body
    * @param properties additional properties necessary for parsing. Must not be null.
-   * @return list of {@link BatchPart}
+   * @return list of {@link BatchRequestPart}
    * @throws BatchException if parsing fails
    */
-  public static List<BatchPart> parseBatchRequest(final String contentType, final InputStream content, final EntityProviderBatchProperties properties) throws BatchException {
+  public static List<BatchRequestPart> parseBatchRequest(final String contentType, final InputStream content, final EntityProviderBatchProperties properties) throws BatchException {
     return createEntityProvider().parseBatchRequest(contentType, content, properties);
   }
 
@@ -695,4 +722,27 @@ public final class EntityProvider {
     return createEntityProvider().writeBatchResponse(batchResponseParts);
   }
 
+  /**
+   * Create Batch Request body as InputStream.
+   * 
+   * @param batchParts a list of BatchPartRequests {@link BatchPart}
+   * @param boundary 
+   * @return Batch Request as InputStream
+   */
+  public static InputStream writeBatchRequest(final List<BatchPart> batchParts, final String boundary) {
+    return createEntityProvider().writeBatchRequest(batchParts, boundary);
+  }
+
+  /** 
+   * Parse Batch Response body (as {@link InputStream}) and provide a list of single responses as {@link BatchSingleResponse}
+   *
+   * @param content response body
+   * @param contentType format of content in the given input stream (inclusive boundary parameter)
+   * @return list of {@link BatchSingleResponse}
+   * @throws BatchException 
+   */
+  public static List<BatchSingleResponse> parseBatchResponse(final InputStream content, final String contentType) throws BatchException {
+    return createEntityProvider().parseBatchResponse(contentType, content);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderReadProperties.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderReadProperties.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderReadProperties.java
index f64aa93..97e4002 100644
--- a/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderReadProperties.java
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderReadProperties.java
@@ -25,28 +25,28 @@ import java.util.Map;
 import org.apache.olingo.odata2.api.ep.callback.OnReadInlineContent;
 
 /**
- * The {@link EntityProviderReadProperties} contains all necessary settings to read an entity with the {@link EntityProvider}.
- * 
- * The main settings are
+ * <p>The {@link EntityProviderReadProperties} contains all necessary settings
+ * to read an entity with the {@link EntityProvider}.</p>
+ * <p>The main settings are
  * <ul>
  * <li>the <code>mergeSemantic</code></li>
  * <li>the <code>callback for inlined navigation properties</code></li>
  * <li>and the <code>type mappings</code></li>
  * </ul>
- * 
+ * </p>
  *  
  */
 public class EntityProviderReadProperties {
   /** Callback which is necessary if entity contains inlined navigation properties. */
   private OnReadInlineContent callback;
   /**
-   * if merge is <code>true</code> the input content is in context of an <b>merge</b> (e.g. MERGE, PATCH) read request, 
-   * otherwise if <code>false</code> it is an <b>none merge</b> (e.g. CREATE) read request
+   * if merge is <code>true</code> the input content is in context of a <b>merge</b> (e.g. MERGE, PATCH) read request, 
+   * otherwise if <code>false</code> it is a <b>non-merge</b> (e.g. CREATE) read request
    */
   private boolean merge;
   /**
-   * typeMappings contains mappings from <code>edm property name</code> to <code>java class</code> which should be used 
-   * for a type mapping during read of content. If according <code>edm property</code> can not be read
+   * typeMappings contains mappings from <code>edm property name</code> to <code>java class</code> which should be used
+   * for a type mapping during reading of content. If according <code>edm property</code> can not be read
    * into given <code>java class</code> an {@link EntityProviderException} is thrown.
    * Supported mappings are documented in {@link org.apache.olingo.odata2.api.edm.EdmSimpleType}.
    */
@@ -78,6 +78,14 @@ public class EntityProviderReadProperties {
     return callback;
   }
 
+  /**
+   * <p>Gets the merge semantics.</p>
+   * <p>Merge semantics is set if the input content has to be treated in the context
+   * of a request to merge incoming data with existing data (e.g., indicated by the
+   * HTTP verbs <code>MERGE</code> or <code>PATCH</code> in a server application).
+   * Otherwise the request, even if not all data are supplied, is supposed to
+   * overwrite the existing entity completely.</p>
+   */
   public boolean getMergeSemantic() {
     return merge;
   }
@@ -97,6 +105,11 @@ public class EntityProviderReadProperties {
       addTypeMappings(propertiesFrom.typeMappings);
     }
 
+    /**
+     * Sets the merge semantics.
+     * @param mergeSemantic whether merge semantics is requested
+     * @see EntityProviderReadProperties#getMergeSemantic()
+     */
     public EntityProviderReadPropertiesBuilder mergeSemantic(final boolean mergeSemantic) {
       properties.merge = mergeSemantic;
       return this;

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
----------------------------------------------------------------------
diff --git a/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java b/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
index 6590bc5..88c87cd 100644
--- a/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
+++ b/odata-api/src/main/java/org/apache/olingo/odata2/api/rt/RuntimeDelegate.java
@@ -22,6 +22,9 @@ import java.io.InputStream;
 
 import org.apache.olingo.odata2.api.ODataService;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart.BatchResponsePartBuilder;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSet.BatchChangeSetBuilder;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart.BatchChangeSetPartBuilder;
+import org.apache.olingo.odata2.api.client.batch.BatchQueryPart.BatchQueryPartBuilder;
 import org.apache.olingo.odata2.api.edm.Edm;
 import org.apache.olingo.odata2.api.edm.EdmSimpleType;
 import org.apache.olingo.odata2.api.edm.EdmSimpleTypeFacade;
@@ -96,6 +99,12 @@ public abstract class RuntimeDelegate {
 
     protected abstract ODataRequestBuilder createODataRequestBuilder();
 
+    protected abstract BatchChangeSetBuilder createBatchChangeSetBuilder();
+
+    protected abstract BatchQueryPartBuilder createBatchQueryRequestBuilder();
+
+    protected abstract BatchChangeSetPartBuilder createBatchChangeSetRequest();
+
   }
 
   /**
@@ -185,4 +194,16 @@ public abstract class RuntimeDelegate {
   public static ODataRequestBuilder createODataRequestBuilder() {
     return RuntimeDelegate.getInstance().createODataRequestBuilder();
   }
+
+  public static BatchChangeSetBuilder createBatchChangeSetBuilder() {
+    return RuntimeDelegate.getInstance().createBatchChangeSetBuilder();
+  }
+
+  public static BatchQueryPartBuilder createBatchQueryPartBuilder() {
+    return RuntimeDelegate.getInstance().createBatchQueryRequestBuilder();
+  }
+
+  public static BatchChangeSetPartBuilder createBatchChangeSetPartBuilder() {
+    return RuntimeDelegate.getInstance().createBatchChangeSetRequest();
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetImpl.java
new file mode 100644
index 0000000..2db859c
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetImpl.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSet;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
+
+public class BatchChangeSetImpl extends BatchChangeSet {
+  private List<BatchChangeSetPart> requests = new ArrayList<BatchChangeSetPart>();
+
+  @Override
+  public void add(final BatchChangeSetPart request) {
+    requests.add(request);
+  }
+
+  @Override
+  public List<BatchChangeSetPart> getChangeSetParts() {
+    return Collections.unmodifiableList(requests);
+  }
+
+  public class BatchChangeSetBuilderImpl extends BatchChangeSetBuilder {
+
+    @Override
+    public BatchChangeSet build() {
+      return BatchChangeSetImpl.this;
+    }
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetPartImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetPartImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetPartImpl.java
new file mode 100644
index 0000000..9546017
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchChangeSetPartImpl.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
+
+public class BatchChangeSetPartImpl extends BatchChangeSetPart {
+  private String method;
+  private Map<String, String> headers = new HashMap<String, String>();
+  private String body;
+  private String uri;
+  public String contentId;
+  private static final String CHANGE_METHODS = "(PUT|POST|DELETE|MERGE|PATCH)";
+
+  @Override
+  public Map<String, String> getHeaders() {
+    return Collections.unmodifiableMap(headers);
+  }
+
+  @Override
+  public String getBody() {
+    return body;
+  }
+
+  @Override
+  public String getMethod() {
+    return method;
+  }
+
+  @Override
+  public String getUri() {
+    return uri;
+  }
+
+  @Override
+  public String getContentId() {
+    return contentId;
+  }
+
+  public class BatchChangeSetRequestBuilderImpl extends BatchChangeSetPartBuilder {
+    private String method;
+    private Map<String, String> headers = new HashMap<String, String>();
+    private String body;
+    private String uri;
+    private String contentId;
+
+    @Override
+    public BatchChangeSetPart build() {
+      if (method == null || uri == null) {
+        throw new IllegalArgumentException();
+      }
+      BatchChangeSetPartImpl.this.method = method;
+      BatchChangeSetPartImpl.this.headers = headers;
+      BatchChangeSetPartImpl.this.body = body;
+      BatchChangeSetPartImpl.this.uri = uri;
+      BatchChangeSetPartImpl.this.contentId = contentId;
+      return BatchChangeSetPartImpl.this;
+    }
+
+    @Override
+    public BatchChangeSetPartBuilder headers(final Map<String, String> headers) {
+      this.headers = headers;
+      return this;
+    }
+
+    @Override
+    public BatchChangeSetPartBuilder body(final String body) {
+      this.body = body;
+      return this;
+    }
+
+    @Override
+    public BatchChangeSetPartBuilder uri(final String uri) {
+      this.uri = uri;
+      return this;
+    }
+
+    @Override
+    public BatchChangeSetPartBuilder method(final String method) {
+      if (method != null && method.matches(CHANGE_METHODS)) {
+        this.method = method;
+      } else {
+        throw new IllegalArgumentException();
+      }
+      return this;
+    }
+
+    @Override
+    public BatchChangeSetPartBuilder contentId(final String contentId) {
+      this.contentId = contentId;
+      return this;
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchConstants.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchConstants.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchConstants.java
deleted file mode 100644
index 052365d..0000000
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchConstants.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*******************************************************************************
- * 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.batch;
-
-public class BatchConstants {
-
-  public static final String HTTP_APPLICATION_HTTP = "application/http";
-
-  public static final String MULTIPART_MIXED = "multipart/mixed";
-
-  public static final String BINARY_ENCODING = "binary";
-
-  public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
-
-  public static final String HTTP_CONTENT_ID = "Content-Id";
-
-  public static final String MIME_HEADER_CONTENT_ID = "MimeHeader-ContentId";
-
-  public static final String REQUEST_HEADER_CONTENT_ID = "RequestHeader-ContentId";
-}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHandlerImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHandlerImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHandlerImpl.java
index 22dfd70..8869a33 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHandlerImpl.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHandlerImpl.java
@@ -29,7 +29,7 @@ import java.util.Map;
 import org.apache.olingo.odata2.api.ODataService;
 import org.apache.olingo.odata2.api.ODataServiceFactory;
 import org.apache.olingo.odata2.api.batch.BatchHandler;
-import org.apache.olingo.odata2.api.batch.BatchPart;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart;
 import org.apache.olingo.odata2.api.commons.HttpHeaders;
 import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
@@ -55,7 +55,7 @@ public class BatchHandlerImpl implements BatchHandler {
   }
 
   @Override
-  public BatchResponsePart handleBatchPart(final BatchPart batchPart) throws ODataException {
+  public BatchResponsePart handleBatchPart(final BatchRequestPart batchPart) throws ODataException {
     if (batchPart.isChangeSet()) {
       List<ODataRequest> changeSetRequests = batchPart.getRequests();
       contentIdMap = new HashMap<String, String>();
@@ -66,8 +66,8 @@ public class BatchHandlerImpl implements BatchHandler {
       }
       ODataRequest request = batchPart.getRequests().get(0);
       ODataRequestHandler handler = createHandler(request);
-      String mimeHeaderContentId = request.getRequestHeaderValue(BatchConstants.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
-      String requestHeaderContentId = request.getRequestHeaderValue(BatchConstants.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
+      String mimeHeaderContentId = request.getRequestHeaderValue(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
+      String requestHeaderContentId = request.getRequestHeaderValue(BatchHelper.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
       ODataResponse response = setContentIdHeader(handler.handle(request), mimeHeaderContentId, requestHeaderContentId);
       List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
       responses.add(response);
@@ -78,8 +78,8 @@ public class BatchHandlerImpl implements BatchHandler {
   @Override
   public ODataResponse handleRequest(final ODataRequest suppliedRequest) throws ODataException {
     ODataRequest request;
-    String mimeHeaderContentId = suppliedRequest.getRequestHeaderValue(BatchConstants.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
-    String requestHeaderContentId = suppliedRequest.getRequestHeaderValue(BatchConstants.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
+    String mimeHeaderContentId = suppliedRequest.getRequestHeaderValue(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
+    String requestHeaderContentId = suppliedRequest.getRequestHeaderValue(BatchHelper.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH));
 
     List<PathSegment> odataSegments = suppliedRequest.getPathInfo().getODataSegments();
     if (!odataSegments.isEmpty() && odataSegments.get(0).getPath().matches("\\$.*")) {
@@ -142,12 +142,12 @@ public class BatchHandlerImpl implements BatchHandler {
   private ODataResponse setContentIdHeader(final ODataResponse response, final String mimeHeaderContentId, final String requestHeaderContentId) {
     ODataResponse modifiedResponse;
     if (requestHeaderContentId != null && mimeHeaderContentId != null) {
-      modifiedResponse = ODataResponse.fromResponse(response).header(BatchConstants.REQUEST_HEADER_CONTENT_ID, requestHeaderContentId)
-          .header(BatchConstants.MIME_HEADER_CONTENT_ID, mimeHeaderContentId).build();
+      modifiedResponse = ODataResponse.fromResponse(response).header(BatchHelper.REQUEST_HEADER_CONTENT_ID, requestHeaderContentId)
+          .header(BatchHelper.MIME_HEADER_CONTENT_ID, mimeHeaderContentId).build();
     } else if (requestHeaderContentId != null) {
-      modifiedResponse = ODataResponse.fromResponse(response).header(BatchConstants.REQUEST_HEADER_CONTENT_ID, requestHeaderContentId).build();
+      modifiedResponse = ODataResponse.fromResponse(response).header(BatchHelper.REQUEST_HEADER_CONTENT_ID, requestHeaderContentId).build();
     } else if (mimeHeaderContentId != null) {
-      modifiedResponse = ODataResponse.fromResponse(response).header(BatchConstants.MIME_HEADER_CONTENT_ID, mimeHeaderContentId).build();
+      modifiedResponse = ODataResponse.fromResponse(response).header(BatchHelper.MIME_HEADER_CONTENT_ID, mimeHeaderContentId).build();
     } else {
       return response;
     }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHelper.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHelper.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHelper.java
new file mode 100644
index 0000000..38d02ae
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchHelper.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.io.UnsupportedEncodingException;
+import java.util.UUID;
+
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+
+public class BatchHelper {
+
+  public static final String BINARY_ENCODING = "binary";
+
+  public static final String DEFAULT_ENCODING = "utf-8";
+
+  public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+
+  public static final String HTTP_CONTENT_ID = "Content-Id";
+
+  public static final String MIME_HEADER_CONTENT_ID = "MimeHeader-ContentId";
+
+  public static final String REQUEST_HEADER_CONTENT_ID = "RequestHeader-ContentId";
+
+  protected static String generateBoundary(final String value) {
+    return value + "_" + UUID.randomUUID().toString();
+  }
+
+  protected static byte[] getBytes(final String body) {
+    try {
+      return body.getBytes(DEFAULT_ENCODING);
+    } catch (UnsupportedEncodingException e) {
+      throw new ODataRuntimeException(e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchPartImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchPartImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchPartImpl.java
deleted file mode 100644
index 665b01b..0000000
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchPartImpl.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*******************************************************************************
- * 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.batch;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.olingo.odata2.api.batch.BatchPart;
-import org.apache.olingo.odata2.api.processor.ODataRequest;
-
-public class BatchPartImpl implements BatchPart {
-
-  private List<ODataRequest> requests = new ArrayList<ODataRequest>();
-  private boolean isChangeSet;
-
-  public BatchPartImpl() {}
-
-  public BatchPartImpl(final boolean isChangeSet, final List<ODataRequest> requests) {
-    this.isChangeSet = isChangeSet;
-    this.requests = requests;
-  }
-
-  @Override
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-
-  public void setChangeSet(final boolean isChangeSet) {
-    this.isChangeSet = isChangeSet;
-  }
-
-  @Override
-  public List<ODataRequest> getRequests() {
-    return Collections.unmodifiableList(requests);
-  }
-
-  public void setRequests(final List<ODataRequest> requests) {
-    this.requests = requests;
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchQueryPartImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchQueryPartImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchQueryPartImpl.java
new file mode 100644
index 0000000..e22a3b3
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchQueryPartImpl.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.client.batch.BatchQueryPart;
+
+public class BatchQueryPartImpl extends BatchQueryPart {
+  private String method;
+  private Map<String, String> headers = new HashMap<String, String>();
+  private String uri;
+  private String contentId;
+  private static final String GET = "GET";
+
+  @Override
+  public Map<String, String> getHeaders() {
+    return Collections.unmodifiableMap(headers);
+  }
+
+  @Override
+  public String getMethod() {
+    return method;
+  }
+
+  @Override
+  public String getUri() {
+    return uri;
+  }
+
+  @Override
+  public String getContentId() {
+    return contentId;
+  }
+
+  public class BatchQueryRequestBuilderImpl extends BatchQueryPartBuilder {
+    private String method;
+    private Map<String, String> headers = new HashMap<String, String>();
+    private String uri;
+    private String contentId;
+
+    @Override
+    public BatchQueryPart build() {
+      if (method == null || uri == null) {
+        throw new IllegalArgumentException();
+      }
+      BatchQueryPartImpl.this.method = method;
+      BatchQueryPartImpl.this.headers = headers;
+      BatchQueryPartImpl.this.uri = uri;
+      BatchQueryPartImpl.this.contentId = contentId;
+      return BatchQueryPartImpl.this;
+    }
+
+    @Override
+    public BatchQueryPartBuilder headers(final Map<String, String> headers) {
+      this.headers = headers;
+      return this;
+    }
+
+    @Override
+    public BatchQueryPartBuilder uri(final String uri) {
+      this.uri = uri;
+      return this;
+    }
+
+    @Override
+    public BatchQueryPartBuilder method(final String method) {
+      if (method != null && method.matches(GET)) {
+        this.method = method;
+      } else {
+        throw new IllegalArgumentException();
+      }
+      return this;
+    }
+
+    @Override
+    public BatchQueryPartBuilder contentId(final String contentId) {
+      this.contentId = contentId;
+      return this;
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
index 4c0ae32..e37d572 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
@@ -21,10 +21,10 @@ package org.apache.olingo.odata2.core.batch;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -37,11 +37,13 @@ import java.util.regex.MatchResult;
 import java.util.regex.Pattern;
 
 import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.batch.BatchPart;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
 import org.apache.olingo.odata2.api.commons.HttpHeaders;
 import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
 import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
 import org.apache.olingo.odata2.api.processor.ODataRequest;
+import org.apache.olingo.odata2.api.processor.ODataRequest.ODataRequestBuilder;
 import org.apache.olingo.odata2.api.uri.PathInfo;
 import org.apache.olingo.odata2.api.uri.PathSegment;
 import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
@@ -64,7 +66,7 @@ public class BatchRequestParser {
   private static final Pattern REG_EX_ANY_BOUNDARY_STRING = Pattern.compile("--" + ANY_CHARACTERS + REG_EX_ZERO_OR_MORE_WHITESPACES);
   private static final Pattern REG_EX_REQUEST_LINE = Pattern.compile("(GET|POST|PUT|DELETE|MERGE|PATCH)\\s(.*)\\s?" + REG_EX_VERSION + REG_EX_ZERO_OR_MORE_WHITESPACES);
   private static final Pattern REG_EX_BOUNDARY_PARAMETER = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE + "boundary=(\".*\"|.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE + BatchConstants.MULTIPART_MIXED);
+  private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED);
   private static final Pattern REG_EX_QUERY_PARAMETER = Pattern.compile("((?:\\$|)[^=]+)=([^=]+)");
 
   private static final String REG_EX_BOUNDARY = "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
@@ -74,19 +76,21 @@ public class BatchRequestParser {
   private String boundary;
   private String currentMimeHeaderContentId;
   private int currentLineNumber = 0;
-  private static Set<String> HTTP_CHANGESET_METHODS;
-  private static Set<String> HTTP_BATCH_METHODS;
+  private final static Set<String> HTTP_CHANGESET_METHODS;
+  private final static Set<String> HTTP_BATCH_METHODS;
 
   static {
-    HTTP_CHANGESET_METHODS = new HashSet<String>();
-    HTTP_CHANGESET_METHODS.add("POST");
-    HTTP_CHANGESET_METHODS.add("PUT");
-    HTTP_CHANGESET_METHODS.add("DELETE");
-    HTTP_CHANGESET_METHODS.add("MERGE");
-    HTTP_CHANGESET_METHODS.add("PATCH");
-
-    HTTP_BATCH_METHODS = new HashSet<String>();
-    HTTP_BATCH_METHODS.add("GET");
+    HashSet<String> httpChangesetMethods = new HashSet<String>();
+    httpChangesetMethods.add("POST");
+    httpChangesetMethods.add("PUT");
+    httpChangesetMethods.add("DELETE");
+    httpChangesetMethods.add("MERGE");
+    httpChangesetMethods.add("PATCH");
+    HTTP_CHANGESET_METHODS = Collections.unmodifiableSet(httpChangesetMethods);
+
+    HashSet<String> httpBatchMethods = new HashSet<String>();
+    httpBatchMethods.add("GET");
+    HTTP_BATCH_METHODS = Collections.unmodifiableSet(httpBatchMethods);
   }
 
   public BatchRequestParser(final String contentType, final EntityProviderBatchProperties properties) {
@@ -94,10 +98,10 @@ public class BatchRequestParser {
     batchRequestPathInfo = properties.getPathInfo();
   }
 
-  public List<BatchPart> parse(final InputStream in) throws BatchException {
-    Scanner scanner = new Scanner(in, "UTF-8").useDelimiter(LF);
+  public List<BatchRequestPart> parse(final InputStream in) throws BatchException {
+    Scanner scanner = new Scanner(in, BatchHelper.DEFAULT_ENCODING).useDelimiter(LF);
     baseUri = getBaseUri();
-    List<BatchPart> requestList;
+    List<BatchRequestPart> requestList;
     try {
       requestList = parseBatchRequest(scanner);
     } finally {// NOPMD (suppress DoNotThrowExceptionInFinally)
@@ -111,12 +115,12 @@ public class BatchRequestParser {
     return requestList;
   }
 
-  private List<BatchPart> parseBatchRequest(final Scanner scanner) throws BatchException {
-    List<BatchPart> requests = new LinkedList<BatchPart>();
+  private List<BatchRequestPart> parseBatchRequest(final Scanner scanner) throws BatchException {
+    List<BatchRequestPart> requests = new LinkedList<BatchRequestPart>();
     if (contentTypeMime != null) {
       boundary = getBoundary(contentTypeMime);
       parsePreamble(scanner);
-      String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
+      final String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
       while (scanner.hasNext() && !scanner.hasNext(closeDelimiter)) {
         requests.add(parseMultipart(scanner, boundary, false));
         parseNewLine(scanner);
@@ -131,7 +135,6 @@ public class BatchRequestParser {
       throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
     }
     return requests;
-
   }
 
   //The method parses additional information prior to the first boundary delimiter line
@@ -142,37 +145,37 @@ public class BatchRequestParser {
     }
   }
 
-  private BatchPart parseMultipart(final Scanner scanner, final String boundary, final boolean isChangeSet) throws BatchException {
+  private BatchRequestPart parseMultipart(final Scanner scanner, final String boundary, final boolean isChangeSet) throws BatchException {
     Map<String, String> mimeHeaders = new HashMap<String, String>();
-    BatchPart multipart = null;
+    BatchRequestPart multipart = null;
     List<ODataRequest> requests = new ArrayList<ODataRequest>();
     if (scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
       scanner.next();
       currentLineNumber++;
       mimeHeaders = parseHeaders(scanner);
-      currentMimeHeaderContentId = mimeHeaders.get(BatchConstants.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
+      currentMimeHeaderContentId = mimeHeaders.get(BatchHelper.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
 
       String contentType = mimeHeaders.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
       if (contentType == null) {
         throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
       }
       if (isChangeSet) {
-        if (BatchConstants.HTTP_APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
-          validateEncoding(mimeHeaders.get(BatchConstants.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
+        if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
+          validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
           parseNewLine(scanner);// mandatory
 
           requests.add(parseRequest(scanner, isChangeSet));
-          multipart = new BatchPartImpl(false, requests);
+          multipart = new BatchRequestPartImpl(false, requests);
         } else {
-          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(BatchConstants.HTTP_APPLICATION_HTTP));
+          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.APPLICATION_HTTP));
         }
       } else {
-        if (BatchConstants.HTTP_APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
-          validateEncoding(mimeHeaders.get(BatchConstants.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
+        if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
+          validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
           parseNewLine(scanner);// mandatory
           requests.add(parseRequest(scanner, isChangeSet));
-          multipart = new BatchPartImpl(false, requests);
-        } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + BatchConstants.MULTIPART_MIXED + ANY_CHARACTERS)) {
+          multipart = new BatchRequestPartImpl(false, requests);
+        } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED + ANY_CHARACTERS)) {
           String changeSetBoundary = getBoundary(contentType);
           if (boundary.equals(changeSetBoundary)) {
             throw new BatchException(BatchException.INVALID_CHANGESET_BOUNDARY.addContent(currentLineNumber));
@@ -181,16 +184,14 @@ public class BatchRequestParser {
           parseNewLine(scanner);// mandatory
           Pattern changeSetCloseDelimiter = Pattern.compile("--" + changeSetBoundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES);
           while (!scanner.hasNext(changeSetCloseDelimiter)) {
-            BatchPart part = parseMultipart(scanner, changeSetBoundary, true);
-            if (part.getRequests().size() == 1) {
-              changeSetRequests.add(part.getRequests().get(0));
-            }
+            BatchRequestPart part = parseMultipart(scanner, changeSetBoundary, true);
+            changeSetRequests.addAll(part.getRequests());
           }
           scanner.next(changeSetCloseDelimiter);
           currentLineNumber++;
-          multipart = new BatchPartImpl(true, changeSetRequests);
+          multipart = new BatchRequestPartImpl(true, changeSetRequests);
         } else {
-          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(BatchConstants.MULTIPART_MIXED + " or " + BatchConstants.HTTP_APPLICATION_HTTP));
+          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP));
         }
       }
     } else if (scanner.hasNext(boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
@@ -208,60 +209,62 @@ public class BatchRequestParser {
   }
 
   private ODataRequest parseRequest(final Scanner scanner, final boolean isChangeSet) throws BatchException {
-    try {
-      ODataRequest request;
-      if (scanner.hasNext(REG_EX_REQUEST_LINE)) {
-        scanner.next(REG_EX_REQUEST_LINE);
-        currentLineNumber++;
-        String method = null;
-        String uri = null;
-        MatchResult result = scanner.match();
-        if (result.groupCount() == 2) {
-          method = result.group(1);
-          uri = result.group(2).trim();
-        } else {
-          currentLineNumber++;
-          throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(scanner.next()).addContent(currentLineNumber));
-        }
-        PathInfo pathInfo = parseRequestUri(uri);
-        Map<String, String> queryParameters = parseQueryParameters(uri);
-        if (isChangeSet) {
-          if (!HTTP_CHANGESET_METHODS.contains(method)) {
-            throw new BatchException(BatchException.INVALID_CHANGESET_METHOD.addContent(currentLineNumber));
-          }
-        } else if (!HTTP_BATCH_METHODS.contains(method)) {
-          throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD.addContent(currentLineNumber));
-        }
-        ODataHttpMethod httpMethod = ODataHttpMethod.valueOf(method);
-        Map<String, List<String>> headers = parseRequestHeaders(scanner);
-        if (currentMimeHeaderContentId != null) {
-          List<String> headerList = new ArrayList<String>();
-          headerList.add(currentMimeHeaderContentId);
-          headers.put(BatchConstants.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
-        }
-
-        String contentType = getContentTypeHeader(headers);
-        List<String> acceptHeaders = getAcceptHeader(headers);
-        List<Locale> acceptLanguages = getAcceptLanguageHeader(headers);
-        parseNewLine(scanner);
-        InputStream body;
-        body = new ByteArrayInputStream("".getBytes("UTF-8"));
-        if (isChangeSet) {
-          body = parseBody(scanner);
-        }
-        if (contentType != null) {
-          request = ODataRequest.method(httpMethod).queryParameters(queryParameters).requestHeaders(headers).pathInfo(pathInfo).acceptableLanguages(acceptLanguages).contentType(contentType).body(body).acceptHeaders(acceptHeaders).build();
-        } else {
-          request = ODataRequest.method(httpMethod).queryParameters(queryParameters).requestHeaders(headers).pathInfo(pathInfo).acceptableLanguages(acceptLanguages).body(body).acceptHeaders(acceptHeaders).build();
-        }
+    if (scanner.hasNext(REG_EX_REQUEST_LINE)) {
+      scanner.next(REG_EX_REQUEST_LINE);
+      currentLineNumber++;
+      final String method;
+      final String uri;
+      MatchResult result = scanner.match();
+      if (result.groupCount() == 2) {
+        method = result.group(1);
+        uri = result.group(2).trim();
       } else {
         currentLineNumber++;
         throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(scanner.next()).addContent(currentLineNumber));
       }
-      return request;
-    } catch (UnsupportedEncodingException e) {
-      throw new ODataRuntimeException(e);
+      PathInfo pathInfo = parseRequestUri(uri);
+      Map<String, String> queryParameters = parseQueryParameters(uri);
+      if (isChangeSet) {
+        if (!HTTP_CHANGESET_METHODS.contains(method)) {
+          throw new BatchException(BatchException.INVALID_CHANGESET_METHOD.addContent(currentLineNumber));
+        }
+      } else if (!HTTP_BATCH_METHODS.contains(method)) {
+        throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD.addContent(currentLineNumber));
+      }
+      ODataHttpMethod httpMethod = ODataHttpMethod.valueOf(method);
+      Map<String, List<String>> headers = parseRequestHeaders(scanner);
+      if (currentMimeHeaderContentId != null) {
+        List<String> headerList = new ArrayList<String>();
+        headerList.add(currentMimeHeaderContentId);
+        headers.put(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
+      }
+
+      String contentType = getContentTypeHeader(headers);
+      List<String> acceptHeaders = getAcceptHeader(headers);
+      List<Locale> acceptLanguages = getAcceptLanguageHeader(headers);
+      parseNewLine(scanner);
+      InputStream body = new ByteArrayInputStream(new byte[0]);
+      if (isChangeSet) {
+        body = parseBody(scanner);
+      }
+
+      ODataRequestBuilder requestBuilder = ODataRequest.method(httpMethod)
+          .queryParameters(queryParameters)
+          .requestHeaders(headers)
+          .pathInfo(pathInfo)
+          .acceptableLanguages(acceptLanguages)
+          .body(body)
+          .acceptHeaders(acceptHeaders);
+
+      if (contentType != null) {
+        requestBuilder = requestBuilder.contentType(contentType);
+      }
+      return requestBuilder.build();
+    } else {
+      currentLineNumber++;
+      throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(scanner.next()).addContent(currentLineNumber));
     }
+
   }
 
   private Map<String, List<String>> parseRequestHeaders(final Scanner scanner) throws BatchException {
@@ -280,7 +283,7 @@ public class BatchRequestParser {
           } else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(headerName)) {
             List<String> acceptLanguageHeaders = parseAcceptableLanguages(headerValue);
             headers.put(headerName, acceptLanguageHeaders);
-          } else if (!BatchConstants.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
+          } else if (!BatchHelper.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
             if (headers.containsKey(headerName)) {
               headers.get(headerName).add(headerValue);
             } else {
@@ -291,7 +294,7 @@ public class BatchRequestParser {
           } else {
             List<String> headerList = new ArrayList<String>();
             headerList.add(headerValue);
-            headers.put(BatchConstants.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
+            headers.put(BatchHelper.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
           }
         }
       } else {
@@ -306,40 +309,65 @@ public class BatchRequestParser {
     PathInfoImpl pathInfo = new PathInfoImpl();
     pathInfo.setServiceRoot(batchRequestPathInfo.getServiceRoot());
     pathInfo.setPrecedingPathSegment(batchRequestPathInfo.getPrecedingSegments());
-    Scanner uriScanner = new Scanner(uri);
-    Pattern regexRequestUri = Pattern.compile("(?:" + baseUri + ")?/?([^?]+)(\\?.*)?");
-    if (uriScanner.hasNext(regexRequestUri)) {
-      uriScanner.next(regexRequestUri);
-      MatchResult result = uriScanner.match();
-      if (result.groupCount() == 2) {
-        String odataPathSegmentsAsString = result.group(1);
-        String queryParametersAsString = result.group(2) != null ? result.group(2) : "";
-        pathInfo.setODataPathSegment(parseODataPathSegments(odataPathSegmentsAsString));
-        try {
-          if (!odataPathSegmentsAsString.startsWith("$")) {
-            String requestUri = baseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
-            pathInfo.setRequestUri(new URI(requestUri));
+    final String odataPathSegmentsAsString;
+    final String queryParametersAsString;
+    try {
+      Scanner uriScanner = new Scanner(uri).useDelimiter(LF);
+      URI uriObject = new URI(uri);
+      if (uriObject.isAbsolute()) {
+        Pattern regexRequestUri = Pattern.compile(baseUri + "/([^/][^?]*)(\\?.*)?");
+        if (uriScanner.hasNext(regexRequestUri)) {
+          uriScanner.next(regexRequestUri);
+          MatchResult result = uriScanner.match();
+          if (result.groupCount() == 2) {
+            odataPathSegmentsAsString = result.group(1);
+            queryParametersAsString = result.group(2) != null ? result.group(2) : "";
+          } else {
+            uriScanner.close();
+            throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
           }
-        } catch (URISyntaxException e) {
+        } else {
           uriScanner.close();
-          throw new BatchException(BatchException.INVALID_URI, e);
+          throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
         }
       } else {
-        uriScanner.close();
-        throw new BatchException(BatchException.INVALID_URI);
+        Pattern regexRequestUri = Pattern.compile("([^/][^?]*)(\\?.*)?");
+        if (uriScanner.hasNext(regexRequestUri)) {
+          uriScanner.next(regexRequestUri);
+          MatchResult result = uriScanner.match();
+          if (result.groupCount() == 2) {
+            odataPathSegmentsAsString = result.group(1);
+            queryParametersAsString = result.group(2) != null ? result.group(2) : "";
+          } else {
+            uriScanner.close();
+            throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
+          }
+        } else if (uriScanner.hasNext("/(.*)")) {
+          uriScanner.close();
+          throw new BatchException(BatchException.UNSUPPORTED_ABSOLUTE_PATH.addContent(currentLineNumber));
+        } else {
+          uriScanner.close();
+          throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
+        }
+
       }
-    } else {
       uriScanner.close();
-      throw new BatchException(BatchException.INVALID_URI);
+      pathInfo.setODataPathSegment(parseODataPathSegments(odataPathSegmentsAsString));
+      if (!odataPathSegmentsAsString.startsWith("$")) {
+        String requestUri = baseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
+        pathInfo.setRequestUri(new URI(requestUri));
+      }
+      return pathInfo;
+    } catch (URISyntaxException e) {
+      throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber), e);
     }
-    uriScanner.close();
-    return pathInfo;
+
   }
 
   private Map<String, String> parseQueryParameters(final String uri) throws BatchException {
-    Scanner uriScanner = new Scanner(uri);
+    Scanner uriScanner = new Scanner(uri).useDelimiter("\n");
     Map<String, String> queryParametersMap = new HashMap<String, String>();
-    Pattern regex = Pattern.compile("(?:" + baseUri + ")?/?" + "[^?]+" + "\\?(.*)");
+    Pattern regex = Pattern.compile("(?:" + baseUri + "/)?" + "[^?]+" + "\\?(.*)");
     if (uriScanner.hasNext(regex)) {
       uriScanner.next(regex);
       MatchResult uriResult = uriScanner.match();
@@ -362,7 +390,7 @@ public class BatchRequestParser {
 
       } else {
         uriScanner.close();
-        throw new BatchException(BatchException.INVALID_URI);
+        throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
       }
     }
     uriScanner.close();
@@ -388,30 +416,28 @@ public class BatchRequestParser {
   }
 
   private InputStream parseBody(final Scanner scanner) {
-    try {
-      String body = null;
-      InputStream requestBody;
-      while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
-        if (!scanner.hasNext(REG_EX_ZERO_OR_MORE_WHITESPACES)) {
-          if (body == null) {
-            body = scanner.next();
-          } else {
-            body = body + LF + scanner.next();
-          }
+    StringBuilder body = null;
+    final InputStream requestBody;
+
+    while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
+      if (!scanner.hasNext(REG_EX_ZERO_OR_MORE_WHITESPACES)) {
+        if (body == null) {
+          body = new StringBuilder(scanner.next());
         } else {
-          scanner.next();
+          body.append(LF).append(scanner.next());
         }
-        currentLineNumber++;
-      }
-      if (body != null) {
-        requestBody = new ByteArrayInputStream(body.getBytes("UTF-8"));
       } else {
-        requestBody = new ByteArrayInputStream("".getBytes("UTF-8"));
+        scanner.next();
       }
-      return requestBody;
-    } catch (UnsupportedEncodingException e) {
-      throw new ODataRuntimeException(e);
+      currentLineNumber++;
+    }
+
+    if (body != null) {
+      requestBody = new ByteArrayInputStream(BatchHelper.getBytes(body.toString()));
+    } else {
+      requestBody = new ByteArrayInputStream(new byte[0]);
     }
+    return requestBody;
   }
 
   private String getBoundary(final String contentType) throws BatchException {
@@ -420,7 +446,7 @@ public class BatchRequestParser {
       contentTypeScanner.next(REG_EX_CONTENT_TYPE);
     } else {
       contentTypeScanner.close();
-      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(BatchConstants.MULTIPART_MIXED));
+      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
     }
     if (contentTypeScanner.hasNext(REG_EX_BOUNDARY_PARAMETER)) {
       contentTypeScanner.next(REG_EX_BOUNDARY_PARAMETER);
@@ -438,7 +464,7 @@ public class BatchRequestParser {
   }
 
   private void validateEncoding(final String encoding) throws BatchException {
-    if (!BatchConstants.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+    if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
       throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING);
     }
   }


[3/4] Batch Support, Validation in Deserialization

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestPartImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestPartImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestPartImpl.java
new file mode 100644
index 0000000..80e5cd8
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestPartImpl.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
+import org.apache.olingo.odata2.api.processor.ODataRequest;
+
+public class BatchRequestPartImpl implements BatchRequestPart {
+
+  private List<ODataRequest> requests = new ArrayList<ODataRequest>();
+  private boolean isChangeSet;
+
+  public BatchRequestPartImpl() {}
+
+  public BatchRequestPartImpl(final boolean isChangeSet, final List<ODataRequest> requests) {
+    this.isChangeSet = isChangeSet;
+    this.requests = requests;
+  }
+
+  @Override
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  public void setChangeSet(final boolean isChangeSet) {
+    this.isChangeSet = isChangeSet;
+  }
+
+  @Override
+  public List<ODataRequest> getRequests() {
+    return Collections.unmodifiableList(requests);
+  }
+
+  public void setRequests(final List<ODataRequest> requests) {
+    this.requests = requests;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestWriter.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestWriter.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestWriter.java
new file mode 100644
index 0000000..3eda8e1
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestWriter.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSet;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
+import org.apache.olingo.odata2.api.client.batch.BatchPart;
+import org.apache.olingo.odata2.api.client.batch.BatchQueryPart;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+
+public class BatchRequestWriter {
+  private static final String REG_EX_BOUNDARY = "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
+  private static final String COLON = ":";
+  private static final String SP = " ";
+  private static final String LF = "\r\n";
+  private String batchBoundary;
+  private StringBuilder writer = new StringBuilder();
+
+  public InputStream writeBatchRequest(final List<BatchPart> batchParts, final String boundary) {
+    if (boundary.matches(REG_EX_BOUNDARY)) {
+      batchBoundary = boundary;
+    } else {
+      throw new IllegalArgumentException();
+    }
+    for (BatchPart batchPart : batchParts) {
+      writer.append("--" + boundary).append(LF);
+      if (batchPart instanceof BatchChangeSet) {
+        appendChangeSet((BatchChangeSet) batchPart);
+      } else if (batchPart instanceof BatchQueryPart) {
+        BatchQueryPart request = (BatchQueryPart) batchPart;
+        appendRequestBodyPart(request.getMethod(), request.getUri(), null, request.getHeaders(), request.getContentId());
+
+      }
+    }
+    writer.append("--").append(boundary).append("--").append(LF).append(LF);
+    InputStream batchRequestBody;
+    batchRequestBody = new ByteArrayInputStream(BatchHelper.getBytes(writer.toString()));
+    return batchRequestBody;
+  }
+
+  private void appendChangeSet(final BatchChangeSet batchChangeSet) {
+    String boundary = BatchHelper.generateBoundary("changeset");
+    while (boundary.equals(batchBoundary) || !boundary.matches(REG_EX_BOUNDARY)) {
+      boundary = BatchHelper.generateBoundary("changeset");
+    }
+    writer.append(HttpHeaders.CONTENT_TYPE).append(COLON).append(SP).append(HttpContentType.MULTIPART_MIXED + "; boundary=" + boundary).append(LF).append(LF);
+    for (BatchChangeSetPart request : batchChangeSet.getChangeSetParts()) {
+      writer.append("--").append(boundary).append(LF);
+      appendRequestBodyPart(request.getMethod(), request.getUri(), request.getBody(), request.getHeaders(), request.getContentId());
+    }
+    writer.append("--").append(boundary).append("--").append(LF).append(LF);
+  }
+
+  private void appendRequestBodyPart(final String method, final String uri, final String body, final Map<String, String> headers, final String contentId) {
+    boolean isContentLengthPresent = false;
+    writer.append(HttpHeaders.CONTENT_TYPE).append(COLON).append(SP).append(HttpContentType.APPLICATION_HTTP).append(LF);
+    writer.append(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING).append(COLON).append(SP).append("binary").append(LF);
+    if (contentId != null) {
+      writer.append(BatchHelper.HTTP_CONTENT_ID).append(COLON).append(SP).append(contentId).append(LF);
+    }
+    String contentLength = getHeaderValue(headers, HttpHeaders.CONTENT_LENGTH);
+    if (contentLength != null && !contentLength.isEmpty()) {
+      isContentLengthPresent = true;
+    }
+    writer.append(LF);
+    writer.append(method).append(SP).append(uri).append(SP).append("HTTP/1.1");
+    writer.append(LF);
+
+    if (!isContentLengthPresent && body != null && !body.isEmpty()) {
+      writer.append(HttpHeaders.CONTENT_LENGTH).append(COLON).append(SP).append(BatchHelper.getBytes(body).length).append(LF);
+
+    }
+    appendHeader(headers);
+
+    if (body != null && !body.isEmpty()) {
+      writer.append(LF);
+      writer.append(body);
+    }
+    writer.append(LF).append(LF);
+  }
+
+  private void appendHeader(final Map<String, String> headers) {
+    for (Map.Entry<String, String> headerMap : headers.entrySet()) {
+      String name = headerMap.getKey();
+      writer.append(name).append(COLON).append(SP).append(headerMap.getValue()).append(LF);
+
+    }
+  }
+
+  private String getHeaderValue(final Map<String, String> headers, final String headerName) {
+    for (Map.Entry<String, String> header : headers.entrySet()) {
+      if (headerName.equalsIgnoreCase(header.getKey())) {
+        return header.getValue();
+      }
+    }
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
new file mode 100644
index 0000000..15ce60c
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
@@ -0,0 +1,354 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.regex.MatchResult;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+
+public class BatchResponseParser {
+
+  private static final String LF = "\n";
+  private static final String REG_EX_OPTIONAL_WHITESPACE = "\\s?";
+  private static final String REG_EX_ZERO_OR_MORE_WHITESPACES = "\\s*";
+  private static final String ANY_CHARACTERS = ".*";
+
+  private static final Pattern REG_EX_BLANK_LINE = Pattern.compile("(|" + REG_EX_ZERO_OR_MORE_WHITESPACES + ")");
+  private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):" + REG_EX_OPTIONAL_WHITESPACE + "(.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
+  private static final Pattern REG_EX_VERSION = Pattern.compile("(?:HTTP/[0-9]\\.[0-9])");
+  private static final Pattern REG_EX_ANY_BOUNDARY_STRING = Pattern.compile("--" + ANY_CHARACTERS + REG_EX_ZERO_OR_MORE_WHITESPACES);
+  private static final Pattern REG_EX_STATUS_LINE = Pattern.compile(REG_EX_VERSION + "\\s" + "([0-9]{3})\\s([\\S ]+)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
+  private static final Pattern REG_EX_BOUNDARY_PARAMETER = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE + "boundary=(\".*\"|.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
+  private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED);
+
+  private static final String REG_EX_BOUNDARY = "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+ \\(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
+
+  private String contentTypeMime;
+  private String boundary;
+  private String currentContentId;
+  private int currentLineNumber = 0;
+
+  public BatchResponseParser(final String contentType) {
+    contentTypeMime = contentType;
+  }
+
+  public List<BatchSingleResponse> parse(final InputStream in) throws BatchException {
+    Scanner scanner = new Scanner(in, BatchHelper.DEFAULT_ENCODING).useDelimiter(LF);
+    List<BatchSingleResponse> responseList;
+    try {
+      responseList = Collections.unmodifiableList(parseBatchResponse(scanner));
+    } finally {// NOPMD (suppress DoNotThrowExceptionInFinally)
+      scanner.close();
+      try {
+        in.close();
+      } catch (IOException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+    return responseList;
+  }
+
+  private List<BatchSingleResponse> parseBatchResponse(final Scanner scanner) throws BatchException {
+    List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
+    if (contentTypeMime != null) {
+      boundary = getBoundary(contentTypeMime);
+      parsePreamble(scanner);
+      final String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
+      while (scanner.hasNext() && !scanner.hasNext(closeDelimiter)) {
+        responses.addAll(parseMultipart(scanner, boundary, false));
+      }
+      if (scanner.hasNext(closeDelimiter)) {
+        scanner.next(closeDelimiter);
+        currentLineNumber++;
+      } else {
+        throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(currentLineNumber));
+      }
+    } else {
+      throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
+    }
+    return responses;
+
+  }
+
+  //The method parses additional information prior to the first boundary delimiter line
+  private void parsePreamble(final Scanner scanner) {
+    while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
+      scanner.next();
+      currentLineNumber++;
+    }
+  }
+
+  private List<BatchSingleResponse> parseMultipart(final Scanner scanner, final String boundary, final boolean isChangeSet) throws BatchException {
+    Map<String, String> mimeHeaders = new HashMap<String, String>();
+    List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
+    if (scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
+      scanner.next();
+      currentLineNumber++;
+      mimeHeaders = parseMimeHeaders(scanner);
+      currentContentId = mimeHeaders.get(BatchHelper.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
+
+      final String contentType = mimeHeaders.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
+      if (contentType == null) {
+        throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
+      }
+      if (isChangeSet) {
+        if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
+          validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
+          parseNewLine(scanner);// mandatory
+          BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
+          responses.add(response);
+        } else {
+          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.APPLICATION_HTTP));
+        }
+      } else {
+        if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
+          validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
+          parseNewLine(scanner);// mandatory
+          BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
+          responses.add(response);
+        } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED + ANY_CHARACTERS)) {
+          String changeSetBoundary = getBoundary(contentType);
+          if (boundary.equals(changeSetBoundary)) {
+            throw new BatchException(BatchException.INVALID_CHANGESET_BOUNDARY.addContent(currentLineNumber));
+          }
+          parseNewLine(scanner);// mandatory
+          Pattern changeSetCloseDelimiter = Pattern.compile("--" + changeSetBoundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES);
+          while (!scanner.hasNext(changeSetCloseDelimiter)) {
+            responses.addAll(parseMultipart(scanner, changeSetBoundary, true));
+          }
+          scanner.next(changeSetCloseDelimiter);
+          currentLineNumber++;
+          parseNewLine(scanner);
+        } else {
+          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP));
+        }
+      }
+    } else if (scanner.hasNext(boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
+      currentLineNumber++;
+      throw new BatchException(BatchException.INVALID_BOUNDARY_DELIMITER.addContent(currentLineNumber));
+    } else if (scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
+      currentLineNumber++;
+      throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(currentLineNumber));
+    } else {
+      currentLineNumber++;
+      throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(currentLineNumber));
+    }
+    return responses;
+
+  }
+
+  private BatchSingleResponseImpl parseResponse(final Scanner scanner, final boolean isChangeSet) throws BatchException {
+    BatchSingleResponseImpl response = new BatchSingleResponseImpl();
+    if (scanner.hasNext(REG_EX_STATUS_LINE)) {
+      scanner.next(REG_EX_STATUS_LINE);
+      currentLineNumber++;
+      final String statusCode;
+      final String statusInfo;
+      MatchResult result = scanner.match();
+      if (result.groupCount() == 2) {
+        statusCode = result.group(1);
+        statusInfo = result.group(2);
+      } else {
+        currentLineNumber++;
+        throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(currentLineNumber));
+      }
+
+      Map<String, String> headers = parseResponseHeaders(scanner);
+      parseNewLine(scanner);
+      String contentLengthHeader = getHeaderValue(headers, HttpHeaders.CONTENT_LENGTH);
+      String body = (contentLengthHeader != null) ? parseBody(scanner, Integer.parseInt(contentLengthHeader)) : parseBody(scanner);
+      response.setStatusCode(statusCode);
+      response.setStatusInfo(statusInfo);
+      response.setHeaders(headers);
+      response.setContentId(currentContentId);
+      response.setBody(body);
+    } else {
+      currentLineNumber++;
+      throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(currentLineNumber));
+    }
+    return response;
+  }
+
+  private void validateEncoding(final String encoding) throws BatchException {
+    if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+      throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING);
+    }
+  }
+
+  private Map<String, String> parseMimeHeaders(final Scanner scanner) throws BatchException {
+    Map<String, String> headers = new HashMap<String, String>();
+    while (scanner.hasNext() && !(scanner.hasNext(REG_EX_BLANK_LINE))) {
+      if (scanner.hasNext(REG_EX_HEADER)) {
+        scanner.next(REG_EX_HEADER);
+        currentLineNumber++;
+        MatchResult result = scanner.match();
+        if (result.groupCount() == 2) {
+          String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
+          String headerValue = result.group(2).trim();
+          headers.put(headerName, headerValue);
+        }
+      } else {
+        throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next()));
+      }
+    }
+    return headers;
+  }
+
+  private Map<String, String> parseResponseHeaders(final Scanner scanner) throws BatchException {
+    Map<String, String> headers = new HashMap<String, String>();
+    while (scanner.hasNext() && !scanner.hasNext(REG_EX_BLANK_LINE)) {
+      if (scanner.hasNext(REG_EX_HEADER)) {
+        scanner.next(REG_EX_HEADER);
+        currentLineNumber++;
+        MatchResult result = scanner.match();
+        if (result.groupCount() == 2) {
+          String headerName = result.group(1).trim();
+          String headerValue = result.group(2).trim();
+          if (BatchHelper.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
+            if (currentContentId == null) {
+              currentContentId = headerValue;
+            }
+          } else {
+            headers.put(headerName, headerValue);
+          }
+        }
+      } else {
+        currentLineNumber++;
+        throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next()).addContent(currentLineNumber));
+      }
+    }
+    return headers;
+  }
+
+  private String getHeaderValue(final Map<String, String> headers, final String headerName) {
+    for (Map.Entry<String, String> header : headers.entrySet()) {
+      if (headerName.equalsIgnoreCase(header.getKey())) {
+        return header.getValue();
+      }
+    }
+    return null;
+  }
+
+  private String parseBody(final Scanner scanner) {
+    StringBuilder body = null;
+    while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
+      if (!scanner.hasNext(REG_EX_ZERO_OR_MORE_WHITESPACES)) {
+        if (body == null) {
+          body = new StringBuilder(scanner.next());
+        } else {
+          body.append(LF).append(scanner.next());
+        }
+      } else {
+        scanner.next();
+      }
+      currentLineNumber++;
+    }
+    String responseBody = body != null ? body.toString() : null;
+    return responseBody;
+  }
+
+  private String parseBody(final Scanner scanner, final int contentLength) {
+    StringBuilder body = null;
+    int length = 0;
+    while (scanner.hasNext() && length < contentLength) {
+      if (!scanner.hasNext(REG_EX_ZERO_OR_MORE_WHITESPACES)) {
+        String nextLine = scanner.next();
+        length += BatchHelper.getBytes(nextLine).length;
+        if (body == null) {
+          body = new StringBuilder(nextLine);
+        } else {
+          body.append(LF).append(nextLine);
+        }
+      } else {
+        scanner.next();
+      }
+      currentLineNumber++;
+      if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
+        scanner.next();
+        currentLineNumber++;
+      }
+    }
+    String responseBody = body != null ? body.toString() : null;
+    return responseBody;
+  }
+
+  private String getBoundary(final String contentType) throws BatchException {
+    Scanner contentTypeScanner = new Scanner(contentType).useDelimiter(";\\s?");
+    if (contentTypeScanner.hasNext(REG_EX_CONTENT_TYPE)) {
+      contentTypeScanner.next(REG_EX_CONTENT_TYPE);
+    } else {
+      contentTypeScanner.close();
+      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
+    }
+    if (contentTypeScanner.hasNext(REG_EX_BOUNDARY_PARAMETER)) {
+      contentTypeScanner.next(REG_EX_BOUNDARY_PARAMETER);
+      MatchResult result = contentTypeScanner.match();
+      contentTypeScanner.close();
+      if (result.groupCount() == 1 && result.group(1).trim().matches(REG_EX_BOUNDARY)) {
+        return trimQuota(result.group(1).trim());
+      } else {
+        throw new BatchException(BatchException.INVALID_BOUNDARY);
+      }
+    } else {
+      contentTypeScanner.close();
+      throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
+    }
+  }
+
+  private void parseNewLine(final Scanner scanner) throws BatchException {
+    if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
+      scanner.next();
+      currentLineNumber++;
+    } else {
+      currentLineNumber++;
+      if (scanner.hasNext()) {
+        throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent(scanner.next()).addContent(currentLineNumber));
+      } else {
+        throw new BatchException(BatchException.TRUNCATED_BODY.addContent(currentLineNumber));
+
+      }
+    }
+  }
+
+  private String trimQuota(String boundary) {
+    if (boundary.matches("\".*\"")) {
+      boundary = boundary.replace("\"", "");
+    }
+    boundary = boundary.replaceAll("\\)", "\\\\)");
+    boundary = boundary.replaceAll("\\(", "\\\\(");
+    boundary = boundary.replaceAll("\\?", "\\\\?");
+    boundary = boundary.replaceAll("\\+", "\\\\+");
+    return boundary;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseWriter.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseWriter.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseWriter.java
index e705724..ae0c07b 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseWriter.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseWriter.java
@@ -21,10 +21,10 @@ package org.apache.olingo.odata2.core.batch;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
-import java.util.UUID;
 
 import org.apache.olingo.odata2.api.batch.BatchException;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
 import org.apache.olingo.odata2.api.commons.HttpHeaders;
 import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
 import org.apache.olingo.odata2.api.exception.ODataMessageException;
@@ -34,50 +34,54 @@ public class BatchResponseWriter {
   private static final String COLON = ":";
   private static final String SP = " ";
   private static final String LF = "\r\n";
-  private StringBuilder writer = new StringBuilder();
+  private ResponseWriter writer = new ResponseWriter();
 
   public ODataResponse writeResponse(final List<BatchResponsePart> batchResponseParts) throws BatchException {
-    String boundary = generateBoundary("batch");
-    appendResponseBody(batchResponseParts, boundary);
+    String boundary = BatchHelper.generateBoundary("batch");
+    appendResponsePart(batchResponseParts, boundary);
     String batchResponseBody = writer.toString();
-    return ODataResponse.entity(batchResponseBody).status(HttpStatusCodes.ACCEPTED).
-        header(HttpHeaders.CONTENT_TYPE, BatchConstants.MULTIPART_MIXED + "; boundary=" + boundary)
-        .header(HttpHeaders.CONTENT_LENGTH, "" + batchResponseBody.length()).build();
-
+    return ODataResponse.entity(batchResponseBody).status(HttpStatusCodes.ACCEPTED)
+        .header(HttpHeaders.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + "; boundary=" + boundary)
+        .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(writer.length()))
+        .build();
   }
 
   private void appendChangeSet(final BatchResponsePart batchResponsePart) throws BatchException {
-    String boundary = generateBoundary("changeset");
-    writer.append(HttpHeaders.CONTENT_TYPE).append(COLON).append(SP).append("multipart/mixed; boundary=" + boundary).append(LF).append(LF);
+    String boundary = BatchHelper.generateBoundary("changeset");
+    writer.append(HttpHeaders.CONTENT_TYPE).append(COLON).append(SP)
+        .append("multipart/mixed; boundary=" + boundary).append(LF).append(LF);
     for (ODataResponse response : batchResponsePart.getResponses()) {
       writer.append("--").append(boundary).append(LF);
-      appendResponseBodyPart(response);
+      appendResponsePartBody(response);
     }
     writer.append("--").append(boundary).append("--").append(LF).append(LF);
   }
 
-  private void appendResponseBody(final List<BatchResponsePart> batchResponseParts, final String boundary) throws BatchException {
-
+  private void appendResponsePart(final List<BatchResponsePart> batchResponseParts, final String boundary) throws BatchException {
     for (BatchResponsePart batchResponsePart : batchResponseParts) {
       writer.append("--").append(boundary).append(LF);
       if (batchResponsePart.isChangeSet()) {
         appendChangeSet(batchResponsePart);
       } else {
         ODataResponse response = batchResponsePart.getResponses().get(0);
-        appendResponseBodyPart(response);
+        appendResponsePartBody(response);
       }
     }
     writer.append("--").append(boundary).append("--");
   }
 
-  private void appendResponseBodyPart(final ODataResponse response) throws BatchException {
-    writer.append(HttpHeaders.CONTENT_TYPE).append(COLON).append(SP).append(BatchConstants.HTTP_APPLICATION_HTTP).append(LF);
-    writer.append(BatchConstants.HTTP_CONTENT_TRANSFER_ENCODING).append(COLON).append(SP).append("binary").append(LF);
-    if (response.getHeader(BatchConstants.MIME_HEADER_CONTENT_ID) != null) {
-      writer.append(BatchConstants.HTTP_CONTENT_ID).append(COLON).append(SP).append(response.getHeader(BatchConstants.MIME_HEADER_CONTENT_ID)).append(LF);
+  private void appendResponsePartBody(final ODataResponse response) throws BatchException {
+    writer.append(HttpHeaders.CONTENT_TYPE).append(COLON).append(SP)
+        .append(HttpContentType.APPLICATION_HTTP).append(LF);
+    writer.append(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING).append(COLON).append(SP)
+        .append(BatchHelper.BINARY_ENCODING).append(LF);
+    if (response.getHeader(BatchHelper.MIME_HEADER_CONTENT_ID) != null) {
+      writer.append(BatchHelper.HTTP_CONTENT_ID).append(COLON).append(SP)
+          .append(response.getHeader(BatchHelper.MIME_HEADER_CONTENT_ID)).append(LF);
     }
     writer.append(LF);
-    writer.append("HTTP/1.1").append(SP).append(response.getStatus().getStatusCode()).append(SP).append(response.getStatus().getInfo()).append(LF);
+    writer.append("HTTP/1.1").append(SP).append(String.valueOf(response.getStatus().getStatusCode())).append(SP)
+        .append(response.getStatus().getInfo()).append(LF);
     appendHeader(response);
     if (!HttpStatusCodes.NO_CONTENT.equals(response.getStatus())) {
       String body;
@@ -87,7 +91,8 @@ public class BatchResponseWriter {
       } else {
         body = response.getEntity().toString();
       }
-      writer.append(HttpHeaders.CONTENT_LENGTH).append(COLON).append(SP).append(body.length()).append(LF).append(LF);
+      writer.append(HttpHeaders.CONTENT_LENGTH).append(COLON).append(SP)
+          .append(String.valueOf(BatchHelper.getBytes(body).length)).append(LF).append(LF);
       writer.append(body);
     }
     writer.append(LF).append(LF);
@@ -95,18 +100,15 @@ public class BatchResponseWriter {
 
   private void appendHeader(final ODataResponse response) {
     for (String name : response.getHeaderNames()) {
-      if (!BatchConstants.MIME_HEADER_CONTENT_ID.equalsIgnoreCase(name) && !BatchConstants.REQUEST_HEADER_CONTENT_ID.equalsIgnoreCase(name)) {
+      if (!BatchHelper.MIME_HEADER_CONTENT_ID.equalsIgnoreCase(name) && !BatchHelper.REQUEST_HEADER_CONTENT_ID.equalsIgnoreCase(name)) {
         writer.append(name).append(COLON).append(SP).append(response.getHeader(name)).append(LF);
-      } else if (BatchConstants.REQUEST_HEADER_CONTENT_ID.equalsIgnoreCase(name)) {
-        writer.append(BatchConstants.HTTP_CONTENT_ID).append(COLON).append(SP).append(response.getHeader(name)).append(LF);
+      } else if (BatchHelper.REQUEST_HEADER_CONTENT_ID.equalsIgnoreCase(name)) {
+        writer.append(BatchHelper.HTTP_CONTENT_ID).append(COLON).append(SP)
+            .append(response.getHeader(name)).append(LF);
       }
     }
   }
 
-  private String generateBoundary(final String value) {
-    return value + "_" + UUID.randomUUID().toString();
-  }
-
   private String readBody(final InputStream in) throws BatchException {
     byte[] tmp = new byte[2048];
     int count;
@@ -115,7 +117,7 @@ public class BatchResponseWriter {
     try {
       count = in.read(tmp);
       while (count >= 0) {
-        b.append(new String(tmp, 0, count));
+        b.append(new String(tmp, 0, count, BatchHelper.DEFAULT_ENCODING));
         count = in.read(tmp);
       }
     } catch (IOException e) {
@@ -133,4 +135,24 @@ public class BatchResponseWriter {
     return b.toString();
   }
 
+  private static class ResponseWriter {
+    private StringBuilder sb = new StringBuilder();
+    private int length = 0;
+
+    public ResponseWriter append(final String content) {
+      length += BatchHelper.getBytes(content).length;
+      sb.append(content);
+      return this;
+    }
+
+    public int length() {
+      return length;
+    }
+
+    @Override
+    public String toString() {
+      return sb.toString();
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchSingleResponseImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchSingleResponseImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchSingleResponseImpl.java
new file mode 100644
index 0000000..337b550
--- /dev/null
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchSingleResponseImpl.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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.batch;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+
+public class BatchSingleResponseImpl implements BatchSingleResponse {
+
+  private String statusCode;
+  private String statusInfo;
+  private String body;
+  private Map<String, String> headers = new HashMap<String, String>();
+  private String contentId;
+
+  @Override
+  public String getStatusCode() {
+    return statusCode;
+  }
+
+  @Override
+  public String getStatusInfo() {
+    return statusInfo;
+  }
+
+  @Override
+  public String getBody() {
+    return body;
+  }
+
+  @Override
+  public Map<String, String> getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public String getContentId() {
+    return contentId;
+  }
+
+  @Override
+  public String getHeader(final String name) {
+    return headers.get(name);
+  }
+
+  @Override
+  public Set<String> getHeaderNames() {
+    return headers.keySet();
+  }
+
+  public void setStatusCode(final String statusCode) {
+    this.statusCode = statusCode;
+  }
+
+  public void setStatusInfo(final String statusInfo) {
+    this.statusInfo = statusInfo;
+  }
+
+  public void setBody(final String body) {
+    this.body = body;
+  }
+
+  public void setHeaders(final Map<String, String> headers) {
+    this.headers = headers;
+  }
+
+  public void setContentId(final String contentId) {
+    this.contentId = contentId;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/Encoder.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/Encoder.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/Encoder.java
index 22f6cfd..8739b62 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/Encoder.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/Encoder.java
@@ -50,7 +50,6 @@ public class Encoder {
 
   // Character classes from RFC 3986
   private final static String UNRESERVED = "-._~"; // + ALPHA + DIGIT
-  private final static String GEN_DELIMS = ":/?#[]@";
   // RFC 3986 says: "For consistency, URI producers and normalizers should
   // use uppercase hexadecimal digits for all percent-encodings."
   private final static String[] hex = { "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F", "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", "%28", "%29", "%2A", "%2B", "%2C", "%2D", "%2E", "%2F", "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37", "%38", "%39", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F", "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47", "%48", "%49", "%4A", "%4B", "%4C", "%4D", "%4E", "%4F", "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57", "%58", "%59", "%5A", "%5B", "%5C", "%5D", "%5E", "%5F", "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67", "%68", "%69", "%6A", "%6B", "%6C", "%6D", "%6E", "%6F", "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77", "%78", "%79", "%7A", "%7B", "%7C", "%7D", "%7E", "%7F", "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", "%88"
 ,

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmBinary.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmBinary.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmBinary.java
index 5485163..2c9ea44 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmBinary.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmBinary.java
@@ -146,6 +146,6 @@ public class EdmBinary extends AbstractSimpleType {
 
   @Override
   public String toUriLiteral(final String literal) throws EdmSimpleTypeException {
-    return "binary'" + Hex.encodeHexString(Base64.decodeBase64(literal)).toUpperCase(Locale.ROOT) + "'";
+    return "binary'" + String.valueOf(Hex.encodeHex(Base64.decodeBase64(literal), false)) + "'";
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java
index 2370a06..d1bfedd 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmDateTime.java
@@ -141,7 +141,7 @@ public class EdmDateTime extends AbstractSimpleType {
     // off temporarily.
     dateTimeValue.setLenient(false);
     try {
-      dateTimeValue.getTimeInMillis();
+      dateTimeValue.get(Calendar.MILLISECOND);
     } catch (final IllegalArgumentException e) {
       throw new EdmSimpleTypeException(EdmSimpleTypeException.LITERAL_ILLEGAL_CONTENT.addContent(value), e);
     }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java
index 99d273e..217e772 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/EdmTime.java
@@ -85,15 +85,12 @@ public class EdmTime extends AbstractSimpleType {
     Calendar dateTimeValue = Calendar.getInstance();
     dateTimeValue.clear();
 
-    if (matcher.group(1) != null) {
-      dateTimeValue.set(Calendar.HOUR_OF_DAY, Integer.parseInt(matcher.group(1)));
-    }
-    if (matcher.group(2) != null) {
-      dateTimeValue.set(Calendar.MINUTE, Integer.parseInt(matcher.group(2)));
-    }
-    if (matcher.group(3) != null) {
-      dateTimeValue.set(Calendar.SECOND, Integer.parseInt(matcher.group(3)));
-    }
+    dateTimeValue.set(Calendar.HOUR_OF_DAY,
+        matcher.group(1) == null ? 0 : Integer.parseInt(matcher.group(1)));
+    dateTimeValue.set(Calendar.MINUTE,
+        matcher.group(2) == null ? 0 : Integer.parseInt(matcher.group(2)));
+    dateTimeValue.set(Calendar.SECOND,
+        matcher.group(3) == null ? 0 : Integer.parseInt(matcher.group(3)));
 
     if (matcher.group(4) != null) {
       if (facets == null || facets.getPrecision() == null || facets.getPrecision() >= matcher.group(4).length()) {

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/parser/EdmParser.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/parser/EdmParser.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/parser/EdmParser.java
index 8e3e3dd..ef15551 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/parser/EdmParser.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/edm/parser/EdmParser.java
@@ -758,7 +758,7 @@ public class EdmParser {
       annotationAttribute.setName(reader.getAttributeLocalName(i));
       annotationAttribute.setPrefix(reader.getAttributePrefix(i));
       String namespace = reader.getAttributeNamespace(i);
-      if (!DEFAULT_NAMESPACE.equals(namespace)) {
+      if (namespace != null && !isDefaultNamespace(namespace)) {
         annotationAttribute.setNamespace(namespace);
       }
       annotationAttributes.add(annotationAttribute);
@@ -781,7 +781,8 @@ public class EdmParser {
   private List<AnnotationAttribute> readAnnotationAttribute(final XMLStreamReader reader) {
     List<AnnotationAttribute> annotationAttributes = new ArrayList<AnnotationAttribute>();
     for (int i = 0; i < reader.getAttributeCount(); i++) {
-      if (!mandatoryNamespaces.containsValue(reader.getAttributeNamespace(i)) && !DEFAULT_NAMESPACE.equals(reader.getAttributeNamespace(i))) {
+      String namespace = reader.getAttributeNamespace(i);
+      if (namespace != null && !isDefaultNamespace(namespace) && !mandatoryNamespaces.containsValue(namespace)) {
         annotationAttributes.add(new AnnotationAttribute().setName(reader.getAttributeLocalName(i)).
             setPrefix(reader.getAttributePrefix(i)).setNamespace(reader.getAttributeNamespace(i)).setText(reader.getAttributeValue(i)));
       }
@@ -792,6 +793,10 @@ public class EdmParser {
     return annotationAttributes;
   }
 
+  private boolean isDefaultNamespace(final String namespace) {
+    return namespace.isEmpty();
+  }
+
   private void checkAllMandatoryNamespacesAvailable() throws EntityProviderException {
     if (!xmlNamespaceMap.containsValue(Edm.NAMESPACE_EDMX_2007_06)) {
       throw new EntityProviderException(EntityProviderException.INVALID_NAMESPACE.addContent(Edm.NAMESPACE_EDMX_2007_06));
@@ -807,7 +812,7 @@ public class EdmParser {
     for (int i = 0; i < namespaceCount; i++) {
       String namespacePrefix = reader.getNamespacePrefix(i);
       String namespaceUri = reader.getNamespaceURI(i);
-      if (namespacePrefix == null || DEFAULT_NAMESPACE.equals(namespacePrefix)) {
+      if (namespacePrefix == null || isDefaultNamespace(namespacePrefix)) {
         namespacePrefix = Edm.PREFIX_EDM;
       }
       xmlNamespaceMap.put(namespacePrefix, namespaceUri);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/JsonEntityProvider.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/JsonEntityProvider.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/JsonEntityProvider.java
index aa0056c..40cfd7a 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/JsonEntityProvider.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/JsonEntityProvider.java
@@ -293,14 +293,12 @@ public class JsonEntityProvider implements ContentTypeBasedEntityProvider {
 
   @Override
   public ODataFeed readFeed(final EdmEntitySet entitySet, final InputStream content, final EntityProviderReadProperties properties) throws EntityProviderException {
-    JsonEntityConsumer jec = new JsonEntityConsumer();
-    return jec.readFeed(entitySet, content, properties);
+    return new JsonEntityConsumer().readFeed(entitySet, content, properties);
   }
 
   @Override
   public ODataEntry readEntry(final EdmEntitySet entitySet, final InputStream content, final EntityProviderReadProperties properties) throws EntityProviderException {
-    JsonEntityConsumer jec = new JsonEntityConsumer();
-    return jec.readEntry(entitySet, content, properties);
+    return new JsonEntityConsumer().readEntry(entitySet, content, properties);
   }
 
   @Override
@@ -320,8 +318,7 @@ public class JsonEntityProvider implements ContentTypeBasedEntityProvider {
 
   @Override
   public ServiceDocument readServiceDocument(final InputStream serviceDocument) throws EntityProviderException {
-    JsonServiceDocumentConsumer serviceDocConsumer = new JsonServiceDocumentConsumer();
-    return serviceDocConsumer.parseJson(serviceDocument);
+    return new JsonServiceDocumentConsumer().parseJson(serviceDocument);
   }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/ProviderFacadeImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/ProviderFacadeImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/ProviderFacadeImpl.java
index 42c04b9..13eb918 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/ProviderFacadeImpl.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/ProviderFacadeImpl.java
@@ -23,8 +23,10 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.batch.BatchPart;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart;
+import org.apache.olingo.odata2.api.client.batch.BatchPart;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
 import org.apache.olingo.odata2.api.edm.Edm;
 import org.apache.olingo.odata2.api.edm.EdmEntitySet;
 import org.apache.olingo.odata2.api.edm.EdmFunctionImport;
@@ -43,6 +45,8 @@ import org.apache.olingo.odata2.api.processor.ODataErrorContext;
 import org.apache.olingo.odata2.api.processor.ODataResponse;
 import org.apache.olingo.odata2.api.servicedocument.ServiceDocument;
 import org.apache.olingo.odata2.core.batch.BatchRequestParser;
+import org.apache.olingo.odata2.core.batch.BatchRequestWriter;
+import org.apache.olingo.odata2.core.batch.BatchResponseParser;
 import org.apache.olingo.odata2.core.batch.BatchResponseWriter;
 import org.apache.olingo.odata2.core.commons.ContentType;
 import org.apache.olingo.odata2.core.edm.parser.EdmxProvider;
@@ -189,8 +193,8 @@ public class ProviderFacadeImpl implements EntityProviderInterface {
   }
 
   @Override
-  public List<BatchPart> parseBatchRequest(final String contentType, final InputStream content, final EntityProviderBatchProperties properties) throws BatchException {
-    List<BatchPart> batchParts = new BatchRequestParser(contentType, properties).parse(content);
+  public List<BatchRequestPart> parseBatchRequest(final String contentType, final InputStream content, final EntityProviderBatchProperties properties) throws BatchException {
+    List<BatchRequestPart> batchParts = new BatchRequestParser(contentType, properties).parse(content);
     return batchParts;
   }
 
@@ -200,4 +204,16 @@ public class ProviderFacadeImpl implements EntityProviderInterface {
     return batchWriter.writeResponse(batchResponseParts);
   }
 
+  @Override
+  public InputStream writeBatchRequest(final List<BatchPart> batchParts, final String boundary) {
+    BatchRequestWriter batchWriter = new BatchRequestWriter();
+    return batchWriter.writeBatchRequest(batchParts, boundary);
+  }
+
+  @Override
+  public List<BatchSingleResponse> parseBatchResponse(final String contentType, final InputStream content) throws BatchException {
+    List<BatchSingleResponse> responses = new BatchResponseParser(contentType).parse(content);
+    return responses;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/aggregator/EntityPropertyInfo.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/aggregator/EntityPropertyInfo.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/aggregator/EntityPropertyInfo.java
index 7f96671..83ab4df 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/aggregator/EntityPropertyInfo.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/aggregator/EntityPropertyInfo.java
@@ -30,12 +30,12 @@ import org.apache.olingo.odata2.api.edm.EdmType;
  *  
  */
 public class EntityPropertyInfo {
-  private String name;
-  private EdmType type;
-  private EdmFacets facets;
-  private EdmCustomizableFeedMappings customMapping;
-  private String mimeType;
-  private EdmMapping mapping;
+  private final String name;
+  private final EdmType type;
+  private final EdmFacets facets;
+  private final EdmCustomizableFeedMappings customMapping;
+  private final String mimeType;
+  private final EdmMapping mapping;
 
   EntityPropertyInfo(final String name, final EdmType type, final EdmFacets facets, final EdmCustomizableFeedMappings customizableFeedMapping, final String mimeType, final EdmMapping mapping) {
     this.name = name;
@@ -57,13 +57,7 @@ public class EntityPropertyInfo {
   }
 
   public boolean isMandatory() {
-    if (facets == null) {
-      return false;
-    } else if (facets.isNullable() == null) {
-      return false;
-    } else {
-      return !facets.isNullable();
-    }
+    return !(facets == null || facets.isNullable() == null || facets.isNullable());
   }
 
   public boolean isComplex() {

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntityConsumer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntityConsumer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntityConsumer.java
index cdfce38..4d32750 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntityConsumer.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntityConsumer.java
@@ -50,9 +50,7 @@ public class JsonEntityConsumer {
       EntityInfoAggregator eia = EntityInfoAggregator.create(entitySet);
       reader = createJsonReader(content);
 
-      JsonEntryConsumer jec = new JsonEntryConsumer(reader, eia, properties);
-      ODataEntry result = jec.readSingleEntry();
-      return result;
+      return new JsonEntryConsumer(reader, eia, properties).readSingleEntry();
     } catch (UnsupportedEncodingException e) {
       cachedException = new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
       throw cachedException;

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumer.java
index f81e589..5a6d486 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumer.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonEntryConsumer.java
@@ -107,11 +107,9 @@ public class JsonEntryConsumer {
 
   private void readEntryContent() throws IOException, EdmException, EntityProviderException {
     while (reader.hasNext()) {
-      String name = reader.nextName();
+      final String name = reader.nextName();
       handleName(name);
     }
-
-    //TODO: validate created entry
   }
 
   private void handleName(final String name) throws IOException, EdmException, EntityProviderException {

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumer.java
index 37c0b68..a1bd139 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumer.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/JsonPropertyConsumer.java
@@ -141,6 +141,9 @@ public class JsonPropertyConsumer {
   private Object readComplexProperty(final JsonReader reader, final EntityComplexPropertyInfo complexPropertyInfo, final Object typeMapping) throws EdmException, EntityProviderException, IOException {
     if (reader.peek().equals(JsonToken.NULL)) {
       reader.nextNull();
+      if (complexPropertyInfo.isMandatory()) {
+        throw new EntityProviderException(EntityProviderException.INVALID_PROPERTY_VALUE.addContent(complexPropertyInfo.getName()));
+      }
       return null;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumer.java
index 678a95f..d065d4d 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumer.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntityConsumer.java
@@ -87,12 +87,10 @@ public class XmlEntityConsumer {
     EntityProviderException cachedException = null;
 
     try {
-      XmlEntryConsumer xec = new XmlEntryConsumer();
       reader = createStaxReader(content);
-
       EntityInfoAggregator eia = EntityInfoAggregator.create(entitySet);
-      ODataEntry result = xec.readEntry(reader, eia, properties);
-      return result;
+
+      return new XmlEntryConsumer().readEntry(reader, eia, properties);
     } catch (EntityProviderException e) {
       cachedException = e;
       throw cachedException;

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntryConsumer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntryConsumer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntryConsumer.java
index b27c385..a71b986 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntryConsumer.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlEntryConsumer.java
@@ -19,7 +19,6 @@
 package org.apache.olingo.odata2.core.ep.consumer;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -86,10 +85,6 @@ public class XmlEntryConsumer {
         }
       }
 
-      if (!readProperties.getMergeSemantic()) {
-        validateMandatoryPropertiesAvailable(eia, readEntryResult);
-      }
-
       return readEntryResult;
     } catch (XMLStreamException e) {
       throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
@@ -105,8 +100,7 @@ public class XmlEntryConsumer {
   }
 
   /**
-   * Initialize the {@link XmlEntryConsumer} to be ready for read of an entry.
-   * 
+   * Initializes the {@link XmlEntryConsumer} to be ready for reading an entry.
    * @param readProperties
    * @throws EntityProviderException
    */
@@ -122,27 +116,6 @@ public class XmlEntryConsumer {
     foundPrefix2NamespaceUri.putAll(readProperties.getValidatedPrefixNamespaceUris());
   }
 
-  /**
-   * Validates that all mandatory properties are found and set at the {@link #readEntryResult} entity.
-   * If a mandatory property is missing an {@link EntityProviderException} is thrown.
-   * 
-   * @param eia entity info which contains the information which properties are mandatory
-   * @param entry entry for which the mandatory properties are validated
-   * @throws EntityProviderException if a mandatory property is missing
-   */
-  private void validateMandatoryPropertiesAvailable(final EntityInfoAggregator eia, final ODataEntryImpl entry) throws EntityProviderException {
-    Collection<EntityPropertyInfo> propertyInfos = new ArrayList<EntityPropertyInfo>(eia.getPropertyInfos());
-    propertyInfos.removeAll(eia.getKeyPropertyInfos());
-    Map<String, Object> data = entry.getProperties();
-
-    for (EntityPropertyInfo entityPropertyInfo : propertyInfos) {
-      boolean mandatory = entityPropertyInfo.isMandatory();
-      if (mandatory && !data.containsKey(entityPropertyInfo.getName())) {
-        throw new EntityProviderException(EntityProviderException.MISSING_PROPERTY.addContent(entityPropertyInfo.getName()));
-      }
-    }
-  }
-
   private void handleStartedTag(final XMLStreamReader reader, final EntityInfoAggregator eia, final EntityProviderReadProperties readProperties)
       throws EntityProviderException, XMLStreamException, EdmException {
 
@@ -408,8 +381,7 @@ public class XmlEntryConsumer {
    * 
    * @param isFeed
    * @param inlineEntries
-   * @return 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
+   * @return
    */
   private Object extractODataEntity(final boolean isFeed, final List<ODataEntry> inlineEntries) {
     if (isFeed) {
@@ -453,8 +425,7 @@ public class XmlEntryConsumer {
    * 
    * @param readProperties
    * @param navigationProperty
-   * @return the created {@link EntityProviderReadProperties} which can be used for reading of inline properties/entrys of navigation links within
-   * this current read entry.
+   * @return
    * @throws EntityProviderException 
    */
   private EntityProviderReadProperties createInlineProperties(final EntityProviderReadProperties readProperties, final EdmNavigationProperty navigationProperty) throws EntityProviderException {

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumer.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumer.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumer.java
index d604033..0ce8f1e 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumer.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/consumer/XmlPropertyConsumer.java
@@ -31,6 +31,7 @@ import org.apache.olingo.odata2.api.edm.EdmFacets;
 import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
 import org.apache.olingo.odata2.api.edm.EdmProperty;
 import org.apache.olingo.odata2.api.edm.EdmSimpleType;
+import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
 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.EntityInfoAggregator;
@@ -43,7 +44,8 @@ import org.apache.olingo.odata2.core.ep.util.FormatXml;
  */
 public class XmlPropertyConsumer {
 
-  public static final String TRUE = "true";
+  protected static final String TRUE = "true";
+  protected static final String FALSE = "false";
 
   public Map<String, Object> readProperty(final XMLStreamReader reader, final EdmProperty property, final boolean merge) throws EntityProviderException {
     return readProperty(reader, property, merge, null);
@@ -102,67 +104,58 @@ public class XmlPropertyConsumer {
   }
 
   protected Object readStartedElement(final XMLStreamReader reader, final EntityPropertyInfo propertyInfo, final EntityTypeMapping typeMappings) throws EntityProviderException, EdmException {
-    Map<String, Object> name2Value = new HashMap<String, Object>();
+    final String name = propertyInfo.getName();
     Object result = null;
 
     try {
-      reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_D_2007_08, propertyInfo.getName());
+      reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_D_2007_08, name);
       final String nullAttribute = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, FormatXml.M_NULL);
 
+      if (!(nullAttribute == null || TRUE.equals(nullAttribute) || FALSE.equals(nullAttribute))) {
+        throw new EntityProviderException(EntityProviderException.COMMON);
+      }
+
       if (TRUE.equals(nullAttribute)) {
+        if (propertyInfo.isMandatory()) {
+          throw new EntityProviderException(EntityProviderException.INVALID_PROPERTY_VALUE.addContent(name));
+        }
         reader.nextTag();
       } else if (propertyInfo.isComplex()) {
         final String typeAttribute = reader.getAttributeValue(Edm.NAMESPACE_M_2007_08, FormatXml.M_TYPE);
-        String expectedTypeAttributeValue = propertyInfo.getType().getNamespace() + Edm.DELIMITER + propertyInfo.getType().getName();
-        if (typeAttribute != null && !expectedTypeAttributeValue.equals(typeAttribute)) {
-          throw new EntityProviderException(EntityProviderException.INVALID_COMPLEX_TYPE.addContent(expectedTypeAttributeValue).addContent(typeAttribute));
+        if (typeAttribute != null) {
+          final String expectedTypeAttributeValue = propertyInfo.getType().getNamespace() + Edm.DELIMITER + propertyInfo.getType().getName();
+          if (!expectedTypeAttributeValue.equals(typeAttribute)) {
+            throw new EntityProviderException(EntityProviderException.INVALID_COMPLEX_TYPE.addContent(expectedTypeAttributeValue).addContent(typeAttribute));
+          }
         }
 
         reader.nextTag();
+        Map<String, Object> name2Value = new HashMap<String, Object>();
         while (reader.hasNext() && !reader.isEndElement()) {
-          String childName = reader.getLocalName();
-          EntityPropertyInfo childProperty = getChildProperty(childName, propertyInfo);
+          final String childName = reader.getLocalName();
+          final EntityPropertyInfo childProperty = ((EntityComplexPropertyInfo) propertyInfo).getPropertyInfo(childName);
           if (childProperty == null) {
             throw new EntityProviderException(EntityProviderException.INVALID_PROPERTY.addContent(childName));
           }
-          Object value = readStartedElement(reader, childProperty, typeMappings.getEntityTypeMapping(propertyInfo.getName()));
+          final Object value = readStartedElement(reader, childProperty, typeMappings.getEntityTypeMapping(name));
           name2Value.put(childName, value);
           reader.nextTag();
         }
+        result = name2Value;
       } else {
-        Class<?> mapping = typeMappings.getMappingClass(propertyInfo.getName());
-        result = convert(propertyInfo, reader.getElementText(), mapping);
+        result = convert(propertyInfo, reader.getElementText(), typeMappings.getMappingClass(name));
       }
-      reader.require(XMLStreamConstants.END_ELEMENT, Edm.NAMESPACE_D_2007_08, propertyInfo.getName());
+      reader.require(XMLStreamConstants.END_ELEMENT, Edm.NAMESPACE_D_2007_08, name);
 
-      // if reading finished check which result must be returned
-      if (result != null) {
-        return result;
-      } else if (!name2Value.isEmpty()) {
-        return name2Value;
-      }
+      return result;
     } catch (XMLStreamException e) {
       throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e);
     }
-    return null;
   }
 
-  private EntityPropertyInfo getChildProperty(final String childPropertyName, final EntityPropertyInfo property) throws EdmException, EntityProviderException {
-    if (property.isComplex()) {
-      EntityComplexPropertyInfo complex = (EntityComplexPropertyInfo) property;
-      return complex.getPropertyInfo(childPropertyName);
-    }
-    throw new EntityProviderException(EntityProviderException.INVALID_PROPERTY.addContent(
-        "Expected complex property but found simple for property with name '" + property.getName() + "'"));
-  }
-
-  private Object convert(final EntityPropertyInfo property, final String value, final Class<?> typeMapping) throws EdmException, EntityProviderException {
-    if (!property.isComplex()) {
-      EdmSimpleType type = (EdmSimpleType) property.getType();
-      return type.valueOfString(value, EdmLiteralKind.DEFAULT, property.getFacets(),
-          typeMapping == null ? type.getDefaultType() : typeMapping);
-    }
-    throw new EntityProviderException(EntityProviderException.INVALID_PROPERTY.addContent(
-        "Expected simple property but found complex for property with name '" + property.getName() + "'"));
+  private Object convert(final EntityPropertyInfo property, final String value, final Class<?> typeMapping) throws EdmSimpleTypeException {
+    final EdmSimpleType type = (EdmSimpleType) property.getType();
+    return type.valueOfString(value, EdmLiteralKind.DEFAULT, property.getFacets(),
+        typeMapping == null ? type.getDefaultType() : typeMapping);
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorService.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorService.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorService.java
index 19eb01e..f5298c1 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorService.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/processor/ODataSingleProcessorService.java
@@ -206,7 +206,8 @@ public class ODataSingleProcessorService implements ODataService {
     }
 
     if (processorFeature == BatchProcessor.class) {
-      result.add(HttpContentType.MULTIPART_MIXED);
+      //set wildcard for now to ignore accept header completely, reasoning: there is only one representation for $batch
+      result.add(HttpContentType.WILDCARD);
     } else if (processorFeature == EntityProcessor.class) {
       result.add(HttpContentType.APPLICATION_ATOM_XML_ENTRY_UTF8);
       result.add(HttpContentType.APPLICATION_ATOM_XML_UTF8);

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
index fb127e3..0b99827 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/rt/RuntimeDelegateImpl.java
@@ -22,6 +22,9 @@ import java.io.InputStream;
 
 import org.apache.olingo.odata2.api.ODataService;
 import org.apache.olingo.odata2.api.batch.BatchResponsePart.BatchResponsePartBuilder;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSet.BatchChangeSetBuilder;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart.BatchChangeSetPartBuilder;
+import org.apache.olingo.odata2.api.client.batch.BatchQueryPart.BatchQueryPartBuilder;
 import org.apache.olingo.odata2.api.edm.Edm;
 import org.apache.olingo.odata2.api.edm.EdmSimpleType;
 import org.apache.olingo.odata2.api.edm.EdmSimpleTypeFacade;
@@ -36,6 +39,9 @@ import org.apache.olingo.odata2.api.rt.RuntimeDelegate.RuntimeDelegateInstance;
 import org.apache.olingo.odata2.api.uri.UriParser;
 import org.apache.olingo.odata2.core.ODataRequestImpl;
 import org.apache.olingo.odata2.core.ODataResponseImpl;
+import org.apache.olingo.odata2.core.batch.BatchChangeSetImpl;
+import org.apache.olingo.odata2.core.batch.BatchChangeSetPartImpl;
+import org.apache.olingo.odata2.core.batch.BatchQueryPartImpl;
 import org.apache.olingo.odata2.core.batch.BatchResponsePartImpl;
 import org.apache.olingo.odata2.core.edm.EdmSimpleTypeFacadeImpl;
 import org.apache.olingo.odata2.core.edm.parser.EdmxProvider;
@@ -102,4 +108,23 @@ public class RuntimeDelegateImpl extends RuntimeDelegateInstance {
     return request.new ODataRequestBuilderImpl();
   }
 
+  @Override
+  protected BatchChangeSetBuilder createBatchChangeSetBuilder() {
+    BatchChangeSetImpl changeSet = new BatchChangeSetImpl();
+    return changeSet.new BatchChangeSetBuilderImpl();
+  }
+
+  @Override
+  protected BatchQueryPartBuilder createBatchQueryRequestBuilder() {
+    BatchQueryPartImpl batchQueryRequest = new BatchQueryPartImpl();
+    return batchQueryRequest.new BatchQueryRequestBuilderImpl();
+  }
+
+  @Override
+  protected BatchChangeSetPartBuilder createBatchChangeSetRequest() {
+    BatchChangeSetPartImpl batchChangeSetRequest = new BatchChangeSetPartImpl();
+    ;
+    return batchChangeSetRequest.new BatchChangeSetRequestBuilderImpl();
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/45c27419/odata-core/src/main/resources/i18n.properties
----------------------------------------------------------------------
diff --git a/odata-core/src/main/resources/i18n.properties b/odata-core/src/main/resources/i18n.properties
index 1627223..b28bf50 100644
--- a/odata-core/src/main/resources/i18n.properties
+++ b/odata-core/src/main/resources/i18n.properties
@@ -118,7 +118,8 @@ org.apache.olingo.odata2.api.batch.BatchException.MISSING_CLOSE_DELIMITER=Missin
 org.apache.olingo.odata2.api.batch.BatchException.INVALID_QUERY_OPERATION_METHOD=Invalid method: a Query Operation cannot contain insert, update or delete requests at line '%1$s'.
 org.apache.olingo.odata2.api.batch.BatchException.INVALID_CHANGESET_METHOD= Invalid method: a ChangeSet cannot contain retrieve requests at line '%1$s'.
 org.apache.olingo.odata2.api.batch.BatchException.INVALID_QUERY_PARAMETER=Invalid query parameters.
-org.apache.olingo.odata2.api.batch.BatchException.INVALID_URI=Invalid URI.
+org.apache.olingo.odata2.api.batch.BatchException.INVALID_URI=Invalid URI: line '%1$s'.
+org.apache.olingo.odata2.api.batch.BatchException.UNSUPPORTED_ABSOLUTE_PATH = An absolute-path in request line is not supported: line '%1$s'.
 org.apache.olingo.odata2.api.batch.BatchException.INVALID_BOUNDARY=Invalid boundary at line '%1$s'.
 org.apache.olingo.odata2.api.batch.BatchException.NO_MATCH_WITH_BOUNDARY_STRING=The boundary string does not match the boundary from the Content-Type header field '%1$s': line '%2$s'.
 org.apache.olingo.odata2.api.batch.BatchException.MISSING_CONTENT_TYPE=No Content-Type field for MIME-header is present.
@@ -132,6 +133,7 @@ org.apache.olingo.odata2.api.batch.BatchException.MISSING_BLANK_LINE=Expected em
 org.apache.olingo.odata2.api.batch.BatchException.INVALID_PATHINFO=PathInfo should not be null.
 org.apache.olingo.odata2.api.batch.BatchException.MISSING_METHOD=Missing method in request line '%1$s'.
 org.apache.olingo.odata2.api.batch.BatchException.INVALID_REQUEST_LINE=Invalid request line '%1$s' at line '%2$s'.
+org.apache.olingo.odata2.api.batch.BatchException.INVALID_REQUEST_LINE=Invalid status line '%1$s' at line '%2$s'.
 org.apache.olingo.odata2.api.batch.BatchException.TRUNCATED_BODY=Body is truncated: line '%1$s'.
 
 ##################################