OLINGO-1577 Determine content type from HTTP response

This commit is contained in:
Daniel Heid 2022-11-11 12:03:14 +01:00 committed by Michael Bolz
parent dba911f333
commit 532719421a
6 changed files with 177 additions and 56 deletions

View File

@ -18,11 +18,6 @@
*/ */
package org.apache.olingo.ext.proxy.commons; package org.apache.olingo.ext.proxy.commons;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.olingo.client.api.communication.ODataServerErrorException; import org.apache.olingo.client.api.communication.ODataServerErrorException;
import org.apache.olingo.client.api.communication.request.ODataBatchableRequest; import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
import org.apache.olingo.client.api.communication.request.ODataRequest; import org.apache.olingo.client.api.communication.request.ODataRequest;
@ -37,10 +32,16 @@ import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResp
import org.apache.olingo.client.api.communication.response.ODataResponse; import org.apache.olingo.client.api.communication.response.ODataResponse;
import org.apache.olingo.client.core.communication.header.ODataErrorResponseChecker; import org.apache.olingo.client.core.communication.header.ODataErrorResponseChecker;
import org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem; import org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.ext.proxy.AbstractService; import org.apache.olingo.ext.proxy.AbstractService;
import org.apache.olingo.ext.proxy.api.ODataFlushException; import org.apache.olingo.ext.proxy.api.ODataFlushException;
import org.apache.olingo.ext.proxy.api.ODataResponseError; import org.apache.olingo.ext.proxy.api.ODataResponseError;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/** /**
* {@link org.apache.olingo.ext.proxy.api.PersistenceManager} implementation using OData batch requests to implement * {@link org.apache.olingo.ext.proxy.api.PersistenceManager} implementation using OData batch requests to implement
* high-level user transactions: all read-write operations will be packed in a batch request to the OData service when * high-level user transactions: all read-write operations will be packed in a batch request to the OData service when
@ -50,6 +51,8 @@ public class TransactionalPersistenceManagerImpl extends AbstractPersistenceMana
private static final long serialVersionUID = -3320312269235907501L; private static final long serialVersionUID = -3320312269235907501L;
private static final ContentType DEFAULT_CONTENT_TYPE = ContentType.JSON;
public TransactionalPersistenceManagerImpl(final AbstractService<?> factory) { public TransactionalPersistenceManagerImpl(final AbstractService<?> factory) {
super(factory); super(factory);
} }
@ -103,11 +106,12 @@ public class TransactionalPersistenceManagerImpl extends AbstractPersistenceMana
final ODataResponse res = chgres.next(); final ODataResponse res = chgres.next();
if (res.getStatusCode() >= 400) { if (res.getStatusCode() >= 400) {
ContentType contentType = ContentType.fromAcceptHeader(request.getAccept());
errors.add(new ODataResponseError(ODataErrorResponseChecker.checkResponse( errors.add(new ODataResponseError(ODataErrorResponseChecker.checkResponse(
service.getClient(), service.getClient(),
new ResponseStatusLine(res), new ResponseStatusLine(res),
res.getRawResponse(), res.getRawResponse(),
((ODataRequest) request).getAccept()), index, requests.get(index))); contentType), index, requests.get(index)));
if (!service.getClient().getConfiguration().isContinueOnError()) { if (!service.getClient().getConfiguration().isContinueOnError()) {
throw new ODataFlushException(response.getStatusCode(), errors); throw new ODataFlushException(response.getStatusCode(), errors);
} }

View File

@ -18,11 +18,6 @@
*/ */
package org.apache.olingo.client.core.communication.header; package org.apache.olingo.client.core.communication.header;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.StatusLine; import org.apache.http.StatusLine;
import org.apache.olingo.client.api.ODataClient; import org.apache.olingo.client.api.ODataClient;
@ -32,10 +27,14 @@ import org.apache.olingo.client.api.serialization.ODataDeserializerException;
import org.apache.olingo.commons.api.ex.ODataError; import org.apache.olingo.commons.api.ex.ODataError;
import org.apache.olingo.commons.api.ex.ODataRuntimeException; import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.format.ContentType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
public final class ODataErrorResponseChecker { public final class ODataErrorResponseChecker {
protected static final Logger LOG = LoggerFactory.getLogger(ODataErrorResponseChecker.class); protected static final Logger LOG = LoggerFactory.getLogger(ODataErrorResponseChecker.class);
@ -49,7 +48,7 @@ public final class ODataErrorResponseChecker {
public static ODataRuntimeException checkResponse( public static ODataRuntimeException checkResponse(
final ODataClient odataClient, final StatusLine statusLine, final InputStream entity, final ODataClient odataClient, final StatusLine statusLine, final InputStream entity,
final String accept) { final ContentType contentType) {
ODataRuntimeException result; ODataRuntimeException result;
InputStream entityForException = null; InputStream entityForException = null;
@ -57,10 +56,9 @@ public final class ODataErrorResponseChecker {
if (entity == null) { if (entity == null) {
result = new ODataClientErrorException(statusLine); result = new ODataClientErrorException(statusLine);
} else { } else {
final ContentType contentType = accept.contains("xml") ? ContentType.APPLICATION_ATOM_XML : ContentType.JSON;
ODataError error = new ODataError(); ODataError error = new ODataError();
if (!accept.contains("text/plain")) { if (!contentType.isCompatible(ContentType.TEXT_PLAIN)) {
try { try {
byte[] bytes = IOUtils.toByteArray(entity); byte[] bytes = IOUtils.toByteArray(entity);
entityForException = new ByteArrayInputStream(bytes); entityForException = new ByteArrayInputStream(bytes);

View File

@ -15,9 +15,7 @@
*/ */
package org.apache.olingo.client.core.communication.request; package org.apache.olingo.client.core.communication.request;
import java.io.IOException; import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.olingo.client.api.EdmEnabledODataClient; import org.apache.olingo.client.api.EdmEnabledODataClient;
@ -25,16 +23,18 @@ import org.apache.olingo.client.api.ODataClient;
import org.apache.olingo.client.api.communication.ODataClientErrorException; import org.apache.olingo.client.api.communication.ODataClientErrorException;
import org.apache.olingo.client.core.communication.header.ODataErrorResponseChecker; import org.apache.olingo.client.core.communication.header.ODataErrorResponseChecker;
import org.apache.olingo.commons.api.ex.ODataRuntimeException; import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.api.format.ContentType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
public abstract class AbstractRequest { public abstract class AbstractRequest {
/** /**
* Logger. * Logger.
*/ */
protected static final Logger LOG = LoggerFactory.getLogger(AbstractRequest.class); protected static final Logger LOG = LoggerFactory.getLogger(AbstractRequest.class);
private static final String TEXT_CONTENT_TYPE = "text/plain";
protected void checkRequest(final ODataClient odataClient, final HttpUriRequest request) { protected void checkRequest(final ODataClient odataClient, final HttpUriRequest request) {
// If using and Edm enabled client, checks that the cached service root matches the request URI // If using and Edm enabled client, checks that the cached service root matches the request URI
@ -53,14 +53,13 @@ public abstract class AbstractRequest {
final ODataClient odataClient, final HttpResponse response, final String accept) { final ODataClient odataClient, final HttpResponse response, final String accept) {
if (response.getStatusLine().getStatusCode() >= 400) { if (response.getStatusLine().getStatusCode() >= 400) {
Header contentTypeHeader = response.getEntity() != null ? response.getEntity().getContentType() : null; final ContentType contentType = determineContentType(response, accept);
try { try {
final ODataRuntimeException exception = ODataErrorResponseChecker.checkResponse( final ODataRuntimeException exception = ODataErrorResponseChecker.checkResponse(
odataClient, odataClient,
response.getStatusLine(), response.getStatusLine(),
response.getEntity() == null ? null : response.getEntity().getContent(), response.getEntity() == null ? null : response.getEntity().getContent(),
(contentTypeHeader != null && contentType);
contentTypeHeader.getValue().contains(TEXT_CONTENT_TYPE)) ? TEXT_CONTENT_TYPE : accept);
if (exception != null) { if (exception != null) {
if (exception instanceof ODataClientErrorException) { if (exception instanceof ODataClientErrorException) {
((ODataClientErrorException)exception).setHeaderInfo(response.getAllHeaders()); ((ODataClientErrorException)exception).setHeaderInfo(response.getAllHeaders());
@ -73,4 +72,18 @@ public abstract class AbstractRequest {
} }
} }
} }
private static ContentType determineContentType(HttpResponse response, String accept) {
if (response.getEntity() == null
|| response.getEntity().getContentType() == null
|| StringUtils.isBlank(response.getEntity().getContentType().getValue())) {
return ContentType.fromAcceptHeader(accept);
}
try {
return ContentType.create(response.getEntity().getContentType().getValue());
} catch (Exception exception) {
return ContentType.JSON;
}
}
} }

View File

@ -18,17 +18,6 @@
*/ */
package org.apache.olingo.client.core; package org.apache.olingo.client.core;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Map;
import org.apache.http.StatusLine; import org.apache.http.StatusLine;
import org.apache.olingo.client.api.ODataClient; import org.apache.olingo.client.api.ODataClient;
import org.apache.olingo.client.api.communication.ODataClientErrorException; import org.apache.olingo.client.api.communication.ODataClientErrorException;
@ -41,6 +30,17 @@ import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.format.ContentType;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ErrorTest extends AbstractTest { public class ErrorTest extends AbstractTest {
private ODataError error(final String name, final ContentType contentType) throws ODataDeserializerException { private ODataError error(final String name, final ContentType contentType) throws ODataDeserializerException {
@ -91,7 +91,7 @@ public class ErrorTest extends AbstractTest {
when(statusLine.toString()).thenReturn("Internal Server Error"); when(statusLine.toString()).thenReturn("Internal Server Error");
ODataClientErrorException exp = (ODataClientErrorException) ODataErrorResponseChecker. ODataClientErrorException exp = (ODataClientErrorException) ODataErrorResponseChecker.
checkResponse(odataClient, statusLine, entity, "Json"); checkResponse(odataClient, statusLine, entity, ContentType.JSON);
assertTrue(exp.getMessage().contains("(500) Internal Server Error")); assertTrue(exp.getMessage().contains("(500) Internal Server Error"));
ODataError error = exp.getODataError(); ODataError error = exp.getODataError();
assertTrue(error.getMessage().startsWith("Internal Server Error")); assertTrue(error.getMessage().startsWith("Internal Server Error"));
@ -112,7 +112,7 @@ public class ErrorTest extends AbstractTest {
when(statusLine.toString()).thenReturn("Internal Server Error"); when(statusLine.toString()).thenReturn("Internal Server Error");
ODataServerErrorException exp = (ODataServerErrorException) ODataErrorResponseChecker. ODataServerErrorException exp = (ODataServerErrorException) ODataErrorResponseChecker.
checkResponse(odataClient, statusLine, entity, "Json"); checkResponse(odataClient, statusLine, entity, ContentType.JSON);
assertTrue(exp.getMessage().startsWith("Internal Server Error")); assertTrue(exp.getMessage().startsWith("Internal Server Error"));
} }
@ -124,7 +124,7 @@ public class ErrorTest extends AbstractTest {
when(statusLine.toString()).thenReturn("Internal Server Error"); when(statusLine.toString()).thenReturn("Internal Server Error");
ODataRuntimeException exp = ODataErrorResponseChecker. ODataRuntimeException exp = ODataErrorResponseChecker.
checkResponse(odataClient, statusLine, null, "Json"); checkResponse(odataClient, statusLine, null, ContentType.JSON);
assertTrue(exp.getMessage().startsWith("Internal Server Error")); assertTrue(exp.getMessage().startsWith("Internal Server Error"));
} }
@ -138,7 +138,7 @@ public class ErrorTest extends AbstractTest {
when(statusLine.getReasonPhrase()).thenReturn("Forbidden"); when(statusLine.getReasonPhrase()).thenReturn("Forbidden");
ODataClientErrorException exp = (ODataClientErrorException) ODataErrorResponseChecker. ODataClientErrorException exp = (ODataClientErrorException) ODataErrorResponseChecker.
checkResponse(odataClient, statusLine, entity, "text/plain"); checkResponse(odataClient, statusLine, entity, ContentType.TEXT_PLAIN);
assertEquals(exp.getStatusLine().getStatusCode(), 403); assertEquals(exp.getStatusLine().getStatusCode(), 403);
ODataError error = exp.getODataError(); ODataError error = exp.getODataError();
assertTrue(error.getMessage().equals("CSRF Validation Exception")); assertTrue(error.getMessage().equals("CSRF Validation Exception"));

View File

@ -204,6 +204,39 @@ public final class ContentType {
TypeUtil.parseParameters(params, parameters); TypeUtil.parseParameters(params, parameters);
} }
/**
* Uses the first MIME type from the accept header to determine the content type.
*
* @param accept The accept header content, e.g. text/html,application/xhtml+xml,application/xml, may be null
* @return The content type according to the accept header's first MIME type. Defaults to application/json if the
* accept header does not contain valid information. Never null.
*/
public static ContentType fromAcceptHeader(String accept) {
if (accept == null || accept.trim().isEmpty()) {
return JSON;
}
String acceptType = accept.split(",")[0];
if (acceptType == null || acceptType.trim().isEmpty()) {
return JSON;
}
int semicolonIndex = acceptType.indexOf(';');
String cleanedAcceptType;
if (semicolonIndex == -1) {
cleanedAcceptType = acceptType.trim();
} else {
cleanedAcceptType = acceptType.trim().substring(0, semicolonIndex).trim();
if (cleanedAcceptType.trim().isEmpty()) {
return JSON;
}
}
try {
return create(cleanedAcceptType);
} catch (Exception exception) {
return accept.contains("xml") ? APPLICATION_ATOM_XML : JSON;
}
}
/** Gets the type of this content type. */ /** Gets the type of this content type. */
public String getType() { public String getType() {
return type; return type;

View File

@ -18,6 +18,8 @@
*/ */
package org.apache.olingo.commons.api.format; package org.apache.olingo.commons.api.format;
import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
@ -26,8 +28,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import org.junit.Test;
public class ContentTypeTest { public class ContentTypeTest {
@Test @Test
@ -120,4 +120,77 @@ public class ContentTypeTest {
assertNotNull(e); assertNotNull(e);
} }
} }
@Test
public void firstFromValidMultiAcceptHeader() {
ContentType contentType = ContentType.fromAcceptHeader("application/xml ; q=0.9, application/xhtml+xml,*/*;q=0.8 ");
assertEquals(ContentType.APPLICATION_XML, contentType);
}
@Test
public void missingMimeTypefromAcceptHeaderDefaultsToJson() {
ContentType contentType = ContentType.fromAcceptHeader(";q=0.9");
assertEquals(ContentType.JSON, contentType);
}
@Test
public void fromValidSingleAcceptHeader() {
ContentType contentType = ContentType.fromAcceptHeader("application/xml");
assertEquals(ContentType.APPLICATION_XML, contentType);
}
@Test
public void fromAcceptHeaderDefaultsToJsonIfNull() {
ContentType contentType = ContentType.fromAcceptHeader(null);
assertEquals(ContentType.JSON, contentType);
}
@Test
public void fromAcceptHeaderDefaultsToJsonIfEmpty() {
ContentType contentType = ContentType.fromAcceptHeader("");
assertEquals(ContentType.JSON, contentType);
}
@Test
public void fromAcceptHeaderDefaultsToJsonIfBlank() {
ContentType contentType = ContentType.fromAcceptHeader(" ");
assertEquals(ContentType.JSON, contentType);
}
@Test
public void fromAcceptHeaderDefaultsToJsonIfInvalid() {
ContentType contentType = ContentType.fromAcceptHeader("invalid");
assertEquals(ContentType.JSON, contentType);
}
@Test
public void fromAcceptHeaderDefaultsToJsonIfFirstValueBlank() {
ContentType contentType = ContentType.fromAcceptHeader(" ,text/plain ");
assertEquals(ContentType.JSON, contentType);
}
} }