blob: 3d2b78f1efb12535f5c64d99468b831d46f52570 [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.odata2.core.batch;
import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
import org.apache.olingo.odata2.api.commons.HttpHeaders;
import org.apache.olingo.odata2.api.processor.ODataResponse;
import org.apache.olingo.odata2.core.commons.ContentType;
import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.UUID;
public class BatchHelper {
public static final String BINARY_ENCODING = "binary";
public static final String UTF8_ENCODING = "UTF-8";
public static final String ISO_ENCODING = "ISO-8859-1";
private static String DEFAULT_ENCODING = "ISO-8859-1";
public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
public static final String HTTP_CONTENT_ID = "Content-Id";
public static final String MIME_HEADER_CONTENT_ID = "MimeHeader-ContentId";
public static final String REQUEST_HEADER_CONTENT_ID = "RequestHeader-ContentId";
protected static Charset DEFAULT_CHARSET = Charset.forName(DEFAULT_ENCODING);
protected static String generateBoundary(final String value) {
return value + "_" + UUID.randomUUID().toString();
}
protected static byte[] getBytes(final String body) {
try {
return body.getBytes(DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
throw new ODataRuntimeException(e);
}
}
public static Charset extractCharset(Map<String, String> headers) {
String contentType = null;
for (Map.Entry<String, String> s : headers.entrySet()) {
if(s.getKey().equalsIgnoreCase(HttpHeaders.CONTENT_TYPE)) {
contentType = s.getValue();
break;
}
}
return getCharset(contentType);
}
public static Charset extractCharset(ContentType contentType) {
if (contentType != null) {
final String charsetValue = contentType.getParameters().get(ContentType.PARAMETER_CHARSET);
if (charsetValue == null) {
if (contentType.isCompatible(ContentType.APPLICATION_JSON) || contentType.getSubtype().contains("xml")) {
setDefaultValues(UTF8_ENCODING);
return Charset.forName(UTF8_ENCODING);
}
} else {
setDefaultValues(charsetValue);
return Charset.forName(charsetValue);
}
}
setDefaultValues(ISO_ENCODING);
return Charset.forName(ISO_ENCODING);
}
private static Charset getCharset(String contentType) {
ContentType ct = ContentType.parse(contentType);
if(ct != null) {
String charsetString = ct.getParameters().get(ContentType.PARAMETER_CHARSET);
if (charsetString != null && Charset.isSupported(charsetString)) {
setDefaultValues(charsetString);
return Charset.forName(charsetString);
} else {
if (ct.isCompatible(ContentType.APPLICATION_JSON) || ct.getSubtype().contains("xml")) {
setDefaultValues(UTF8_ENCODING);
return Charset.forName(UTF8_ENCODING);
}
}
}
setDefaultValues(ISO_ENCODING);
return Charset.forName(ISO_ENCODING);
}
private static void setDefaultValues(String contentType) {
DEFAULT_CHARSET = Charset.forName(contentType);
DEFAULT_ENCODING = contentType;
}
/**
* Builder class to create the body and the header.
*/
static class BodyBuilder {
public static final int DEFAULT_SIZE = 8192;
private final Charset CHARSET_ISO_8859_1 = Charset.forName("iso-8859-1");
private ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_SIZE);
private boolean isClosed = false;
public byte[] getContent() {
isClosed = true;
byte[] tmp = new byte[buffer.position()];
buffer.flip();
buffer.get(tmp, 0, buffer.limit());
return tmp;
}
public InputStream getContentAsStream() {
return new ByteArrayInputStream(getContent());
}
public String getContentAsString(Charset charset) {
return new String(getContent(), charset);
}
public int getLength() {
return (buffer.limit() > buffer.position() ? buffer.limit(): buffer.position());
}
public BodyBuilder append(String string) {
byte [] b = string.getBytes(DEFAULT_CHARSET);
put(b);
return this;
}
private void put(byte[] b) {
if(isClosed) {
throw new RuntimeException("BodyBuilder is closed.");
}
if(buffer.remaining() < b.length) {
buffer.flip();
int newSize = (buffer.limit() * 2) + b.length;
ByteBuffer tmp = ByteBuffer.allocate(newSize);
tmp.put(buffer);
buffer = tmp;
}
buffer.put(b);
}
public BodyBuilder append(int statusCode) {
return append(String.valueOf(statusCode));
}
public BodyBuilder append(Body body) {
put(body.getContent());
return this;
}
public String toString() {
return new String(buffer.array(), 0, buffer.position());
}
/**
* Fetch the calibrated length in case of binary data.
* Since after applying the charset the content length changes.
* If the previously generated length is sent back then the batch response
* body is seen truncated
* @param batchResponseBody
* @return
*/
public int calculateLength(Object batchResponseBody) {
if (batchResponseBody != null) {
if (batchResponseBody instanceof String) {
if (DEFAULT_ENCODING.equalsIgnoreCase(ISO_ENCODING)) {
try {
return ((String) batchResponseBody).getBytes(UTF8_ENCODING).length;
} catch (UnsupportedEncodingException e) {
throw new ODataRuntimeException(e);
}
} else {
return getLength();
}
} else {
return getLength();
}
}
return getLength();
}
}
/**
* Body part which is read and stored as bytes (no charset conversion).
*/
static class Body {
private static final int BUFFER_SIZE = 8192;
public static final byte[] EMPTY_BYTES = new byte[0];
private final byte[] content;
public Body(BatchChangeSetPart response) {
this.content = getBody(response);
}
public Body(ODataResponse response) {
this.content = getBody(response);
}
public Body() {
this.content = EMPTY_BYTES;
setDefaultValues(ISO_ENCODING);
}
public int getLength() {
return content.length;
}
public byte[] getContent() {
return content;
}
public boolean isEmpty() {
return content.length == 0;
}
private byte[] getBody(final BatchChangeSetPart response) {
if (response == null || response.getBodyAsBytes() == null) {
return EMPTY_BYTES;
}
return response.getBodyAsBytes();
}
private byte[] getBody(final ODataResponse response) {
if (response == null) {
return EMPTY_BYTES;
}
Object entity = response.getEntity();
if(entity == null) {
return EMPTY_BYTES;
} else if(entity instanceof InputStream) {
ReadableByteChannel ic = null;
WritableByteChannel oc = null;
try {
extractCharset(ContentType.parse(response.getHeader("Content-Type")));
ByteArrayOutputStream output = new ByteArrayOutputStream();
ByteBuffer inBuffer = ByteBuffer.allocate(BUFFER_SIZE);
ic = Channels.newChannel((InputStream) entity);
oc = Channels.newChannel(output);
while (ic.read(inBuffer) > 0) {
inBuffer.flip();
oc.write(inBuffer);
inBuffer.rewind();
}
return output.toByteArray();
} catch (IOException e) {
throw new ODataRuntimeException("Error on reading request content");
} finally {
try {
if (ic != null) {
ic.close();
}
} catch (IOException e) {
throw new ODataRuntimeException("Error closing the Readable Byte Channel", e);
}
try {
if (oc != null) {
oc.close();
}
} catch (IOException e) {
throw new ODataRuntimeException("Error closing the Writable Byte Channel", e);
}
}
} else if (entity instanceof byte[]) {
setDefaultValues(ISO_ENCODING);
return (byte[]) entity;
} else if(entity instanceof String) {
setDefaultValues(UTF8_ENCODING);
return ((String) entity).getBytes(DEFAULT_CHARSET);
} else {
throw new ODataRuntimeException("Error on reading request content for entity type:" + entity.getClass());
}
}
}
}