[OLINGO-337] improve AcceptType class to allow '*'

This commit is contained in:
Stephan Klevenz 2014-07-03 12:54:07 +02:00
parent 434246abe9
commit f4640911af
6 changed files with 162 additions and 85 deletions

View File

@ -23,7 +23,6 @@ import static org.junit.Assert.assertEquals;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import org.apache.olingo.commons.api.http.HttpHeader;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -43,7 +42,6 @@ public class PingITCase {
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET"); connection.setRequestMethod("GET");
connection.setRequestProperty(HttpHeader.ACCEPT, "application/json");
connection.connect(); connection.connect();
int code = connection.getResponseCode(); int code = connection.getResponseCode();
@ -59,7 +57,6 @@ public class PingITCase {
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET"); connection.setRequestMethod("GET");
connection.setRequestProperty(HttpHeader.ACCEPT, "application/json");
connection.connect(); connection.connect();
int code = connection.getResponseCode(); int code = connection.getResponseCode();

View File

@ -46,11 +46,8 @@ import java.util.regex.Pattern;
*/ */
public class AcceptType { public class AcceptType {
private static final String MEDIA_TYPE_WILDCARD = "*"; public static final AcceptType WILDCARD = create(TypeUtil.MEDIA_TYPE_WILDCARD, TypeUtil.MEDIA_TYPE_WILDCARD,
private static final String PARAMETER_Q = "q"; createParameterMap(), 1F);
private static final Pattern Q_PARAMETER_VALUE_PATTERN = Pattern.compile("1|0|1\\.0{1,3}|0\\.\\d{1,3}");
public static final AcceptType WILDCARD = create(MEDIA_TYPE_WILDCARD, MEDIA_TYPE_WILDCARD, createParameterMap(), 1F);
private final String type; private final String type;
private final String subtype; private final String subtype;
@ -81,25 +78,51 @@ public class AcceptType {
} }
List<String> typeSubtype = new ArrayList<String>(); List<String> typeSubtype = new ArrayList<String>();
parameters = createParameterMap(); parameters = createParameterMap();
ContentType.parse(type, typeSubtype, parameters); parse(type, typeSubtype, parameters);
this.type = typeSubtype.get(0); this.type = typeSubtype.get(0);
subtype = typeSubtype.get(1); subtype = typeSubtype.get(1);
if (MEDIA_TYPE_WILDCARD.equals(this.type) && !MEDIA_TYPE_WILDCARD.equals(subtype)) { if (TypeUtil.MEDIA_TYPE_WILDCARD.equals(this.type) && !TypeUtil.MEDIA_TYPE_WILDCARD.equals(subtype)) {
throw new IllegalArgumentException("Illegal combination of WILDCARD type with NONE WILDCARD subtype."); throw new IllegalArgumentException("Illegal combination of WILDCARD type with NONE WILDCARD subtype.");
} }
final String q = parameters.get(PARAMETER_Q); final String q = parameters.get(TypeUtil.PARAMETER_Q);
if (q == null) { if (q == null) {
quality = 1F; quality = 1F;
} else { } else {
if (Q_PARAMETER_VALUE_PATTERN.matcher(q).matches()) { try {
quality = Float.valueOf(q); quality = Float.valueOf(q);
} else { } catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Illegal quality parameter."); throw new IllegalArgumentException("Illegal quality parameter.", e);
} }
parameters.remove(PARAMETER_Q);
} }
} }
private static void
parse(final String format, final List<String> typeSubtype, final Map<String, String> parameters) {
final String[] typesAndParameters = format.split(TypeUtil.PARAMETER_SEPARATOR, 2);
final String types = typesAndParameters[0];
final String params = (typesAndParameters.length > 1 ? typesAndParameters[1] : null);
String[] tokens = types.split(TypeUtil.TYPE_SUBTYPE_SEPARATOR);
if (tokens.length == 1) {
typeSubtype.add(tokens[0]);
typeSubtype.add(TypeUtil.MEDIA_TYPE_WILDCARD);
} else 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 {
typeSubtype.add(tokens[0]);
typeSubtype.add(tokens[1]);
}
} else {
throw new IllegalArgumentException("Too many '" + TypeUtil.TYPE_SUBTYPE_SEPARATOR + "' in format '" + format
+ "'.");
}
TypeUtil.parseParameters(params, parameters);
}
/** /**
* Creates an accept type. * Creates an accept type.
* @param type * @param type
@ -172,7 +195,7 @@ public class AcceptType {
result.append(';').append(key).append('=').append(parameters.get(key)); result.append(';').append(key).append('=').append(parameters.get(key));
} }
if (quality < 1F) { if (quality < 1F) {
result.append(';').append(PARAMETER_Q).append('=').append(quality); result.append(';').append(TypeUtil.PARAMETER_Q).append('=').append(quality);
} }
return result.toString(); return result.toString();
} }
@ -189,13 +212,13 @@ public class AcceptType {
* @return whether this accept type matches the given content type * @return whether this accept type matches the given content type
*/ */
public boolean matches(final ContentType contentType) { public boolean matches(final ContentType contentType) {
if (type.equals(MEDIA_TYPE_WILDCARD)) { if (type.equals(TypeUtil.MEDIA_TYPE_WILDCARD)) {
return true; return true;
} }
if (!type.equalsIgnoreCase(contentType.getType())) { if (!type.equalsIgnoreCase(contentType.getType())) {
return false; return false;
} }
if (subtype.equals(MEDIA_TYPE_WILDCARD)) { if (subtype.equals(TypeUtil.MEDIA_TYPE_WILDCARD)) {
return true; return true;
} }
if (!subtype.equalsIgnoreCase(contentType.getSubtype())) { if (!subtype.equalsIgnoreCase(contentType.getSubtype())) {
@ -246,13 +269,13 @@ public class AcceptType {
if (compare != 0) { if (compare != 0) {
return compare; return compare;
} }
compare = (a1.getType().equals(MEDIA_TYPE_WILDCARD) ? 1 : 0) compare = (a1.getType().equals(TypeUtil.MEDIA_TYPE_WILDCARD) ? 1 : 0)
- (a2.getType().equals(MEDIA_TYPE_WILDCARD) ? 1 : 0); - (a2.getType().equals(TypeUtil.MEDIA_TYPE_WILDCARD) ? 1 : 0);
if (compare != 0) { if (compare != 0) {
return compare; return compare;
} }
compare = (a1.getSubtype().equals(MEDIA_TYPE_WILDCARD) ? 1 : 0) compare = (a1.getSubtype().equals(TypeUtil.MEDIA_TYPE_WILDCARD) ? 1 : 0)
- (a2.getSubtype().equals(MEDIA_TYPE_WILDCARD) ? 1 : 0); - (a2.getSubtype().equals(TypeUtil.MEDIA_TYPE_WILDCARD) ? 1 : 0);
if (compare != 0) { if (compare != 0) {
return compare; return compare;
} }

View File

@ -24,7 +24,6 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TreeMap; import java.util.TreeMap;
@ -46,36 +45,28 @@ import java.util.TreeMap;
*/ */
public class ContentType { public class ContentType {
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 = "/";
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 ContentType APPLICATION_XML = create("application", "xml"); public static final ContentType APPLICATION_XML = create("application", "xml");
public static final ContentType APPLICATION_XML_CS_UTF_8 = create(APPLICATION_XML, PARAMETER_CHARSET, public static final ContentType APPLICATION_XML_CS_UTF_8 = create(APPLICATION_XML, TypeUtil.PARAMETER_CHARSET,
CHARSET_UTF_8); TypeUtil.CHARSET_UTF_8);
public static final ContentType APPLICATION_ATOM_XML = create("application", "atom+xml"); 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, public static final ContentType APPLICATION_ATOM_XML_CS_UTF_8 = create(APPLICATION_ATOM_XML,
PARAMETER_CHARSET, CHARSET_UTF_8); TypeUtil.PARAMETER_CHARSET, TypeUtil.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 = create(APPLICATION_ATOM_XML,TypeUtil. PARAMETER_TYPE, "entry");
public static final ContentType APPLICATION_ATOM_XML_ENTRY_CS_UTF_8 = create(APPLICATION_ATOM_XML_ENTRY, public static final ContentType APPLICATION_ATOM_XML_ENTRY_CS_UTF_8 = create(APPLICATION_ATOM_XML_ENTRY,
PARAMETER_CHARSET, CHARSET_UTF_8); TypeUtil. PARAMETER_CHARSET, TypeUtil.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 = create(APPLICATION_ATOM_XML,TypeUtil. PARAMETER_TYPE, "feed");
public static final ContentType APPLICATION_ATOM_XML_FEED_CS_UTF_8 = create(APPLICATION_ATOM_XML_FEED, public static final ContentType APPLICATION_ATOM_XML_FEED_CS_UTF_8 = create(APPLICATION_ATOM_XML_FEED,
PARAMETER_CHARSET, CHARSET_UTF_8); TypeUtil. PARAMETER_CHARSET,TypeUtil.CHARSET_UTF_8);
public static final ContentType APPLICATION_ATOM_SVC = create("application", "atomsvc+xml"); 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, public static final ContentType APPLICATION_ATOM_SVC_CS_UTF_8 = create(APPLICATION_ATOM_SVC,
PARAMETER_CHARSET, CHARSET_UTF_8); TypeUtil. PARAMETER_CHARSET, TypeUtil.CHARSET_UTF_8);
public static final ContentType APPLICATION_JSON = create("application", "json"); public static final ContentType APPLICATION_JSON = create("application", "json");
public static final ContentType APPLICATION_JSON_CS_UTF_8 = create(APPLICATION_JSON, public static final ContentType APPLICATION_JSON_CS_UTF_8 = create(APPLICATION_JSON,
PARAMETER_CHARSET, CHARSET_UTF_8); TypeUtil. PARAMETER_CHARSET,TypeUtil. CHARSET_UTF_8);
public static final ContentType APPLICATION_OCTET_STREAM = create("application", "octet-stream"); 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 = create("text", "plain");
public static final ContentType TEXT_PLAIN_CS_UTF_8 = create(TEXT_PLAIN, PARAMETER_CHARSET, CHARSET_UTF_8); public static final ContentType TEXT_PLAIN_CS_UTF_8 = create(TEXT_PLAIN, TypeUtil.PARAMETER_CHARSET,TypeUtil. CHARSET_UTF_8);
public static final ContentType MULTIPART_MIXED = create("multipart", "mixed"); public static final ContentType MULTIPART_MIXED = create("multipart", "mixed");
public static final ContentType APPLICATION_XHTML_XML = create("application", "xhtml+xml"); public static final ContentType APPLICATION_XHTML_XML = create("application", "xhtml+xml");
@ -118,7 +109,7 @@ public class ContentType {
} }
int len = type.length(); int len = type.length();
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
if (type.charAt(i) == WHITESPACE_CHAR) { if (type.charAt(i) == TypeUtil.WHITESPACE_CHAR) {
throw new IllegalArgumentException("Illegal whitespace found for type '" + type + "'."); throw new IllegalArgumentException("Illegal whitespace found for type '" + type + "'.");
} }
} }
@ -196,14 +187,14 @@ public class ContentType {
} }
} }
protected static void private static void
parse(final String format, final List<String> typeSubtype, final Map<String, String> parameters) { parse(final String format, final List<String> typeSubtype, final Map<String, String> parameters) {
final String[] typesAndParameters = format.split(PARAMETER_SEPARATOR, 2); final String[] typesAndParameters = format.split(TypeUtil.PARAMETER_SEPARATOR, 2);
final String types = typesAndParameters[0]; final String types = typesAndParameters[0];
final String params = (typesAndParameters.length > 1 ? typesAndParameters[1] : null); final String params = (typesAndParameters.length > 1 ? typesAndParameters[1] : null);
if (types.contains(TYPE_SUBTYPE_SEPARATOR)) { if (types.contains(TypeUtil.TYPE_SUBTYPE_SEPARATOR)) {
String[] tokens = types.split(TYPE_SUBTYPE_SEPARATOR); String[] tokens = types.split(TypeUtil.TYPE_SUBTYPE_SEPARATOR);
if (tokens.length == 2) { if (tokens.length == 2) {
if (tokens[0] == null || tokens[0].isEmpty()) { if (tokens[0] == null || tokens[0].isEmpty()) {
throw new IllegalArgumentException("No type found in format '" + format + "'."); throw new IllegalArgumentException("No type found in format '" + format + "'.");
@ -214,45 +205,14 @@ public class ContentType {
typeSubtype.add(tokens[1]); typeSubtype.add(tokens[1]);
} }
} else { } else {
throw new IllegalArgumentException("Too many '" + TYPE_SUBTYPE_SEPARATOR + "' in format '" + format + "'."); throw new IllegalArgumentException("Too many '" +TypeUtil.TYPE_SUBTYPE_SEPARATOR + "' in format '" + format + "'.");
} }
} else { } else {
throw new IllegalArgumentException("No separator '" + TYPE_SUBTYPE_SEPARATOR throw new IllegalArgumentException("No separator '" +TypeUtil.TYPE_SUBTYPE_SEPARATOR
+ "' was found in format '" + format + "'."); + "' was found in format '" + format + "'.");
} }
parseParameters(params, parameters); TypeUtil.parseParameters(params, parameters);
}
/**
* Valid input are <code>;</code> separated <code>key=value</code> pairs
* without spaces between key and value.
* <p>
* 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.
* </p>
*
* @param parameters
* @param parameterMap
*/
private static void parseParameters(final String parameters, final Map<String, String> parameterMap) {
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 + "').");
}
parameterMap.put(key, value);
}
}
} }
public String getType() { public String getType() {
@ -406,7 +366,7 @@ public class ContentType {
public String toContentTypeString() { public String toContentTypeString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(type).append(TYPE_SUBTYPE_SEPARATOR).append(subtype); sb.append(type).append(TypeUtil.TYPE_SUBTYPE_SEPARATOR).append(subtype);
for (String key : parameters.keySet()) { for (String key : parameters.keySet()) {
sb.append(";").append(key).append("=").append(parameters.get(key)); sb.append(";").append(key).append("=").append(parameters.get(key));

View File

@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.commons.api.format;
import java.util.Locale;
import java.util.Map;
class TypeUtil {
static final String MEDIA_TYPE_WILDCARD = "*";
static final String PARAMETER_Q = "q";
static final char WHITESPACE_CHAR = ' ';
static final String PARAMETER_SEPARATOR = ";";
static final String PARAMETER_KEY_VALUE_SEPARATOR = "=";
static final String TYPE_SUBTYPE_SEPARATOR = "/";
static final String TYPE_SUBTYPE_WILDCARD = "*";
static final String PARAMETER_TYPE = "type";
static final String PARAMETER_CHARSET = "charset";
static final String CHARSET_UTF_8 = "UTF-8";
/**
* Valid input are <code>;</code> separated <code>key=value</code> pairs
* without spaces between key and value.
* <p>
* 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.
* </p>
*
* @param parameters
* @param parameterMap
*/
static void parseParameters(final String parameters, final Map<String, String> parameterMap) {
if (parameters != null) {
String[] splittedParameters = parameters.split(TypeUtil.PARAMETER_SEPARATOR);
for (String parameter : splittedParameters) {
String[] keyValue = parameter.split(TypeUtil.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 + "').");
}
parameterMap.put(key, value);
}
}
}
}

View File

@ -23,7 +23,6 @@ import static org.junit.Assert.assertNotNull;
import java.util.List; import java.util.List;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
public class AcceptTypeTest { public class AcceptTypeTest {
@ -45,16 +44,21 @@ public class AcceptTypeTest {
assertEquals(1, atl.size()); assertEquals(1, atl.size());
assertEquals("a/a", atl.get(0).toString()); assertEquals("a/a", atl.get(0).toString());
} }
@Test(expected = IllegalArgumentException.class)
public void testWrongQParameter() {
AcceptType.create(" a/a;q=z ");
}
@Test @Test
@Ignore("buggy and not yet fixed")
public void testWildcard() { public void testWildcard() {
List<AcceptType> atl = AcceptType.create("*; q=.2"); List<AcceptType> atl = AcceptType.create("*; q=.2");
assertNotNull(atl); assertNotNull(atl);
assertEquals(1, atl.size()); assertEquals(1, atl.size());
assertEquals("", atl.get(0).getType()); assertEquals("*", atl.get(0).getType());
assertEquals("", atl.get(0).getSubtype()); assertEquals("*", atl.get(0).getSubtype());
assertEquals(".2", atl.get(0).getParameters().get("q")); assertEquals(".2", atl.get(0).getParameters().get("q"));
assertEquals(new Float(0.2), atl.get(0).getQuality());
} }
} }

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.commons.api.format;
public class ContentTypeTest {
}