From ab6fd5e2b8b2b01731c2ba771763e6b126baa28b Mon Sep 17 00:00:00 2001 From: Michael Bolz Date: Tue, 17 Jun 2014 14:53:44 +0200 Subject: [PATCH] [OLINGO-317] Refactored ContentType --- .../commons/AbstractPersistenceManager.java | 6 +- .../commons/EntityInvocationHandler.java | 12 +- .../TransactionalPersistenceManagerImpl.java | 3 +- .../apache/olingo/fit/AbstractServices.java | 8 +- .../fit/v3/EntityRetrieveTestITCase.java | 2 +- .../olingo/fit/v3/EntitySetTestITCase.java | 4 +- .../olingo/fit/v3/MediaEntityTestITCase.java | 2 +- .../olingo/fit/v3/PropertyTestITCase.java | 2 +- .../olingo/fit/v3/QueryOptionsTestITCase.java | 2 +- .../olingo/fit/v4/AuthBatchTestITCase.java | 6 +- .../apache/olingo/fit/v4/BatchTestITCase.java | 2 +- .../olingo/fit/v4/ConformanceTestITCase.java | 6 +- .../fit/v4/EntityRetrieveTestITCase.java | 2 +- .../olingo/fit/v4/EntitySetTestITCase.java | 4 +- .../fit/v4/PropertyValueTestITCase.java | 4 +- .../olingo/fit/v4/QueryOptionsTestITCase.java | 2 +- .../client/api/CommonConfiguration.java | 9 +- .../olingo/client/core/Configuration.java | 6 +- .../request/AbstractODataBasicRequest.java | 2 +- .../request/AbstractODataRequest.java | 9 +- .../request/batch/ODataBatchUtilities.java | 6 +- .../batch/v3/ODataBatchRequestImpl.java | 2 +- .../batch/v4/ODataBatchRequestImpl.java | 2 +- .../invoke/AbstractODataInvokeRequest.java | 6 +- .../AbstractODataStreamedEntityRequest.java | 4 +- .../commons/api/domain/ODataLinkType.java | 8 +- .../commons/api/format/ContentType.java | 707 +++++++++++++++++- .../commons/api/format/ODataFormat.java | 137 ++-- .../core/serialization/AtomSerializer.java | 2 +- .../tecsvc/processor/SampleJsonProcessor.java | 4 +- 30 files changed, 832 insertions(+), 139 deletions(-) diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java index 492c7517d..d53e68ea2 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java @@ -354,9 +354,9 @@ abstract class AbstractPersistenceManager implements PersistenceManager { final ODataMediaEntityUpdateRequest req = factory.getClient().getCUDRequestFactory().getMediaEntityUpdateRequest(uri, input); - req.setContentType(StringUtils.isBlank(handler.getEntity().getMediaContentType()) - ? ODataFormat.WILDCARD.toString() - : ODataFormat.fromString(handler.getEntity().getMediaContentType()).toString()); + if (StringUtils.isNotBlank(handler.getEntity().getMediaContentType())) { + req.setContentType(ODataFormat.fromString(handler.getEntity().getMediaContentType()).toString()); + } if (StringUtils.isNotBlank(handler.getETag())) { req.setIfMatch(handler.getETag()); diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java index aa8192185..531353cad 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java @@ -320,13 +320,11 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler && typeRef.getAnnotation(EntityType.class).hasStream() && contentSource != null) { - final String contentType = - StringUtils.isBlank(getEntity().getMediaContentType()) ? "*/*" : getEntity().getMediaContentType(); - - final ODataMediaRequest retrieveReq = getClient().getRetrieveRequestFactory(). - getMediaEntityRequest(contentSource); - retrieveReq.setFormat(ODataFormat.fromString(contentType)); - + final ODataMediaRequest retrieveReq = getClient().getRetrieveRequestFactory() + .getMediaEntityRequest(contentSource); + if (StringUtils.isNotBlank(getEntity().getMediaContentType())) { + retrieveReq.setFormat(ODataFormat.fromString(getEntity().getMediaContentType())); + } this.stream = retrieveReq.execute().getBody(); } diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java index 73a3f2940..1165830f8 100644 --- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java +++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java @@ -54,7 +54,8 @@ public class TransactionalPersistenceManagerImpl extends AbstractPersistenceMana protected void doFlush(final PersistenceChanges changes, final TransactionItems items) { final CommonODataBatchRequest request = factory.getClient().getBatchRequestFactory().getBatchRequest(factory.getClient().getServiceRoot()); - ((ODataRequest) request).setAccept(factory.getClient().getConfiguration().getDefaultBatchAcceptFormat()); + String accept = factory.getClient().getConfiguration().getDefaultBatchAcceptFormat().toContentTypeString(); + ((ODataRequest) request).setAccept(accept); final BatchManager streamManager = (BatchManager) ((ODataStreamedRequest) request).payloadManager(); diff --git a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java index f701b2a21..4db765906 100644 --- a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java +++ b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java @@ -123,6 +123,8 @@ public abstract class AbstractServices { private static final Pattern REF_PATTERN = Pattern.compile("([$]\\d+)"); protected static final String BOUNDARY = "batch_243234_25424_ef_892u748"; + protected static final String MULTIPART_MIXED = "multipart/mixed";//ContentType.MULTIPART_MIXED.toContentTypeString(); + protected static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; protected final ODataServiceVersion version; protected final Metadata metadata; @@ -194,8 +196,8 @@ public abstract class AbstractServices { @POST @Path("/$batch") - @Consumes(ContentType.MULTIPART_MIXED) - @Produces(ContentType.APPLICATION_OCTET_STREAM + ";boundary=" + BOUNDARY) + @Consumes(MULTIPART_MIXED) + @Produces(APPLICATION_OCTET_STREAM + ";boundary=" + BOUNDARY) public Response batch( @HeaderParam("Authorization") @DefaultValue(StringUtils.EMPTY) String authorization, @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer, @@ -611,7 +613,7 @@ public abstract class AbstractServices { final String entityKey; if (xml.isMediaContent(entitySetName)) { entry = new EntityImpl(); - entry.setMediaContentType(ContentType.WILDCARD); + entry.setMediaContentType(ContentType.APPLICATION_OCTET_STREAM.toContentTypeString()); entry.setType(entitySet.getType()); entityKey = xml.getDefaultEntryKey(entitySetName, entry); diff --git a/fit/src/test/java/org/apache/olingo/fit/v3/EntityRetrieveTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v3/EntityRetrieveTestITCase.java index 832b538b3..39fc464ec 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v3/EntityRetrieveTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v3/EntityRetrieveTestITCase.java @@ -150,7 +150,7 @@ public class EntityRetrieveTestITCase extends AbstractTestITCase { appendEntitySetSegment("Car").appendKeySegment(16); final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); - req.setFormat(format.toString(client.getServiceVersion())); + req.setFormat(format.getContentType(client.getServiceVersion()).toContentTypeString()); final ODataRawResponse res = req.execute(); assertNotNull(res); diff --git a/fit/src/test/java/org/apache/olingo/fit/v3/EntitySetTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v3/EntitySetTestITCase.java index 61acf0a56..83a0a5a8e 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v3/EntitySetTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v3/EntitySetTestITCase.java @@ -150,7 +150,7 @@ public class EntitySetTestITCase extends AbstractTestITCase { uriBuilder.appendEntitySetSegment("Product").inlineCount(URIBuilder.InlineCount.allpages); final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); - req.setFormat(format.toString(client.getServiceVersion())); + req.setFormat(format.getContentType(client.getServiceVersion()).toContentTypeString()); final ODataRawResponse res = req.execute(); assertNotNull(res); @@ -164,7 +164,7 @@ public class EntitySetTestITCase extends AbstractTestITCase { uriBuilder.appendEntitySetSegment("Car"); final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); - req.setFormat(format.toString(client.getServiceVersion())); + req.setFormat(format.getContentType(client.getServiceVersion()).toContentTypeString()); final ODataRawResponse res = req.execute(); assertNotNull(res); diff --git a/fit/src/test/java/org/apache/olingo/fit/v3/MediaEntityTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v3/MediaEntityTestITCase.java index a29c10f58..26f1b2979 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v3/MediaEntityTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v3/MediaEntityTestITCase.java @@ -51,7 +51,7 @@ public class MediaEntityTestITCase extends AbstractTestITCase { appendEntitySetSegment("Car").appendKeySegment(12); final ODataMediaRequest retrieveReq = client.getRetrieveRequestFactory().getMediaEntityRequest(builder.build()); - retrieveReq.setFormat(ODataFormat.WILDCARD); + retrieveReq.setAccept("*/*"); final ODataRetrieveResponse retrieveRes = retrieveReq.execute(); assertEquals(200, retrieveRes.getStatusCode()); diff --git a/fit/src/test/java/org/apache/olingo/fit/v3/PropertyTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v3/PropertyTestITCase.java index 6866a06dc..107fec783 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v3/PropertyTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v3/PropertyTestITCase.java @@ -348,7 +348,7 @@ public class PropertyTestITCase extends AbstractTestITCase { appendEntitySetSegment("Customer").appendKeySegment(-10).appendPropertySegment("BackupContactInfo"); final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); - req.setFormat(format.toString(client.getServiceVersion())); + req.setFormat(format.getContentType(client.getServiceVersion()).toContentTypeString()); final ODataRawResponse res = req.execute(); assertNotNull(res); diff --git a/fit/src/test/java/org/apache/olingo/fit/v3/QueryOptionsTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v3/QueryOptionsTestITCase.java index 940aa74d2..09f4a3381 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v3/QueryOptionsTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v3/QueryOptionsTestITCase.java @@ -106,7 +106,7 @@ public class QueryOptionsTestITCase extends AbstractTestITCase { final ODataRetrieveResponse res = req.execute(); assertNotNull(res); assertTrue(res.getContentType().replaceAll(" ", ""). - startsWith(ODataFormat.JSON.toString(client.getServiceVersion()))); + startsWith(ODataFormat.JSON.getContentType(client.getServiceVersion()).toContentTypeString())); } /** diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/AuthBatchTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/AuthBatchTestITCase.java index 73f1578d1..d09db8608 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/AuthBatchTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/AuthBatchTestITCase.java @@ -41,7 +41,7 @@ import org.junit.Test; public class AuthBatchTestITCase extends AbstractTestITCase { - private final static String ACCEPT = ContentType.APPLICATION_OCTET_STREAM; + private final static ContentType ACCEPT = ContentType.APPLICATION_OCTET_STREAM; @Test public void clean() throws EdmPrimitiveTypeException { @@ -66,8 +66,8 @@ public class AuthBatchTestITCase extends AbstractTestITCase { private void batchRequest(final ODataClient client, final String baseURL) throws EdmPrimitiveTypeException { // create your request final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(baseURL); - request.setAccept(ACCEPT); - request.addCustomHeader("User-Agent", "Microsoft ADO.NET Data Client xxx"); + request.setAccept(ACCEPT.toContentTypeString()); + request.addCustomHeader("User-Agent", "Apache Olingo OData Client"); request.addCustomHeader(HeaderName.acceptCharset, "UTF-8"); final BatchManager streamManager = request.payloadManager(); diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/BatchTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/BatchTestITCase.java index c4bac4055..a4914185f 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/BatchTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/BatchTestITCase.java @@ -77,7 +77,7 @@ public class BatchTestITCase extends AbstractTestITCase { private static final int MAX = 10000; - private final static String ACCEPT = ContentType.APPLICATION_OCTET_STREAM; + private final static String ACCEPT = ContentType.APPLICATION_OCTET_STREAM.toContentTypeString(); @Test public void stringStreaming() { diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/ConformanceTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/ConformanceTestITCase.java index d2303b2fe..fc1c2e3cd 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/ConformanceTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/ConformanceTestITCase.java @@ -124,13 +124,13 @@ public class ConformanceTestITCase extends AbstractTestITCase { // check for Content-Type assertEquals( - ODataFormat.JSON_FULL_METADATA.toString(ODataServiceVersion.V40), + ODataFormat.JSON_FULL_METADATA.getContentType(ODataServiceVersion.V40).toContentTypeString(), req.getHeader("Content-Type")); assertEquals( - ODataFormat.JSON_FULL_METADATA.toString(ODataServiceVersion.V40), + ODataFormat.JSON_FULL_METADATA.getContentType(ODataServiceVersion.V40).toContentTypeString(), req.getHeader(HeaderName.contentType.toString())); assertEquals( - ODataFormat.JSON_FULL_METADATA.toString(ODataServiceVersion.V40), + ODataFormat.JSON_FULL_METADATA.getContentType(ODataServiceVersion.V40).toContentTypeString(), req.getContentType()); final ODataEntity created = req.execute().getBody(); diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/EntityRetrieveTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/EntityRetrieveTestITCase.java index 79b73e904..4937d7a8c 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/EntityRetrieveTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/EntityRetrieveTestITCase.java @@ -172,7 +172,7 @@ public class EntityRetrieveTestITCase extends AbstractTestITCase { appendEntitySetSegment("People").appendKeySegment(5); final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); - req.setFormat(format.toString(client.getServiceVersion())); + req.setFormat(format.getContentType(client.getServiceVersion()).toContentTypeString()); final ODataRawResponse res = req.execute(); assertNotNull(res); diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/EntitySetTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/EntitySetTestITCase.java index a428d3d98..0b87fbe72 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/EntitySetTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/EntitySetTestITCase.java @@ -49,7 +49,7 @@ public class EntitySetTestITCase extends AbstractTestITCase { final URIBuilder uriBuilder = client.newURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("People"); final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); - req.setFormat(format.toString(client.getServiceVersion())); + req.setFormat(format.getContentType(client.getServiceVersion()).toContentTypeString()); final ODataRawResponse res = req.execute(); assertNotNull(res); @@ -74,7 +74,7 @@ public class EntitySetTestITCase extends AbstractTestITCase { appendEntitySetSegment("People").count(true); final ODataRawRequest req = client.getRetrieveRequestFactory().getRawRequest(uriBuilder.build()); - req.setFormat(format.toString(client.getServiceVersion())); + req.setFormat(format.getContentType(client.getServiceVersion()).toContentTypeString()); final ODataRawResponse res = req.execute(); assertNotNull(res); diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/PropertyValueTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/PropertyValueTestITCase.java index 14866d016..ed9c3add8 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/PropertyValueTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/PropertyValueTestITCase.java @@ -101,7 +101,7 @@ public class PropertyValueTestITCase extends AbstractTestITCase { final URIBuilder uriBuilder = client.newURIBuilder(testStaticServiceRootURL). appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("PDC"); final ODataValueRequest req = client.getRetrieveRequestFactory().getPropertyValueRequest(uriBuilder.build()); - req.setAccept(ODataFormat.ATOM.toString(ODataServiceVersion.V40)); + req.setAccept(ODataFormat.ATOM.getContentType(ODataServiceVersion.V40).toContentTypeString()); req.execute().getBody(); } @@ -110,7 +110,7 @@ public class PropertyValueTestITCase extends AbstractTestITCase { final URIBuilder uriBuilder = client.newURIBuilder(testStaticServiceRootURL). appendEntitySetSegment("People").appendKeySegment(5).appendPropertySegment("PDC"); final ODataValueRequest req = client.getRetrieveRequestFactory().getPropertyValueRequest(uriBuilder.build()); - req.setAccept(ODataFormat.XML.toString(client.getServiceVersion())); + req.setAccept(ODataFormat.XML.getContentType(client.getServiceVersion()).toContentTypeString()); req.execute().getBody(); } diff --git a/fit/src/test/java/org/apache/olingo/fit/v4/QueryOptionsTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/v4/QueryOptionsTestITCase.java index 19221e984..b55f4ef26 100644 --- a/fit/src/test/java/org/apache/olingo/fit/v4/QueryOptionsTestITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/v4/QueryOptionsTestITCase.java @@ -130,7 +130,7 @@ public class QueryOptionsTestITCase extends AbstractTestITCase { final ODataRetrieveResponse res = req.execute(); assertNotNull(res); assertTrue(res.getContentType().replaceAll(" ", ""). - startsWith(ODataFormat.JSON.toString(client.getServiceVersion()))); + startsWith(ODataFormat.JSON.getContentType(client.getServiceVersion()).toContentTypeString())); } /** diff --git a/lib/client-api/src/main/java/org/apache/olingo/client/api/CommonConfiguration.java b/lib/client-api/src/main/java/org/apache/olingo/client/api/CommonConfiguration.java index 62708ebf6..95319f9ce 100644 --- a/lib/client-api/src/main/java/org/apache/olingo/client/api/CommonConfiguration.java +++ b/lib/client-api/src/main/java/org/apache/olingo/client/api/CommonConfiguration.java @@ -22,6 +22,7 @@ import java.util.concurrent.ExecutorService; import org.apache.olingo.client.api.http.HttpClientFactory; import org.apache.olingo.client.api.http.HttpUriRequestFactory; +import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.format.ODataFormat; /** @@ -33,13 +34,13 @@ public interface CommonConfiguration { * Gets the configured default Accept header value format for a batch request. * @return configured default Accept header value for a batch request. */ - String getDefaultBatchAcceptFormat(); + ContentType getDefaultBatchAcceptFormat(); /** * Set the default Accept header value format for a batch request. * @param contentType default Accept header value. */ - void setDefaultBatchAcceptFormat(String contentType); + void setDefaultBatchAcceptFormat(ContentType contentType); /** * Gets the configured OData format for AtomPub exchanges. If this configuration parameter doesn't exist the @@ -69,8 +70,8 @@ public interface CommonConfiguration { * Gets the configured OData value format. If this configuration parameter doesn't exist the TEXT format will be used * as default. * - * @return configured OData value format if specified; TEXT format otherwise. - * @see ODataFormat#TEXT + * @return configured OData value format if specified; TEXT_PLAIN format otherwise. + * @see ODataFormat#TEXT_PLAIN */ ODataFormat getDefaultValueFormat(); diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/Configuration.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/Configuration.java index a67bc11a7..2dc1993f5 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/Configuration.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/Configuration.java @@ -82,12 +82,12 @@ public class Configuration implements CommonConfiguration { } @Override - public String getDefaultBatchAcceptFormat() { - return getProperty(DEFAULT_BATCH_ACCEPT_FORMAT, ContentType.MULTIPART_MIXED).toString(); + public ContentType getDefaultBatchAcceptFormat() { + return (ContentType) getProperty(DEFAULT_BATCH_ACCEPT_FORMAT, ContentType.MULTIPART_MIXED); } @Override - public void setDefaultBatchAcceptFormat(final String contentType) { + public void setDefaultBatchAcceptFormat(final ContentType contentType) { setProperty(DEFAULT_BATCH_ACCEPT_FORMAT, contentType); } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataBasicRequest.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataBasicRequest.java index cfb111247..cb72202a7 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataBasicRequest.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataBasicRequest.java @@ -57,7 +57,7 @@ public abstract class AbstractODataBasicRequest @Override public void setFormat(final ODataFormat format) { if (format != null) { - final String formatString = format.toString(odataClient.getServiceVersion()); + final String formatString = format.getContentType(odataClient.getServiceVersion()).toContentTypeString(); setAccept(formatString); setContentType(formatString); } diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataRequest.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataRequest.java index 6a8e8d756..58412fc7e 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataRequest.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/AbstractODataRequest.java @@ -184,7 +184,9 @@ public abstract class AbstractODataRequest extends AbstractRequest implements OD @Override public String getAccept() { final String acceptHead = odataHeaders.getHeader(HeaderName.accept); - return StringUtils.isBlank(acceptHead) ? getDefaultFormat().toString(odataClient.getServiceVersion()) : acceptHead; + return StringUtils.isBlank(acceptHead) ? + getDefaultFormat().getContentType(odataClient.getServiceVersion()).toContentTypeString() : + acceptHead; } @Override @@ -205,8 +207,9 @@ public abstract class AbstractODataRequest extends AbstractRequest implements OD @Override public String getContentType() { final String contentTypeHead = odataHeaders.getHeader(HeaderName.contentType); - return StringUtils.isBlank(contentTypeHead) - ? getDefaultFormat().toString(odataClient.getServiceVersion()) : contentTypeHead; + return StringUtils.isBlank(contentTypeHead) ? + getDefaultFormat().getContentType(odataClient.getServiceVersion()).toContentTypeString() : + contentTypeHead; } @Override 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 index da46d8248..b0429ac14 100644 --- 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 @@ -95,7 +95,7 @@ public class ODataBatchUtilities { *

* Usually used to read an entire batch part. * - * @param batchController batch controller. + * @param controller 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. @@ -111,7 +111,7 @@ public class ODataBatchUtilities { *

* Usually used to read an entire batch part. * - * @param batchController batch controller. + * @param controller 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. @@ -302,7 +302,7 @@ public class ODataBatchUtilities { final String contentType = headers.containsKey(HeaderName.contentType.toString()) ? headers.get(HeaderName.contentType.toString()).toString() : StringUtils.EMPTY; - if (contentType.contains(ContentType.MULTIPART_MIXED)) { + if (contentType.contains(ContentType.MULTIPART_MIXED.toContentTypeString())) { nextItemType = BatchItemType.CHANGESET; } else if (contentType.contains(ODataBatchConstants.ITEM_CONTENT_TYPE)) { nextItemType = BatchItemType.RETRIEVE; diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v3/ODataBatchRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v3/ODataBatchRequestImpl.java index 3fbfddb4f..93821daff 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v3/ODataBatchRequestImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v3/ODataBatchRequestImpl.java @@ -49,7 +49,7 @@ public class ODataBatchRequestImpl public ODataBatchRequestImpl(final ODataClient odataClient, final URI uri) { super(odataClient, uri); - setAccept(odataClient.getConfiguration().getDefaultBatchAcceptFormat()); + setAccept(odataClient.getConfiguration().getDefaultBatchAcceptFormat().toContentTypeString()); } @Override diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v4/ODataBatchRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v4/ODataBatchRequestImpl.java index 0c4354970..8fea8ce81 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v4/ODataBatchRequestImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/v4/ODataBatchRequestImpl.java @@ -48,7 +48,7 @@ public class ODataBatchRequestImpl public ODataBatchRequestImpl(final ODataClient odataClient, final URI uri) { super(odataClient, uri); - setAccept(odataClient.getConfiguration().getDefaultBatchAcceptFormat()); + setAccept(odataClient.getConfiguration().getDefaultBatchAcceptFormat().toContentTypeString()); } @Override diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/AbstractODataInvokeRequest.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/AbstractODataInvokeRequest.java index 714dd5e86..5546778d6 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/AbstractODataInvokeRequest.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/AbstractODataInvokeRequest.java @@ -97,9 +97,9 @@ public abstract class AbstractODataInvokeRequest } private String getActualFormat(final ODataFormat format) { - return (CommonODataProperty.class.isAssignableFrom(reference) && format == ODataFormat.ATOM) - ? ODataFormat.XML.toString(odataClient.getServiceVersion()) - : format.toString(odataClient.getServiceVersion()); + return ((CommonODataProperty.class.isAssignableFrom(reference) && format == ODataFormat.ATOM) + ? ODataFormat.XML : format) + .getContentType(odataClient.getServiceVersion()).toContentTypeString(); } @Override diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/streamed/AbstractODataStreamedEntityRequest.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/streamed/AbstractODataStreamedEntityRequest.java index edbe31e0f..506270a9c 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/streamed/AbstractODataStreamedEntityRequest.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/streamed/AbstractODataStreamedEntityRequest.java @@ -50,7 +50,7 @@ public abstract class AbstractODataStreamedEntityRequestMedia Type format as defined in + * RFC 7231, chapter 3.1.1.1. + *

+ * media-type = type "/" subtype *( OWS ";" OWS parameter )
+ * type = token
+ * subtype = token
+ * OWS = *( SP / HTAB )  ; optional whitespace
+ * 
+ * + * Especially for Accept Header as defined in + * RFC 7231, chapter 5.3.2: + *
+ * Accept = #( media-range [ accept-params ] )
+ * media-range = ( "*/*"
+ *               / ( type "/" "*" )
+ *               / ( type "/" subtype )
+ *               ) *( OWS ";" OWS parameter )
+ * accept-params  = weight *( accept-ext )
+ * accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ]
+ * weight = OWS ";" OWS "q=" qvalue
+ * qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
+ * 
+ * + * Once created a {@link ContentType} is IMMUTABLE. + */ +public class ContentType { - public static final String APPLICATION_ATOM_XML = "application/atom+xml"; + private static final Comparator Q_PARAMETER_COMPARATOR = new Comparator() { + @Override + public int compare(final String o1, final String o2) { + Float f1 = parseQParameterValue(o1); + Float f2 = parseQParameterValue(o2); + return f2.compareTo(f1); + } + }; - public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded"; + private static final char WHITESPACE_CHAR = ' '; + private static final String PARAMETER_SEPARATOR = ";"; + private static final String PARAMETER_KEY_VALUE_SEPARATOR = "="; + private static final String TYPE_SUBTYPE_SEPARATOR = "/"; + private static final String MEDIA_TYPE_WILDCARD = "*"; - public static final String APPLICATION_JSON = "application/json"; + public static final String PARAMETER_Q = "q"; + public static final String PARAMETER_TYPE = "type"; + public static final String PARAMETER_CHARSET = "charset"; + public static final String CHARSET_UTF_8 = "UTF-8"; - public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + private static final Pattern Q_PARAMETER_VALUE_PATTERN = Pattern.compile("1|0|1\\.0{1,3}|0\\.\\d{1,3}"); - public static final String MULTIPART_MIXED = "multipart/mixed"; + public static final ContentType WILDCARD = create(MEDIA_TYPE_WILDCARD, MEDIA_TYPE_WILDCARD); - public static final String APPLICATION_SVG_XML = "application/svg+xml"; + public static final ContentType APPLICATION_XHTML_XML = create("application", "xhtml+xml"); + public static final ContentType APPLICATION_SVG_XML = create("application", "svg+xml"); + public static final ContentType APPLICATION_FORM_URLENCODED = create("application", "x-www-form-urlencoded"); + public static final ContentType MULTIPART_FORM_DATA = create("multipart", "form-data"); + public static final ContentType TEXT_XML = create("text", "xml"); + public static final ContentType TEXT_HTML = create("text", "html"); - public static final String APPLICATION_XHTML_XML = "application/xhtml+xml"; + public static final ContentType APPLICATION_XML = create("application", "xml"); + public static final ContentType APPLICATION_XML_CS_UTF_8 = create(APPLICATION_XML, PARAMETER_CHARSET, + CHARSET_UTF_8); + public static final ContentType APPLICATION_ATOM_XML = create("application", "atom+xml"); + public static final ContentType APPLICATION_ATOM_XML_CS_UTF_8 = create(APPLICATION_ATOM_XML, + PARAMETER_CHARSET, CHARSET_UTF_8); + public static final ContentType APPLICATION_ATOM_XML_ENTRY = create(APPLICATION_ATOM_XML, PARAMETER_TYPE, "entry"); + public static final ContentType APPLICATION_ATOM_XML_ENTRY_CS_UTF_8 = create(APPLICATION_ATOM_XML_ENTRY, + PARAMETER_CHARSET, CHARSET_UTF_8); + public static final ContentType APPLICATION_ATOM_XML_FEED = create(APPLICATION_ATOM_XML, PARAMETER_TYPE, "feed"); + public static final ContentType APPLICATION_ATOM_XML_FEED_CS_UTF_8 = ContentType.create(APPLICATION_ATOM_XML_FEED, + PARAMETER_CHARSET, CHARSET_UTF_8); + public static final ContentType APPLICATION_ATOM_SVC = create("application", "atomsvc+xml"); + public static final ContentType APPLICATION_ATOM_SVC_CS_UTF_8 = create(APPLICATION_ATOM_SVC, + PARAMETER_CHARSET, CHARSET_UTF_8); + public static final ContentType APPLICATION_JSON = create("application", "json"); + public static final ContentType APPLICATION_JSON_CS_UTF_8 = create(APPLICATION_JSON, + PARAMETER_CHARSET, CHARSET_UTF_8); + public static final ContentType APPLICATION_OCTET_STREAM = create("application", "octet-stream"); + public static final ContentType TEXT_PLAIN = create("text", "plain"); + public static final ContentType TEXT_PLAIN_CS_UTF_8 = ContentType + .create(TEXT_PLAIN, PARAMETER_CHARSET, CHARSET_UTF_8); + public static final ContentType MULTIPART_MIXED = create("multipart", "mixed"); - public static final String APPLICATION_XML = "application/xml"; + private final String type; + private final String subtype; + private final Map parameters; - public static final String MULTIPART_FORM_DATA = "multipart/form-data"; + private ContentType(final String type) { + if (type == null) { + throw new IllegalArgumentException("Type parameter MUST NOT be null."); + } + this.type = validateType(type); + subtype = null; + parameters = Collections.emptyMap(); + } - public static final String TEXT_HTML = "text/html"; + /** + * Creates a content type from type, subtype, and parameters. + * @param type + * @param subtype + * @param parameters + */ + private ContentType(final String type, final String subtype, final Map parameters) { + if ((type == null || MEDIA_TYPE_WILDCARD.equals(type)) && !MEDIA_TYPE_WILDCARD.equals(subtype)) { + throw new IllegalArgumentException("Illegal combination of WILDCARD type with NONE WILDCARD subtype."); + } + this.type = validateType(type); + this.subtype = validateType(subtype); - public static final String TEXT_PLAIN = "text/plain"; + if (parameters == null) { + this.parameters = Collections.emptyMap(); + } else { + this.parameters = new TreeMap(new Comparator() { + @Override + public int compare(final String o1, final String o2) { + return o1.compareToIgnoreCase(o2); + } + }); + this.parameters.putAll(parameters); + this.parameters.remove(PARAMETER_Q); + } + } - public static final String TEXT_XML = "text/xml"; + private String validateType(final String type) { + if (type == null || type.isEmpty()) { + return MEDIA_TYPE_WILDCARD; + } + int len = type.length(); + for (int i = 0; i < len; i++) { + if (type.charAt(i) == WHITESPACE_CHAR) { + throw new IllegalArgumentException("Illegal whitespace found for type '" + type + "'."); + } + } + return type; + } - public static final String WILDCARD = "*/*"; + /** + * Validates if given format is parseable and can be used as input for {@link #create(String)} method. + * @param format to be validated string + * @return true if format is parseable otherwise false + */ + public static boolean isParseable(final String format) { + try { + return ContentType.create(format) != null; + } catch (IllegalArgumentException e) { + return false; + } + } - public static final EnumMap> formatPerVersion = - new EnumMap>(ODataServiceVersion.class); + /** + * Creates a content type from type and subtype. + * @param type + * @param subtype + * @return a new ContentType object + */ + public static ContentType create(final String type, final String subtype) { + return new ContentType(type, subtype, null); + } - static { - final Map v3 = new HashMap(); - v3.put(ODataFormat.JSON_NO_METADATA.name(), ContentType.APPLICATION_JSON + ";odata=nometadata"); - v3.put(ODataFormat.JSON.name(), ContentType.APPLICATION_JSON + ";odata=minimalmetadata"); - v3.put(ODataFormat.JSON_FULL_METADATA.name(), ContentType.APPLICATION_JSON + ";odata=fullmetadata"); - formatPerVersion.put(ODataServiceVersion.V30, v3); + /** + * + * @param contentType + * @param parameterKey + * @param parameterValue + * @return a new ContentType object + */ + public static ContentType create(final ContentType contentType, + final String parameterKey, final String parameterValue) { + ContentType ct = new ContentType(contentType.type, contentType.subtype, contentType.parameters); + ct.parameters.put(parameterKey, parameterValue); + return ct; + } - final Map v4 = new HashMap(); - v4.put(ODataFormat.JSON_NO_METADATA.name(), ContentType.APPLICATION_JSON + ";odata.metadata=none"); - v4.put(ODataFormat.JSON.name(), ContentType.APPLICATION_JSON + ";odata.metadata=minimal"); - v4.put(ODataFormat.JSON_FULL_METADATA.name(), ContentType.APPLICATION_JSON + ";odata.metadata=full"); - formatPerVersion.put(ODataServiceVersion.V40, v4); + /** + * Create a {@link ContentType} based on given input string (format). + * Supported format is Media Type format as defined in RFC 7231, chapter 3.1.1.1. + * @param format a string in format as defined in RFC 7231, chapter 3.1.1.1 + * @return a new ContentType object + * @throws IllegalArgumentException if input string is not parseable + */ + public static ContentType create(final String format) { + if (format == null) { + throw new IllegalArgumentException("Parameter format MUST NOT be NULL."); + } + + // split 'types' and 'parameters' + String[] typesAndParameters = format.split(PARAMETER_SEPARATOR, 2); + String types = typesAndParameters[0]; + String parameters = (typesAndParameters.length > 1 ? typesAndParameters[1] : null); + // + Map parametersMap = parseParameters(parameters); + // + if (types.contains(TYPE_SUBTYPE_SEPARATOR)) { + String[] tokens = types.split(TYPE_SUBTYPE_SEPARATOR); + if (tokens.length == 2) { + if (tokens[0] == null || tokens[0].isEmpty()) { + throw new IllegalArgumentException("No type found in format '" + format + "'."); + } else if (tokens[1] == null || tokens[1].isEmpty()) { + throw new IllegalArgumentException("No subtype found in format '" + format + "'."); + } else { + return new ContentType(tokens[0], tokens[1], parametersMap); + } + } else { + throw new IllegalArgumentException("Too many '" + TYPE_SUBTYPE_SEPARATOR + "' in format '" + format + "'."); + } + } else if (MEDIA_TYPE_WILDCARD.equals(types)) { + return ContentType.WILDCARD; + } else { + throw new IllegalArgumentException("No separator '" + TYPE_SUBTYPE_SEPARATOR + "' was found in format '" + format + + "'."); + } + } + + /** + * Create a list of {@link ContentType} based on given input strings (contentTypes). + * + * Supported format is Media Type format as defined in RFC 7231, chapter 3.1.1.1. + * If one of the given strings can not be parsed an exception is thrown (hence no list is returned with the parseable + * strings). + * @param contentTypeStrings a list of strings in format as defined in RFC 2616 section 3.7 + * @return a list of new ContentType object + * @throws IllegalArgumentException if one of the given input string is not parseable this exceptions is thrown + */ + public static List create(final List contentTypeStrings) { + List contentTypes = new ArrayList(contentTypeStrings.size()); + for (String contentTypeString : contentTypeStrings) { + contentTypes.add(create(contentTypeString)); + } + return contentTypes; + } + + /** + * Parses the given input string (format) and returns created {@link ContentType} if input was valid or + * return NULL if input was not parseable. + * + * For the definition of the supported format see {@link #create(String)}. + * + * @param format a string in format as defined in RFC 7231, chapter 3.1.1.1 + * @return a new ContentType object + */ + public static ContentType parse(final String format) { + try { + return ContentType.create(format); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Sort given list (which must contains content-type formatted string) for their {@value #PARAMETER_Q} value + * as defined in RFC 7231, chapter 3.1.1.1, and RFC 7231, chapter 5.3.1. + * + * Attention: For invalid values a {@value #PARAMETER_Q} value from -1 is used for sorting. + * + * @param toSort list which is sorted and hence re-arranged + */ + public static void sortForQParameter(final List toSort) { + Collections.sort(toSort, ContentType.Q_PARAMETER_COMPARATOR); + } + + /** + * Valid input are ; separated key=value pairs + * without spaces between key and value. + * Attention: q parameter is validated but not added to result map + * + *

+ * See RFC 7231: + * The type, subtype, and parameter name tokens are case-insensitive. + * Parameter values might or might not be case-sensitive, depending on + * the semantics of the parameter name. The presence or absence of a + * parameter might be significant to the processing of a media-type, + * depending on its definition within the media type registry. + *

+ * + * @param parameters + * @return Map with keys mapped to values + */ + private static Map parseParameters(final String parameters) { + Map parameterMap = new HashMap(); + if (parameters != null) { + String[] splittedParameters = parameters.split(PARAMETER_SEPARATOR); + for (String parameter : splittedParameters) { + String[] keyValue = parameter.split(PARAMETER_KEY_VALUE_SEPARATOR); + String key = keyValue[0].trim().toLowerCase(Locale.ENGLISH); + String value = keyValue.length > 1 ? keyValue[1] : null; + if (value != null && Character.isWhitespace(value.charAt(0))) { + throw new IllegalArgumentException("Value of parameter '" + key + "' starts with whitespace ('" + parameters + + "')."); + } + if (PARAMETER_Q.equals(key.toLowerCase(Locale.ENGLISH))) { + // q parameter is only validated but not added + if (!Q_PARAMETER_VALUE_PATTERN.matcher(value).matches()) { + throw new IllegalArgumentException("Value of 'q' parameter is not valid (q='" + value + "')."); + } + } else { + parameterMap.put(key, value); + } + } + } + return parameterMap; + } + + /** + * Parse value of {@value #PARAMETER_Q} parameter out of content type/parameters. + * If no {@value #PARAMETER_Q} parameter is in content type/parameters parameter found + * 1 is returned. + * If {@value #PARAMETER_Q} parameter is invalid -1 is returned. + * + * @param contentType parameter which is parsed for {@value #PARAMETER_Q} parameter value + * @return value of {@value #PARAMETER_Q} parameter or 1 or -1 + */ + private static Float parseQParameterValue(final String contentType) { + if (contentType != null) { + String[] splittedParameters = contentType.split(PARAMETER_SEPARATOR); + for (String parameter : splittedParameters) { + String[] keyValue = parameter.split(PARAMETER_KEY_VALUE_SEPARATOR); + String key = keyValue[0].trim().toLowerCase(Locale.ENGLISH); + if (PARAMETER_Q.equalsIgnoreCase(key)) { + String value = keyValue.length > 1 ? keyValue[1] : null; + if (Q_PARAMETER_VALUE_PATTERN.matcher(value).matches()) { + return Float.valueOf(value); + } + return Float.valueOf(-1); + } + } + } + return Float.valueOf(1); + } + + /** + * Check if parameter with key value is an allowed parameter. + * @param key + * @return + */ + private static boolean isParameterAllowed(final String key) { + return key != null && !PARAMETER_Q.equals(key.toLowerCase(Locale.ENGLISH)); + } + + public String getType() { + return type; + } + + public String getSubtype() { + return subtype; + } + + /** + * + * @return parameters of this {@link ContentType} as unmodifiable map. + */ + public Map getParameters() { + return Collections.unmodifiableMap(parameters); + } + + @Override + public int hashCode() { + return 1; + } + + /** + * {@link ContentType}s are equal + *
    + *
  • if type, subtype and all parameters have the same value.
  • + *
  • if type and/or subtype is set to "*" (in such a case the parameters are + * ignored).
  • + *
+ * + * @return true if both instances are equal (see definition above), otherwise false. + */ + @Override + public boolean equals(final Object obj) { + // NULL validation is done in method 'isEqualWithoutParameters(obj)' + Boolean compatible = isEqualWithoutParameters(obj); + + if (compatible == null) { + ContentType other = (ContentType) obj; + + // parameter checks + if (parameters == null) { + if (other.parameters != null) { + return false; + } + } else if (parameters.size() == other.parameters.size()) { + Iterator> entries = parameters.entrySet().iterator(); + Iterator> otherEntries = other.parameters.entrySet().iterator(); + while (entries.hasNext()) { + Entry e = entries.next(); + Entry oe = otherEntries.next(); + + if (!areEqual(e.getKey(), oe.getKey())) { + return false; + } + if (!areEqual(e.getValue(), oe.getValue())) { + return false; + } + } + } else { + return false; + } + return true; + } else { + // all tests run + return compatible.booleanValue(); + } + } + + /** + * {@link ContentType}s are compatible + *
    + *
  • if type, subtype have the same value.
  • + *
  • if type and/or subtype is set to "*"
  • + *
+ * The set parameters are always ignored (for compare with parameters see {@link #equals(Object)} + * ). + * + * @return true if both instances are equal (see definition above), otherwise false. + */ + public boolean isCompatible(final ContentType obj) { + Boolean compatible = isEqualWithoutParameters(obj); + if (compatible == null) { + return true; + } + return compatible.booleanValue(); + } + + /** + * Check equal without parameters. + * It is possible that no decision about equal/none equal can be determined a NULL is + * returned. + * + * @param obj to checked object + * @return true if both instances are equal (see definition above), otherwise false + * or NULL if no decision about equal/none equal could be determined. + */ + private Boolean isEqualWithoutParameters(final Object obj) { + // basic checks + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + ContentType other = (ContentType) obj; + + // subtype checks + if (subtype == null) { + if (other.subtype != null) { + return false; + } + } else if (!subtype.equals(other.subtype)) { + if (other.subtype == null) { + return false; + } else if (!subtype.equals(MEDIA_TYPE_WILDCARD) && !other.subtype.equals(MEDIA_TYPE_WILDCARD)) { + return false; + } + } + + // type checks + if (type == null) { + if (other.type != null) { + return false; + } + } else if (!type.equals(other.type)) { + if (!type.equals(MEDIA_TYPE_WILDCARD) && !other.type.equals(MEDIA_TYPE_WILDCARD)) { + return false; + } + } + + // if wildcards are set, content types are defined as 'equal' + if (countWildcards() > 0 || other.countWildcards() > 0) { + return true; + } + + return null; + } + + /** + * Check whether both string are equal ignoring the case of the strings. + * + * @param first first string + * @param second second string + * @return true if both strings are equal (by ignoring the case), otherwise false is + * returned + */ + private static boolean areEqual(final String first, final String second) { + if (first == null) { + if (second != null) { + return false; + } + } else if (!first.equalsIgnoreCase(second)) { + return false; + } + return true; + } + + /** + * Get {@link ContentType} as string as defined in RFC 7231 (http://www.ietf.org/rfc/rfc7231.txt, chapter 3.1.1.1: + * Media Type) + * @return string representation of ContentType object + */ + public String toContentTypeString() { + StringBuilder sb = new StringBuilder(); + + sb.append(type).append(TYPE_SUBTYPE_SEPARATOR).append(subtype); + + for (String key : parameters.keySet()) { + if (isParameterAllowed(key)) { + String value = parameters.get(key); + sb.append(";").append(key).append("=").append(value); + } + } + return sb.toString(); + } + + @Override + public String toString() { + return toContentTypeString(); + } + + /** + * Find best match between this {@link ContentType} and the {@link ContentType} in the list. + * If a match (this {@link ContentType} is equal to a {@link ContentType} in list) is found either this or the + * {@link ContentType} from the list is returned based on which {@link ContentType} has less "**" characters set + * (checked with {@link #compareWildcardCounts(ContentType)}. + * If no match (none {@link ContentType} in list is equal to this {@link ContentType}) is found NULL is + * returned. + * + * @param toMatchContentTypes list of {@link ContentType}s which are matches against this {@link ContentType} + * @return best matched content type in list or NULL if none content type match to this content type + * instance + */ + public ContentType match(final List toMatchContentTypes) { + for (ContentType supportedContentType : toMatchContentTypes) { + if (equals(supportedContentType)) { + if (compareWildcardCounts(supportedContentType) < 0) { + return this; + } else { + return supportedContentType; + } + } + } + return null; + } + + /** + * Find best match between this {@link ContentType} and the {@link ContentType} in the list ignoring all set + * parameters. + * If a match (this {@link ContentType} is equal to a {@link ContentType} in list) is found either this or the + * {@link ContentType} from the list is returned based on which {@link ContentType} has less "**" characters set + * (checked with {@link #compareWildcardCounts(ContentType)}. + * If no match (none {@link ContentType} in list is equal to this {@link ContentType}) is found NULL is + * returned. + * + * @param toMatchContentTypes list of {@link ContentType}s which are matches against this {@link ContentType} + * @return best matched content type in list or NULL if none content type match to this content type + * instance + */ + public ContentType matchCompatible(final List toMatchContentTypes) { + for (ContentType supportedContentType : toMatchContentTypes) { + if (isCompatible(supportedContentType)) { + if (compareWildcardCounts(supportedContentType) < 0) { + return this; + } else { + return supportedContentType; + } + } + } + return null; + } + + /** + * Check if a valid compatible match for this {@link ContentType} exists in given list. + * Compatible in this case means that all set parameters are ignored. + * For more detail what a valid match is see {@link #matchCompatible(List)}. + * + * @param toMatchContentTypes list of {@link ContentType}s which are matches against this {@link ContentType} + * @return true if a compatible content type was found in given list + * or false if none compatible content type match was found + */ + public boolean hasCompatible(final List toMatchContentTypes) { + return matchCompatible(toMatchContentTypes) != null; + } + + /** + * Check if a valid match for this {@link ContentType} exists in given list. + * For more detail what a valid match is see {@link #match(List)}. + * + * @param toMatchContentTypes list of {@link ContentType}s which are matches against this {@link ContentType} + * @return true if a matching content type was found in given list + * or false if none matching content type match was found + */ + public boolean hasMatch(final List toMatchContentTypes) { + return match(toMatchContentTypes) != null; + } + + /** + * Compare wildcards counts/weights of both {@link ContentType}. + * + * The smaller {@link ContentType} has lesser weighted wildcards then the bigger {@link ContentType}. + * As result this method returns this object weighted wildcards minus the given parameter object weighted wildcards. + * + * A type wildcard is weighted with 2 and a subtype wildcard is weighted with 1. + * + * @param otherContentType {@link ContentType} to be compared to + * @return this object weighted wildcards minus the given parameter object weighted wildcards. + */ + public int compareWildcardCounts(final ContentType otherContentType) { + return countWildcards() - otherContentType.countWildcards(); + } + + private int countWildcards() { + int count = 0; + if (MEDIA_TYPE_WILDCARD.equals(type)) { + count += 2; + } + if (MEDIA_TYPE_WILDCARD.equals(subtype)) { + count++; + } + return count; + } + + /** + * + * @return true if type or subtype of this instance is a "*". + */ + public boolean hasWildcard() { + return (MEDIA_TYPE_WILDCARD.equals(type) || MEDIA_TYPE_WILDCARD.equals(subtype)); + } + + /** + * + * @return true if both type and subtype of this instance are a "*". + */ + public boolean isWildcard() { + return (MEDIA_TYPE_WILDCARD.equals(type) && MEDIA_TYPE_WILDCARD.equals(subtype)); + } + + public static List convert(final List types) { + List results = new ArrayList(); + for (String contentType : types) { + results.add(ContentType.create(contentType)); + } + return results; + } + + /** + * Check if a valid match for given content type formated string (toMatch) exists in given list. + * Therefore the given content type formated string (toMatch) is converted into a {@link ContentType} + * with a simple {@link #create(String)} call (during which an exception can occur). + * + * For more detail in general see {@link #hasMatch(List)} and for what a valid match is see {@link #match(List)}. + * + * @param toMatch content type formated string (toMatch) for which is checked if a match exists in given + * list + * @param matchExamples list of {@link ContentType}s which are matches against content type formated string + * (toMatch) + * @return true if a matching content type was found in given list + * or false if none matching content type match was found + */ + public static boolean match(final String toMatch, final ContentType... matchExamples) { + ContentType toMatchContentType = ContentType.create(toMatch); + + return toMatchContentType.hasMatch(Arrays.asList(matchExamples)); } } diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ODataFormat.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ODataFormat.java index 37d073118..bc122e261 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ODataFormat.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/format/ODataFormat.java @@ -20,6 +20,9 @@ package org.apache.olingo.commons.api.format; import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; +import java.util.HashMap; +import java.util.Map; + /** * Available formats to be used in various contexts. */ @@ -38,8 +41,6 @@ public enum ODataFormat { ATOM(ContentType.APPLICATION_ATOM_XML), // media formats - MEDIA_TYPE_WILDCARD("*"), - WILDCARD(ContentType.WILDCARD), APPLICATION_XML(ContentType.APPLICATION_XML), APPLICATION_ATOM_XML(ContentType.APPLICATION_ATOM_XML), APPLICATION_XHTML_XML(ContentType.APPLICATION_XHTML_XML), @@ -52,79 +53,127 @@ public enum ODataFormat { TEXT_XML(ContentType.TEXT_XML), TEXT_HTML(ContentType.TEXT_HTML); - private final String format; + private static final String JSON_METADATA_PARAMETER_V3 = "odata"; + private static final String JSON_METADATA_PARAMETER_V4 = "odata.metadata"; - ODataFormat() { - this.format = null; + private static final Map> FORMAT_PER_VERSION = new + HashMap>(); + + static { + final Map v3 = new HashMap(); + v3.put(ODataFormat.JSON_NO_METADATA, ContentType.create( + ContentType.APPLICATION_JSON, JSON_METADATA_PARAMETER_V3, "nometadata")); + v3.put(ODataFormat.JSON, ContentType.create( + ContentType.APPLICATION_JSON, JSON_METADATA_PARAMETER_V3, "minimalmetadata")); + v3.put(ODataFormat.JSON_FULL_METADATA, ContentType.create( + ContentType.APPLICATION_JSON, JSON_METADATA_PARAMETER_V3, "fullmetadata")); + FORMAT_PER_VERSION.put(ODataServiceVersion.V30, v3); + + final Map v4 = new HashMap(); + v4.put(ODataFormat.JSON_NO_METADATA, ContentType.create( + ContentType.APPLICATION_JSON, JSON_METADATA_PARAMETER_V4, "none")); + v4.put(ODataFormat.JSON, ContentType.create( + ContentType.APPLICATION_JSON, JSON_METADATA_PARAMETER_V4, "minimal")); + v4.put(ODataFormat.JSON_FULL_METADATA, ContentType.create( + ContentType.APPLICATION_JSON, JSON_METADATA_PARAMETER_V4, "full")); + FORMAT_PER_VERSION.put(ODataServiceVersion.V40, v4); } - ODataFormat(final String format) { - this.format = format; + private final ContentType contentType; + + ODataFormat(final ContentType contentType) { + this.contentType = contentType; + } + + ODataFormat() { + this.contentType = null; } /** - * Gets format as a string. - * + * Gets format as {@link ContentType}. * @param version OData service version. - * @return format as a string. + * @return format as ContentType. */ - public String toString(final ODataServiceVersion version) { + public ContentType getContentType(final ODataServiceVersion version) { if (version.ordinal() < ODataServiceVersion.V30.ordinal()) { throw new IllegalArgumentException("Unsupported version " + version); } - return format == null ? ContentType.formatPerVersion.get(version).get(this.name()) : format; + return contentType == null ? FORMAT_PER_VERSION.get(version).get(this) : contentType; } @Override public String toString() { - if (format == null) { + if (contentType == null) { throw new UnsupportedOperationException(); } else { - return format; + return contentType.toContentTypeString(); } } /** - * Gets OData format from its string representation. + * Gets OData format from a content type. * - * @param format string representation of the format. + * @param contentType content type * @return OData format. */ - public static ODataFormat fromString(final String format) { - ODataFormat result = null; - - final StringBuffer _format = new StringBuffer(); - - final String[] parts = format.split(";"); - _format.append(parts[0].trim()); - if (ContentType.APPLICATION_JSON.equals(parts[0].trim())) { - if (parts.length > 1) { - if (parts[1].trim().equalsIgnoreCase("charset=UTF-8")) { - result = ODataFormat.JSON; - } else { - _format.append(';').append(parts[1].trim()); - } - } else { - result = ODataFormat.JSON; - } + public static ODataFormat fromContentType(final ContentType contentType) { + if (contentType == null) { + return null; + } + if (contentType.hasWildcard()) { + throw new IllegalArgumentException("Content Type must be fully specified!"); } - if (result == null) { - final String candidate = _format.toString(); - for (ODataFormat value : values()) { - if (candidate.equals(value.toString(ODataServiceVersion.V30)) - || candidate.equals(value.toString(ODataServiceVersion.V40))) { - result = value; - break; + if (contentType.isCompatible(ContentType.APPLICATION_ATOM_XML) + || contentType.isCompatible(ContentType.APPLICATION_ATOM_SVC)) { + return ATOM; + } else if (contentType.isCompatible(ContentType.APPLICATION_XML)) { + return XML; + } else if (contentType.isCompatible(ContentType.APPLICATION_JSON)) { + String jsonVariant = contentType.getParameters().get(JSON_METADATA_PARAMETER_V3); + if (jsonVariant != null) { + for (ODataFormat candidate : FORMAT_PER_VERSION.get(ODataServiceVersion.V30).keySet()) { + if (FORMAT_PER_VERSION.get(ODataServiceVersion.V30).get(candidate).getParameters() + .get(JSON_METADATA_PARAMETER_V3) + .equals(jsonVariant)) { + return candidate; + } } } + jsonVariant = contentType.getParameters().get(JSON_METADATA_PARAMETER_V4); + if (jsonVariant != null) { + for (ODataFormat candidate : FORMAT_PER_VERSION.get(ODataServiceVersion.V40).keySet()) { + if (FORMAT_PER_VERSION.get(ODataServiceVersion.V40).get(candidate).getParameters() + .get(JSON_METADATA_PARAMETER_V4) + .equals(jsonVariant)) { + return candidate; + } + } + } + return JSON; + } else if (contentType.isCompatible(ContentType.APPLICATION_OCTET_STREAM)) { + return APPLICATION_OCTET_STREAM; + } else if (contentType.isCompatible(ContentType.TEXT_PLAIN)) { + return TEXT_PLAIN; + } else if (contentType.isCompatible(ContentType.APPLICATION_XHTML_XML)) { + return APPLICATION_XHTML_XML; + } else if (contentType.isCompatible(ContentType.APPLICATION_SVG_XML)) { + return APPLICATION_SVG_XML; + } else if (contentType.isCompatible(ContentType.APPLICATION_FORM_URLENCODED)) { + return APPLICATION_FORM_URLENCODED; + } else if (contentType.isCompatible(ContentType.MULTIPART_FORM_DATA)) { + return MULTIPART_FORM_DATA; + } else if (contentType.isCompatible(ContentType.TEXT_XML)) { + return TEXT_XML; + } else if (contentType.isCompatible(ContentType.TEXT_HTML)) { + return TEXT_HTML; } - if (result == null) { - throw new IllegalArgumentException("Unsupported format: " + format); - } + throw new IllegalArgumentException("Unsupported content Type: " + contentType); + } - return result; + public static ODataFormat fromString(final String contentType) { + return contentType == null ? null : fromContentType(ContentType.parse(contentType)); } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/AtomSerializer.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/AtomSerializer.java index fb2a04181..74a76ad5e 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/AtomSerializer.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/serialization/AtomSerializer.java @@ -319,7 +319,7 @@ public class AtomSerializer extends AbstractAtomDealer implements ODataSerialize writer.writeStartElement(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.PROPERTIES); properties(writer, entity.getProperties()); } else { - writer.writeAttribute(Constants.ATTR_TYPE, ContentType.APPLICATION_XML); + writer.writeAttribute(Constants.ATTR_TYPE, ContentType.APPLICATION_XML.toContentTypeString()); writer.writeStartElement(version.getNamespaceMap().get(ODataServiceVersion.NS_METADATA), Constants.PROPERTIES); properties(writer, entity.getProperties()); writer.writeEndElement(); diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java index 7e56c559e..5c6c636fd 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/SampleJsonProcessor.java @@ -72,7 +72,7 @@ public class SampleJsonProcessor implements EntitySetProcessor, EntityProcessor LOG.info("Finished in " + (System.nanoTime() - time) / 1000 + " microseconds"); response.setStatusCode(200); - response.setHeader("Content-Type", ContentType.APPLICATION_JSON); + response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toContentTypeString()); } @Override @@ -91,7 +91,7 @@ public class SampleJsonProcessor implements EntitySetProcessor, EntityProcessor LOG.info("Finished in " + (System.nanoTime() - time) / 1000 + " microseconds"); response.setStatusCode(200); - response.setHeader("Content-Type", ContentType.APPLICATION_JSON); + response.setHeader("Content-Type", ContentType.APPLICATION_JSON.toContentTypeString()); } protected Entity createEntity() {