You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by fm...@apache.org on 2014/03/13 16:23:46 UTC
[4/7] OLINGO-205 ODataJClient request/response layer implementation
imported
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java
new file mode 100644
index 0000000..b4ae74e
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java
@@ -0,0 +1,483 @@
+/*
+ * 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.request;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.DecompressingHttpClient;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.V3Configuration;
+import org.apache.olingo.client.api.communication.ODataClientErrorException;
+import org.apache.olingo.client.api.communication.ODataServerErrorException;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import org.apache.olingo.client.api.communication.header.ODataHeaderValues;
+import org.apache.olingo.client.api.communication.header.ODataHeaders;
+import org.apache.olingo.client.api.communication.request.ODataRequest;
+import org.apache.olingo.client.api.communication.request.ODataStreamer;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.apache.olingo.client.api.format.ODataMediaFormat;
+import org.apache.olingo.client.api.format.ODataPubFormat;
+import org.apache.olingo.client.api.format.ODataValueFormat;
+import org.apache.olingo.client.api.http.HttpClientException;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.data.JSONErrorImpl;
+import org.apache.olingo.client.core.data.XMLErrorImpl;
+import org.apache.olingo.client.api.data.Error;
+import org.apache.olingo.client.core.communication.header.ODataHeadersImpl;
+import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract representation of an OData request. Get instance by using factories.
+ *
+ * @param <T> Accepted content-type formats by the request in object.
+ *
+ * @see CUDRequestFactory
+ * @see BatchRequestFactory
+ * @see InvokeRequestFactory
+ * @see StreamedRequestFactory
+ */
+public class ODataRequestImpl<T extends Enum<T>> implements ODataRequest {
+
+ /**
+ * Logger.
+ */
+ protected static final Logger LOG = LoggerFactory.getLogger(ODataRequest.class);
+
+ protected final ODataClient odataClient;
+
+ protected final Class<T> formatRef;
+
+ /**
+ * OData request method.
+ */
+ protected final HttpMethod method;
+
+ /**
+ * OData request header.
+ */
+ protected final ODataHeadersImpl odataHeaders;
+
+ /**
+ * Target URI.
+ */
+ protected final URI uri;
+
+ /**
+ * HTTP client.
+ */
+ protected final HttpClient httpClient;
+
+ /**
+ * HTTP request.
+ */
+ protected final HttpUriRequest request;
+
+ /**
+ * Constructor.
+ *
+ * @param odataClient client instance getting this request
+ * @param formatRef reference class for the format being used
+ * @param method HTTP request method. If configured X-HTTP-METHOD header will be used.
+ * @param uri OData request URI.
+ */
+ protected ODataRequestImpl(final ODataClient odataClient,
+ final Class<T> formatRef, final HttpMethod method, final URI uri) {
+
+ this.odataClient = odataClient;
+
+ this.formatRef = formatRef;
+ this.method = method;
+
+ // initialize default headers
+ this.odataHeaders = (ODataHeadersImpl) odataClient.getVersionHeaders();
+
+ // target uri
+ this.uri = uri;
+
+ HttpClient _httpClient = odataClient.getConfiguration().getHttpClientFactory().
+ createHttpClient(this.method, this.uri);
+ if (odataClient.getConfiguration().isGzipCompression()) {
+ _httpClient = new DecompressingHttpClient(_httpClient);
+ }
+ this.httpClient = _httpClient;
+
+ this.request = odataClient.getConfiguration().getHttpUriRequestFactory().
+ createHttpUriRequest(this.method, this.uri);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ public T getDefaultFormat() {
+ return (T) (formatRef.equals(ODataPubFormat.class)
+ ? odataClient.getConfiguration().getDefaultPubFormat()
+ : (formatRef.equals(ODataValueFormat.class)
+ ? odataClient.getConfiguration().getDefaultValueFormat()
+ : (formatRef.equals(ODataMediaFormat.class)
+ ? odataClient.getConfiguration().getDefaultMediaFormat()
+ : odataClient.getConfiguration().getDefaultFormat())));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Collection<String> getHeaderNames() {
+ return odataHeaders.getHeaderNames();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getHeader(final String name) {
+ return odataHeaders.getHeader(name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest setAccept(final String value) {
+ odataHeaders.setHeader(HeaderName.accept, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest setIfMatch(final String value) {
+ odataHeaders.setHeader(HeaderName.ifMatch, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest setIfNoneMatch(final String value) {
+ odataHeaders.setHeader(HeaderName.ifNoneMatch, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest setPrefer(final String value) {
+ odataHeaders.setHeader(HeaderName.prefer, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest setXHTTPMethod(final String value) {
+ odataHeaders.setHeader(HeaderName.xHttpMethod, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest setContentType(final String value) {
+ odataHeaders.setHeader(HeaderName.contentType, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest setSlug(final String value) {
+ odataHeaders.setHeader(HeaderName.slug, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ODataRequest addCustomHeader(final String name, final String value) {
+ odataHeaders.setHeader(name, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getAccept() {
+ final String acceptHead = odataHeaders.getHeader(HeaderName.accept);
+ return StringUtils.isBlank(acceptHead) ? getDefaultFormat().toString() : acceptHead;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getIfMatch() {
+ return odataHeaders.getHeader(HeaderName.ifMatch);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getIfNoneMatch() {
+ return odataHeaders.getHeader(HeaderName.ifNoneMatch);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getPrefer() {
+ return odataHeaders.getHeader(HeaderName.prefer);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getContentType() {
+ final String contentTypeHead = odataHeaders.getHeader(HeaderName.contentType);
+ return StringUtils.isBlank(contentTypeHead) ? getDefaultFormat().toString() : contentTypeHead;
+ }
+
+ /**
+ * ${@inheritDoc }
+ */
+ @Override
+ public HttpMethod getMethod() {
+ return method;
+ }
+
+ /**
+ * Gets request headers.
+ *
+ * @return request headers.
+ */
+ public ODataHeaders getHeader() {
+ return odataHeaders;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public byte[] toByteArray() {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ final StringBuilder requestBuilder = new StringBuilder();
+ requestBuilder.append(getMethod().toString()).append(" ").
+ append(uri.toString()).append(" ").append("HTTP/1.1");
+
+ baos.write(requestBuilder.toString().getBytes());
+
+ baos.write(ODataStreamer.CRLF);
+
+ // Set Content-Type and Accept headers with default values, if not yet set
+ if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.contentType))) {
+ setContentType(getContentType());
+ }
+ if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.accept))) {
+ setAccept(getAccept());
+ }
+
+ for (String name : getHeaderNames()) {
+ final String value = getHeader(name);
+
+ if (StringUtils.isNotBlank(value)) {
+ baos.write((name + ": " + value).getBytes());
+ baos.write(ODataStreamer.CRLF);
+ }
+ }
+
+ return baos.toByteArray();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ IOUtils.closeQuietly(baos);
+ }
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public InputStream rawExecute() {
+ try {
+ final HttpEntity httpEntity = doExecute().getEntity();
+ return httpEntity == null ? null : httpEntity.getContent();
+ } catch (IOException e) {
+ throw new HttpClientException(e);
+ } catch (RuntimeException e) {
+ this.request.abort();
+ throw new HttpClientException(e);
+ }
+ }
+
+ /**
+ * Builds the request and execute it.
+ *
+ * @return HttpReponse object.
+ */
+ protected HttpResponse doExecute() {
+ // Set Content-Type and Accept headers with default values, if not yet set
+ if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.contentType))) {
+ setContentType(getContentType());
+ }
+ if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.accept))) {
+ setAccept(getAccept());
+ }
+
+ // Add header for KeyAsSegment management
+ if (odataClient.getServiceVersion() == ODataServiceVersion.V30
+ && ((V3Configuration) odataClient.getConfiguration()).isKeyAsSegment()) {
+ addCustomHeader(
+ HeaderName.dataServiceUrlConventions.toString(), ODataHeaderValues.keyAsSegment);
+ }
+
+ // Add all available headers
+ for (String key : getHeaderNames()) {
+ this.request.addHeader(key, odataHeaders.getHeader(key));
+ }
+
+ if (LOG.isDebugEnabled()) {
+ for (Header header : this.request.getAllHeaders()) {
+ LOG.debug("HTTP header being sent: " + header);
+ }
+ }
+
+ final HttpResponse response;
+ try {
+ response = this.httpClient.execute(this.request);
+ } catch (IOException e) {
+ throw new HttpClientException(e);
+ } catch (RuntimeException e) {
+ this.request.abort();
+ throw new HttpClientException(e);
+ }
+
+ if (response.getStatusLine().getStatusCode() >= 500) {
+ throw new ODataServerErrorException(response.getStatusLine());
+ } else if (response.getStatusLine().getStatusCode() >= 400) {
+ try {
+ final HttpEntity httpEntity = response.getEntity();
+ if (httpEntity == null) {
+ throw new ODataClientErrorException(response.getStatusLine());
+ } else {
+ final boolean isXML = getAccept().indexOf("json") == -1;
+ Error error;
+
+ try {
+ error = odataClient.getReader().readError(httpEntity.getContent(), isXML);
+ } catch (IllegalArgumentException e) {
+ LOG.warn("Error deserializing error response", e);
+ error = getGenericError(
+ response.getStatusLine().getStatusCode(),
+ response.getStatusLine().getReasonPhrase(),
+ isXML);
+ }
+
+ throw new ODataClientErrorException(response.getStatusLine(), error);
+ }
+ } catch (IOException e) {
+ throw new HttpClientException(
+ "Received '" + response.getStatusLine() + "' but could not extract error body", e);
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * Gets an empty response that can be initialized by a stream.
+ * <p>
+ * This method has to be used to build response items about a batch request.
+ *
+ * @param <V> ODataResppnse type.
+ * @return empty OData response instance.
+ */
+ @SuppressWarnings("unchecked")
+ public <V extends ODataResponse> V getResponseTemplate() {
+
+ for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
+ if (ODataResponse.class.isAssignableFrom(clazz)) {
+ try {
+ final Constructor<?> constructor = clazz.getDeclaredConstructor(this.getClass());
+ constructor.setAccessible(true);
+ return (V) constructor.newInstance(this);
+ } catch (Exception e) {
+ LOG.error("Error retrieving response class template instance", e);
+ }
+ }
+ }
+
+ throw new IllegalStateException("No response class template has been found");
+ }
+
+ private Error getGenericError(final int code, final String errorMsg, final boolean isXML) {
+ final Error error;
+ if (isXML) {
+ error = new XMLErrorImpl();
+ final XMLErrorImpl.Message msg = new XMLErrorImpl.Message(
+ Collections.singletonMap("", (Object) errorMsg));
+
+ ((XMLErrorImpl) error).setMessage(msg);
+ ((XMLErrorImpl) error).setCode(String.valueOf(code));
+ } else {
+ error = new JSONErrorImpl();
+ final JSONErrorImpl.Message msg = new JSONErrorImpl.Message();
+ msg.setValue(errorMsg);
+
+ ((JSONErrorImpl) error).setMessage(msg);
+ ((JSONErrorImpl) error).setCode(String.valueOf(code));
+ }
+
+ return error;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java
new file mode 100644
index 0000000..c92a243
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java
@@ -0,0 +1,36 @@
+/**
+ * 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.request.batch;
+
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.batch.BatchRequestFactory;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+
+/**
+ * OData batch request factory class.
+ */
+public abstract class AbstractBatchRequestFactory implements BatchRequestFactory {
+
+ private static final long serialVersionUID = -3875283254713404483L;
+
+ protected final ODataClient client;
+
+ protected AbstractBatchRequestFactory(final ODataClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public ODataBatchRequest getBatchRequest(final String serviceRoot) {
+ return new ODataBatchRequestImpl(client, client.getURIBuilder(serviceRoot).appendBatchSegment().build());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java
new file mode 100644
index 0000000..e85ce36
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java
@@ -0,0 +1,124 @@
+/*
+ * 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.request.batch;
+
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequestItem;
+import org.apache.olingo.client.core.communication.request.AbstractODataStreamer;
+
+/**
+ * Abstract representation of a batch request item.
+ */
+public abstract class AbstractODataBatchRequestItem extends AbstractODataStreamer
+ implements ODataBatchRequestItem {
+
+ /**
+ * Stream started check.
+ */
+ protected boolean hasStreamedSomething = false;
+
+ /**
+ * Stream open check.
+ */
+ private boolean open = false;
+
+ /**
+ * OData batch request.
+ */
+ protected ODataBatchRequest req;
+
+ /**
+ * Constructor.
+ *
+ * @param req OData batch request.
+ */
+ public AbstractODataBatchRequestItem(final ODataBatchRequest req) {
+ super(req.getOutputStream());
+ this.open = true;
+ this.req = req;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOpen() {
+ return open;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() {
+ closeItem();
+ open = false;
+ }
+
+ /**
+ * Stream the given request header.
+ * <p>
+ * Use this method to stream changeset items.
+ *
+ * @param request request to be batched.
+ * @param contentId changeset item id.
+ */
+ protected void streamRequestHeader(final ODataBatchableRequest request, final int contentId) {
+ //stream batch content type
+ stream(ODataBatchConstants.ITEM_CONTENT_TYPE_LINE.getBytes());
+ newLine();
+ stream(ODataBatchConstants.ITEM_TRANSFER_ENCODING_LINE.getBytes());
+ newLine();
+ stream((ODataBatchConstants.CHANGESET_CONTENT_ID_NAME + ":" + contentId).getBytes());
+ newLine();
+ newLine();
+ }
+
+ /**
+ * Stream the given request header.
+ *
+ * @param request request to be batched.
+ */
+ protected void streamRequestHeader(final ODataBatchableRequest request) {
+ //stream batch content type
+ stream(ODataBatchConstants.ITEM_CONTENT_TYPE_LINE.getBytes());
+ newLine();
+ stream(ODataBatchConstants.ITEM_TRANSFER_ENCODING_LINE.getBytes());
+ newLine();
+ newLine();
+
+ stream(request.toByteArray());
+ newLine();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasStreamedSomething() {
+ return hasStreamedSomething;
+ }
+
+ /**
+ * Closes the current item.
+ */
+ protected abstract void closeItem();
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/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
new file mode 100644
index 0000000..b0a9ce9
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
@@ -0,0 +1,146 @@
+/*
+ * 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.request.batch;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchResponseItem;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract representation of a response item about a batch request.
+ */
+public abstract class AbstractODataBatchResponseItem implements ODataBatchResponseItem {
+
+ /**
+ * Logger.
+ */
+ protected static final Logger LOG = LoggerFactory.getLogger(ODataBatchResponseItem.class);
+
+ /**
+ * Expected OData responses for the current batch response item.
+ */
+ protected final Map<String, ODataResponse> responses = new HashMap<String, ODataResponse>();
+
+ /**
+ * Expected OData responses iterator.
+ */
+ protected Iterator<ODataResponse> expectedItemsIterator;
+
+ /**
+ * Changeset controller. Gives more information about the type of batch item.
+ */
+ private final boolean changeset;
+
+ /**
+ * Batch response line iterator.
+ */
+ protected ODataBatchLineIterator batchLineIterator;
+
+ /**
+ * Batch boundary.
+ */
+ protected String boundary;
+
+ /**
+ * Gives information about the batch response item status.
+ */
+ protected boolean closed = false;
+
+ /**
+ * Constructor.
+ *
+ * @param isChangeset 'TRUE' if the current batch response item is a changeset.
+ */
+ public AbstractODataBatchResponseItem(boolean isChangeset) {
+ this.changeset = isChangeset;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public void addResponse(final String contentId, final ODataResponse res) {
+ if (closed) {
+ throw new IllegalStateException("Invalid batch item because explicitely closed");
+ }
+ responses.put(contentId, res);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public void initFromBatch(final ODataBatchLineIterator batchLineIterator, final String boundary) {
+ if (closed) {
+ throw new IllegalStateException("Invalid batch item because explicitely closed");
+ }
+ LOG.debug("Init from batch - boundary '{}'", boundary);
+ this.batchLineIterator = batchLineIterator;
+ this.boundary = boundary;
+ }
+
+ /**
+ * Gets response about the given contentId.
+ *
+ * @param contentId response identifier (a specific contentId in case of changeset item).
+ * @return ODataResponse corresponding to the given contentId.
+ */
+ protected ODataResponse getResponse(final String contentId) {
+ if (closed) {
+ throw new IllegalStateException("Invalid batch item because explicitely closed");
+ }
+ return responses.get(contentId);
+ }
+
+ /**
+ * Gets OData responses iterator.
+ *
+ * @return OData responses iterator.
+ */
+ protected Iterator<ODataResponse> getResponseIterator() {
+ if (closed) {
+ throw new IllegalStateException("Invalid batch item because explicitely closed");
+ }
+ return responses.values().iterator();
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public final boolean isChangeset() {
+ return changeset;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public void close() {
+ for (ODataResponse response : responses.values()) {
+ response.close();
+ }
+ closed = true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java
new file mode 100644
index 0000000..c2377d8
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java
@@ -0,0 +1,89 @@
+/*
+ * 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.request.batch;
+
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+
+/**
+ * Utility class used to communicate batch info.
+ */
+public class ODataBatchController {
+
+ /**
+ * Batch validity.
+ */
+ private boolean validBatch = true;
+
+ /**
+ * Batch boundary.
+ */
+ private final String boundary;
+
+ /**
+ * Batch line iterator.
+ */
+ private final ODataBatchLineIterator batchLineIterator;
+
+ /**
+ * Constructor.
+ *
+ * @param batchLineIterator batch line iterator.
+ * @param boundary batch boundary.
+ */
+ public ODataBatchController(final ODataBatchLineIterator batchLineIterator, final String boundary) {
+ this.batchLineIterator = batchLineIterator;
+ this.boundary = boundary;
+ }
+
+ /**
+ * Checks if batch is valid.
+ *
+ * @return batch validity.
+ */
+ public boolean isValidBatch() {
+ return validBatch;
+ }
+
+ /**
+ * Sets batch validity.
+ *
+ * @param validBatch validity.
+ */
+ public void setValidBatch(final boolean validBatch) {
+ this.validBatch = validBatch;
+ }
+
+ /**
+ * Gest batch boundary.
+ *
+ * @return batch boundary.
+ */
+ public String getBoundary() {
+ return boundary;
+ }
+
+ /**
+ * Gest batch line iterator.
+ *
+ * @return batch line iterator.
+ */
+ public ODataBatchLineIterator getBatchLineIterator() {
+ return batchLineIterator;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java
new file mode 100644
index 0000000..34d621a
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java
@@ -0,0 +1,93 @@
+/*
+ * 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.request.batch;
+
+import org.apache.commons.io.LineIterator;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+
+/**
+ * Batch line iterator class.
+ */
+public class ODataBatchLineIteratorImpl implements ODataBatchLineIterator {
+
+ /**
+ * Stream line iterator.
+ */
+ private final LineIterator batchLineIterator;
+
+ /**
+ * Last cached line.
+ */
+ private String current;
+
+ /**
+ * Constructor.
+ *
+ * @param batchLineIterator stream line iterator.
+ */
+ public ODataBatchLineIteratorImpl(final LineIterator batchLineIterator) {
+ this.batchLineIterator = batchLineIterator;
+ this.current = null;
+ }
+
+ /**
+ * Checks if batch has next line.
+ *
+ * @return 'TRUE' if has next line; 'FALSE' otherwise.
+ */
+ @Override
+ public boolean hasNext() {
+ return batchLineIterator.hasNext();
+ }
+
+ /**
+ * Gets next line.
+ *
+ * @return next line.
+ */
+ @Override
+ public String next() {
+ return nextLine();
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public String nextLine() {
+ current = batchLineIterator.nextLine();
+ return current;
+ }
+
+ /**
+ * Unsupported operation.
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Unsupported operation");
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public String getCurrent() {
+ return current;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..054f467
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java
@@ -0,0 +1,255 @@
+/*
+ * 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.request.batch;
+
+import java.io.IOException;
+import java.io.PipedOutputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.ODataClient;
+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.ODataBatchRequestItem;
+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.response.ODataBatchResponse;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.AbstractODataStreamManager;
+import org.apache.olingo.client.core.communication.request.streamed.AbstractODataStreamedRequest;
+import org.apache.olingo.client.core.communication.response.ODataResponseImpl;
+import org.apache.olingo.client.core.communication.response.batch.ODataBatchResponseManager;
+
+/**
+ * This class implements a batch request.
+ */
+public class ODataBatchRequestImpl extends AbstractODataStreamedRequest<ODataBatchResponse, BatchStreamManager>
+ implements ODataBatchRequest {
+
+ /**
+ * Batch request boundary.
+ */
+ private final String boundary;
+
+ /**
+ * Expected batch response items.
+ */
+ private final List<ODataBatchResponseItem> expectedResItems = new ArrayList<ODataBatchResponseItem>();
+
+ /**
+ * Constructor.
+ *
+ * @param odataClient client instance getting this request
+ * @param uri batch request URI (http://serviceRoot/$batch)
+ */
+ ODataBatchRequestImpl(final ODataClient odataClient, final URI uri) {
+ super(odataClient, HttpMethod.POST, uri);
+
+ // create a random UUID value for boundary
+ boundary = "batch_" + UUID.randomUUID().toString();
+
+ // specify the contentType header
+ setContentType(ODataBatchConstants.MULTIPART_CONTENT_TYPE + ";" + ODataBatchConstants.BOUNDARY + "=" + boundary);
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ protected BatchStreamManager getStreamManager() {
+ if (streamManager == null) {
+ streamManager = new BatchStreamManagerImpl(this);
+ }
+ return (BatchStreamManager) streamManager;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public PipedOutputStream getOutputStream() {
+ return getStreamManager().getBodyStreamWriter();
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public ODataBatchRequestImpl rawAppend(final byte[] toBeStreamed) throws IOException {
+ getStreamManager().getBodyStreamWriter().write(toBeStreamed);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public ODataBatchRequestImpl rawAppend(final byte[] toBeStreamed, int off, int len) throws IOException {
+ getStreamManager().getBodyStreamWriter().write(toBeStreamed, off, len);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This operation is unsupported by a batch request.
+ */
+ @Override
+ public void batch(ODataBatchRequest req) {
+ throw new UnsupportedOperationException("A batch request is not batchable");
+ }
+
+ /**
+ * This class implements a response to a batch request.
+ *
+ * @see com.msopentech.odatajclient.engine.communication.request.ODataBatchRequest
+ */
+ private class ODataBatchResponseImpl extends ODataResponseImpl implements ODataBatchResponse {
+
+ /**
+ * Constructor.
+ *
+ * @param client HTTP client.
+ * @param res HTTP response.
+ */
+ private ODataBatchResponseImpl(final HttpClient client, final HttpResponse res) {
+ super(client, res);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Iterator<ODataBatchResponseItem> getBody() {
+ return new ODataBatchResponseManager(this, expectedResItems);
+ }
+ }
+
+ /**
+ * Batch request payload management.
+ */
+ public class BatchStreamManagerImpl extends AbstractODataStreamManager<ODataBatchResponse> {
+
+ /**
+ * Batch request current item.
+ */
+ private ODataBatchRequestItem currentItem = null;
+
+ /**
+ * batch request reference.
+ */
+ private final ODataBatchRequest req;
+
+ /**
+ * Private constructor.
+ *
+ * @param req batch request reference.
+ */
+ private BatchStreamManagerImpl(final ODataBatchRequest req) {
+ super(ODataBatchRequestImpl.this.futureWrapper);
+ this.req = req;
+ }
+
+ /**
+ * Gets a changeset batch item instance. A changeset can be submitted embedded into a batch request only.
+ *
+ * @return ODataChangeset instance.
+ */
+ public ODataChangeset addChangeset() {
+ closeCurrentItem();
+
+ // stream dash boundary
+ streamDashBoundary();
+
+ final ODataChangesetResponseItem expectedResItem = new ODataChangesetResponseItem();
+ expectedResItems.add(expectedResItem);
+
+ currentItem = new ODataChangesetImpl(req, expectedResItem);
+
+ return (ODataChangeset) currentItem;
+ }
+
+ /**
+ * Gets a retrieve batch item instance. A retrieve item can be submitted embedded into a batch request only.
+ *
+ * @return ODataRetrieve instance.
+ */
+ public ODataRetrieve addRetrieve() {
+ closeCurrentItem();
+
+ // stream dash boundary
+ streamDashBoundary();
+
+ final ODataRetrieveResponseItem expectedResItem = new ODataRetrieveResponseItem();
+ currentItem = new ODataRetrieveImpl(req, expectedResItem);
+
+ expectedResItems.add(expectedResItem);
+
+ return (ODataRetrieve) currentItem;
+ }
+
+ /**
+ * Close the current streamed item.
+ */
+ private void closeCurrentItem() {
+ if (currentItem != null) {
+ currentItem.close();
+ }
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ protected ODataBatchResponse getResponse(final long timeout, final TimeUnit unit) {
+ closeCurrentItem();
+ streamCloseDelimiter();
+ finalizeBody();
+ return new ODataBatchResponseImpl(httpClient, getHttpResponse(timeout, unit));
+ }
+
+ /**
+ * Streams dash boundary.
+ */
+ private void streamDashBoundary() {
+ // preamble
+ newLine();
+
+ // stream batch-boundary
+ stream(("--" + boundary).getBytes());
+ newLine();
+ }
+
+ /**
+ * Streams close delimiter.
+ */
+ private void streamCloseDelimiter() {
+ // stream close-delimiter
+ newLine();
+ stream(("--" + boundary + "--").getBytes());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java
new file mode 100644
index 0000000..a33f7cb
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java
@@ -0,0 +1,329 @@
+/*
+ * 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.request.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.LineIterator;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.Constants;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import org.apache.olingo.client.api.communication.request.ODataStreamer;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class for batch requests and responses.
+ */
+public class ODataBatchUtilities {
+
+ public static enum BatchItemType {
+
+ NONE,
+ CHANGESET,
+ RETRIEVE
+
+ }
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(ODataBatchUtilities.class);
+
+ /**
+ * Response line syntax.
+ */
+ private static final Pattern RESPONSE_PATTERN =
+ Pattern.compile("HTTP/\\d\\.\\d (\\d+) (.*)", Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Reads batch part taking source and delimiter (boundary) from given batch controller.
+ * <p>
+ * Usually used to consume/discard useless lines.
+ *
+ * @param batchController batch controller.
+ * @param checkCurrent if 'TRUE' the current line will be included into the delimiter verification.
+ * @return latest read line.
+ */
+ public static String readBatchPart(final ODataBatchController batchController, final boolean checkCurrent) {
+ return readBatchPart(batchController, null, -1, checkCurrent);
+ }
+
+ /**
+ * Reads the given number of line from the given batch wrapped into the batch controller.
+ * <p>
+ * Usually used to consume/discard useless lines.
+ *
+ * @param batchController batch controller.
+ * @param count number of batch line to be read.
+ * @return latest read line.
+ */
+ public static String readBatchPart(final ODataBatchController batchController, final int count) {
+ return readBatchPart(batchController, null, count, true);
+ }
+
+ /**
+ * Reads batch part taking source and delimiter (boundary) from given batch controller.
+ * <p>
+ * Usually used to read an entire batch part.
+ *
+ * @param batchController batch controller.
+ * @param os destination stream of batch part (null to discard).
+ * @param checkCurrent if 'TRUE' the current line will be included into the delimiter verification.
+ * @return latest read line.
+ */
+ public static String readBatchPart(
+ final ODataBatchController controller, final OutputStream os, final boolean checkCurrent) {
+
+ return readBatchPart(controller, os, -1, checkCurrent);
+ }
+
+ /**
+ * Reads batch part taking source and delimiter (boundary) from given batch controller.
+ * <p>
+ * Usually used to read an entire batch part.
+ *
+ * @param batchController batch controller.
+ * @param os destination stream of batch part (null to discard).
+ * @param count number of batch line to be read.
+ * @param checkCurrent if 'TRUE' the current line will be included into the delimiter verification.
+ * @return latest read line.
+ */
+ public static String readBatchPart(
+ final ODataBatchController controller, final OutputStream os, final int count, final boolean checkCurrent) {
+
+ String currentLine;
+
+ synchronized (controller.getBatchLineIterator()) {
+ currentLine = checkCurrent ? controller.getBatchLineIterator().getCurrent() : null;
+
+ if (count < 0) {
+ try {
+
+ boolean notEndLine = isNotEndLine(controller, currentLine);
+
+ while (controller.isValidBatch() && notEndLine && controller.getBatchLineIterator().hasNext()) {
+
+ currentLine = controller.getBatchLineIterator().nextLine();
+ LOG.debug("Read line '{}' (end-line '{}')", currentLine, controller.getBoundary());
+
+ notEndLine = isNotEndLine(controller, currentLine);
+
+ if (notEndLine && os != null) {
+ os.write(currentLine.getBytes(Constants.UTF8));
+ os.write(ODataStreamer.CRLF);
+ }
+ }
+
+ } catch (IOException e) {
+ LOG.error("Error reading batch part", e);
+ throw new IllegalStateException(e);
+ }
+
+ } else {
+ for (int i = 0;
+ controller.isValidBatch() && controller.getBatchLineIterator().hasNext() && i < count; i++) {
+ currentLine = controller.getBatchLineIterator().nextLine();
+ }
+ }
+ }
+
+ return currentLine;
+ }
+
+ /**
+ * Reads headers from the batch starting from the given position.
+ *
+ * @param iterator batch iterator.
+ * @return Map of header name in header values.
+ */
+ public static Map<String, Collection<String>> readHeaders(final ODataBatchLineIterator iterator) {
+ final Map<String, Collection<String>> target =
+ new TreeMap<String, Collection<String>>(String.CASE_INSENSITIVE_ORDER);
+
+ readHeaders(iterator, target);
+ return target;
+ }
+
+ /**
+ * Reads headers from the batch starting from the given position.
+ * <p>
+ * Retrieved headers will be added to the map given by target parameter.
+ *
+ * @param iterator batch iterator.
+ * @param target destination of the retrieved headers.
+ */
+ public static void readHeaders(
+ final ODataBatchLineIterator iterator, final Map<String, Collection<String>> target) {
+
+ try {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ readBatchPart(new ODataBatchController(iterator, null), baos, true);
+
+ final LineIterator headers = IOUtils.lineIterator(new ByteArrayInputStream(baos.toByteArray()), Constants.UTF8);
+ while (headers.hasNext()) {
+ final String line = headers.nextLine().trim();
+ if (StringUtils.isNotBlank(line)) {
+ addHeaderLine(line, target);
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Error retrieving headers", e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Parses and adds the given header line to the given target map.
+ *
+ * @param headerLine header line to be added.
+ * @param targetMap target map.
+ */
+ public static void addHeaderLine(final String headerLine, final Map<String, Collection<String>> targetMap) {
+ final int sep = headerLine.indexOf(':');
+ if (sep > 0 && sep < headerLine.length() - 1) {
+ final String key = headerLine.substring(0, sep).trim();
+ final Collection<String> value;
+ if (targetMap.containsKey(key)) {
+ value = targetMap.get(key);
+ } else {
+ value = new HashSet<String>();
+ targetMap.put(key, value);
+ }
+ value.add(headerLine.substring(sep + 1, headerLine.length()).trim());
+ }
+ }
+
+ /**
+ * Retrieved batch boundary from the given content-type header values.
+ *
+ * @param contentType content-types.
+ * @return batch boundary.
+ */
+ public static String getBoundaryFromHeader(final Collection<String> contentType) {
+ final String boundaryKey = ODataBatchConstants.BOUNDARY + "=";
+
+ if (contentType == null || contentType.isEmpty() || !contentType.toString().contains(boundaryKey)) {
+ throw new IllegalArgumentException("Invalid content type");
+ }
+
+ final String headerValue = contentType.toString();
+
+ final int start = headerValue.indexOf(boundaryKey) + boundaryKey.length();
+ int end = headerValue.indexOf(';', start);
+
+ if (end < 0) {
+ end = headerValue.indexOf(']', start);
+ }
+
+ final String res = headerValue.substring(start, end);
+ return res.startsWith("--") ? res : "--" + res;
+ }
+
+ /**
+ * Retrieves response line from the given position.
+ *
+ * @param iterator batch iterator.
+ * @return retrieved response line.
+ */
+ public static Map.Entry<Integer, String> readResponseLine(final ODataBatchLineIterator iterator) {
+ final String line = readBatchPart(new ODataBatchController(iterator, null), 1);
+ LOG.debug("Response line '{}'", line);
+
+ final Matcher matcher = RESPONSE_PATTERN.matcher(line.trim());
+
+ if (matcher.matches()) {
+ return new AbstractMap.SimpleEntry<Integer, String>(Integer.valueOf(matcher.group(1)), matcher.group(2));
+ }
+
+ throw new IllegalArgumentException("Invalid response line '" + line + "'");
+ }
+
+ /**
+ * Retrieves headers of the next batch item.
+ *
+ * @param iterator batch line iterator.
+ * @param boundary batch boundary.
+ * @return batch item headers.
+ */
+ public static Map<String, Collection<String>> nextItemHeaders(
+ final ODataBatchLineIterator iterator, final String boundary) {
+
+ final Map<String, Collection<String>> headers =
+ new TreeMap<String, Collection<String>>(String.CASE_INSENSITIVE_ORDER);
+
+ final String line = ODataBatchUtilities.readBatchPart(new ODataBatchController(iterator, boundary), true);
+
+ if (line != null && line.trim().equals(boundary)) {
+ ODataBatchUtilities.readHeaders(iterator, headers);
+ }
+
+ LOG.debug("Retrieved batch item headers {}", headers);
+ return headers;
+ }
+
+ /**
+ * Retrieves item type from item headers.
+ *
+ * @param headers batch item headers.
+ * @return batch item type.
+ */
+ public static BatchItemType getItemType(final Map<String, Collection<String>> headers) {
+
+ final BatchItemType nextItemType;
+
+ final String contentType = headers.containsKey(HeaderName.contentType.toString())
+ ? headers.get(HeaderName.contentType.toString()).toString() : StringUtils.EMPTY;
+
+ if (contentType.contains(ODataBatchConstants.MULTIPART_CONTENT_TYPE)) {
+ nextItemType = BatchItemType.CHANGESET;
+ } else if (contentType.contains(ODataBatchConstants.ITEM_CONTENT_TYPE)) {
+ nextItemType = BatchItemType.RETRIEVE;
+ } else {
+ nextItemType = BatchItemType.NONE;
+ }
+
+ LOG.debug("Retrieved next item type {}", nextItemType);
+ return nextItemType;
+ }
+
+ /**
+ * Checks if the given line is the expected end-line.
+ *
+ * @param controller batch controller.
+ * @param line line to be checked.
+ * @return 'TRUE' if the line is not the end-line; 'FALSE' otherwise.
+ */
+ private static boolean isNotEndLine(final ODataBatchController controller, final String line) {
+ return line == null
+ || (StringUtils.isBlank(controller.getBoundary()) && StringUtils.isNotBlank(line))
+ || (StringUtils.isNotBlank(controller.getBoundary()) && !line.startsWith(controller.getBoundary()));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java
new file mode 100644
index 0000000..4bb486e
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java
@@ -0,0 +1,128 @@
+/*
+ * 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.request.batch;
+
+import java.util.UUID;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataChangeset;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.ODataRequestImpl;
+
+/**
+ * Changeset wrapper for the corresponding batch item.
+ */
+public class ODataChangesetImpl extends AbstractODataBatchRequestItem
+ implements ODataChangeset {
+
+ /**
+ * ContentId.
+ */
+ private int contentId = 0;
+
+ /**
+ * Changeset boundary.
+ */
+ private final String boundary;
+
+ /**
+ * Expected changeset response items.
+ */
+ private final ODataChangesetResponseItem expectedResItem;
+
+ /**
+ * Constructor.
+ *
+ * @param req batch request.
+ * @param expectedResItem expected OData response items.
+ */
+ ODataChangesetImpl(final ODataBatchRequest req, final ODataChangesetResponseItem expectedResItem) {
+ super(req);
+ this.expectedResItem = expectedResItem;
+
+ // create a random UUID value for boundary
+ boundary = "changeset_" + UUID.randomUUID().toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getLastContentId() {
+ return contentId;
+ }
+
+ /**
+ * Close changeset item an send changeset request footer.
+ */
+ @Override
+ protected void closeItem() {
+ // stream close-delimiter
+ if (hasStreamedSomething) {
+ newLine();
+ stream(("--" + boundary + "--").getBytes());
+ newLine();
+ newLine();
+ }
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public ODataChangeset addRequest(final ODataBatchableRequest request) {
+ if (!isOpen()) {
+ throw new IllegalStateException("Current batch item is closed");
+ }
+
+ if (request.getMethod() == HttpMethod.GET) {
+ throw new IllegalArgumentException("Invalid request. GET method not allowed in changeset");
+ }
+
+ if (!hasStreamedSomething) {
+ stream((HeaderName.contentType.toString() + ": "
+ + ODataBatchConstants.MULTIPART_CONTENT_TYPE + ";boundary=" + boundary).getBytes());
+
+ newLine();
+ newLine();
+
+ hasStreamedSomething = true;
+ }
+
+ contentId++;
+
+ // preamble
+ newLine();
+
+ // stream batch-boundary
+ stream(("--" + boundary).getBytes());
+ newLine();
+
+ // stream the request
+ streamRequestHeader(request, contentId);
+
+ request.batch(req, String.valueOf(contentId));
+
+ // add request to the list
+ expectedResItem.addResponse(String.valueOf(contentId), ((ODataRequestImpl) request).getResponseTemplate());
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/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
new file mode 100644
index 0000000..4a077b5
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
@@ -0,0 +1,129 @@
+/*
+ * 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.request.batch;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+
+/**
+ * Changeset wrapper for the corresponding batch item.
+ */
+public class ODataChangesetResponseItem extends AbstractODataBatchResponseItem {
+
+ /**
+ * Last cached OData response.
+ */
+ private ODataResponse current = null;
+
+ /**
+ * Constructor.
+ */
+ public ODataChangesetResponseItem() {
+ 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();
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public ODataResponse next() {
+
+ if (current != null) {
+ current.close();
+ }
+
+ if (closed) {
+ throw new IllegalStateException("Invalid request - the item has been closed");
+ }
+
+ if (hasNext()) {
+ // consume item for condition above (like a counter ...)
+ expectedItemsIterator.next();
+ } else {
+ throw new NoSuchElementException("No item found");
+ }
+
+ final Map<String, Collection<String>> nextItemHeaders =
+ ODataBatchUtilities.nextItemHeaders(batchLineIterator, boundary);
+
+ if (nextItemHeaders.isEmpty()) {
+ throw new IllegalStateException("Expected item not 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);
+
+ Collection<String> contentId = nextItemHeaders.get(ODataBatchConstants.CHANGESET_CONTENT_ID_NAME);
+
+ if (contentId == null || contentId.isEmpty()) {
+ contentId = headers.get(ODataBatchConstants.CHANGESET_CONTENT_ID_NAME);
+
+ if (contentId == null || contentId.isEmpty()) {
+ throw new IllegalStateException("Content-ID is missing");
+ }
+ }
+
+ current = getResponse(contentId.iterator().next());
+
+ if (current == null) {
+ throw new IllegalStateException("Unexpected '" + contentId + "' item found");
+ }
+
+ current.initFromBatch(responseLine, headers, batchLineIterator, boundary);
+
+ if (current.getStatusCode() >= 400) {
+ // found error .... consume expeted items
+ while (expectedItemsIterator.hasNext()) {
+ expectedItemsIterator.next();
+ }
+ }
+
+ return current;
+ }
+
+ /**
+ * Unsupported operation.
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Not supported operation.");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java
new file mode 100644
index 0000000..fb348a4
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java
@@ -0,0 +1,81 @@
+/*
+ * 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.request.batch;
+
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataRetrieve;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.ODataRequestImpl;
+
+/**
+ * Retrieve request wrapper for the corresponding batch item.
+ */
+public class ODataRetrieveImpl extends AbstractODataBatchRequestItem
+ implements ODataRetrieve {
+
+ private final ODataRetrieveResponseItem expectedResItem;
+
+ /**
+ * Constructor.
+ *
+ * @param req batch request.
+ * @param expectedResItem expected batch response item.
+ */
+ ODataRetrieveImpl(final ODataBatchRequest req, final ODataRetrieveResponseItem expectedResItem) {
+ super(req);
+ this.expectedResItem = expectedResItem;
+ }
+
+ /**
+ * Close item.
+ */
+ @Override
+ protected void closeItem() {
+ // nop
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public ODataRetrieve setRequest(final ODataBatchableRequest request) {
+ if (!isOpen()) {
+ throw new IllegalStateException("Current batch item is closed");
+ }
+
+ if (((ODataRequestImpl) request).getMethod() != HttpMethod.GET) {
+ throw new IllegalArgumentException("Invalid request. Only GET method is allowed");
+ }
+
+ hasStreamedSomething = true;
+
+ // stream the request
+ streamRequestHeader(request);
+
+ // close before in order to avoid any further setRequest calls.
+ close();
+
+ // add request to the list
+ expectedResItem.addResponse(
+ ODataRetrieveResponseItem.RETRIEVE_CONTENT_ID, ((ODataRequestImpl) request).getResponseTemplate());
+
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/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
new file mode 100644
index 0000000..4a30e2e
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
@@ -0,0 +1,85 @@
+/*
+ * 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.request.batch;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+
+/**
+ * Retrieve response wrapper for the corresponding batch item.
+ */
+public class ODataRetrieveResponseItem extends AbstractODataBatchResponseItem {
+
+ public static final String RETRIEVE_CONTENT_ID = "__RETRIEVE__";
+
+ /**
+ * Constructor.
+ */
+ public ODataRetrieveResponseItem() {
+ super(false);
+ }
+
+ /**
+ * {@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);
+ }
+
+ /**
+ * Unsupported operation.
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Operation not supported.");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java
new file mode 100644
index 0000000..865de64
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.request.batch;
+
+import org.apache.olingo.client.api.ODataV3Client;
+import org.apache.olingo.client.api.communication.request.batch.V3BatchRequestFactory;
+
+public class V3BatchRequestFactoryImpl extends AbstractBatchRequestFactory
+ implements V3BatchRequestFactory {
+
+ private static final long serialVersionUID = -6271567229804128570L;
+
+ public V3BatchRequestFactoryImpl(final ODataV3Client client) {
+ super(client);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java
new file mode 100644
index 0000000..0e61d4c
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.request.batch;
+
+import org.apache.olingo.client.api.ODataV4Client;
+import org.apache.olingo.client.api.communication.request.batch.V4BatchRequestFactory;
+
+public class V4BatchRequestFactoryImpl extends AbstractBatchRequestFactory
+ implements V4BatchRequestFactory {
+
+ private static final long serialVersionUID = 788349446729208639L;
+
+ public V4BatchRequestFactoryImpl(final ODataV4Client client) {
+ super(client);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java
new file mode 100644
index 0000000..b5128e6
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java
@@ -0,0 +1,197 @@
+/*
+ * 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.request.cud;
+
+import java.net.URI;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.UpdateType;
+import org.apache.olingo.client.api.communication.request.cud.CUDRequestFactory;
+import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest;
+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.ODataLinkCreateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataLinkUpdateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataPropertyUpdateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataValueUpdateRequest;
+import org.apache.olingo.client.api.domain.ODataEntity;
+import org.apache.olingo.client.api.domain.ODataLink;
+import org.apache.olingo.client.api.domain.ODataPrimitiveValue;
+import org.apache.olingo.client.api.domain.ODataProperty;
+import org.apache.olingo.client.api.http.HttpMethod;
+
+public abstract class AbstractCUDRequestFactory implements CUDRequestFactory {
+
+ private static final long serialVersionUID = -2723641791198745990L;
+
+ protected final ODataClient client;
+
+ protected AbstractCUDRequestFactory(final ODataClient client) {
+ this.client = client;
+ }
+
+ @Override
+ public ODataEntityCreateRequest getEntityCreateRequest(final URI targetURI, final ODataEntity entity) {
+ return new ODataEntityCreateRequestImpl(client, targetURI, entity);
+ }
+
+ @Override
+ public ODataEntityUpdateRequest getEntityUpdateRequest(
+ final URI targetURI, final UpdateType type, final ODataEntity changes) {
+
+ final ODataEntityUpdateRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataEntityUpdateRequestImpl(client, HttpMethod.POST, targetURI, changes);
+ req.setXHTTPMethod(type.getMethod().name());
+ } else {
+ req = new ODataEntityUpdateRequestImpl(client, type.getMethod(), targetURI, changes);
+ }
+
+ return req;
+ }
+
+ @Override
+ public ODataEntityUpdateRequest getEntityUpdateRequest(final UpdateType type, final ODataEntity entity) {
+ if (entity.getEditLink() == null) {
+ throw new IllegalArgumentException("No edit link found");
+ }
+
+ final ODataEntityUpdateRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataEntityUpdateRequestImpl(client, HttpMethod.POST, entity.getEditLink(), entity);
+ req.setXHTTPMethod(type.getMethod().name());
+ } else {
+ req = new ODataEntityUpdateRequestImpl(client, type.getMethod(), entity.getEditLink(), entity);
+ }
+
+ return req;
+ }
+
+ @Override
+ public ODataValueUpdateRequest getValueUpdateRequest(
+ final URI targetURI, final UpdateType type, final ODataPrimitiveValue value) {
+
+ final ODataValueUpdateRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataValueUpdateRequestImpl(client, HttpMethod.POST, targetURI, value);
+ req.setXHTTPMethod(type.getMethod().name());
+ } else {
+ req = new ODataValueUpdateRequestImpl(client, type.getMethod(), targetURI, value);
+ }
+
+ return req;
+ }
+
+ @Override
+ public ODataPropertyUpdateRequest getPropertyPrimitiveValueUpdateRequest(
+ final URI targetURI, final ODataProperty property) {
+
+ if (!property.hasPrimitiveValue()) {
+ throw new IllegalArgumentException("A primitive value is required");
+ }
+
+ final ODataPropertyUpdateRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.POST, targetURI, property);
+ req.setXHTTPMethod(HttpMethod.PUT.name());
+ } else {
+ req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.PUT, targetURI, property);
+ }
+
+ return req;
+ }
+
+ @Override
+ public ODataPropertyUpdateRequest getPropertyComplexValueUpdateRequest(
+ final URI targetURI, final UpdateType type, final ODataProperty property) {
+
+ if (!property.hasComplexValue()) {
+ throw new IllegalArgumentException("A complex value is required");
+ }
+
+ final ODataPropertyUpdateRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.POST, targetURI, property);
+ req.setXHTTPMethod(type.getMethod().name());
+ } else {
+ req = new ODataPropertyUpdateRequestImpl(client, type.getMethod(), targetURI, property);
+ }
+
+ return req;
+ }
+
+ @Override
+ public ODataPropertyUpdateRequest getPropertyCollectionValueUpdateRequest(
+ final URI targetURI, final ODataProperty property) {
+
+ if (!property.hasCollectionValue()) {
+ throw new IllegalArgumentException("A collection value is required");
+ }
+
+ final ODataPropertyUpdateRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.POST, targetURI, property);
+ req.setXHTTPMethod(HttpMethod.PUT.name());
+ } else {
+ req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.PUT, targetURI, property);
+ }
+
+ return req;
+ }
+
+ @Override
+ public ODataLinkCreateRequest getLinkCreateRequest(final URI targetURI, final ODataLink link) {
+ return new ODataLinkCreateRequestImpl(client, targetURI, link);
+ }
+
+ @Override
+ public ODataLinkUpdateRequest getLinkUpdateRequest(
+ final URI targetURI, final UpdateType type, final ODataLink link) {
+
+ final ODataLinkUpdateRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataLinkUpdateRequestImpl(client, HttpMethod.POST, targetURI, link);
+ req.setXHTTPMethod(type.getMethod().name());
+ } else {
+ req = new ODataLinkUpdateRequestImpl(client, type.getMethod(), targetURI, link);
+ }
+
+ return req;
+ }
+
+ @Override
+ public ODataDeleteRequest getDeleteRequest(final URI targetURI) {
+ final ODataDeleteRequest req;
+
+ if (client.getConfiguration().isUseXHTTPMethod()) {
+ req = new ODataDeleteRequestImpl(client, HttpMethod.POST, targetURI);
+ req.setXHTTPMethod(HttpMethod.DELETE.name());
+ } else {
+ req = new ODataDeleteRequestImpl(client, HttpMethod.DELETE, targetURI);
+ }
+
+ return req;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java
new file mode 100644
index 0000000..3010867
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java
@@ -0,0 +1,93 @@
+/*
+ * 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.request.cud;
+
+import java.io.InputStream;
+import java.net.URI;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest;
+import org.apache.olingo.client.api.communication.response.ODataDeleteResponse;
+import org.apache.olingo.client.api.format.ODataPubFormat;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.AbstractODataBasicRequest;
+import org.apache.olingo.client.core.communication.response.ODataResponseImpl;
+
+/**
+ * This class implements an OData delete request.
+ */
+public class ODataDeleteRequestImpl extends AbstractODataBasicRequest<ODataDeleteResponse, ODataPubFormat>
+ implements ODataDeleteRequest, ODataBatchableRequest {
+
+ /**
+ * Constructor.
+ *
+ * @param odataClient client instance getting this request
+ * @param method HTTP method to be used
+ * @param uri URI of the entity to be deleted.
+ */
+ ODataDeleteRequestImpl(final ODataClient odataClient, final HttpMethod method, final URI uri) {
+ super(odataClient, ODataPubFormat.class, method, uri);
+ }
+
+ /**
+ * {@inheritDoc }
+ * <p>
+ * No payload: null will be returned.
+ */
+ @Override
+ protected InputStream getPayload() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc }
+ */
+ @Override
+ public ODataDeleteResponse execute() {
+ return new ODataDeleteResponseImpl(httpClient, doExecute());
+ }
+
+ /**
+ * Response class about an ODataDeleteRequest.
+ */
+ private class ODataDeleteResponseImpl extends ODataResponseImpl implements ODataDeleteResponse {
+
+ /**
+ * Constructor.
+ * <p>
+ * Just to create response templates to be initialized from batch.
+ */
+ private ODataDeleteResponseImpl() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param client HTTP client.
+ * @param res HTTP response.
+ */
+ private ODataDeleteResponseImpl(final HttpClient client, final HttpResponse res) {
+ super(client, res);
+ this.close();
+ }
+ }
+}