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 00e22f37d..4f9d970df 100644 --- a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java +++ b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java @@ -18,12 +18,12 @@ */ package org.apache.olingo.fit; -import org.apache.olingo.commons.api.data.Container; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import org.apache.olingo.commons.api.data.Feed; import org.apache.olingo.commons.api.data.Link; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; -import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.core.data.AtomFeedImpl; import org.apache.olingo.commons.core.data.LinkImpl; import org.apache.olingo.fit.metadata.Metadata; @@ -33,24 +33,28 @@ import org.apache.olingo.fit.utils.ConstantKey; import org.apache.olingo.fit.utils.Constants; import org.apache.olingo.fit.utils.DataBinder; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; 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.AbstractMap; import java.util.Arrays; import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.mail.Header; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; @@ -65,12 +69,20 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.cxf.jaxrs.ext.multipart.Attachment; +import org.apache.cxf.jaxrs.ext.multipart.Multipart; +import org.apache.cxf.jaxrs.ext.multipart.MultipartBody; +import org.apache.olingo.commons.api.data.Container; import org.apache.olingo.commons.api.data.Entry; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.core.data.AtomEntryImpl; import org.apache.olingo.commons.core.data.AtomPropertyImpl; import org.apache.olingo.commons.core.data.AtomSerializer; @@ -81,13 +93,13 @@ import org.apache.olingo.commons.core.data.PrimitiveValueImpl; import org.apache.olingo.fit.metadata.EntitySet; import org.apache.olingo.fit.metadata.EntityType; import org.apache.olingo.fit.metadata.NavigationProperty; +import org.apache.olingo.fit.methods.MERGE; +import org.apache.olingo.fit.methods.PATCH; +import org.apache.olingo.fit.serializer.FITAtomDeserializer; import org.apache.olingo.fit.utils.Accept; import org.apache.olingo.fit.utils.FSManager; import org.apache.olingo.fit.utils.Commons; -import org.apache.olingo.fit.methods.MERGE; -import org.apache.olingo.fit.methods.PATCH; -import org.apache.olingo.fit.serializer.FITAtomDeserializer; import org.apache.olingo.fit.utils.AbstractJSONUtilities; import org.apache.olingo.fit.utils.AbstractUtilities; import org.apache.olingo.fit.utils.AbstractXMLUtilities; @@ -102,6 +114,10 @@ public abstract class AbstractServices { */ protected static final Logger LOG = LoggerFactory.getLogger(AbstractServices.class); + private Pattern requestPatter = Pattern.compile("(.*) (http://.*) HTTP/.*"); + + private static final String boundary = "batch_243234_25424_ef_892u748"; + protected final ODataServiceVersion version; protected final AbstractXMLUtilities xml; @@ -174,6 +190,197 @@ public abstract class AbstractServices { } } + @POST + @Path("/$batch") + @Consumes("multipart/mixed") + @Produces("application/octet-stream; boundary=" + boundary) + public Response batch(final @Multipart MultipartBody attachment) { + try { + return xml.createBatchResponse(exploreMultipart(attachment.getAllAttachments(), boundary), boundary); + } catch (IOException e) { + return xml.createFaultResponse(Accept.XML.toString(version), e); + } + } + + private Response bodyPartRequest(final MimeBodyPart body) throws Exception { + + @SuppressWarnings("unchecked") + final Enumeration
en = (Enumeration
) body.getAllHeaders(); + + Header header = en.nextElement(); + final String request = header.getName() + ":" + header.getValue(); + final Matcher matcher = requestPatter.matcher(request); + + if (matcher.find()) { + final MultivaluedMap headers = new MultivaluedHashMap(); + + while (en.hasMoreElements()) { + header = en.nextElement(); + headers.putSingle(header.getName(), header.getValue()); + } + + String method = matcher.group(1); + if ("PATCH".equals(method) || "MERGE".equals(method)) { + headers.putSingle("X-HTTP-METHOD", method); + method = "POST"; + } + + final String url = matcher.group(2); + + final WebClient client = WebClient.create(url); + + client.headers(headers); + + return client.invoke(method, body.getDataHandler().getInputStream()); + } else { + return null; + } + } + + public InputStream exploreMultipart(final List 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 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())); + if (res.getStatus() > 400) { + throw new Exception("Failure processing changeset"); + } + + addSingleBatchResponse(res, lastContebtID, chbos); + } + 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()); + + return new ByteArrayInputStream(bos.toByteArray()); + } + + private void addItemIntro(final ByteArrayOutputStream bos) throws IOException { + bos.write("Content-Type: application/http".getBytes()); + bos.write(Constants.CRLF); + bos.write("Content-Transfer-Encoding: binary".getBytes()); + bos.write(Constants.CRLF); + bos.write(Constants.CRLF); + } + + private void addChangesetItemIntro( + final ByteArrayOutputStream bos, final String contentId, final String cboundary) throws IOException { + bos.write(("--" + cboundary).getBytes()); + bos.write(Constants.CRLF); + bos.write(("Content-ID: " + contentId).getBytes()); + bos.write(Constants.CRLF); + addItemIntro(bos); + } + + private void addSingleBatchResponse( + final Response response, final ByteArrayOutputStream bos) throws IOException { + addSingleBatchResponse(response, null, bos); + } + + private 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()); + bos.write(" ".getBytes()); + bos.write(response.getStatusInfo().getReasonPhrase().getBytes()); + bos.write(Constants.CRLF); + + for (Map.Entry> header : response.getHeaders().entrySet()) { + StringBuilder builder = new StringBuilder(); + for (Object value : header.getValue()) { + if (builder.length() > 0) { + builder.append(", "); + } + builder.append(value.toString()); + } + builder.insert(0, ": ").insert(0, header.getKey()); + bos.write(builder.toString().getBytes()); + bos.write(Constants.CRLF); + } + + if (StringUtils.isNotBlank(contentId)) { + bos.write(("Content-ID: " + contentId).getBytes()); + bos.write(Constants.CRLF); + } + + bos.write(Constants.CRLF); + + final Object entity = response.getEntity(); + if (entity != null) { + bos.write(IOUtils.toByteArray((InputStream) entity)); + bos.write(Constants.CRLF); + } + + bos.write(Constants.CRLF); + } + + private 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) + throws IOException { + addSingleBatchResponse(xml.createFaultResponse(Accept.XML.toString(version), e), contentId, bos); + } + @MERGE @Path("/{entitySetName}({entityId})") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON}) @@ -386,7 +593,7 @@ public abstract class AbstractServices { } else { final Container jcontainer = mapper.readValue(IOUtils.toInputStream(entity), new TypeReference() { - }); + }); entry = (new DataBinder(version)). getAtomEntry(jcontainer.getObject()); @@ -459,13 +666,13 @@ public abstract class AbstractServices { replaceAll("\"Salary\":[0-9]*,", "\"Salary\":0,"). replaceAll("\"Title\":\".*\"", "\"Title\":\"[Sacked]\""). replaceAll("\\.*\\", - "0"). + "0"). replaceAll("\\.*\\", "[Sacked]"); final FSManager fsManager = FSManager.instance(version); fsManager.putInMemory(IOUtils.toInputStream(newContent, "UTF-8"), fsManager.getAbsolutePath(Commons.getEntityBasePath("Person", entityId) + Constants.get(version, - ConstantKey.ENTITY), utils.getKey())); + ConstantKey.ENTITY), utils.getKey())); return utils.getValue().createResponse(null, null, utils.getKey(), Response.Status.NO_CONTENT); } catch (Exception e) { @@ -517,9 +724,9 @@ public abstract class AbstractServices { final Long newSalary = Long.valueOf(salaryMatcher.group(1)) + n; newContent = newContent. replaceAll("\"Salary\":" + salaryMatcher.group(1) + ",", - "\"Salary\":" + newSalary + ","). + "\"Salary\":" + newSalary + ","). replaceAll("\\" + salaryMatcher.group(1) + "", - "" + newSalary + ""); + "" + newSalary + ""); } FSManager.instance(version).putInMemory(IOUtils.toInputStream(newContent, "UTF-8"), @@ -651,7 +858,7 @@ public abstract class AbstractServices { mapper.writeValue( writer, new JsonFeedContainer(container.getContextURL(), container.getMetadataETag(), - new DataBinder(version).getJsonFeed(container.getObject()))); + new DataBinder(version).getJsonFeed(container.getObject()))); } return xml.createResponse(new ByteArrayInputStream(content.toByteArray()), @@ -833,7 +1040,7 @@ public abstract class AbstractServices { final ObjectMapper mapper = Commons.getJsonMapper(version); mapper.writeValue( writer, new JsonEntryContainer(container.getContextURL(), container.getMetadataETag(), - (new DataBinder(version)).getJsonEntry((AtomEntryImpl) container.getObject()))); + (new DataBinder(version)).getJsonEntry((AtomEntryImpl) container.getObject()))); } return xml.createResponse(new ByteArrayInputStream(content.toByteArray()), diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java index 9c0ef9483..8ef447259 100644 --- a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java +++ b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java @@ -60,6 +60,21 @@ public abstract class AbstractUtilities { protected final static Pattern entityUriPattern = Pattern.compile(".*\\/.*\\(.*\\)"); + /** + * Batch/Changeset content type. + */ + public static final String MULTIPART_CONTENT_TYPE = "multipart/mixed"; + + /** + * Batch item content type. + */ + public static final String ITEM_CONTENT_TYPE = "application/http"; + + /** + * Boundary key. + */ + public static final String BOUNDARY = "boundary"; + public AbstractUtilities(final ODataServiceVersion version) throws Exception { this.version = version; this.fsManager = FSManager.instance(version); @@ -399,6 +414,13 @@ public abstract class AbstractUtilities { return createResponse(entity, etag, accept, null); } + public Response createBatchResponse(final InputStream stream, final String boundary) { + + final Response.ResponseBuilder builder = Response.accepted(stream); + builder.header(Constants.get(version, ConstantKey.ODATA_SERVICE_VERSION), version.toString() + ";"); + return builder.build(); + } + public Response createResponse( final InputStream entity, final String etag, final Accept accept, final Response.Status status) { final Response.ResponseBuilder builder = Response.ok(); diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java b/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java index 5066cb02e..975a537e8 100644 --- a/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java +++ b/fit/src/main/java/org/apache/olingo/fit/utils/Constants.java @@ -29,6 +29,11 @@ public class Constants { private final static Map constants = new EnumMap(ConstantKey.class); + /** + * CR/LF. + */ + public static final byte[] CRLF = {13, 10}; + public static Charset encoding = Charset.forName("UTF-8"); static { diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java index 67b3043f7..a99c5ef5f 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/header/HeaderName.java @@ -59,7 +59,7 @@ public enum HeaderName { *
  • multipart/mixed
  • * */ - contentType("Content-Type", Arrays.asList(ODataServiceVersion.V30)), + contentType("Content-Type", Arrays.asList(ODataServiceVersion.V30, ODataServiceVersion.V40)), /** * This header is a custom HTTP header defined for protocol versioning purposes. This header MAY be present on any * request or response message. diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityCreateRequest.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityCreateRequest.java index 73471b910..ddd59752f 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityCreateRequest.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityCreateRequest.java @@ -19,6 +19,7 @@ package org.apache.olingo.client.api.communication.request.cud; import org.apache.olingo.client.api.communication.request.ODataBasicRequest; +import org.apache.olingo.client.api.communication.request.ODataBatchableRequest; import org.apache.olingo.client.api.communication.response.ODataEntityCreateResponse; import org.apache.olingo.commons.api.domain.CommonODataEntity; import org.apache.olingo.commons.api.format.ODataPubFormat; @@ -29,5 +30,5 @@ import org.apache.olingo.commons.api.format.ODataPubFormat; * @param concrete ODataEntity implementation */ public interface ODataEntityCreateRequest - extends ODataBasicRequest, ODataPubFormat> { + extends ODataBasicRequest, ODataPubFormat>, ODataBatchableRequest { } diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityUpdateRequest.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityUpdateRequest.java index 3f0289765..341028e4a 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityUpdateRequest.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/cud/ODataEntityUpdateRequest.java @@ -19,6 +19,7 @@ package org.apache.olingo.client.api.communication.request.cud; import org.apache.olingo.client.api.communication.request.ODataBasicRequest; +import org.apache.olingo.client.api.communication.request.ODataBatchableRequest; import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResponse; import org.apache.olingo.commons.api.domain.CommonODataEntity; import org.apache.olingo.commons.api.format.ODataPubFormat; @@ -29,5 +30,5 @@ import org.apache.olingo.commons.api.format.ODataPubFormat; * @param concrete ODataEntity implementation */ public interface ODataEntityUpdateRequest - extends ODataBasicRequest, ODataPubFormat> { + extends ODataBasicRequest, ODataPubFormat>, ODataBatchableRequest { } diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/retrieve/ODataRetrieveRequest.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/retrieve/ODataRetrieveRequest.java index 073f52054..61e505cd3 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/retrieve/ODataRetrieveRequest.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/communication/request/retrieve/ODataRetrieveRequest.java @@ -19,11 +19,13 @@ package org.apache.olingo.client.api.communication.request.retrieve; import org.apache.olingo.client.api.communication.request.ODataBasicRequest; +import org.apache.olingo.client.api.communication.request.ODataBatchableRequest; import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; import org.apache.olingo.commons.api.format.Format; /** * This is an abstract representation of an OData retrieve query request returning one or more result item. */ -public interface ODataRetrieveRequest extends ODataBasicRequest, T> { +public interface ODataRetrieveRequest + extends ODataBasicRequest, T>, ODataBatchableRequest { } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java index 9ebf783af..165a5146f 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java @@ -151,7 +151,8 @@ public class ODataBatchRequestImpl extends AbstractODataStreamedRequest { + public class BatchStreamManagerImpl extends AbstractODataStreamManager + implements BatchStreamManager { /** * Batch request current item. @@ -178,6 +179,7 @@ public class ODataBatchRequestImpl extends AbstractODataStreamedRequest extends AbstractODataBasicRequest, ODataPubFormat> - implements ODataEntityCreateRequest, ODataBatchableRequest { + implements ODataEntityCreateRequest { /** * Entity to be created. diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataEntityUpdateRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataEntityUpdateRequestImpl.java index 9a84de322..67be0d437 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataEntityUpdateRequestImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataEntityUpdateRequestImpl.java @@ -25,7 +25,6 @@ import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.olingo.client.api.CommonODataClient; -import org.apache.olingo.client.api.communication.request.ODataBatchableRequest; import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest; import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResponse; import org.apache.olingo.commons.api.domain.CommonODataEntity; @@ -39,11 +38,12 @@ import org.apache.olingo.commons.api.data.Entry; /** * This class implements an OData update request. + * * @param concrete ODataEntity implementation */ public class ODataEntityUpdateRequestImpl extends AbstractODataBasicRequest, ODataPubFormat> - implements ODataEntityUpdateRequest, ODataBatchableRequest { + implements ODataEntityUpdateRequest { /** * Changes to be applied. diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/retrieve/AbstractODataRetrieveRequest.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/retrieve/AbstractODataRetrieveRequest.java index 53e46c16b..f1ae24b3f 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/retrieve/AbstractODataRetrieveRequest.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/retrieve/AbstractODataRetrieveRequest.java @@ -23,7 +23,6 @@ import java.net.URI; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.olingo.client.api.CommonODataClient; -import org.apache.olingo.client.api.communication.request.ODataBatchableRequest; import org.apache.olingo.client.api.communication.request.retrieve.ODataRetrieveRequest; import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; import org.apache.olingo.commons.api.format.Format; @@ -36,7 +35,7 @@ import org.apache.olingo.client.core.communication.response.AbstractODataRespons */ public abstract class AbstractODataRetrieveRequest extends AbstractODataBasicRequest, T> - implements ODataRetrieveRequest, ODataBatchableRequest { + implements ODataRetrieveRequest { /** * Private constructor. diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/BatchTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/BatchTestITCase.java index 8ee3efdc6..4365600c1 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/BatchTestITCase.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v3/BatchTestITCase.java @@ -18,389 +18,429 @@ */ package org.apache.olingo.client.core.it.v3; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.URI; +import java.util.Iterator; +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.request.ODataStreamManager; +import org.apache.olingo.client.api.communication.request.batch.BatchStreamManager; +import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest; +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.ODataRetrieve; +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.v3.UpdateType; +import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest; +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.v3.URIBuilder; +import org.apache.olingo.client.core.communication.request.AbstractODataStreamManager; +import org.apache.olingo.client.core.communication.request.Wrapper; +import org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem; +import org.apache.olingo.client.core.communication.request.batch.ODataRetrieveResponseItem; +import org.apache.olingo.client.core.communication.request.retrieve.ODataEntityRequestImpl; +import org.apache.olingo.client.core.communication.request.retrieve.ODataEntityRequestImpl.ODataEntityResponseImpl; +import org.apache.olingo.client.core.uri.URIUtils; +import org.apache.olingo.commons.api.domain.v3.ODataEntity; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.format.ODataPubFormat; +import org.junit.Ignore; +import org.junit.Test; + public class BatchTestITCase extends AbstractTestITCase { -// private static String PREFIX = "!!PREFIX!!"; -// -// private static String SUFFIX = "!!SUFFIX!!"; -// -// private static int MAX = 10000; -// -// @Test -// @Ignore -// public void stringStreaming() { -// final TestStreamManager streaming = new TestStreamManager(); -// -// new StreamingThread(streaming).start(); -// -// streaming.addObject((PREFIX + "\n").getBytes()); -// -// for (int i = 0; i <= MAX; i++) { -// streaming.addObject((i + ") send info\n").getBytes()); -// } -// -// streaming.addObject((SUFFIX).getBytes()); -// streaming.finalizeBody(); -// } -// -// @Test -// @Ignore -// public void emptyBatchRequest() { -// // create your request -// final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); -// -// final BatchStreamManager payload = request.execute(); -// final ODataBatchResponse response = payload.getResponse(); -// -// assertEquals(202, response.getStatusCode()); -// assertEquals("Accepted", response.getStatusMessage()); -// -// final Iterator iter = response.getBody(); -// assertFalse(iter.hasNext()); -// } -// -// @Test -// @Ignore -// public void changesetWithError() { -// // create your request -// final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); -// -// final BatchStreamManager payload = request.execute(); -// final ODataChangeset changeset = payload.addChangeset(); -// -// URIBuilder targetURI; -// ODataEntityCreateRequest createReq; -// -// targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customer"); -// for (int i = 1; i <= 2; i++) { -// // Create Customer into the changeset -// createReq = client.getCUDRequestFactory().getEntityCreateRequest( -// targetURI.build(), -// getSampleCustomerProfile(100 + i, "Sample customer", false)); -// createReq.setFormat(ODataPubFormat.JSON); -// changeset.addRequest(createReq); -// } -// -// targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("WrongEntitySet"); -// createReq = client.getCUDRequestFactory().getEntityCreateRequest( -// targetURI.build(), -// getSampleCustomerProfile(105, "Sample customer", false)); -// createReq.setFormat(ODataPubFormat.JSON); -// changeset.addRequest(createReq); -// -// targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customer"); -// for (int i = 3; i <= 4; i++) { -// // Create Customer into the changeset -// createReq = client.getCUDRequestFactory().getEntityCreateRequest( -// targetURI.build(), -// getSampleCustomerProfile(100 + i, "Sample customer", false)); -// createReq.setFormat(ODataPubFormat.ATOM); -// changeset.addRequest(createReq); -// } -// -// final ODataBatchResponse response = payload.getResponse(); -// assertEquals(202, response.getStatusCode()); -// assertEquals("Accepted", response.getStatusMessage()); -// -// final Iterator iter = response.getBody(); -// final ODataChangesetResponseItem chgResponseItem = (ODataChangesetResponseItem) iter.next(); -// -// final ODataResponse res = chgResponseItem.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 -// @Ignore -// public void changesetWithReference() { -// // create your request -// final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); -// final BatchStreamManager streamManager = request.execute(); -// -// final ODataChangeset changeset = streamManager.addChangeset(); -// ODataEntity customer = getSampleCustomerProfile(20, "sample customer", false); -// -// URIBuilder uriBuilder = client.getURIBuilder(testAuthServiceRootURL).appendEntitySetSegment("Customer"); -// -// // add create request -// final ODataEntityCreateRequest createReq = -// client.getCUDRequestFactory().getEntityCreateRequest(uriBuilder.build(), customer); -// -// changeset.addRequest(createReq); -// -// // retrieve request reference -// int createRequestRef = changeset.getLastContentId(); -// -// // add update request: link CustomerInfo(17) to the new customer -// final ODataEntity customerChanges = client.getObjectFactory().newEntity(customer.getName()); -// customerChanges.addLink(client.getObjectFactory().newEntityNavigationLink( -// "Info", -// client.getURIBuilder(testAuthServiceRootURL).appendEntitySetSegment("CustomerInfo"). -// appendKeySegment(17).build())); -// -// final ODataEntityUpdateRequest updateReq = client.getCUDRequestFactory().getEntityUpdateRequest( -// URI.create("$" + createRequestRef), UpdateType.PATCH, customerChanges); -// -// changeset.addRequest(updateReq); -// -// final ODataBatchResponse response = streamManager.getResponse(); -// assertEquals(202, response.getStatusCode()); -// assertEquals("Accepted", response.getStatusMessage()); -// -// // verify response payload ... -// final Iterator 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); -// -// customer = ((ODataEntityCreateResponse) res).getBody(); -// -// ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest( -// URIUtils.getURI(testStaticServiceRootURL, customer.getEditLink().toASCIIString() + "/Info")); -// -// assertEquals(Integer.valueOf(17), -// req.execute().getBody().getProperty("CustomerInfoId").getPrimitiveValue().toCastValue()); -// -// res = chgitem.next(); -// assertEquals(204, res.getStatusCode()); -// assertTrue(res instanceof ODataEntityUpdateResponse); -// -// // clean ... -// assertEquals(204, client.getCUDRequestFactory().getDeleteRequest( -// URIUtils.getURI(testStaticServiceRootURL, customer.getEditLink().toASCIIString())).execute(). -// getStatusCode()); -// -// try { -// client.getRetrieveRequestFactory().getEntityRequest( -// URIUtils.getURI(testStaticServiceRootURL, customer.getEditLink().toASCIIString())). -// execute().getBody(); -// fail(); -// } catch (Exception e) { -// // ignore -// } -// } -// -// @Test -// @Ignore -// public void batchRequest() { -// // create your request -// final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); -// -// final BatchStreamManager streamManager = request.execute(); -// -// // ------------------------------------------- -// // Add retrieve item -// // ------------------------------------------- -// ODataRetrieve retrieve = streamManager.addRetrieve(); -// -// // prepare URI -// URIBuilder targetURI = client.getURIBuilder(testStaticServiceRootURL); -// targetURI.appendEntitySetSegment("Customer").appendKeySegment(-10). -// expand("Logins").select("CustomerId,Logins/Username"); -// -// // create new request -// ODataEntityRequest queryReq = client.getRetrieveRequestFactory().getEntityRequest(targetURI.build()); -// queryReq.setFormat(ODataPubFormat.ATOM); -// -// retrieve.setRequest(queryReq); -// // ------------------------------------------- -// -// // ------------------------------------------- -// // Add changeset item -// // ------------------------------------------- -// final ODataChangeset changeset = streamManager.addChangeset(); -// -// // Update Product into the changeset -// targetURI = client.getURIBuilder(testStaticServiceRootURL). -// appendEntitySetSegment("Product").appendKeySegment(-10); -// final URI editLink = targetURI.build(); -// -// final ODataEntity merge = client.getObjectFactory().newEntity(TEST_PRODUCT_TYPE); -// merge.setEditLink(editLink); -// -// merge.getProperties().add(client.getObjectFactory().newPrimitiveProperty( -// "Description", client.getPrimitiveValueBuilder().setText("new description from batch").build())); -// -// final ODataEntityUpdateRequest changeReq = -// client.getCUDRequestFactory().getEntityUpdateRequest(UpdateType.MERGE, merge); -// changeReq.setFormat(ODataPubFormat.JSON_FULL_METADATA); -// changeReq.setIfMatch(getETag(editLink)); -// -// changeset.addRequest(changeReq); -// -// // Create Customer into the changeset -// targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customer"); -// final ODataEntity original = getSampleCustomerProfile(1000, "Sample customer", false); -// final ODataEntityCreateRequest createReq = -// client.getCUDRequestFactory().getEntityCreateRequest(targetURI.build(), original); -// createReq.setFormat(ODataPubFormat.ATOM); -// changeset.addRequest(createReq); -// // ------------------------------------------- -// -// // ------------------------------------------- -// // Add retrieve item -// // ------------------------------------------- -// retrieve = streamManager.addRetrieve(); -// -// // prepare URI -// targetURI = client.getURIBuilder(testStaticServiceRootURL). -// appendEntitySetSegment("Product").appendKeySegment(-10); -// -// // create new request -// queryReq = client.getRetrieveRequestFactory().getEntityRequest(targetURI.build()); -// -// retrieve.setRequest(queryReq); -// // ------------------------------------------- -// -// final ODataBatchResponse response = streamManager.getResponse(); -// assertEquals(202, response.getStatusCode()); -// assertEquals("Accepted", response.getStatusMessage()); -// -// final Iterator iter = response.getBody(); -// -// // retrive the first item (ODataRetrieve) -// ODataBatchResponseItem item = iter.next(); -// assertTrue(item instanceof ODataRetrieveResponseItem); -// -// ODataRetrieveResponseItem retitem = (ODataRetrieveResponseItem) item; -// ODataResponse res = retitem.next(); -// assertTrue(res instanceof ODataEntityResponseImpl); -// assertEquals(200, res.getStatusCode()); -// assertEquals("OK", res.getStatusMessage()); -// -// ODataEntityResponseImpl entres = (ODataEntityResponseImpl) res; -// ODataEntity entity = entres.getBody(); -// assertEquals(new Integer(-10), entity.getProperty("CustomerId").getPrimitiveValue().toCastValue()); -// -// // retrieve the second item (ODataChangeset) -// item = iter.next(); -// assertTrue(item instanceof ODataChangesetResponseItem); -// -// final ODataChangesetResponseItem chgitem = (ODataChangesetResponseItem) item; -// res = chgitem.next(); -// assertTrue(res instanceof ODataEntityUpdateResponse); -// assertEquals(204, res.getStatusCode()); -// assertEquals("No Content", res.getStatusMessage()); -// -// res = chgitem.next(); -// assertTrue(res instanceof ODataEntityCreateResponse); -// assertEquals(201, res.getStatusCode()); -// assertEquals("Created", res.getStatusMessage()); -// -// final ODataEntityCreateResponse createres = (ODataEntityCreateResponse) res; -// entity = createres.getBody(); -// assertEquals(new Integer(1000), entity.getProperty("CustomerId").getPrimitiveValue().toCastValue()); -// -// // retrive the third item (ODataRetrieve) -// 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()); -// -// entres = (ODataEntityResponseImpl) res; -// entity = entres.getBody(); -// assertEquals("new description from batch", -// entity.getProperty("Description").getPrimitiveValue().toCastValue()); -// -// assertFalse(iter.hasNext()); -// } -// -// private static class TestStreamManager extends AbstractODataStreamManager { -// -// public TestStreamManager() { -// super(new Wrapper>()); -// } -// -// public ODataStreamManager addObject(byte[] src) { -// stream(src); -// return this; -// } -// -// @Override -// protected ODataBatchResponse getResponse(long timeout, TimeUnit unit) { -// throw new UnsupportedOperationException("Not supported yet."); -// } -// }; -// -// /** -// * To be used for debug purposes. -// */ -// private static class StreamingThread extends Thread { -// -// private final TestStreamManager streaming; -// -// public StreamingThread(final TestStreamManager streaming) { -// this.streaming = streaming; -// } -// -// @Override -// public void run() { -// try { -// final StringBuilder builder = new StringBuilder(); -// -// byte[] buff = new byte[1024]; -// -// int len; -// -// while ((len = streaming.getBody().read(buff)) >= 0) { -// builder.append(new String(buff, 0, len)); -// } -// -// assertTrue(builder.toString().startsWith(PREFIX)); -// assertTrue(builder.toString().contains((MAX / 2) + ") send info")); -// assertTrue(builder.toString().contains((MAX / 3) + ") send info")); -// assertTrue(builder.toString().contains((MAX / 20) + ") send info")); -// assertTrue(builder.toString().contains((MAX / 30) + ") send info")); -// assertTrue(builder.toString().contains(MAX + ") send info")); -// assertTrue(builder.toString().endsWith(SUFFIX)); -// -// } catch (IOException e) { -// fail(); -// } -// } -// } -// -// private static class BatchStreamingThread extends Thread { -// -// private final BatchStreamManager streaming; -// -// public BatchStreamingThread(final BatchStreamManager streaming) { -// this.streaming = streaming; -// } -// -// @Override -// public void run() { -// try { -// final StringBuilder builder = new StringBuilder(); -// -// byte[] buff = new byte[1024]; -// -// int len; -// -// while ((len = streaming.getBody().read(buff)) >= 0) { -// builder.append(new String(buff, 0, len)); -// } -// -// LOG.debug("Batch request {}", builder.toString()); -// -// assertTrue(builder.toString().contains("Content-Id:2")); -// assertTrue(builder.toString().contains("GET " + servicesODataServiceRootURL)); -// } catch (IOException e) { -// fail(); -// } -// } -// } + private static String PREFIX = "!!PREFIX!!"; + + private static String SUFFIX = "!!SUFFIX!!"; + + private static int MAX = 10000; + + @Test + public void stringStreaming() { + final TestStreamManager streaming = new TestStreamManager(); + + new StreamingThread(streaming).start(); + + streaming.addObject((PREFIX + "\n").getBytes()); + + for (int i = 0; i <= MAX; i++) { + streaming.addObject((i + ") send info\n").getBytes()); + } + + streaming.addObject((SUFFIX).getBytes()); + streaming.finalizeBody(); + } + + @Test + public void emptyBatchRequest() { + // create your request + final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); + + final BatchStreamManager payload = request.execute(); + final ODataBatchResponse response = payload.getResponse(); + + assertEquals(202, response.getStatusCode()); + assertEquals("Accepted", response.getStatusMessage()); + + final Iterator iter = response.getBody(); + assertFalse(iter.hasNext()); + } + + @Test + public void changesetWithError() { + // create your request + final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); + + final BatchStreamManager payload = request.execute(); + final ODataChangeset changeset = payload.addChangeset(); + + URIBuilder targetURI; + ODataEntityCreateRequest createReq; + + targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customer"); + for (int i = 1; i <= 2; i++) { + // Create Customer into the changeset + createReq = client.getCUDRequestFactory().getEntityCreateRequest( + targetURI.build(), + getSampleCustomerProfile(100 + i, "Sample customer", false)); + createReq.setFormat(ODataPubFormat.JSON); + changeset.addRequest(createReq); + } + + targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("WrongEntitySet"); + createReq = client.getCUDRequestFactory().getEntityCreateRequest( + targetURI.build(), + getSampleCustomerProfile(105, "Sample customer", false)); + createReq.setFormat(ODataPubFormat.JSON); + changeset.addRequest(createReq); + + targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customer"); + for (int i = 3; i <= 4; i++) { + // Create Customer into the changeset + createReq = client.getCUDRequestFactory().getEntityCreateRequest( + targetURI.build(), + getSampleCustomerProfile(100 + i, "Sample customer", false)); + createReq.setFormat(ODataPubFormat.ATOM); + changeset.addRequest(createReq); + } + + final ODataBatchResponse response = payload.getResponse(); + assertEquals(202, response.getStatusCode()); + assertEquals("Accepted", response.getStatusMessage()); + + final Iterator iter = response.getBody(); + final ODataChangesetResponseItem chgResponseItem = (ODataChangesetResponseItem) iter.next(); + + final ODataResponse res = chgResponseItem.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 + @Ignore + @SuppressWarnings("unchecked") + public void changesetWithReference() throws EdmPrimitiveTypeException { + // create your request + final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); + final BatchStreamManager streamManager = request.execute(); + + final ODataChangeset changeset = streamManager.addChangeset(); + ODataEntity customer = getSampleCustomerProfile(20, "sample customer", false); + + URIBuilder uriBuilder = client.getURIBuilder(testAuthServiceRootURL).appendEntitySetSegment("Customer"); + + // add create request + final ODataEntityCreateRequest createReq = + client.getCUDRequestFactory().getEntityCreateRequest(uriBuilder.build(), customer); + + changeset.addRequest(createReq); + + // retrieve request reference + int createRequestRef = changeset.getLastContentId(); + + // add update request: link CustomerInfo(17) to the new customer + final ODataEntity customerChanges = client.getObjectFactory().newEntity(customer.getTypeName()); + customerChanges.addLink(client.getObjectFactory().newEntityNavigationLink( + "Info", + client.getURIBuilder(testAuthServiceRootURL).appendEntitySetSegment("CustomerInfo"). + appendKeySegment(17).build())); + + final ODataEntityUpdateRequest updateReq = client.getCUDRequestFactory().getEntityUpdateRequest( + URI.create("$" + createRequestRef), UpdateType.PATCH, customerChanges); + + changeset.addRequest(updateReq); + + final ODataBatchResponse response = streamManager.getResponse(); + assertEquals(202, response.getStatusCode()); + assertEquals("Accepted", response.getStatusMessage()); + + // verify response payload ... + final Iterator 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); + + customer = ((ODataEntityCreateResponse) res).getBody(); + + ODataEntityRequest req = client.getRetrieveRequestFactory().getEntityRequest( + URIUtils.getURI(testStaticServiceRootURL, customer.getEditLink().toASCIIString() + "/Info")); + + assertEquals(Integer.valueOf(17), + req.execute().getBody().getProperty("CustomerInfoId").getPrimitiveValue().toCastValue(Integer.class)); + + res = chgitem.next(); + assertEquals(204, res.getStatusCode()); + assertTrue(res instanceof ODataEntityUpdateResponse); + + // clean ... + assertEquals(204, client.getCUDRequestFactory().getDeleteRequest( + URIUtils.getURI(testStaticServiceRootURL, customer.getEditLink().toASCIIString())).execute(). + getStatusCode()); + + try { + client.getRetrieveRequestFactory().getEntityRequest( + URIUtils.getURI(testStaticServiceRootURL, customer.getEditLink().toASCIIString())). + execute().getBody(); + fail(); + } catch (Exception e) { + // ignore + } + } + + @Test + @SuppressWarnings("unchecked") + public void batchRequest() throws EdmPrimitiveTypeException { + // create your request + final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(testStaticServiceRootURL); + + final BatchStreamManager streamManager = request.execute(); + + // ------------------------------------------- + // Add retrieve item + // ------------------------------------------- + ODataRetrieve retrieve = streamManager.addRetrieve(); + + // prepare URI + URIBuilder targetURI = client.getURIBuilder(testStaticServiceRootURL); + targetURI.appendEntitySetSegment("Customer").appendKeySegment(-10). + expand("Logins").select("CustomerId,Logins/Username"); + + // create new request + ODataEntityRequest queryReq = client.getRetrieveRequestFactory().getEntityRequest(targetURI.build()); + queryReq.setFormat(ODataPubFormat.ATOM); + + retrieve.setRequest(queryReq); + // ------------------------------------------- + + // ------------------------------------------- + // Add changeset item + // ------------------------------------------- + final ODataChangeset changeset = streamManager.addChangeset(); + + // Update Product into the changeset + targetURI = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("Product").appendKeySegment(-10); + final URI editLink = targetURI.build(); + + final ODataEntity merge = client.getObjectFactory().newEntity(TEST_PRODUCT_TYPE); + merge.setEditLink(editLink); + + merge.getProperties().add(client.getObjectFactory().newPrimitiveProperty( + "Description", + client.getObjectFactory().newPrimitiveValueBuilder().buildString("new description from batch"))); + + final ODataEntityUpdateRequest changeReq = + client.getCUDRequestFactory().getEntityUpdateRequest(UpdateType.MERGE, merge); + changeReq.setFormat(ODataPubFormat.JSON_FULL_METADATA); + changeReq.setIfMatch(getETag(editLink)); + + changeset.addRequest(changeReq); + + // Create Customer into the changeset + targetURI = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customer"); + final ODataEntity original = getSampleCustomerProfile(1000, "Sample customer", false); + final ODataEntityCreateRequest createReq = + client.getCUDRequestFactory().getEntityCreateRequest(targetURI.build(), original); + createReq.setFormat(ODataPubFormat.ATOM); + changeset.addRequest(createReq); + // ------------------------------------------- + + // ------------------------------------------- + // Add retrieve item + // ------------------------------------------- + retrieve = streamManager.addRetrieve(); + + // prepare URI + targetURI = client.getURIBuilder(testStaticServiceRootURL). + appendEntitySetSegment("Product").appendKeySegment(-10); + + // create new request + queryReq = client.getRetrieveRequestFactory().getEntityRequest(targetURI.build()); + + retrieve.setRequest(queryReq); + // ------------------------------------------- + + final ODataBatchResponse response = streamManager.getResponse(); + assertEquals(202, response.getStatusCode()); + assertEquals("Accepted", response.getStatusMessage()); + final Iterator iter = response.getBody(); + + // retrive the first item (ODataRetrieve) + ODataBatchResponseItem item = iter.next(); + assertTrue(item instanceof ODataRetrieveResponseItem); + + ODataRetrieveResponseItem retitem = (ODataRetrieveResponseItem) item; + ODataResponse res = retitem.next(); + assertTrue(res instanceof ODataEntityResponseImpl); + assertEquals(200, res.getStatusCode()); + assertEquals("OK", res.getStatusMessage()); + + ODataEntityRequestImpl.ODataEntityResponseImpl entres = + (ODataEntityRequestImpl.ODataEntityResponseImpl) res; + + ODataEntity entity = entres.getBody(); + assertEquals(new Integer(-10), entity.getProperty("CustomerId").getPrimitiveValue().toCastValue(Integer.class)); + + // retrieve the second item (ODataChangeset) + item = iter.next(); + assertTrue(item instanceof ODataChangesetResponseItem); + + final ODataChangesetResponseItem chgitem = (ODataChangesetResponseItem) item; + res = chgitem.next(); + assertTrue(res instanceof ODataEntityUpdateResponse); + assertEquals(204, res.getStatusCode()); + assertEquals("No Content", res.getStatusMessage()); + + res = chgitem.next(); + assertTrue(res instanceof ODataEntityCreateResponse); + assertEquals(201, res.getStatusCode()); + assertEquals("Created", res.getStatusMessage()); + + final ODataEntityCreateResponse createres = (ODataEntityCreateResponse) res; + entity = createres.getBody(); + assertEquals(new Integer(1000), entity.getProperty("CustomerId").getPrimitiveValue().toCastValue(Integer.class)); + + // retrive the third item (ODataRetrieve) + 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()); + + entres = (ODataEntityRequestImpl.ODataEntityResponseImpl) res; + entity = entres.getBody(); + assertEquals("new description from batch", + entity.getProperty("Description").getPrimitiveValue().toCastValue(String.class)); + + assertFalse(iter.hasNext()); + } + + private static class TestStreamManager extends AbstractODataStreamManager { + + public TestStreamManager() { + super(new Wrapper>()); + } + + public ODataStreamManager addObject(byte[] src) { + stream(src); + return this; + } + + @Override + protected ODataBatchResponse getResponse(long timeout, TimeUnit unit) { + throw new UnsupportedOperationException("Not supported yet."); + } + }; + + /** + * To be used for debug purposes. + */ + private static class StreamingThread extends Thread { + + private final TestStreamManager streaming; + + public StreamingThread(final TestStreamManager streaming) { + this.streaming = streaming; + } + + @Override + public void run() { + try { + final StringBuilder builder = new StringBuilder(); + + byte[] buff = new byte[1024]; + + int len; + + while ((len = streaming.getBody().read(buff)) >= 0) { + builder.append(new String(buff, 0, len)); + } + + assertTrue(builder.toString().startsWith(PREFIX)); + assertTrue(builder.toString().contains((MAX / 2) + ") send info")); + assertTrue(builder.toString().contains((MAX / 3) + ") send info")); + assertTrue(builder.toString().contains((MAX / 20) + ") send info")); + assertTrue(builder.toString().contains((MAX / 30) + ") send info")); + assertTrue(builder.toString().contains(MAX + ") send info")); + assertTrue(builder.toString().endsWith(SUFFIX)); + + } catch (IOException e) { + fail(); + } + } + } + + private static class BatchStreamingThread extends Thread { + + private final BatchStreamManager streaming; + + public BatchStreamingThread(final BatchStreamManager streaming) { + this.streaming = streaming; + } + + @Override + public void run() { + try { + final StringBuilder builder = new StringBuilder(); + + byte[] buff = new byte[1024]; + + int len; + + while ((len = streaming.getBody().read(buff)) >= 0) { + builder.append(new String(buff, 0, len)); + } + + LOG.debug("Batch request {}", builder.toString()); + + assertTrue(builder.toString().contains("Content-Id:2")); + assertTrue(builder.toString().contains("GET " + testStaticServiceRootURL)); + } catch (IOException e) { + fail(); + } + } + } }