Repository: olingo-odata4
Updated Branches:
  refs/heads/master e4a4f9e6e -> 6dfdee2ef


[OLINGO-175, OLINGO-205, OLINGO-246] provided batch error V4 + continueOnError 
support


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

Branch: refs/heads/master
Commit: 6dfdee2ef2c5a2fdacab0b9b9a4c130f29fe6aaa
Parents: e4a4f9e
Author: fmartelli <[email protected]>
Authored: Fri Apr 18 11:16:55 2014 +0200
Committer: fmartelli <[email protected]>
Committed: Fri Apr 18 11:16:55 2014 +0200

----------------------------------------------------------------------
 .../org/apache/olingo/fit/AbstractServices.java | 112 ++------
 .../apache/olingo/fit/V3ActionOverloading.java  |  10 +
 .../java/org/apache/olingo/fit/V3Services.java  |  94 +++++++
 .../java/org/apache/olingo/fit/V4Services.java  |  97 ++++++-
 .../communication/header/ODataPreferences.java  |   2 +-
 .../batch/AbstractODataBatchResponseItem.java   |  13 +
 .../batch/ODataChangesetResponseItem.java       |  44 ++--
 .../batch/ODataRetrieveResponseItem.java        |  36 ++-
 .../response/batch/ODataBatchErrorResponse.java | 261 +++++++++++++++++++
 .../batch/ODataBatchResponseManager.java        |   8 +-
 .../client/core/it/v4/BatchTestITCase.java      |  98 ++++++-
 11 files changed, 635 insertions(+), 140 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java 
b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
index b624ebe..6ec8cc0 100644
--- a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
+++ b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
@@ -207,19 +207,25 @@ public abstract class AbstractServices {
   @Path("/$batch")
   @Consumes("multipart/mixed")
   @Produces("application/octet-stream; boundary=" + BOUNDARY)
-  public Response batch(final @Multipart MultipartBody attachment) {
+  public Response batch(
+          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String 
prefer,
+          final @Multipart MultipartBody attachment) {
     try {
-      return 
xml.createBatchResponse(exploreMultipart(attachment.getAllAttachments(), 
BOUNDARY), BOUNDARY);
+      final boolean continueOnError = 
prefer.contains("odata.continue-on-error");
+      
+      return xml.createBatchResponse(
+              exploreMultipart(attachment.getAllAttachments(), BOUNDARY, 
continueOnError), 
+              BOUNDARY);
     } catch (IOException e) {
       return xml.createFaultResponse(Accept.XML.toString(version), e);
     }
   }
 
-  private Response bodyPartRequest(final MimeBodyPart body) throws Exception {
+  protected Response bodyPartRequest(final MimeBodyPart body) throws Exception 
{
     return bodyPartRequest(body, Collections.<String, String>emptyMap());
   }
 
-  private Response bodyPartRequest(final MimeBodyPart body, final Map<String, 
String> references) throws Exception {
+  protected Response bodyPartRequest(final MimeBodyPart body, final 
Map<String, String> references) throws Exception {
 
     @SuppressWarnings("unchecked")
     final Enumeration<Header> en = (Enumeration<Header>) body.getAllHeaders();
@@ -269,93 +275,29 @@ public abstract class AbstractServices {
     }
   }
 
-  public InputStream exploreMultipart(final List<Attachment> attachments, 
final String boundary) throws IOException {
-    final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-
-    Response res = null;
-    try {
-      for (Attachment obj : attachments) {
-        bos.write(("--" + boundary).getBytes());
-        bos.write(Constants.CRLF);
-
-        final Object content = obj.getDataHandler().getContent();
-        if (content instanceof MimeMultipart) {
-          final Map<String, String> references = new HashMap<String, String>();
-
-          final String cboundary = "changeset_" + UUID.randomUUID().toString();
-          bos.write(("Content-Type: multipart/mixed;boundary=" + 
cboundary).getBytes());
-          bos.write(Constants.CRLF);
-          bos.write(Constants.CRLF);
-
-          final ByteArrayOutputStream chbos = new ByteArrayOutputStream();
-          String lastContebtID = null;
-          try {
-            for (int i = 0; i < ((MimeMultipart) content).getCount(); i++) {
-              final MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) 
content).getBodyPart(i);
-              lastContebtID = part.getContentID();
-              addChangesetItemIntro(chbos, lastContebtID, cboundary);
-
-              res = bodyPartRequest(new MimeBodyPart(part.getInputStream()), 
references);
-              if (res.getStatus() >= 400) {
-                throw new Exception("Failure processing changeset");
-              }
-
-              addSingleBatchResponse(res, lastContebtID, chbos);
-              references.put("$" + lastContebtID, 
res.getHeaderString("Location"));
-            }
-            bos.write(chbos.toByteArray());
-            IOUtils.closeQuietly(chbos);
-
-            bos.write(("--" + cboundary + "--").getBytes());
-            bos.write(Constants.CRLF);
-          } catch (Exception e) {
-            LOG.warn("While processing changeset", e);
-            IOUtils.closeQuietly(chbos);
-
-            addChangesetItemIntro(bos, lastContebtID, cboundary);
-            if (res == null || res.getStatus() < 400) {
-              addErrorBatchResponse(e, "1", bos);
-            } else {
-              addSingleBatchResponse(res, lastContebtID, bos);
-            }
-
-            bos.write(("--" + cboundary + "--").getBytes());
-            bos.write(Constants.CRLF);
-          }
-        } else {
-          addItemIntro(bos);
-
-          res = bodyPartRequest(new 
MimeBodyPart(obj.getDataHandler().getInputStream()));
-
-          if (res.getStatus() >= 400) {
-            throw new Exception("Failure processing changeset");
-          }
-
-          addSingleBatchResponse(res, bos);
-        }
-      }
-    } catch (Exception e) {
-      if (res == null || res.getStatus() < 400) {
-        addErrorBatchResponse(e, bos);
-      } else {
-        addSingleBatchResponse(res, bos);
-      }
-    }
-
-    bos.write(("--" + boundary + "--").getBytes());
+  protected abstract InputStream exploreMultipart(
+          final List<Attachment> attachments, final String boundary, final 
boolean continueOnError) 
+          throws IOException;
 
-    return new ByteArrayInputStream(bos.toByteArray());
+  protected void addItemIntro(final ByteArrayOutputStream bos) throws 
IOException {
+    addItemIntro(bos, null);
   }
 
-  private void addItemIntro(final ByteArrayOutputStream bos) throws 
IOException {
+  protected void addItemIntro(final ByteArrayOutputStream bos, final String 
contentId) throws IOException {
     bos.write("Content-Type: application/http".getBytes());
     bos.write(Constants.CRLF);
     bos.write("Content-Transfer-Encoding: binary".getBytes());
     bos.write(Constants.CRLF);
+
+    if (StringUtils.isNotBlank(contentId)) {
+      bos.write(("Content-ID: " + contentId).getBytes());
+      bos.write(Constants.CRLF);
+    }
+
     bos.write(Constants.CRLF);
   }
 
-  private void addChangesetItemIntro(
+  protected void addChangesetItemIntro(
           final ByteArrayOutputStream bos, final String contentId, final 
String cboundary) throws IOException {
     bos.write(("--" + cboundary).getBytes());
     bos.write(Constants.CRLF);
@@ -364,12 +306,12 @@ public abstract class AbstractServices {
     addItemIntro(bos);
   }
 
-  private void addSingleBatchResponse(
+  protected void addSingleBatchResponse(
           final Response response, final ByteArrayOutputStream bos) throws 
IOException {
     addSingleBatchResponse(response, null, bos);
   }
 
-  private void addSingleBatchResponse(
+  protected void addSingleBatchResponse(
           final Response response, final String contentId, final 
ByteArrayOutputStream bos) throws IOException {
     bos.write("HTTP/1.1 ".getBytes());
     
bos.write(String.valueOf(response.getStatusInfo().getStatusCode()).getBytes());
@@ -406,12 +348,12 @@ public abstract class AbstractServices {
     bos.write(Constants.CRLF);
   }
 
-  private void addErrorBatchResponse(final Exception e, final 
ByteArrayOutputStream bos)
+  protected void addErrorBatchResponse(final Exception e, final 
ByteArrayOutputStream bos)
           throws IOException {
     addErrorBatchResponse(e, null, bos);
   }
 
-  private void addErrorBatchResponse(final Exception e, final String 
contentId, final ByteArrayOutputStream bos)
+  protected void addErrorBatchResponse(final Exception e, final String 
contentId, final ByteArrayOutputStream bos)
           throws IOException {
     
addSingleBatchResponse(xml.createFaultResponse(Accept.XML.toString(version), 
e), contentId, bos);
   }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java 
b/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
index a070527..6ddb502 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
@@ -20,7 +20,9 @@ package org.apache.olingo.fit;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 import java.util.Map;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
@@ -37,6 +39,7 @@ import static javax.ws.rs.core.Response.status;
 import javax.ws.rs.core.UriInfo;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.apache.olingo.commons.api.data.Feed;
 import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
 import static org.apache.olingo.fit.AbstractServices.LOG;
@@ -206,4 +209,11 @@ public class V3ActionOverloading extends AbstractServices {
   protected void setInlineCount(Feed feed, String count) {
     throw new UnsupportedOperationException("Not supported yet.");
   }
+
+  @Override
+  protected InputStream exploreMultipart(
+          final List<Attachment> attachments, final String boundary, final 
boolean continueOnError) 
+          throws IOException {
+    throw new UnsupportedOperationException("Not supported yet.");
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/fit/src/main/java/org/apache/olingo/fit/V3Services.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V3Services.java 
b/fit/src/main/java/org/apache/olingo/fit/V3Services.java
index c884af0..a946b0d 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V3Services.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V3Services.java
@@ -18,9 +18,16 @@
  */
 package org.apache.olingo.fit;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
@@ -37,6 +44,7 @@ import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.interceptor.InInterceptors;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.apache.olingo.commons.api.data.Feed;
 import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
 import org.apache.olingo.fit.methods.MERGE;
@@ -107,6 +115,92 @@ public class V3Services extends AbstractServices {
     }
   }
 
+  @Override
+  public InputStream exploreMultipart(
+          final List<Attachment> attachments, final String boundary, final 
boolean contineOnError)
+          throws IOException {
+    final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+    Response res = null;
+    boolean goon = true;
+    for (int i = 0; i < attachments.size() && goon; i++) {
+      try {
+        final Attachment obj = attachments.get(i);
+        bos.write(("--" + boundary).getBytes());
+        bos.write(Constants.CRLF);
+
+        final Object content = obj.getDataHandler().getContent();
+        if (content instanceof MimeMultipart) {
+          final Map<String, String> references = new HashMap<String, String>();
+
+          final String cboundary = "changeset_" + UUID.randomUUID().toString();
+          bos.write(("Content-Type: multipart/mixed;boundary=" + 
cboundary).getBytes());
+          bos.write(Constants.CRLF);
+          bos.write(Constants.CRLF);
+
+          final ByteArrayOutputStream chbos = new ByteArrayOutputStream();
+          String lastContebtID = null;
+          try {
+            for (int j = 0; j < ((MimeMultipart) content).getCount(); j++) {
+              final MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) 
content).getBodyPart(j);
+              lastContebtID = part.getContentID();
+              addChangesetItemIntro(chbos, lastContebtID, cboundary);
+
+              res = bodyPartRequest(new MimeBodyPart(part.getInputStream()), 
references);
+              if (res.getStatus() >= 400) {
+                throw new Exception("Failure processing changeset");
+              }
+
+              addSingleBatchResponse(res, lastContebtID, chbos);
+              references.put("$" + lastContebtID, 
res.getHeaderString("Location"));
+            }
+
+            bos.write(chbos.toByteArray());
+            IOUtils.closeQuietly(chbos);
+
+            bos.write(("--" + cboundary + "--").getBytes());
+            bos.write(Constants.CRLF);
+          } catch (Exception e) {
+            LOG.warn("While processing changeset", e);
+            IOUtils.closeQuietly(chbos);
+
+            addChangesetItemIntro(bos, lastContebtID, cboundary);
+
+            if (res == null || res.getStatus() < 400) {
+              addErrorBatchResponse(e, "1", bos);
+            } else {
+              addSingleBatchResponse(res, lastContebtID, bos);
+            }
+
+            goon = contineOnError;
+          }
+        } else {
+          addItemIntro(bos);
+
+          res = bodyPartRequest(new 
MimeBodyPart(obj.getDataHandler().getInputStream()));
+
+          if (res.getStatus() >= 400) {
+            goon = contineOnError;
+            throw new Exception("Failure processing changeset");
+          }
+
+          addSingleBatchResponse(res, bos);
+        }
+
+      } catch (Exception e) {
+        if (res == null || res.getStatus() < 400) {
+          addErrorBatchResponse(e, bos);
+        } else {
+          addSingleBatchResponse(res, bos);
+        }
+      }
+    }
+
+    bos.write(("--" + boundary + "--").getBytes());
+
+    return new ByteArrayInputStream(bos.toByteArray());
+  }
+
   /**
    * Retrieve links sample.
    *

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/fit/src/main/java/org/apache/olingo/fit/V4Services.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V4Services.java 
b/fit/src/main/java/org/apache/olingo/fit/V4Services.java
index c6de7fe..a9390ed 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V4Services.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V4Services.java
@@ -19,13 +19,18 @@
 package org.apache.olingo.fit;
 
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStreamWriter;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.UUID;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
@@ -42,25 +47,22 @@ import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.interceptor.InInterceptors;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.apache.olingo.commons.api.data.Container;
 import org.apache.olingo.commons.api.data.Feed;
 import org.apache.olingo.commons.api.data.Property;
 import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
 import org.apache.olingo.commons.core.data.AtomEntryImpl;
 import org.apache.olingo.commons.core.data.AtomFeedImpl;
-import org.apache.olingo.commons.core.data.AtomSerializer;
 import org.apache.olingo.commons.core.data.JSONEntryImpl;
 import org.apache.olingo.commons.core.data.JSONFeedImpl;
 import org.apache.olingo.commons.core.edm.EdmTypeInfo;
 import org.apache.olingo.fit.methods.PATCH;
-import org.apache.olingo.fit.serializer.FITAtomDeserializer;
 import org.apache.olingo.fit.serializer.JsonFeedContainer;
 import org.apache.olingo.fit.utils.AbstractUtilities;
 import org.apache.olingo.fit.utils.Accept;
-import org.apache.olingo.fit.utils.Commons;
 import org.apache.olingo.fit.utils.ConstantKey;
 import org.apache.olingo.fit.utils.Constants;
-import org.apache.olingo.fit.utils.DataBinder;
 import org.apache.olingo.fit.utils.FSManager;
 import org.apache.olingo.fit.utils.LinkInfo;
 import org.apache.olingo.fit.utils.ResolvingReferencesInterceptor;
@@ -83,6 +85,91 @@ public class V4Services extends AbstractServices {
     }
   }
 
+  @Override
+  public InputStream exploreMultipart(
+          final List<Attachment> attachments, final String boundary, final 
boolean continueOnError)
+          throws IOException {
+    final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+    Response res = null;
+    boolean goon = true;
+    for (int i = 0; i < attachments.size() && goon; i++) {
+      try {
+        final Attachment obj = attachments.get(i);
+        bos.write(("--" + boundary).getBytes());
+        bos.write(Constants.CRLF);
+
+        final Object content = obj.getDataHandler().getContent();
+        if (content instanceof MimeMultipart) {
+          final ByteArrayOutputStream chbos = new ByteArrayOutputStream();
+          String lastContebtID = null;
+          try {
+            final Map<String, String> references = new HashMap<String, 
String>();
+
+            final String cboundary = "changeset_" + 
UUID.randomUUID().toString();
+            chbos.write(("Content-Type: multipart/mixed;boundary=" + 
cboundary).getBytes());
+            chbos.write(Constants.CRLF);
+            chbos.write(Constants.CRLF);
+
+            for (int j = 0; j < ((MimeMultipart) content).getCount(); j++) {
+              final MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) 
content).getBodyPart(j);
+              lastContebtID = part.getContentID();
+              addChangesetItemIntro(chbos, lastContebtID, cboundary);
+
+              res = bodyPartRequest(new MimeBodyPart(part.getInputStream()), 
references);
+              if (res.getStatus() >= 400) {
+                throw new Exception("Failure processing changeset");
+              }
+
+              addSingleBatchResponse(res, lastContebtID, chbos);
+              references.put("$" + lastContebtID, 
res.getHeaderString("Location"));
+            }
+
+            chbos.write(("--" + cboundary + "--").getBytes());
+            chbos.write(Constants.CRLF);
+
+            bos.write(chbos.toByteArray());
+            IOUtils.closeQuietly(chbos);
+          } catch (Exception e) {
+            LOG.warn("While processing changeset", e);
+            IOUtils.closeQuietly(chbos);
+
+            addItemIntro(bos, lastContebtID);
+
+            if (res == null || res.getStatus() < 400) {
+              addErrorBatchResponse(e, "1", bos);
+            } else {
+              addSingleBatchResponse(res, lastContebtID, bos);
+            }
+
+            goon = continueOnError;
+          }
+        } else {
+          addItemIntro(bos);
+
+          res = bodyPartRequest(new 
MimeBodyPart(obj.getDataHandler().getInputStream()));
+
+          if (res.getStatus() >= 400) {
+            goon = continueOnError;
+            throw new Exception("Failure processing batch item");
+          }
+
+          addSingleBatchResponse(res, bos);
+        }
+      } catch (Exception e) {
+        if (res == null || res.getStatus() < 400) {
+          addErrorBatchResponse(e, bos);
+        } else {
+          addSingleBatchResponse(res, bos);
+        }
+      }
+    }
+
+    bos.write(("--" + boundary + "--").getBytes());
+
+    return new ByteArrayInputStream(bos.toByteArray());
+  }
+
   /**
    * Retrieve entity reference sample.
    *

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java
----------------------------------------------------------------------
diff --git 
a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java
 
b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java
index 6afa950..b6bd5c5 100644
--- 
a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java
+++ 
b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/ODataPreferences.java
@@ -161,7 +161,7 @@ public class ODataPreferences {
    * @return preference.
    */
   public String continueOnError() {
-    return PreferenceNames.callback.isSupportedBy(serviceVersion).toString();
+    return 
PreferenceNames.continueOnError.isSupportedBy(serviceVersion).toString();
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
----------------------------------------------------------------------
diff --git 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
index b0a9ce9..79b8f07 100644
--- 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
+++ 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
@@ -125,6 +125,19 @@ public abstract class AbstractODataBatchResponseItem 
implements ODataBatchRespon
     return responses.values().iterator();
   }
 
+  @Override
+  public boolean hasNext() {
+    if (closed) {
+      throw new IllegalStateException("Invalid request - the item has been 
closed");
+    }
+
+    if (expectedItemsIterator == null) {
+      expectedItemsIterator = responses.values().iterator();
+    }
+
+    return expectedItemsIterator.hasNext();
+  }
+
   /**
    * {@inheritDoc }
    */

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
----------------------------------------------------------------------
diff --git 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
index 4a077b5..63e7cad 100644
--- 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
+++ 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
@@ -23,6 +23,8 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import org.apache.olingo.client.api.ODataBatchConstants;
 import org.apache.olingo.client.api.communication.response.ODataResponse;
+import static 
org.apache.olingo.client.core.communication.request.batch.AbstractODataBatchResponseItem.LOG;
+import 
org.apache.olingo.client.core.communication.response.batch.ODataBatchErrorResponse;
 
 /**
  * Changeset wrapper for the corresponding batch item.
@@ -34,6 +36,8 @@ public class ODataChangesetResponseItem extends 
AbstractODataBatchResponseItem {
    */
   private ODataResponse current = null;
 
+  private boolean unexpected = false;
+
   /**
    * Constructor.
    */
@@ -41,20 +45,8 @@ public class ODataChangesetResponseItem extends 
AbstractODataBatchResponseItem {
     super(true);
   }
 
-  /**
-   * {@inheritDoc }
-   */
-  @Override
-  public boolean hasNext() {
-    if (closed) {
-      throw new IllegalStateException("Invalid request - the item has been 
closed");
-    }
-
-    if (expectedItemsIterator == null) {
-      expectedItemsIterator = responses.values().iterator();
-    }
-
-    return expectedItemsIterator.hasNext();
+  public void setUnexpected() {
+    this.unexpected = true;
   }
 
   /**
@@ -62,7 +54,6 @@ public class ODataChangesetResponseItem extends 
AbstractODataBatchResponseItem {
    */
   @Override
   public ODataResponse next() {
-
     if (current != null) {
       current.close();
     }
@@ -71,6 +62,14 @@ public class ODataChangesetResponseItem extends 
AbstractODataBatchResponseItem {
       throw new IllegalStateException("Invalid request - the item has been 
closed");
     }
 
+    if (unexpected) {
+      return nextUnexpected();
+    } else {
+      return nextExpected();
+    }
+  }
+
+  private ODataResponse nextExpected() {
     if (hasNext()) {
       // consume item for condition above (like a counter ...)
       expectedItemsIterator.next();
@@ -119,6 +118,21 @@ public class ODataChangesetResponseItem extends 
AbstractODataBatchResponseItem {
     return current;
   }
 
+  private ODataResponse nextUnexpected() {
+    final Map.Entry<Integer, String> responseLine = 
ODataBatchUtilities.readResponseLine(batchLineIterator);
+    LOG.debug("Retrieved item response {}", responseLine);
+
+    if (responseLine.getKey() >= 400) {
+      // generate error response
+      final Map<String, Collection<String>> headers = 
ODataBatchUtilities.readHeaders(batchLineIterator);
+      LOG.debug("Retrieved item headers {}", headers);
+
+      return new ODataBatchErrorResponse(responseLine, headers, 
batchLineIterator, boundary);
+    }
+
+    throw new IllegalStateException("Expected item not found");
+  }
+
   /**
    * Unsupported operation.
    */

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
----------------------------------------------------------------------
diff --git 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
index 4a30e2e..2a45bb2 100644
--- 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
+++ 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
@@ -22,6 +22,8 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import org.apache.olingo.client.api.communication.response.ODataResponse;
+import static 
org.apache.olingo.client.core.communication.request.batch.AbstractODataBatchResponseItem.LOG;
+import 
org.apache.olingo.client.core.communication.response.batch.ODataBatchErrorResponse;
 
 /**
  * Retrieve response wrapper for the corresponding batch item.
@@ -41,38 +43,30 @@ public class ODataRetrieveResponseItem extends 
AbstractODataBatchResponseItem {
    * {@inheritDoc }
    */
   @Override
-  public boolean hasNext() {
-    if (closed) {
-      throw new IllegalStateException("Invalid request - the item has been 
closed");
-    }
-
-    if (expectedItemsIterator == null) {
-      expectedItemsIterator = responses.values().iterator();
-    }
-
-    return expectedItemsIterator.hasNext();
-  }
-
-  /**
-   * {@inheritDoc }
-   */
-  @Override
   public ODataResponse next() {
     if (closed) {
       throw new IllegalStateException("Invalid request - the item has been 
closed");
     }
 
-    if (!hasNext()) {
-      throw new NoSuchElementException("No item found");
-    }
-
     final Map.Entry<Integer, String> responseLine = 
ODataBatchUtilities.readResponseLine(batchLineIterator);
     LOG.debug("Retrieved item response {}", responseLine);
 
     final Map<String, Collection<String>> headers = 
ODataBatchUtilities.readHeaders(batchLineIterator);
     LOG.debug("Retrieved item headers {}", headers);
 
-    return expectedItemsIterator.next().initFromBatch(responseLine, headers, 
batchLineIterator, boundary);
+    final ODataResponse res;
+
+    if (responseLine.getKey() >= 400) {
+      // generate error response
+      res = new ODataBatchErrorResponse(responseLine, headers, 
batchLineIterator, boundary);
+    } else {
+      if (!hasNext()) {
+        throw new NoSuchElementException("No item found");
+      }
+      res = expectedItemsIterator.next().initFromBatch(responseLine, headers, 
batchLineIterator, boundary);
+    }
+
+    return res;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchErrorResponse.java
----------------------------------------------------------------------
diff --git 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchErrorResponse.java
 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchErrorResponse.java
new file mode 100644
index 0000000..e1f7085
--- /dev/null
+++ 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchErrorResponse.java
@@ -0,0 +1,261 @@
+/*
+ * 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.client.core.communication.response.batch;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import 
org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.apache.olingo.client.api.http.NoContentException;
+import 
org.apache.olingo.client.core.communication.request.batch.ODataBatchController;
+import 
org.apache.olingo.client.core.communication.request.batch.ODataBatchUtilities;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract representation of an OData response.
+ */
+public class ODataBatchErrorResponse implements ODataResponse {
+
+  /**
+   * Logger.
+   */
+  protected static final org.slf4j.Logger LOG = 
LoggerFactory.getLogger(ODataResponse.class);
+
+  /**
+   * HTTP client.
+   */
+  protected final HttpClient client;
+
+  /**
+   * HTTP response.
+   */
+  protected final HttpResponse res;
+
+  /**
+   * Response headers.
+   */
+  protected final Map<String, Collection<String>> headers =
+          new TreeMap<String, 
Collection<String>>(String.CASE_INSENSITIVE_ORDER);
+
+  /**
+   * Response code.
+   */
+  private int statusCode = -1;
+
+  /**
+   * Response message.
+   */
+  private String statusMessage = null;
+
+  /**
+   * Response body/payload.
+   */
+  private InputStream payload = null;
+
+  /**
+   * Initialization check.
+   */
+  private boolean hasBeenInitialized = false;
+
+  /**
+   * Batch info (if to be batched).
+   */
+  private ODataBatchController batchInfo = null;
+
+  /**
+   * Constructor.
+   */
+  public ODataBatchErrorResponse(
+          final Map.Entry<Integer, String> responseLine,
+          final Map<String, Collection<String>> headers,
+          final ODataBatchLineIterator batchLineIterator,
+          final String boundary) {
+
+    client = null;
+    res = null;
+
+    if (hasBeenInitialized) {
+      throw new IllegalStateException("Request already initialized");
+    }
+
+    this.hasBeenInitialized = true;
+
+    this.batchInfo = new ODataBatchController(batchLineIterator, boundary);
+
+    this.statusCode = responseLine.getKey();
+    this.statusMessage = responseLine.getValue();
+    this.headers.putAll(headers);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Collection<String> getHeaderNames() {
+    return headers.keySet();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Collection<String> getHeader(final String name) {
+    return headers.get(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Collection<String> getHeader(final HeaderName name) {
+    return headers.get(name.toString());
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getContentType() {
+    final Collection<String> contentTypes = getHeader(HeaderName.contentType);
+    return contentTypes == null || contentTypes.isEmpty()
+            ? null
+            : contentTypes.iterator().next();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int getStatusCode() {
+    return statusCode;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getStatusMessage() {
+    return statusMessage;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataResponse initFromBatch(
+          final Map.Entry<Integer, String> responseLine,
+          final Map<String, Collection<String>> headers,
+          final ODataBatchLineIterator batchLineIterator,
+          final String boundary) {
+
+    if (hasBeenInitialized) {
+      throw new IllegalStateException("Request already initialized");
+    }
+
+    this.hasBeenInitialized = true;
+
+    this.batchInfo = new ODataBatchController(batchLineIterator, boundary);
+
+    this.statusCode = responseLine.getKey();
+    this.statusMessage = responseLine.getValue();
+    this.headers.putAll(headers);
+
+    return this;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public void close() {
+    if (client == null) {
+      IOUtils.closeQuietly(payload);
+    } else {
+      this.client.getConnectionManager().shutdown();
+    }
+
+    if (batchInfo != null) {
+      batchInfo.setValidBatch(false);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public InputStream getRawResponse() {
+    if (HttpStatus.SC_NO_CONTENT == getStatusCode()) {
+      throw new NoContentException();
+    }
+
+    if (payload == null && batchInfo.isValidBatch()) {
+      // get input stream till the end of item
+      payload = new PipedInputStream();
+
+      try {
+        final PipedOutputStream os = new PipedOutputStream((PipedInputStream) 
payload);
+
+        new Thread(new Runnable() {
+          @Override
+          public void run() {
+            try {
+              ODataBatchUtilities.readBatchPart(batchInfo, os, true);
+            } catch (Exception e) {
+              LOG.error("Error streaming batch item payload", e);
+            } finally {
+              IOUtils.closeQuietly(os);
+            }
+          }
+        }).start();
+
+      } catch (IOException e) {
+        LOG.error("Error streaming payload response", e);
+        throw new IllegalStateException(e);
+      }
+    }
+
+    return payload;
+  }
+
+  @Override
+  public String getEtag() {
+    return null;
+  }
+
+  @Override
+  public URI getContextURL() {
+    return null;
+  }
+
+  @Override
+  public String getMetadataETag() {
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchResponseManager.java
----------------------------------------------------------------------
diff --git 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchResponseManager.java
 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchResponseManager.java
index 136e1c1..b91dd8a 100644
--- 
a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchResponseManager.java
+++ 
b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/response/batch/ODataBatchResponseManager.java
@@ -32,6 +32,7 @@ import 
org.apache.olingo.client.api.communication.request.batch.ODataBatchRespon
 import org.apache.olingo.client.api.communication.response.ODataBatchResponse;
 import 
org.apache.olingo.client.core.communication.request.batch.ODataBatchLineIteratorImpl;
 import 
org.apache.olingo.client.core.communication.request.batch.ODataBatchUtilities;
+import 
org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -121,18 +122,19 @@ public class ODataBatchResponseManager implements 
Iterator<ODataBatchResponseIte
 
         current.initFromBatch(
                 batchLineIterator,
-                ODataBatchUtilities.getBoundaryFromHeader(
-                nextItemHeaders.get(HeaderName.contentType.toString())));
+                
ODataBatchUtilities.getBoundaryFromHeader(nextItemHeaders.get(HeaderName.contentType.toString())));
         break;
 
       case RETRIEVE:
         if (current.isChangeset()) {
-          throw new IllegalStateException("Unexpected batch item");
+          // Maybe V4 error item
+          ((ODataChangesetResponseItem) current).setUnexpected();
         }
 
         current.initFromBatch(
                 batchLineIterator,
                 batchBoundary);
+
         break;
       default:
         throw new IllegalStateException("Expected item not found");

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6dfdee2e/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/BatchTestITCase.java
----------------------------------------------------------------------
diff --git 
a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/BatchTestITCase.java
 
b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/BatchTestITCase.java
index b1d2194..dcff42f 100644
--- 
a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/BatchTestITCase.java
+++ 
b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/BatchTestITCase.java
@@ -32,6 +32,8 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import org.apache.http.HttpResponse;
 import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import org.apache.olingo.client.api.communication.header.ODataPreferences;
 import org.apache.olingo.client.api.communication.request.ODataStreamManager;
 import 
org.apache.olingo.client.api.communication.request.batch.BatchStreamManager;
 import 
org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
@@ -138,15 +140,91 @@ public class BatchTestITCase extends AbstractTestITCase {
     assertEquals(200, response.getStatusCode());
     assertEquals("OK", response.getStatusMessage());
 
-    final Iterator<ODataBatchResponseItem> iter = response.getBody();
-    final ODataChangesetResponseItem chgResponseItem = 
(ODataChangesetResponseItem) iter.next();
+    // retrieve the first item (ODataRetrieve)
+    ODataBatchResponseItem item = response.getBody().next();
 
-    final ODataResponse res = chgResponseItem.next();
+    ODataChangesetResponseItem retitem = (ODataChangesetResponseItem) item;
+    ODataResponse res = retitem.next();
     assertEquals(404, res.getStatusCode());
     assertEquals("Not Found", res.getStatusMessage());
     assertEquals(Integer.valueOf(3), Integer.valueOf(
             
res.getHeader(ODataBatchConstants.CHANGESET_CONTENT_ID_NAME).iterator().next()));
-    assertFalse(chgResponseItem.hasNext());
+  }
+
+  @Test
+  public void continueOnError() {
+    continueOnError(true);
+  }
+
+  @Test
+  public void doNotContinueOnError() {
+    continueOnError(false);
+  }
+
+  private void continueOnError(final boolean continueOnError) {
+    // create your request
+    final ODataBatchRequest request = 
client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL);
+
+    if (continueOnError) {
+      request.addCustomHeader(HeaderName.prefer, new 
ODataPreferences(client.getServiceVersion()).continueOnError());
+    }
+
+    final BatchStreamManager streamManager = request.execute();
+
+    // -------------------------------------------
+    // Add retrieve item
+    // -------------------------------------------
+    ODataRetrieve retrieve = streamManager.addRetrieve();
+
+    // prepare URI
+    URIBuilder targetURI = client.getURIBuilder(testStaticServiceRootURL);
+    
targetURI.appendEntitySetSegment("UnexistinfEntitySet").appendKeySegment(1);
+
+    // create new request
+    ODataEntityRequest<ODataEntity> queryReq = 
client.getRetrieveRequestFactory().getEntityRequest(targetURI.build());
+    queryReq.setFormat(ODataPubFormat.ATOM);
+
+    retrieve.setRequest(queryReq);
+    // -------------------------------------------
+
+    // -------------------------------------------
+    // Add retrieve item
+    // -------------------------------------------
+    retrieve = streamManager.addRetrieve();
+
+    // prepare URI
+    targetURI = 
client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customers").appendKeySegment(1);
+
+    // create new request
+    queryReq = 
client.getRetrieveRequestFactory().getEntityRequest(targetURI.build());
+
+    retrieve.setRequest(queryReq);
+    // -------------------------------------------
+
+    final ODataBatchResponse response = streamManager.getResponse();
+    assertEquals(200, response.getStatusCode());
+    assertEquals("OK", response.getStatusMessage());
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+
+    // retrieve the first item (ODataRetrieve)
+    ODataBatchResponseItem item = iter.next();
+    assertTrue(item instanceof ODataRetrieveResponseItem);
+
+    ODataRetrieveResponseItem retitem = (ODataRetrieveResponseItem) item;
+    ODataResponse res = retitem.next();
+    assertEquals(404, res.getStatusCode());
+    assertEquals("Not Found", res.getStatusMessage());
+
+    if (continueOnError) {
+      item = iter.next();
+      assertTrue(item instanceof ODataRetrieveResponseItem);
+
+      retitem = (ODataRetrieveResponseItem) item;
+      res = retitem.next();
+      assertTrue(res instanceof ODataEntityResponseImpl);
+      assertEquals(200, res.getStatusCode());
+      assertEquals("OK", res.getStatusMessage());
+    }
   }
 
   @Test
@@ -176,13 +254,13 @@ public class BatchTestITCase extends AbstractTestITCase {
             "OrderDetails",
             
client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("OrderDetails").
             appendKeySegment(new HashMap<String, Object>() {
-              private static final long serialVersionUID = 
3109256773218160485L;
+      private static final long serialVersionUID = 3109256773218160485L;
 
-              {
-                put("OrderID", 7);
-                put("ProductID", 5);
-              }
-            }).build()));
+      {
+        put("OrderID", 7);
+        put("ProductID", 5);
+      }
+    }).build()));
 
     final ODataEntityUpdateRequest<ODataEntity> updateReq = 
client.getCUDRequestFactory().getEntityUpdateRequest(
             URI.create("$" + createRequestRef), UpdateType.PATCH, 
customerChanges);

Reply via email to