parameters, String header, int offset) {
+ while (true) {
+ int equalsIndex = header.indexOf('=', offset);
+ if (equalsIndex < 0)
+ throw new BadRequestException("Malformed parameters: " + header);
+ String name = header.substring(offset, equalsIndex).trim();
+ offset = equalsIndex + 1;
+ if (header.charAt(offset) == '"') {
+ int end = offset;
+ ++offset;
+ do {
+ end = header.indexOf('"', ++end);
+ if (end < 0)
+ throw new BadRequestException("Quoted string is not closed: " + header);
+ } while (header.charAt(end - 1) == '\\');
+ String value = header.substring(offset, end);
+ parameters.put(name, value);
+ offset = end + 1;
+
+ int parameterEndIndex = header.indexOf(';', offset);
+ int itemEndIndex = header.indexOf(',', offset);
+ if (parameterEndIndex == itemEndIndex) {
+ assert itemEndIndex == -1;
+ if (header.substring(offset).trim().length() != 0)
+ throw new BadRequestException("Tailing garbage: " + header);
+ return -1;
+ } else if (parameterEndIndex < 0 || (itemEndIndex >= 0 && itemEndIndex < parameterEndIndex)) {
+ if (header.substring(offset, itemEndIndex).trim().length() != 0)
+ throw new BadRequestException("Garbage after quoted string: " + header);
+ return itemEndIndex + 1;
+ } else {
+ if (header.substring(offset, parameterEndIndex).trim().length() != 0)
+ throw new BadRequestException("Garbage after quoted string: " + header);
+ offset = parameterEndIndex + 1;
+ }
+ } else {
+ int parameterEndIndex = header.indexOf(';', offset);
+ int itemEndIndex = header.indexOf(',', offset);
+ if (parameterEndIndex == itemEndIndex) {
+ assert itemEndIndex == -1;
+ String value = header.substring(offset).trim();
+ parameters.put(name, value);
+ return -1;
+ } else if (parameterEndIndex < 0 || (itemEndIndex >= 0 && itemEndIndex < parameterEndIndex)) {
+ String value = header.substring(offset, itemEndIndex).trim();
+ parameters.put(name, value);
+ return itemEndIndex + 1;
+ } else {
+ String value = header.substring(offset, parameterEndIndex).trim();
+ parameters.put(name, value);
+ offset = parameterEndIndex + 1;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Evaluates and removes the accept parameters.
+ *
+ * accept-params = ";" "q" "=" qvalue *( accept-extension )
+ * accept-extension = ";" token [ "=" ( token | quoted-string ) ]
+ *
+ @param parameters all parameters in order of appearance.
+ @return the qvalue.
+ @see "accept-params
+ */
+ private static QualityValue evaluateAcceptParameters(Map parameters) {
+ Iterator i = parameters.keySet().iterator();
+ while (i.hasNext()) {
+ String name = i.next();
+ if ("q".equals(name)) {
+ if (i.hasNext()) {
+ logger.warn("Accept extensions not supported.");
+ i.remove();
+ do {
+ i.next();
+ i.remove();
+ } while (i.hasNext());
+ return QualityValue.NOT_ACCEPTABLE;
+ } else {
+ String value = parameters.get(name);
+ i.remove();
+ return QualityValue.valueOf(value);
+ }
+ }
+ }
+ return QualityValue.DEFAULT;
+ }
+
+}
Index: jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/request/VariantQuality.java
===================================================================
--- jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/request/VariantQuality.java (revision 0)
+++ jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/core/request/VariantQuality.java (revision 0)
@@ -0,0 +1,83 @@
+package org.jboss.resteasy.core.request;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+
+/**
+ * A individual variant quality bean for the RVSA.
+ @see "RFC 2296"
+ @author Pascal S. de Kloe
+ */
+public class VariantQuality {
+
+ private QualityValue mediaTypeQualityValue = QualityValue.DEFAULT;
+ private QualityValue characterSetQualityValue = QualityValue.DEFAULT;
+ private QualityValue encodingQualityValue = QualityValue.DEFAULT;
+ private QualityValue languageQualityValue = QualityValue.DEFAULT;
+
+
+ public VariantQuality() {
+ }
+
+
+ public void setMediaTypeQualityValue(QualityValue value) {
+ if (value == null)
+ mediaTypeQualityValue = QualityValue.DEFAULT;
+ else
+ mediaTypeQualityValue = value;
+ }
+
+
+ public void setCharacterSetQualityValue(QualityValue value) {
+ if (value == null)
+ characterSetQualityValue = QualityValue.DEFAULT;
+ else
+ characterSetQualityValue = value;
+ }
+
+
+ public void setEncodingQualityValue(QualityValue value) {
+ if (value == null)
+ encodingQualityValue = QualityValue.DEFAULT;
+ else
+ encodingQualityValue = value;
+ }
+
+
+ public void setLanguageQualityValue(QualityValue value) {
+ if (value == null)
+ languageQualityValue = QualityValue.DEFAULT;
+ else
+ languageQualityValue = value;
+ }
+
+
+ /**
+ @return the quality value between zero and one with five decimal places after the point.
+ @see "3.3 Computing overall quality values"
+ */
+ public BigDecimal getOverallQuality() {
+ BigDecimal qt = BigDecimal.valueOf(mediaTypeQualityValue.intValue(), 3);
+ BigDecimal qc = BigDecimal.valueOf(characterSetQualityValue.intValue(), 3);
+ BigDecimal qe = BigDecimal.valueOf(encodingQualityValue.intValue(), 3);
+ BigDecimal ql = BigDecimal.valueOf(languageQualityValue.intValue(), 3);
+ assert qt.compareTo(BigDecimal.ZERO) >= 0 && qt.compareTo(BigDecimal.ONE) <= 0;
+ assert qc.compareTo(BigDecimal.ZERO) >= 0 && qc.compareTo(BigDecimal.ONE) <= 0;
+ assert qe.compareTo(BigDecimal.ZERO) >= 0 && qe.compareTo(BigDecimal.ONE) <= 0;
+ assert ql.compareTo(BigDecimal.ZERO) >= 0 && ql.compareTo(BigDecimal.ONE) <= 0;
+
+ BigDecimal result = qt;
+ result = result.multiply(qc, MathContext.DECIMAL32);
+ result = result.multiply(qe, MathContext.DECIMAL32);
+ result = result.multiply(ql, MathContext.DECIMAL32);
+ assert result.compareTo(BigDecimal.ZERO) >= 0 && result.compareTo(BigDecimal.ONE) <= 0;
+
+ long round5 = result.scaleByPowerOfTen(5).longValue();
+ result = BigDecimal.valueOf(round5, 5);
+ assert result.compareTo(BigDecimal.ZERO) >= 0 && result.compareTo(BigDecimal.ONE) <= 0;
+
+ return result;
+ }
+
+}
Index: jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/specimpl/RequestImpl.java
===================================================================
--- jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/specimpl/RequestImpl.java (revision 841)
+++ jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/specimpl/RequestImpl.java (working copy)
@@ -1,15 +1,13 @@
package org.jboss.resteasy.specimpl;
-import org.jboss.resteasy.plugins.server.servlet.ServletUtil;
+import org.jboss.resteasy.core.request.ServerDrivenNegotiation;
import org.jboss.resteasy.spi.HttpRequest;
-import org.jboss.resteasy.util.AcceptableVariant;
import org.jboss.resteasy.util.DateUtil;
import org.jboss.resteasy.util.HttpHeaderNames;
import org.jboss.resteasy.util.HttpResponseCodes;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
@@ -50,71 +48,17 @@
{
if (variants == null || variants.size() == 0) throw new IllegalArgumentException("Variant list must not be zero");
- List accepts = headers.getAcceptableMediaTypes();
- List languages = ServletUtil.extractLanguages(headers.getRequestHeaders());
- List encodings = convertString(headers.getRequestHeaders().get(HttpHeaderNames.ACCEPT_ENCODING));
+ ServerDrivenNegotiation negotiation = new ServerDrivenNegotiation();
+ MultivaluedMap requestHeaders = headers.getRequestHeaders();
+ negotiation.setAcceptHeaders(requestHeaders.get(HttpHeaderNames.ACCEPT));
+ negotiation.setAcceptCharsetHeaders(requestHeaders.get(HttpHeaderNames.ACCEPT_CHARSET));
+ negotiation.setAcceptEncodingHeaders(requestHeaders.get(HttpHeaderNames.ACCEPT_ENCODING));
+ negotiation.setAcceptLanguageHeaders(requestHeaders.get(HttpHeaderNames.ACCEPT_LANGUAGE));
-
varyHeader = ResponseBuilderImpl.createVaryHeader(variants);
- return pickVariant(variants, accepts, languages, encodings);
+ return negotiation.getBestMatch(variants);
}
- public static Variant pickVariant(List has, List accepts, List languages, List encodings)
- {
- List wants = new ArrayList();
-
- int langSize = languages.size();
- int encodingSize = encodings.size();
- int typeSize = accepts.size();
-
- int i = 0;
-
- if (langSize > 0 || encodingSize > 0 || typeSize > 0)
- {
- do
- {
- MediaType type = null;
- if (i < typeSize) type = accepts.get(i);
- int j = 0;
- do
- {
- String encoding = null;
- if (j < encodingSize) encoding = encodings.get(j);
- int k = 0;
- do
- {
- String language = null;
- if (k < langSize) language = languages.get(k);
- wants.add(new AcceptableVariant(type, language, encoding));
- k++;
- } while (k < langSize);
- j++;
- } while (j < encodingSize);
- i++;
- } while (i < typeSize);
- }
-
-
- return AcceptableVariant.pick(has, wants);
-
- }
-
-
- public List convertString(List tags)
- {
- ArrayList result = new ArrayList();
- if (tags == null) return result;
- for (String tag : tags)
- {
- String[] split = tag.split(",");
- for (String etag : split)
- {
- result.add(etag.trim());
- }
- }
- return result;
- }
-
public List convertEtag(List tags)
{
ArrayList result = new ArrayList();
Index: jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/QualifiedStringHeader.java
===================================================================
--- jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/QualifiedStringHeader.java (revision 774)
+++ jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/QualifiedStringHeader.java (working copy)
@@ -1,114 +0,0 @@
-package org.jboss.resteasy.util;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class QualifiedStringHeader
-{
- private String value;
- private Map parameters = new HashMap();
- private double q = 1.0;
-
- protected QualifiedStringHeader()
- {
-
- }
-
- public QualifiedStringHeader(String value)
- {
- this.value = value;
- }
-
- public String getValue()
- {
- return value;
- }
-
- public Map getParameters()
- {
- return parameters;
- }
-
- public double getQ()
- {
- return q;
- }
-
- public static QualifiedStringHeader parse(String value)
- {
- QualifiedStringHeader header = new QualifiedStringHeader();
- int idx = value.indexOf(";");
- if (idx < 0)
- {
- header.value = value;
- header.q = 1.0;
- }
- else
- {
- header.value = value.substring(0, idx);
- String params = value.substring(idx + 1);
- if (params.startsWith(";")) params = params.substring(1);
- String[] array = params.split(";");
- for (String param : array)
- {
- int pidx = param.indexOf("=");
- String name = param.substring(0, pidx);
- String val = param.substring(pidx + 1);
- if (name.equals("q"))
- {
- header.q = Double.valueOf(val);
- }
- else header.parameters.put(name, val);
- }
- }
- return header;
- }
-
- public boolean equals(Object o)
- {
- if (this == o) return true;
- if (!(o instanceof QualifiedStringHeader)) return false;
-
- QualifiedStringHeader comp = (QualifiedStringHeader) o;
-
- if (!value.equals(comp.value)) return false;
-
- Map params1 = this.getParameters();
- Map params2 = comp.getParameters();
-
- if (params1 == params2) return true;
- if (params1 == null || params2 == null) return false;
- if (params1.size() == 0 && params2.size() == 0) return true;
- int numParams1 = params1.size();
- if (params1.containsKey("q")) numParams1--;
- int numParams2 = params2.size();
- if (params2.containsKey("q")) numParams2--;
-
- if (numParams1 != numParams2) return false;
- if (numParams1 == 0) return true;
-
- for (Map.Entry entry : params1.entrySet())
- {
- String key = entry.getKey();
- if (key.equals("q")) continue;
- String value = entry.getValue();
- String value2 = params2.get(key);
- if (value == value2) continue; // both null
- if (value == null || value2 == null) return false;
- if (value.equals(value2) == false) return false;
- }
- return true;
- }
-
- public int compareWeight(QualifiedStringHeader header)
- {
- if (q == header.q) return 0;
- else if (q < header.q) return 1;
- else return -1;
- }
-
-}
Index: jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/AcceptableVariant.java
===================================================================
--- jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/AcceptableVariant.java (revision 774)
+++ jaxrs/resteasy-jaxrs/src/main/java/org/jboss/resteasy/util/AcceptableVariant.java (working copy)
@@ -1,137 +0,0 @@
-package org.jboss.resteasy.util;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Variant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class AcceptableVariant implements Comparable
-{
- private Variant variant;
- private MediaType type;
- private QualifiedStringHeader language;
- private QualifiedStringHeader encoding;
-
- public AcceptableVariant(Variant variant)
- {
- this.variant = variant;
- this.type = variant.getMediaType();
- if (variant.getLanguage() != null)
- {
- language = new QualifiedStringHeader(LocaleHelper.toLanguageString(variant.getLanguage()));
- }
- if (variant.getEncoding() != null)
- {
- encoding = QualifiedStringHeader.parse(variant.getEncoding());
- }
- }
-
- public AcceptableVariant(MediaType type, String language, String encoding)
- {
- final Locale locale = language == null ? null : LocaleHelper.extractLocale(language.toLowerCase());
- this.variant = new Variant(type, locale, encoding);
- this.type = type;
- if (language != null) this.language = QualifiedStringHeader.parse(language);
- if (encoding != null) this.encoding = QualifiedStringHeader.parse(encoding);
-
- }
-
- public int compareTo(AcceptableVariant acceptableVariant)
- {
- int compare = 0;
- if (type == acceptableVariant.type) compare = 0;
- else if (type != null && acceptableVariant.type != null)
- compare = MediaTypeHelper.compareWeight(type, acceptableVariant.type);
- else if (type == null) compare = 1;
- else if (type != null) compare = -1;
-
- if (compare != 0) return compare;
-
- if (language == acceptableVariant.language) compare = 0;
- else if (language != null && acceptableVariant.language != null)
- compare = language.compareWeight(acceptableVariant.language);
- else if (language == null) compare = 1;
- else if (language != null) compare = -1;
-
-
- if (compare != 0) return compare;
-
- if (encoding == acceptableVariant.encoding) compare = 0;
- else if (encoding != null && acceptableVariant.encoding != null)
- compare = encoding.compareWeight(acceptableVariant.encoding);
- else if (encoding == null) return 1;
- else if (encoding != null) return -1;
-
- return compare;
- }
-
- public Variant getVariant()
- {
- return variant;
- }
-
- public MediaType getType()
- {
- return type;
- }
-
- public QualifiedStringHeader getLanguage()
- {
- return language;
- }
-
- public QualifiedStringHeader getEncoding()
- {
- return encoding;
- }
-
- public static List sort(List acceptable)
- {
- Collections.sort(acceptable);
- List sorted = new ArrayList();
- for (AcceptableVariant v : acceptable)
- {
- sorted.add(v.getVariant());
- }
- return sorted;
- }
-
- public static Variant pick(List has, List acceptable)
- {
- Collections.sort(acceptable);
-
- ArrayList produces = new ArrayList();
- for (Variant v : has) produces.add(new AcceptableVariant(v));
-
- for (AcceptableVariant accept : acceptable)
- {
- for (AcceptableVariant produce : produces)
- {
- boolean match = false;
- if (produce.getType() == null || accept.getType() == null) match = true;
- else match = MediaTypeHelper.equivalent(produce.getType(), accept.getType());
-
- if (!match) continue;
-
- match = false;
- if (produce.getLanguage() == null || accept.getLanguage() == null) match = true;
- else match = produce.getLanguage().equals(accept.getLanguage());
-
- if (!match) continue;
-
- match = false;
- if (produce.getEncoding() == null || accept.getEncoding() == null) match = true;
- else match = produce.getEncoding().equals(accept.getEncoding());
-
- if (match) return produce.getVariant();
- }
- }
- return null;
- }
-}
Index: jaxrs/providers/jaxb/src/test/java/org/jboss/resteasy/test/providers/jaxb/CharacterSetTest.java
===================================================================
--- jaxrs/providers/jaxb/src/test/java/org/jboss/resteasy/test/providers/jaxb/CharacterSetTest.java (revision 0)
+++ jaxrs/providers/jaxb/src/test/java/org/jboss/resteasy/test/providers/jaxb/CharacterSetTest.java (revision 0)
@@ -0,0 +1,137 @@
+package org.jboss.resteasy.test.providers.jaxb;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Variant;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.jboss.resteasy.core.Dispatcher;
+import org.jboss.resteasy.mock.MockDispatcherFactory;
+import org.jboss.resteasy.mock.MockHttpRequest;
+import org.jboss.resteasy.mock.MockHttpResponse;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+
+/**
+ @see RFC 2616 paragraph 14.2: "Accept-Charset"
+ @see RFC 1521 paragraph 7.1.1: "The charset parameter"
+ @author Pascal S. de Kloe
+ */
+@Path("/")
+public class CharacterSetTest {
+
+ private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
+
+ private final String[] characterSets = {"UTF-8", "UTF-16LE"};
+
+ private final DocumentBuilder documentBuilder;
+
+
+ @XmlRootElement(name="test")
+ public static class TestData {
+ private String text = "Some arbitrary text.";
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String value) {
+ text = value;
+ }
+ }
+
+ @GET
+ @Path("plain")
+ @Produces("application/xml")
+ public Response getPlain() {
+ return Response.ok(new TestData()).build();
+ }
+
+ @GET
+ @Path("variant")
+ @Produces("application/xml")
+ public Response getVariant(@Context Request request) {
+ int i = characterSets.length;
+ MediaType[] mediaTypes = new MediaType[i];
+ while (--i >= 0)
+ mediaTypes[i] = MediaType.valueOf("application/xml;charset=" + characterSets[i]);
+ List variants = Variant.mediaTypes(mediaTypes).build();
+ Variant variant = request.selectVariant(variants);
+ if (variant == null)
+ return Response.notAcceptable(variants).build();
+ return Response.ok(new TestData(), variant).build();
+ }
+
+ public CharacterSetTest() throws ParserConfigurationException {
+ documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ dispatcher.getRegistry().addSingletonResource(this);
+ }
+
+ @Test
+ public void plainCharset() throws URISyntaxException, SAXException, IOException {
+ assertCharset("/plain");
+ }
+
+ @Test
+ public void plainAccept() throws URISyntaxException, SAXException, IOException {
+ assertAccept("/plain");
+ }
+
+ @Test
+ public void variantCharset() throws URISyntaxException, SAXException, IOException {
+ assertCharset("/variant");
+ }
+
+ @Test
+ public void variantAccept() throws URISyntaxException, SAXException, IOException {
+ assertAccept("/variant");
+ }
+
+
+ private void assertCharset(String path) throws URISyntaxException, SAXException, IOException {
+ for (String characterSet : characterSets) {
+ MockHttpRequest request = MockHttpRequest.get(path);
+ request.accept("application/xml");
+ request.header("Accept-Charset", characterSet);
+ MockHttpResponse response = new MockHttpResponse();
+ dispatcher.invoke(request, response);
+ assertEquals("Status code.", 200, response.getStatus());
+ assertXMLCharacterSet(characterSet, response);
+ }
+ }
+
+
+ private void assertAccept(String path) throws URISyntaxException, SAXException, IOException {
+ for (String characterSet : characterSets) {
+ MockHttpRequest request = MockHttpRequest.get(path);
+ request.accept("application/xml;charset=" + characterSet);
+ MockHttpResponse response = new MockHttpResponse();
+ dispatcher.invoke(request, response);
+ assertEquals("Status code.", 200, response.getStatus());
+ assertXMLCharacterSet(characterSet, response);
+ }
+ }
+
+ private void assertXMLCharacterSet(String characterSet, MockHttpResponse response) throws SAXException, IOException {
+ Document document = documentBuilder.parse(new ByteArrayInputStream(response.getOutput()));
+ assertEquals("XML encoding.", characterSet, document.getInputEncoding());
+ }
+
+}