You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2013/09/15 16:04:33 UTC
git commit: Moved content type validation from RestUtil to ContentType
Updated Branches:
refs/heads/master 9ee8e0f3a -> 95a43b421
Moved content type validation from RestUtil to ContentType
Project: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/commit/95a43b42
Tree: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/tree/95a43b42
Diff: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/diff/95a43b42
Branch: refs/heads/master
Commit: 95a43b421305dae448284e891971d61c76255df6
Parents: 9ee8e0f
Author: Michael Bolz <mi...@apache.org>
Authored: Sun Sep 15 15:56:07 2013 +0200
Committer: Michael Bolz <mi...@apache.org>
Committed: Sun Sep 15 15:58:04 2013 +0200
----------------------------------------------------------------------
.../olingo/odata2/core/ContentNegotiator.java | 2 +
.../olingo/odata2/core/commons/ContentType.java | 97 ++++++++++++++++++--
.../olingo/odata2/core/rest/RestUtil.java | 19 ++--
.../odata2/core/commons/ContentTypeTest.java | 21 +++++
.../odata2/fit/basic/AcceptHeaderTypeTest.java | 10 +-
.../olingo/odata2/fit/basic/FitLoadTest.java | 8 +-
.../BasicContentNegotiationTest.java | 13 ++-
7 files changed, 142 insertions(+), 28 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/95a43b42/odata-core/src/main/java/org/apache/olingo/odata2/core/ContentNegotiator.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/ContentNegotiator.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/ContentNegotiator.java
index 309a79f..8994d57 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/ContentNegotiator.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/ContentNegotiator.java
@@ -19,6 +19,7 @@
package org.apache.olingo.odata2.core;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -63,6 +64,7 @@ public class ContentNegotiator {
if(uriInfo.getUriType() == UriType.URI5 || uriInfo.getUriType() == UriType.URI4) {
return ContentType.TEXT_PLAIN_CS_UTF_8;
}
+ return doContentNegotiationForAcceptHeader(Arrays.asList("*/*"), ContentType.create(supportedContentTypes));
}
if (uriInfo.getFormat() == null) {
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/95a43b42/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/ContentType.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/ContentType.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/ContentType.java
index 037f70c..5a174e2 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/ContentType.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/commons/ContentType.java
@@ -31,6 +31,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
+import java.util.regex.Pattern;
/**
* Internally used {@link ContentType} for OData library.
@@ -86,9 +87,20 @@ public class ContentType {
KNOWN_MIME_TYPES.add("multipart");
KNOWN_MIME_TYPES.add("text");
}
+
+ private static final Comparator<String> Q_PARAMETER_COMPARATOR = new Comparator<String>() {
+ @Override
+ public int compare(String o1, String o2) {
+ Float f1 = parseQParameterValue(o1);
+ Float f2 = parseQParameterValue(o2);
+ return f2.compareTo(f1);
+ }
+ };
+
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 = "*";
private static final String VERBOSE = "verbose";
@@ -99,6 +111,8 @@ public class ContentType {
public static final String PARAMETER_TYPE = "type";
public static final String CHARSET_UTF_8 = "utf-8";
+ private static final Pattern Q_PARAMETER_VALUE_PATTERN = Pattern.compile("1|0|1\\.0{1,3}|0\\.\\d{1,3}");
+
public static final ContentType WILDCARD = new ContentType(MEDIA_TYPE_WILDCARD, MEDIA_TYPE_WILDCARD);
public static final ContentType APPLICATION_XML = new ContentType("application", "xml", ODataFormat.XML);
@@ -192,6 +206,20 @@ public class ContentType {
}
/**
+ * Validates if given <code>format</code> is parseable and can be used as input for
+ * {@link #create(String)} method.
+ * @param format to be validated string
+ * @return <code>true</code> if format is parseable otherwise <code>false</code>
+ */
+ public static boolean isParseableAsCustom(final String format) {
+ try {
+ return ContentType.create(format) != null;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ /**
* Creates a content type from type and subtype
* @param type
* @param subtype
@@ -364,6 +392,19 @@ public class ContentType {
}
/**
+ * Sort given list (which must contains content type formated string) for their {@value #PARAMETER_Q} value
+ * as defined in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">RFC 2616 section 4.1</a> and
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9">RFC 2616 Section 3.9</a>.
+ *
+ * <b>Attention:</b> For invalid values a {@value #PARAMETER_Q} value from <code>-1</code> is used for sorting.
+ *
+ * @param toSort list which is sorted and hence re-arranged
+ */
+ public static void sortForQParameter(List<String> toSort) {
+ Collections.sort(toSort, ContentType.Q_PARAMETER_COMPARATOR);
+ }
+
+ /**
* Map combination of type/subtype to corresponding {@link ODataFormat}.
*
* @param type type which is used for mapping
@@ -406,6 +447,7 @@ public class ContentType {
/**
* Valid input are <code>;</code> separated <code>key=value</code> pairs
* without spaces between key and value.
+ * <b>Attention:</b> <code>q</code> parameter is validated but not added to result map
*
* <p>
* See RFC 2616:
@@ -423,13 +465,18 @@ public class ContentType {
if (parameters != null) {
String[] splittedParameters = parameters.split(PARAMETER_SEPARATOR);
for (String parameter : splittedParameters) {
- String[] keyValue = parameter.split("=");
+ String[] keyValue = parameter.split(PARAMETER_KEY_VALUE_SEPARATOR);
String key = keyValue[0].trim().toLowerCase(Locale.ENGLISH);
- if (isParameterAllowed(key)) {
- String value = keyValue.length > 1 ? keyValue[1] : null;
- if (value != null && isLws(value.charAt(0))) {
- throw new IllegalArgumentException("Value of parameter '" + key + "' starts with a LWS ('" + parameters + "').");
+ String value = keyValue.length > 1 ? keyValue[1] : null;
+ if (value != null && isLws(value.charAt(0))) {
+ throw new IllegalArgumentException("Value of parameter '" + key + "' starts with a LWS ('" + parameters + "').");
+ }
+ if(PARAMETER_Q.equals(key.toLowerCase(Locale.US))) {
+ // 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);
}
}
@@ -437,6 +484,43 @@ public class ContentType {
return parameterMap;
}
+ /**
+ * Parse value of {@value #PARAMETER_Q} <code>parameter</code> out of content type/parameters.
+ * If no {@value #PARAMETER_Q} <code>parameter</code> is in <code>content type/parameters</code> parameter found
+ * <code>1</code> is returned.
+ * If {@value #PARAMETER_Q} <code>parameter</code> is invalid <code>-1</code> is returned.
+ *
+ * @param contentType parameter which is parsed for {@value #PARAMETER_Q} <code>parameter</code> value
+ * @return value of {@value #PARAMETER_Q} <code>parameter</code> or <code>1</code> or <code>-1</code>
+ */
+ 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 a allowed parameter.
+ *
+ * @param key
+ * @return
+ */
+ private static boolean isParameterAllowed(final String key) {
+ return key != null && !PARAMETER_Q.equals(key.toLowerCase(Locale.US));
+ }
+
/**
* Validate if given character is a linear whitepace (includes <code>horizontal-tab, linefeed, carriage return and space</code>).
*
@@ -455,9 +539,6 @@ public class ContentType {
}
}
- private static boolean isParameterAllowed(final String key) {
- return key != null && !PARAMETER_Q.equals(key.toLowerCase(Locale.US));
- }
/**
* Ensure that charset parameter ({@link #PARAMETER_CHARSET}) is set on returned content type
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/95a43b42/odata-core/src/main/java/org/apache/olingo/odata2/core/rest/RestUtil.java
----------------------------------------------------------------------
diff --git a/odata-core/src/main/java/org/apache/olingo/odata2/core/rest/RestUtil.java b/odata-core/src/main/java/org/apache/olingo/odata2/core/rest/RestUtil.java
index 56b883d..aa46924 100644
--- a/odata-core/src/main/java/org/apache/olingo/odata2/core/rest/RestUtil.java
+++ b/odata-core/src/main/java/org/apache/olingo/odata2/core/rest/RestUtil.java
@@ -27,12 +27,12 @@ import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
@@ -128,29 +128,22 @@ public class RestUtil {
}
public static List<String> extractAcceptHeaders(final SubLocatorParameter param) throws ODataBadRequestException {
- // first validate all accept header content types are 'parseable' and valif from our point of view
List<String> acceptHeaders = param.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT);
+ List<String> toSort = new LinkedList<String>();
if (acceptHeaders != null) {
for (String acceptHeader : acceptHeaders) {
String[] contentTypes = acceptHeader.split(",");
for (String contentType : contentTypes) {
- if (!ContentType.isParseable(contentType.trim())) {
- throw new ODataBadRequestException(ODataBadRequestException.INVALID_HEADER.addContent(HttpHeaders.ACCEPT, acceptHeader));
- }
+ toSort.add(contentType.trim());
}
}
}
- // then get sorted list of acceptable media type from jax-rs
- final List<String> mediaTypes = new ArrayList<String>();
- final List<MediaType> acceptableMediaTypes = param.getHttpHeaders().getAcceptableMediaTypes();
- for (final MediaType mediaType : acceptableMediaTypes) {
- mediaTypes.add(mediaType.toString());
- }
-
- return mediaTypes;
+ ContentType.sortForQParameter(toSort);
+ return toSort;
}
+
public static Map<String, String> extractRequestHeaders(final javax.ws.rs.core.HttpHeaders httpHeaders) {
final MultivaluedMap<String, String> headers = httpHeaders.getRequestHeaders();
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/95a43b42/odata-core/src/test/java/org/apache/olingo/odata2/core/commons/ContentTypeTest.java
----------------------------------------------------------------------
diff --git a/odata-core/src/test/java/org/apache/olingo/odata2/core/commons/ContentTypeTest.java b/odata-core/src/test/java/org/apache/olingo/odata2/core/commons/ContentTypeTest.java
index 7734df4..14cac0f 100644
--- a/odata-core/src/test/java/org/apache/olingo/odata2/core/commons/ContentTypeTest.java
+++ b/odata-core/src/test/java/org/apache/olingo/odata2/core/commons/ContentTypeTest.java
@@ -971,6 +971,27 @@ public class ContentTypeTest extends BaseTest {
assertTrue(ContentType.create("*/*").hasWildcard());
}
+ @Test
+ public void testQParameterSort() {
+ validateSort(Arrays.asList("a1/b1;q=0.2", "a2/b2;q=0.5", "a3/b3;q=0.333"), 1,2,0);
+ validateSort(Arrays.asList("a1/b1;q=0", "a2/b2;q=0.5", "a3/b3;q=0.333"), 1,2,0);
+ validateSort(Arrays.asList("a1/b1;q=1", "a2/b2;q=0.5", "a3/b3;q=0.333"), 0,1,2);
+ validateSort(Arrays.asList("a1/b1;q=1", "a2/b2;q=0.5", "a3/b3;q=1.333"), 0,1,2);
+ validateSort(Arrays.asList("a1/b1;q=0.2", "a2/b2;q=0.9", "a3/b3"), 2,1,0);
+ }
+
+ private void validateSort(List<String> toSort, int ... expectedSequence) {
+ List<String> expected = new ArrayList<String>();
+ for (int i : expectedSequence) {
+ expected.add(toSort.get(i));
+ }
+
+ ContentType.sortForQParameter(toSort);
+ for (int i = 0; i < expectedSequence.length; i++) {
+ assertEquals(expected.get(i), toSort.get(i));
+ }
+ }
+
private Map<String, String> addParameters(final String... content) {
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < content.length - 1; i += 2) {
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/95a43b42/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/AcceptHeaderTypeTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/AcceptHeaderTypeTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/AcceptHeaderTypeTest.java
index eb9b56d..59b2397 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/AcceptHeaderTypeTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/AcceptHeaderTypeTest.java
@@ -31,8 +31,6 @@ import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
-import org.junit.Test;
-
import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.api.edm.provider.EdmProvider;
import org.apache.olingo.odata2.api.exception.ODataException;
@@ -42,6 +40,7 @@ import org.apache.olingo.odata2.api.uri.info.GetEntitySetUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntityUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetMetadataUriInfo;
import org.apache.olingo.odata2.testutil.mock.EdmTestProvider;
+import org.junit.Test;
/**
*
@@ -128,8 +127,13 @@ public class AcceptHeaderTypeTest extends AbstractBasicTest {
}
@Test
+ public void illegalQParameterInContentType() throws Exception {
+ testGetRequest("Rooms('1')", "application/json;q=2", HttpStatusCodes.BAD_REQUEST, "application/xml");
+ }
+
+ @Test
public void illegalSpaceInContentType() throws Exception {
- testGetRequest("Rooms('1')", "application /json", HttpStatusCodes.BAD_REQUEST, "application/json");
+ testGetRequest("Rooms('1')", "application /json", HttpStatusCodes.BAD_REQUEST, "application/xml");
}
@Test
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/95a43b42/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/FitLoadTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/FitLoadTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/FitLoadTest.java
index 3462fd3..2cc063a 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/FitLoadTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/basic/FitLoadTest.java
@@ -27,11 +27,12 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
+import javax.ws.rs.core.HttpHeaders;
+
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
-import org.junit.Test;
-
+import org.apache.olingo.odata2.api.commons.HttpContentType;
import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.processor.ODataResponse;
@@ -40,6 +41,7 @@ import org.apache.olingo.odata2.api.processor.part.MetadataProcessor;
import org.apache.olingo.odata2.api.processor.part.ServiceDocumentProcessor;
import org.apache.olingo.odata2.api.uri.info.GetMetadataUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetServiceDocumentUriInfo;
+import org.junit.Test;
/**
*
@@ -76,8 +78,10 @@ public class FitLoadTest extends AbstractBasicTest {
for (int i = 0; i < LOOP_COUNT; i++) {
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
connection.setRequestMethod("GET");
+ connection.setRequestProperty(HttpHeaders.ACCEPT, HttpContentType.WILDCARD);
connection.connect();
assertEquals(HttpStatusCodes.OK.getStatusCode(), connection.getResponseCode());
+ assertEquals(HttpContentType.APPLICATION_XML_UTF8, connection.getContentType());
}
}
http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/95a43b42/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/contentnegotiation/BasicContentNegotiationTest.java
----------------------------------------------------------------------
diff --git a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/contentnegotiation/BasicContentNegotiationTest.java b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/contentnegotiation/BasicContentNegotiationTest.java
index 4ce1eea..460317d 100644
--- a/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/contentnegotiation/BasicContentNegotiationTest.java
+++ b/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/contentnegotiation/BasicContentNegotiationTest.java
@@ -72,8 +72,6 @@ public class BasicContentNegotiationTest extends AbstractContentNegotiationTest
final String parameter = ";odata=verbose";
performRequestAndValidateResponseForAcceptHeader("Employees('1')", APPLICATION_JSON + parameter, APPLICATION_JSON + parameter);
performRequestAndValidateResponseForAcceptHeader("Employees('1')", APPLICATION_JSON_UTF8 + parameter, APPLICATION_JSON_UTF8 + parameter);
-// performRequestAndValidateResponseForAcceptHeader("Rooms('1')", APPLICATION_XML + parameter, APPLICATION_XML_UTF8 + parameter);
-// performRequestAndValidateResponseForAcceptHeader("Employees('1')", APPLICATION_XML_UTF8 + parameter, APPLICATION_XML_UTF8 + parameter);
performRequestAndValidateResponseForAcceptHeader("Employees('1')/$count", APPLICATION_XML_UTF8 + parameter, TEXT_PLAIN_UTF8);
performRequestAndValidateResponseForAcceptHeader("Employees('1')/$count", APPLICATION_JSON + parameter, TEXT_PLAIN_UTF8 );
performRequestAndValidateResponseForAcceptHeader("Buildings('1')/$count", APPLICATION_XML_UTF8 + parameter, TEXT_PLAIN_UTF8);
@@ -114,6 +112,17 @@ public class BasicContentNegotiationTest extends AbstractContentNegotiationTest
}
@Test
+ public void acceptHeaderToContentTypeIgnoredAcceptHeadersValue() throws Exception {
+ final String expectedImageContentType = "image/jpeg";
+ performRequestAndValidateResponseForAcceptHeader("Employees('1')/$value", APPLICATION_XML_UTF8, expectedImageContentType);
+ performRequestAndValidateResponseForAcceptHeader("Employees('1')/$value", APPLICATION_JSON, expectedImageContentType);
+
+ final String expectedTextContentType = "text/plain;charset=utf-8";
+ performRequestAndValidateResponseForAcceptHeader("Employees('1')/Age/$value", APPLICATION_XML_UTF8, expectedTextContentType);
+ performRequestAndValidateResponseForAcceptHeader("Employees('1')/Age/$value", APPLICATION_JSON, expectedTextContentType);
+ }
+
+ @Test
public void acceptHeaderToContentTypeNotAcceptable() throws Exception {
final String parameterUnknown = ";someUnknownParameter=withAValue";
performRequestAndValidateResponseForNotAcceptable("Employees('1')", APPLICATION_JSON + parameterUnknown);