blob: 835feb639e8b3dc6d32182df4108e1dd2ffb7266 [file] [log] [blame]
/*
* 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 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.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DecompressingHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.olingo.client.api.ODataClient;
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.response.ODataResponse;
import org.apache.olingo.client.api.http.HttpClientException;
import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.apache.olingo.commons.api.http.HttpMethod;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Collection;
/**
* Abstract representation of an OData request. Get instance by using factories.
*
* @see org.apache.olingo.client.api.communication.request.cud.CUDRequestFactory
* @see org.apache.olingo.client.api.communication.request.batch.BatchRequestFactory
* @see org.apache.olingo.client.api.communication.request.invoke.InvokeRequestFactory
*/
public abstract class AbstractODataRequest extends AbstractRequest implements ODataRequest {
private static final byte[] CRLF = {13, 10};
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
protected final ODataClient odataClient;
/**
* OData request method.
*/
protected final HttpMethod method;
/**
* OData request header.
*/
protected final ODataHeaders odataHeaders;
/**
* Target URI.
*/
protected URI uri;
/**
* HTTP client.
*/
protected HttpClient httpClient;
/**
* HTTP request.
*/
protected HttpUriRequest request;
/**
* Constructor.
*
* @param odataClient client instance getting this request
* @param method HTTP request method. If configured X-HTTP-METHOD header will be used.
* @param uri OData request URI.
*/
protected AbstractODataRequest(final ODataClient odataClient, final HttpMethod method, final URI uri) {
super();
this.odataClient = odataClient;
this.method = method;
// initialize default headers
this.odataHeaders = odataClient.newVersionHeaders();
// target uri
this.uri = uri;
this.httpClient = getHttpClient(method, uri);
this.request = odataClient.getConfiguration().getHttpUriRequestFactory().create(this.method, uri);
}
public abstract ContentType getDefaultFormat();
@Override
public URI getURI() {
return uri;
}
@Override
public HttpUriRequest getHttpRequest() {
return request;
}
@Override
public void setURI(final URI uri) {
this.uri = uri;
this.httpClient = getHttpClient(method, uri);
this.request = odataClient.getConfiguration().getHttpUriRequestFactory().create(this.method, this.uri);
}
@Override
public Collection<String> getHeaderNames() {
return odataHeaders.getHeaderNames();
}
@Override
public String getHeader(final String name) {
return odataHeaders.getHeader(name);
}
@Override
public ODataRequest setAccept(final String value) {
odataHeaders.setHeader(HttpHeader.ACCEPT, value);
return this;
}
@Override
public ODataRequest setIfMatch(final String value) {
odataHeaders.setHeader(HttpHeader.IF_MATCH, value);
return this;
}
@Override
public ODataRequest setIfNoneMatch(final String value) {
odataHeaders.setHeader(HttpHeader.IF_NONE_MATCH, value);
return this;
}
@Override
public ODataRequest setPrefer(final String value) {
odataHeaders.setHeader(HttpHeader.PREFER, value);
return this;
}
@Override
public ODataRequest setXHTTPMethod(final String value) {
odataHeaders.setHeader(HttpHeader.X_HTTP_METHOD, value);
return this;
}
@Override
public ODataRequest setContentType(final String value) {
odataHeaders.setHeader(HttpHeader.CONTENT_TYPE, value);
return this;
}
@Override
public ODataRequest addCustomHeader(final String name, final String value) {
odataHeaders.setHeader(name, value);
return this;
}
@Override
public String getAccept() {
final String acceptHead = odataHeaders.getHeader(HttpHeader.ACCEPT);
return StringUtils.isBlank(acceptHead) ? getDefaultFormat().toContentTypeString() : acceptHead;
}
@Override
public String getIfMatch() {
return odataHeaders.getHeader(HttpHeader.IF_MATCH);
}
@Override
public String getIfNoneMatch() {
return odataHeaders.getHeader(HttpHeader.IF_NONE_MATCH);
}
@Override
public String getPrefer() {
return odataHeaders.getHeader(HttpHeader.PREFER);
}
@Override
public String getContentType() {
final String contentTypeHead = odataHeaders.getHeader(HttpHeader.CONTENT_TYPE);
return StringUtils.isBlank(contentTypeHead) ? getDefaultFormat().toContentTypeString() : contentTypeHead;
}
@Override
public HttpMethod getMethod() {
return method;
}
/**
* Gets request headers.
*
* @return request headers.
*/
public ODataHeaders getHeader() {
return odataHeaders;
}
@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(DEFAULT_CHARSET));
baos.write(CRLF);
// Set Content-Type and Accept headers with default values, if not yet set
if (StringUtils.isBlank(odataHeaders.getHeader(HttpHeader.CONTENT_TYPE))) {
setContentType(getContentType());
}
if (StringUtils.isBlank(odataHeaders.getHeader(HttpHeader.ACCEPT))) {
setAccept(getAccept());
}
for (String name : getHeaderNames()) {
final String value = getHeader(name);
if (StringUtils.isNotBlank(value)) {
baos.write((name + ": " + value).getBytes(DEFAULT_CHARSET));
baos.write(CRLF);
}
}
return baos.toByteArray();
} catch (IOException e) {
throw new IllegalStateException(e);
} finally {
IOUtils.closeQuietly(baos);
}
}
@Override
public InputStream rawExecute() {
HttpEntity httpEntity = null;
try {
httpEntity = doExecute().getEntity();
return httpEntity == null ? null : httpEntity.getContent();
} catch (IOException e) {
EntityUtils.consumeQuietly(httpEntity);
throw new HttpClientException(e);
} catch (RuntimeException e) {
this.request.abort();
EntityUtils.consumeQuietly(httpEntity);
throw new HttpClientException(e);
}
}
/**
* Builds the request and execute it.
*
* @return HttpReponse object.
*/
protected HttpResponse doExecute() {
checkRequest(odataClient, request);
// Set Content-Type and Accept headers with default values, if not yet set
if (StringUtils.isBlank(odataHeaders.getHeader(HttpHeader.CONTENT_TYPE))) {
setContentType(getContentType());
}
if (StringUtils.isBlank(odataHeaders.getHeader(HttpHeader.ACCEPT))) {
setAccept(getAccept());
}
// Add header for KeyAsSegment management
if (odataClient.getConfiguration().isKeyAsSegment()) {
addCustomHeader("DataServiceUrlConventions", odataClient.newPreferences().keyAsSegment());
}
// Add all available headers
for (String key : getHeaderNames()) {
request.addHeader(key, odataHeaders.getHeader(key));
}
if (LOG.isDebugEnabled()) {
for (Header header : request.getAllHeaders()) {
LOG.debug("HTTP header being sent: " + header);
}
}
HttpResponse response;
try {
response = httpClient.execute(request);
} catch (IOException e) {
throw new HttpClientException(request.getURI().toASCIIString(), e);
} catch (RuntimeException e) {
request.abort();
throw new HttpClientException(request.getURI().toASCIIString(), e);
}
try {
checkResponse(odataClient, response, getAccept());
} catch (ODataRuntimeException e) {
closeHttpResponse(response);
odataClient.getConfiguration().getHttpClientFactory().close(httpClient);
throw e;
}
return response;
}
private void closeHttpResponse(HttpResponse response) {
if (response instanceof CloseableHttpResponse) {
try {
((CloseableHttpResponse) response).close();
} catch (IOException e) {
LOG.warn("Unable to close response: {}", response, e);
}
}
}
/**
* Gets an empty response that can be initialized by a stream.
* <br/>
* This method has to be used to build response items about a batch request.
*
* @param <V> ODataResponse 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(), ODataClient.class, HttpClient.class, HttpResponse.class);
constructor.setAccessible(true);
return (V) constructor.newInstance(this, odataClient, httpClient, null);
} catch (Exception e) {
LOG.error("Error retrieving response class template instance", e);
}
}
}
throw new IllegalStateException("No response class template has been found");
}
private HttpClient getHttpClient(final HttpMethod method, final URI uri) {
HttpClient client = odataClient.getConfiguration().getHttpClientFactory().create(method, uri);
if (odataClient.getConfiguration().isGzipCompression()) {
client = new DecompressingHttpClient(client);
}
return client;
}
}