You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2014/11/18 16:12:13 UTC

[12/22] olingo-odata4 git commit: Batch IT test case

Batch IT test case

Signed-off-by: Christian Amend <ch...@apache.org>


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

Branch: refs/heads/master
Commit: 4ff5fb9c8ce045e87657dec69f76db52001f1508
Parents: 5f4eb03
Author: Christian Holzer <c....@sap.com>
Authored: Mon Nov 10 17:30:18 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:11:00 2014 +0100

----------------------------------------------------------------------
 .../fit/tecsvc/client/BatchClientITCase.java    | 459 +++++++++++++++++++
 .../server/api/batch/ODataResponsePart.java     |  52 ++-
 .../server/api/processor/BatchProcessor.java    |   3 +-
 .../server/api/processor/DefaultProcessor.java  |  93 +++-
 .../core/batch/handler/BatchPartHandler.java    |   7 +-
 .../batch/handler/ODataResponsePartImpl.java    |  44 --
 .../core/batch/parser/BatchParserCommon.java    |   8 -
 .../core/batch/writer/BatchResponseWriter.java  |   2 +-
 .../batch/writer/ODataResponsePartImpl.java     |  44 --
 .../core/batch/BatchRequestParserTest.java      |  28 +-
 .../batch/handler/MockedBatchHandlerTest.java   |   4 +-
 .../batch/writer/BatchResponseWriterTest.java   |   8 +-
 12 files changed, 627 insertions(+), 125 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java
new file mode 100644
index 0000000..f2e1ab6
--- /dev/null
+++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java
@@ -0,0 +1,459 @@
+/*
+ * 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.fit.tecsvc.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.math.BigDecimal;
+import java.net.URI;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.request.batch.BatchManager;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchResponseItem;
+import org.apache.olingo.client.api.communication.request.batch.ODataChangeset;
+import org.apache.olingo.client.api.communication.request.batch.v4.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataEntityCreateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest;
+import org.apache.olingo.client.api.communication.request.cud.v4.UpdateType;
+import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest;
+import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetRequest;
+import org.apache.olingo.client.api.communication.response.ODataBatchResponse;
+import org.apache.olingo.client.api.communication.response.ODataEntityCreateResponse;
+import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResponse;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.apache.olingo.client.api.uri.v4.URIBuilder;
+import org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem;
+import org.apache.olingo.client.core.uri.URIUtils;
+import org.apache.olingo.commons.api.domain.v4.ODataEntity;
+import org.apache.olingo.commons.api.domain.v4.ODataEntitySet;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.api.format.ODataFormat;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.fit.tecsvc.TecSvcConst;
+import org.apache.olingo.fit.v4.AbstractTestITCase;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class BatchClientITCase extends AbstractTestITCase {
+  private final static String ACCEPT = ContentType.APPLICATION_OCTET_STREAM.toContentTypeString();
+  private static final String SERVICE_URI = TecSvcConst.BASE_URI;
+
+  @Before
+  public void setup() {
+    client.getConfiguration().setContinueOnError(false);
+  }
+
+  @Test
+  public void emptyBatchRequest() {
+    // create your request
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+    final ODataBatchResponse response = payload.getResponse();
+
+    assertEquals(202, response.getStatusCode());
+    assertEquals("Accepted", response.getStatusMessage());
+
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+    assertFalse(iter.hasNext());
+  }
+
+  @Test
+  public void getBatchRequest() {
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+
+    // create new request
+    appendGetRequest(payload, "ESAllPrim", 32767);
+
+    // Fetch result
+    final ODataBatchResponse response = payload.getResponse();
+
+    assertEquals(202, response.getStatusCode());
+    assertEquals("Accepted", response.getStatusMessage());
+
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+    assertTrue(iter.hasNext());
+
+    ODataBatchResponseItem item = iter.next();
+    assertFalse(item.isChangeset());
+
+    ODataResponse oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("538", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+  }
+
+  @Test
+  public void testErrorWithoutContinueOnErrorPreferHeader() {
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+    appendGetRequest(payload, "ESAllPrim", 42); // Error ( Key does not exist )
+    appendGetRequest(payload, "ESAllPrim", 0); // Without error
+
+    // Fetch result
+    final ODataBatchResponse response = payload.getResponse();
+    assertEquals(202, response.getStatusCode());
+
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+
+    // Check first get request
+    assertTrue(iter.hasNext());
+    ODataBatchResponseItem item = iter.next();
+    assertFalse(item.isChangeset());
+
+    ODataResponse oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("538", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+
+    // Check second get request
+    assertTrue(iter.hasNext());
+    item = iter.next();
+    assertFalse(item.isChangeset());
+
+    oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.NOT_FOUND.getStatusCode(), oDataResonse.getStatusCode());
+
+    // Check if third request is available
+    assertFalse(iter.hasNext());
+  }
+
+  @Test
+  public void testErrorWithContinueOnErrorPreferHeader() {
+    client.getConfiguration().setContinueOnError(true);
+
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+    appendGetRequest(payload, "ESAllPrim", 42); // Error ( Key does not exist )
+    appendGetRequest(payload, "ESAllPrim", 0); // Without error
+
+    // Fetch result
+    final ODataBatchResponse response = payload.getResponse();
+    assertEquals(202, response.getStatusCode());
+
+    final Iterator<ODataBatchResponseItem> bodyIterator = response.getBody();
+
+    // Check first get request
+    assertTrue(bodyIterator.hasNext());
+    ODataBatchResponseItem item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    ODataResponse oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("538", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+
+    // Check second get request
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.NOT_FOUND.getStatusCode(), oDataResonse.getStatusCode());
+
+    // Check if third request is available
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("446", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  @Ignore("Not implemented")
+  public void changesetWithReferences() throws EdmPrimitiveTypeException {
+    // create your request
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+    final BatchManager streamManager = request.payloadManager();
+
+    final ODataChangeset changeset = streamManager.addChangeset();
+    ODataEntity esAllPrim = newESAllPrim((short) 23);
+
+    final URIBuilder uriBuilder = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim");
+
+    // add create request
+    final ODataEntityCreateRequest<ODataEntity> createReq =
+        client.getCUDRequestFactory().getEntityCreateRequest(uriBuilder.build(), esAllPrim);
+
+    changeset.addRequest(createReq);
+
+    // retrieve request reference
+    int createRequestRef = changeset.getLastContentId();
+
+    // add update request
+    final ODataEntity customerChanges = client.getObjectFactory().newEntity(esAllPrim.getTypeName());
+    customerChanges.addLink(client.getObjectFactory().newEntitySetNavigationLink(
+        "NavPropertyETTwoPrimMany",
+        client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("NavPropertyETTwoPrimMany").
+            appendKeySegment(new HashMap<String, Object>() {
+              private static final long serialVersionUID = 3109256773218160485L;
+
+              {
+                put("PropertyInt16", 4242);
+                put("PropertyString", "Test");
+              }
+            }).build()));
+
+    final ODataEntityUpdateRequest<ODataEntity> updateReq = client.getCUDRequestFactory().getEntityUpdateRequest(
+        URI.create("$" + createRequestRef), UpdateType.PATCH, customerChanges);
+
+    changeset.addRequest(updateReq);
+
+    final ODataBatchResponse response = streamManager.getResponse();
+    assertEquals(200, response.getStatusCode());
+    assertEquals("OK", response.getStatusMessage());
+
+    // verify response payload ...
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+
+    final ODataBatchResponseItem item = iter.next();
+    assertTrue(item instanceof ODataChangesetResponseItem);
+
+    final ODataChangesetResponseItem chgitem = (ODataChangesetResponseItem) item;
+
+    ODataResponse res = chgitem.next();
+    assertEquals(201, res.getStatusCode());
+    assertTrue(res instanceof ODataEntityCreateResponse);
+
+    esAllPrim = ((ODataEntityCreateResponse<ODataEntity>) res).getBody();
+    final ODataEntitySetRequest<ODataEntitySet> req = client.getRetrieveRequestFactory().getEntitySetRequest(
+        URIUtils.getURI(SERVICE_URI, esAllPrim.getEditLink().toASCIIString() + "/NavPropertyETTwoPrimMany"));
+
+    assertEquals(Integer.valueOf(4242),
+        req.execute().getBody().getEntities().get(0).getProperty("PropertyInt16").getPrimitiveValue().
+            toCastValue(Integer.class));
+
+    res = chgitem.next();
+    assertEquals(204, res.getStatusCode());
+    assertTrue(res instanceof ODataEntityUpdateResponse);
+
+    // clean ...
+    assertEquals(204, client.getCUDRequestFactory().getDeleteRequest(
+        URIUtils.getURI(SERVICE_URI, esAllPrim.getEditLink().toASCIIString())).execute().
+        getStatusCode());
+
+    try {
+      client.getRetrieveRequestFactory().getEntityRequest(
+          URIUtils.getURI(SERVICE_URI, esAllPrim.getEditLink().toASCIIString())).
+          execute().getBody();
+      fail("Entity not deleted");
+    } catch (Exception e) {
+      // ignore
+    }
+  }
+
+  private ODataEntity newESAllPrim(short id) {
+    final ODataEntity entity = getClient().getObjectFactory().
+        newEntity(new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+
+    entity.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyInt16",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildInt16(id)));
+
+    entity.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    return entity;
+  }
+
+  //TODO If write support is implemented, remove ignore tag
+  @Test
+  @Ignore("Not implemented")
+  public void changesetBatchRequest() {
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+    // -----------------------------
+    // - Append get request
+    // -----------------------------
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+
+    // -----------------------------
+    // - Append change set
+    // -----------------------------
+    final ODataChangeset changeset = payload.addChangeset();
+
+    // ------------------------
+    // POST request (Insert)
+    URIBuilder targetURI =
+        client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim");
+    URI editLink = targetURI.build();
+
+    ODataEntity post = client.getObjectFactory().newEntity(
+        new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+
+    post.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyInt16",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 15)));
+
+    post.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    final ODataEntityCreateRequest<ODataEntity> createRequest =
+        client.getCUDRequestFactory().getEntityCreateRequest(editLink, post);
+    createRequest.setFormat(ODataFormat.JSON_FULL_METADATA);
+    createRequest.setContentType("1");
+
+    changeset.addRequest(createRequest);
+
+    // ------------------------
+    // Patch request (Update)
+    targetURI = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim").appendKeySegment(0);
+    editLink = targetURI.build();
+
+    ODataEntity patch = client.getObjectFactory().newEntity(new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+    patch.setEditLink(editLink);
+
+    patch.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    ODataEntityUpdateRequest<ODataEntity> changeReq =
+        client.getCUDRequestFactory().getEntityUpdateRequest(UpdateType.PATCH, patch);
+    changeReq.setFormat(ODataFormat.JSON_FULL_METADATA);
+    changeReq.setContentType("2");
+    changeset.addRequest(changeReq);
+
+    // ------------------------
+    // Patch request (Upsert)
+    targetURI = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim").appendKeySegment(35);
+    editLink = targetURI.build();
+
+    patch = client.getObjectFactory().newEntity(new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+    patch.setEditLink(editLink);
+
+    patch.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    changeReq = client.getCUDRequestFactory().getEntityUpdateRequest(UpdateType.PATCH, patch);
+    changeReq.setFormat(ODataFormat.JSON_FULL_METADATA);
+    changeReq.setContentType("3");
+    changeset.addRequest(changeReq);
+
+    // -----------------------------
+    // - Append get request
+    // -----------------------------
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+
+    // -----------------------------
+    // - Fetch result
+    // -----------------------------
+    final ODataBatchResponse response = payload.getResponse();
+    assertEquals(202, response.getStatusCode());
+    final Iterator<ODataBatchResponseItem> bodyIterator = response.getBody();
+
+    // Check first get request
+    assertTrue(bodyIterator.hasNext());
+    ODataBatchResponseItem item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    // Check change set
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertTrue(item.isChangeset());
+
+    for (int i = 0; i < 3; i++) {
+      assertTrue(item.hasNext());
+      assertTrue(item instanceof ODataChangesetResponseItem);
+      ODataChangesetResponseItem changeSetResponseItem = (ODataChangesetResponseItem) item.next();
+      assertNotNull(changeSetResponseItem);
+
+      ODataResponse chgRequest = changeSetResponseItem.next();
+      final String contentId = chgRequest.getHeader(ODataBatchConstants.CHANGESET_CONTENT_ID_NAME).iterator().next();
+
+      if (contentId == "1") {
+        // Insert
+        assertEquals(HttpStatusCode.CREATED.getStatusCode(), chgRequest.getStatusCode());
+      } else if (contentId == "2") {
+        // Update
+        assertEquals(HttpStatusCode.OK.getStatusCode(), chgRequest.getStatusCode());
+      } else if (contentId == "3") {
+        // Upsert
+        assertEquals(HttpStatusCode.CREATED.getStatusCode(), chgRequest.getStatusCode());
+      } else {
+        fail("Unkonwn content id " + contentId);
+      }
+    }
+    assertFalse(item.hasNext());
+
+    // Check second get request
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+  }
+
+  private void appendGetRequest(final BatchManager manager, final String segment, final Object key) {
+    URIBuilder targetURI = client.newURIBuilder(SERVICE_URI);
+    targetURI.appendEntitySetSegment(segment).appendKeySegment(key);
+
+    ODataEntityRequest<ODataEntity> queryReq = client.getRetrieveRequestFactory().getEntityRequest(targetURI.build());
+    queryReq.setFormat(ODataFormat.JSON);
+    manager.addRequest(queryReq);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
index 8dd7480..c9a914a 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
@@ -18,25 +18,63 @@
  */
 package org.apache.olingo.server.api.batch;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.olingo.server.api.ODataResponse;
 
-public interface ODataResponsePart {
+public class ODataResponsePart {
+  private List<ODataResponse> responses;
+  private boolean isChangeSet;
+  
   /**
-   * Returns a collection of ODataResponses.
-   * Each collections contains at least one {@link ODataResponse}.
+   * Creates a new ODataResponsePart.
    * 
-   * If this instance represents a change set, there are may many ODataResponses
+   * An ODataResponsePart represents a collections of ODataResponses.
+   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
+   * OData batch response.
    *  
-   * @return a list of {@link ODataResponse}
+   * @param responses     A list of {@link ODataResponse}
+   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
+   */
+  public ODataResponsePart(List<ODataResponse> responses, boolean isChangeSet) {
+    this.responses = responses;
+    this.isChangeSet = isChangeSet;
+  }
+  
+  /**
+   * Creates a new ODataResponsePart.
+   * 
+   * An ODataResponsePart represents a collections of ODataResponses.
+   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
+   * OData batch response.
+   *  
+   * @param responses     A single {@link ODataResponse}
+   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
    */
-  public List<ODataResponse> getResponses();
+  public ODataResponsePart(ODataResponse response, boolean isChangeSet) {
+    this.responses = Arrays.asList(new ODataResponse[] { response });
+    this.isChangeSet = isChangeSet;
+  }
   
   /**
    * Returns true if the current instance represents a change set.
    * 
    * @return true or false
    */
-  public boolean isChangeSet();
+  public List<ODataResponse> getResponses() {
+    return responses;
+  }
+  
+  /**
+   * Returns a collection of ODataResponses.
+   * Each collections contains at least one {@link ODataResponse}.
+   * 
+   * If this instance represents a change set, there are may many ODataResponses
+   *  
+   * @return a list of {@link ODataResponse}
+   */
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
index a0a7135..843fa28 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
@@ -24,10 +24,11 @@ import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.batch.BatchOperation;
 import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
 
 public interface BatchProcessor extends Processor {
   void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response);
 
-  List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+  ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
       BatchRequestPart requestPart);
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
index 473c904..25778e3 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
@@ -19,7 +19,11 @@
 package org.apache.olingo.server.api.processor;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
+import org.apache.olingo.commons.api.ODataRuntimeException;
 import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.commons.api.format.ODataFormat;
 import org.apache.olingo.commons.api.http.HttpHeader;
@@ -30,6 +34,10 @@ import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.ODataServerError;
 import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
 import org.apache.olingo.server.api.serializer.ODataSerializer;
 import org.apache.olingo.server.api.serializer.SerializerException;
 import org.apache.olingo.server.api.uri.UriInfo;
@@ -42,8 +50,12 @@ import org.apache.olingo.server.api.uri.UriInfo;
  * <p>This implementation is registered in the ODataHandler by default.
  * The default can be replaced by re-registering a custom implementation.</p>
  */
-public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProcessor, ExceptionProcessor {
-
+public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProcessor, ExceptionProcessor,
+    BatchProcessor {
+  
+  private static final String PREFERENCE_CONTINUE_ON_ERROR = "odata.continue-on-error";
+  private static final String PREFER_HEADER = "Prefer";
+  
   private OData odata;
   private ServiceMetadata serviceMetadata;
 
@@ -89,4 +101,81 @@ public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProce
       response.setHeader(HttpHeader.CONTENT_TYPE, ContentType.APPLICATION_JSON.toContentTypeString());
     }
   }
+
+  @Override
+  public void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response) {
+    boolean continueOnError = shouldContinueOnError(request);
+
+    try {
+      final List<BatchRequestPart> parts = operation.parseBatchRequest(request.getBody());
+      final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
+
+      for (BatchRequestPart part : parts) {
+        final ODataResponsePart responsePart = operation.handleBatchRequest(part);
+        responseParts.add(responsePart);    // Also add failed responses
+
+        if (responsePart.getResponses().get(0).getStatusCode() >= 400
+            && !continueOnError) {
+
+          // Perform some additions actions
+          // ...
+          
+          break; // Stop processing, but serialize all recent requests
+        }
+      }
+
+      operation.writeResponseParts(responseParts, response);  // Serialize responses
+    } catch (BatchException e) {
+      throw new ODataRuntimeException(e);
+    } catch (IOException e) {
+      throw new ODataRuntimeException(e);
+    }
+  }
+
+  private boolean shouldContinueOnError(ODataRequest request) {
+    final List<String> preferValues = request.getHeaders(PREFER_HEADER);
+
+    if (preferValues != null) {
+      for (final String preference : preferValues) {
+        if (PREFERENCE_CONTINUE_ON_ERROR.equals(preference)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+      BatchRequestPart requestPart) {
+    List<ODataResponse> responses = new ArrayList<ODataResponse>();
+
+    for (ODataRequest request : requests) {
+      try {
+        final ODataResponse oDataResponse = operation.handleODataRequest(request, requestPart);
+
+        if (oDataResponse.getStatusCode() < 400) {
+          responses.add(oDataResponse);
+        } else {
+          // Rollback
+          // ...
+
+          // OData Version 4.0 Part 1: Protocol Plus Errata 01
+          // 11.7.4 Responding to a Batch Request
+          //
+          // When a request within a change set fails, the change set response is not represented using
+          // the multipart/mixed media type. Instead, a single response, using the application/http media type
+          // and a Content-Transfer-Encoding header with a value of binary, is returned that applies to all requests
+          // in the change set and MUST be formatted according to the Error Handling defined
+          // for the particular response format.
+
+          return new ODataResponsePart(oDataResponse, false);
+        }
+      } catch (BatchException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+
+    return new ODataResponsePart(responses, true);
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
index 5bec30b..23ba106 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -129,17 +129,14 @@ public class BatchPartHandler {
       final List<ODataResponse> responses = new ArrayList<ODataResponse>();
       responses.add(handleODataRequest(request.getRequests().get(0), request));
       
-      return new ODataResponsePartImpl(responses, false);
+      return new ODataResponsePart(responses, false);
     }
   }
 
   private ODataResponsePart handleChangeSet(BatchRequestPart request) throws BatchException {
-    final List<ODataResponse> responses = new ArrayList<ODataResponse>();
     final BatchChangeSetSorter sorter = new BatchChangeSetSorter(request.getRequests());
-
-    responses.addAll(batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request));
     
-    return new ODataResponsePartImpl(responses, true);
+    return batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request);
   }
 
   private static class UriMapping {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
deleted file mode 100644
index da52a37..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
+++ /dev/null
@@ -1,44 +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.server.core.batch.handler;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-
-public class ODataResponsePartImpl implements ODataResponsePart {
-  private List<ODataResponse> responses;
-  private boolean isChangeSet;
-  
-  public ODataResponsePartImpl(List<ODataResponse> responses, boolean isChangeSet) {
-    this.responses = responses;
-    this.isChangeSet = isChangeSet;
-  }
-
-  @Override
-  public List<ODataResponse> getResponses() {
-    return responses;
-  }
-
-  @Override
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
index 43a09b6..b87f8ef 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
@@ -123,9 +123,6 @@ public class BatchParserCommon {
     // Remove preamble
     if (messageParts.size() > 0) {
       messageParts.remove(0);
-    } else {
-      throw new BatchException("Missing boundary delimiter", BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, ""
-          + lineNumer);
     }
 
     if (!isEndReached) {
@@ -133,11 +130,6 @@ public class BatchParserCommon {
           "" + lineNumer);
     }
 
-    if (messageParts.size() == 0) {
-      throw new BatchException("No boundary delimiter found",
-          BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, boundary, "" + lineNumer);
-    }
-
     return messageParts;
   }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
index 812ea8b..0c13cb9 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
@@ -149,7 +149,7 @@ public class BatchResponseWriter {
     final Map<String, String> header = response.getHeaders();
 
     for (final String key : header.keySet()) {
-      // Requests do never have content id header
+      // Requests do never has a content id header
       if (!key.equalsIgnoreCase(BatchParserCommon.HTTP_CONTENT_ID)) {
         appendHeader(key, header.get(key), writer);
       }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
deleted file mode 100644
index a9711c9..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
+++ /dev/null
@@ -1,44 +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.server.core.batch.writer;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-
-public class ODataResponsePartImpl implements ODataResponsePart {
-  private final List<ODataResponse> responses;
-  private final boolean isChangeSet;
-
-  public ODataResponsePartImpl(final List<ODataResponse> responses, final boolean isChangeSet) {
-    this.responses = responses;
-    this.isChangeSet = isChangeSet;
-  }
-
-  @Override
-  public List<ODataResponse> getResponses() {
-    return responses;
-  }
-
-  @Override
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
index c9778e3..88fdf08 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
@@ -274,7 +274,8 @@ public class BatchRequestParserTest {
         + GET_REQUEST
         + "--batch_8194-cf13-1f56--";
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(0, parts.size());
   }
 
   @Test
@@ -450,14 +451,23 @@ public class BatchRequestParserTest {
         + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
         + CRLF;
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
   }
-
+  
+  @Test
+  public void testEmptyRequest() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56--";
+    
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(0, parts.size());
+  }
+  
   @Test
   public void testBadRequest() throws UnsupportedEncodingException {
     final String batch = "This is a bad request. There is no syntax and also no semantic";
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
   }
 
   @Test
@@ -496,7 +506,7 @@ public class BatchRequestParserTest {
   }
 
   @Test
-  public void testInvalidChangeSetBoundary() throws UnsupportedEncodingException {
+  public void testInvalidChangeSetBoundary() throws UnsupportedEncodingException, BatchException {
     final String batch = "--batch_8194-cf13-1f56" + CRLF
         + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
         + CRLF
@@ -511,7 +521,12 @@ public class BatchRequestParserTest {
         + CRLF
         + "--batch_8194-cf13-1f56--";
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(1, parts.size());
+    
+    final BatchRequestPart part = parts.get(0);
+    assertTrue(part.isChangeSet());
+    assertEquals(0, part.getRequests().size());
   }
 
   @Test
@@ -1261,7 +1276,6 @@ public class BatchRequestParserTest {
     final List<BatchRequestPart> batchRequestParts = parser.parseBatchRequest(in);
 
     assertNotNull(batchRequestParts);
-    assertFalse(batchRequestParts.isEmpty());
 
     return batchRequestParts;
   }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
index e1864e2..7a85ffa 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -535,7 +535,7 @@ public class MockedBatchHandlerTest {
     public void init(OData odata, ServiceMetadata serviceMetadata) {}
 
     @Override
-    public List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+    public ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
         BatchRequestPart requestPart) {
       List<ODataResponse> responses = new ArrayList<ODataResponse>();
 
@@ -557,7 +557,7 @@ public class MockedBatchHandlerTest {
         }
       }
 
-      return responses;
+      return new ODataResponsePart(responses, true);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
index b7169b9..ea05bfb 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
@@ -50,14 +50,14 @@ public class BatchResponseWriterTest {
 
     List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
     responses.add(response);
-    parts.add(new ODataResponsePartImpl(responses, false));
+    parts.add(new ODataResponsePart(responses, false));
 
     ODataResponse changeSetResponse = new ODataResponse();
     changeSetResponse.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
     changeSetResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
     responses = new ArrayList<ODataResponse>(1);
     responses.add(changeSetResponse);
-    parts.add(new ODataResponsePartImpl(responses, true));
+    parts.add(new ODataResponsePart(responses, true));
 
     BatchResponseWriter writer = new BatchResponseWriter();
     ODataResponse batchResponse = new ODataResponse();
@@ -109,7 +109,7 @@ public class BatchResponseWriterTest {
 
     List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
     responses.add(response);
-    parts.add(new ODataResponsePartImpl(responses, false));
+    parts.add(new ODataResponsePart(responses, false));
 
     ODataResponse batchResponse = new ODataResponse();
     new BatchResponseWriter().toODataResponse(parts, batchResponse);
@@ -144,7 +144,7 @@ public class BatchResponseWriterTest {
 
     List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
     responses.add(response);
-    parts.add(new ODataResponsePartImpl(responses, true));
+    parts.add(new ODataResponsePart(responses, true));
 
     BatchResponseWriter writer = new BatchResponseWriter();
     ODataResponse batchResponse = new ODataResponse();