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);
