You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wink.apache.org by bl...@apache.org on 2009/10/04 21:46:08 UTC

svn commit: r821592 - in /incubator/wink/trunk: wink-common/src/main/java/org/apache/wink/common/internal/http/ wink-common/src/main/java/org/apache/wink/common/internal/providers/header/ wink-common/src/main/java/org/apache/wink/common/internal/runtim...

Author: bluk
Date: Sun Oct  4 19:46:08 2009
New Revision: 821592

URL: http://svn.apache.org/viewvc?rev=821592&view=rev
Log:
Consider Accept-Charset header in selectVariant

See [WINK-209]

Added:
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptCharset.java   (with props)
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptCharsetHeaderDelegate.java   (with props)
Modified:
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/runtime/RuntimeDelegateImpl.java
    incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/contexts/RequestImpl.java
    incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/ServerMessageContext.java
    incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java

Added: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptCharset.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptCharset.java?rev=821592&view=auto
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptCharset.java (added)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptCharset.java Sun Oct  4 19:46:08 2009
@@ -0,0 +1,156 @@
+/*
+ * 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.wink.common.internal.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.ws.rs.ext.RuntimeDelegate;
+import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
+
+/**
+ * Represent HTTP Accept-Charset header.
+ * <p>
+ * This version of the API does not support construction.
+ * 
+ * @see <a href='http://tools.ietf.org/html/rfc2616#section-14.4'>RFC 2616
+ *      14.4</a>
+ */
+public class AcceptCharset {
+
+    public static final class ValuedCharset implements Comparable<ValuedCharset> {
+        public final double qValue;
+        public final String charset;
+
+        public ValuedCharset(double qValue, String charset) {
+            this.qValue = qValue;
+            this.charset = charset;
+        }
+
+        public int compareTo(ValuedCharset other) {
+            return Double.compare(qValue, other.qValue);
+        }
+
+        public boolean isWildcard() {
+            return charset == null;
+        }
+    }
+
+    private static final HeaderDelegate<AcceptCharset> delegate =
+                                                                    RuntimeDelegate
+                                                                        .getInstance()
+                                                                        .createHeaderDelegate(AcceptCharset.class);
+
+    private final String                               acceptCharsetHeader;
+    private final boolean                              anyAllowed;
+    private final List<String>                         acceptable;
+    private final List<String>                         banned;
+    private final List<AcceptCharset.ValuedCharset>    valuedCharsets;
+
+    public AcceptCharset(String acceptCharset,
+                         List<String> acceptableCharsets,
+                         List<String> bannedCharsets,
+                         boolean anyCharsetAllowed,
+                         List<AcceptCharset.ValuedCharset> valuedCharsets) {
+        this.acceptCharsetHeader = acceptCharset;
+        this.anyAllowed = anyCharsetAllowed;
+        this.banned = Collections.unmodifiableList(bannedCharsets);
+        boolean isISO8859Explicit = false;
+        for (ValuedCharset vCharset : valuedCharsets) {
+            if ("ISO-8859-1".equalsIgnoreCase(vCharset.charset)) {
+                isISO8859Explicit = true;
+                break;
+            }
+        }
+        if (!isISO8859Explicit && !anyAllowed) {
+            ArrayList<String> acceptableCharsetsTemp = new ArrayList<String>(acceptableCharsets);
+            acceptableCharsetsTemp.add("ISO-8859-1");
+            this.acceptable = Collections.unmodifiableList(acceptableCharsetsTemp);
+
+            ArrayList<AcceptCharset.ValuedCharset> valuedCharsetsTemp =
+                new ArrayList<ValuedCharset>(valuedCharsets);
+            valuedCharsetsTemp.add(new AcceptCharset.ValuedCharset(1.0d, "ISO-8859-1"));
+            this.valuedCharsets = Collections.unmodifiableList(valuedCharsetsTemp);
+        } else {
+            this.acceptable = Collections.unmodifiableList(acceptableCharsets);
+            this.valuedCharsets = Collections.unmodifiableList(valuedCharsets);
+        }
+    }
+
+    /**
+     * Provide a list of character sets which are acceptable for the client. If
+     * any charset is acceptable with some non-zero priority (see
+     * {@link #isAnyCharsetAllowed()}), only character sets more preferable than
+     * wildcard are listed.
+     * 
+     * @return unmodifiable list, never <code>null</code>; the list is sorted
+     *         starting with the most preferable charset
+     */
+    public List<String> getAcceptableCharsets() {
+        return acceptable;
+    }
+
+    /**
+     * Is any character set acceptable? Note that expressions are listed by
+     * {@link #getBannedCharsets()}. This means that the value contains wildcard
+     * (with non-zero priority) of the header is not present at all.
+     * 
+     * @return <code>true</code> if any character set is acceptable
+     */
+    public boolean isAnyCharsetAllowed() {
+        return anyAllowed;
+    }
+
+    /**
+     * A list of non-acceptable (q-value 0) character sets, i.e. exception of
+     * {@link #isAnyCharsetAllowed()}.
+     * 
+     * @return never <code>null</code>; always empty if wildcard is not included
+     */
+    public List<String> getBannedCharsets() {
+        return banned;
+    }
+
+    /**
+     * Creates a new instance of AcceptCharset by parsing the supplied string.
+     * 
+     * @param value the Accept-Charset string
+     * @return the newly created AcceptCharset
+     * @throws IllegalArgumentException if the supplied string cannot be parsed
+     */
+    public static AcceptCharset valueOf(String value) throws IllegalArgumentException {
+        return delegate.fromString(value);
+    }
+
+    public String getAcceptCharsetHeader() {
+        return acceptCharsetHeader;
+    }
+
+    public List<AcceptCharset.ValuedCharset> getValuedCharsets() {
+        return valuedCharsets;
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString(this);
+    }
+
+}

Propchange: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptCharset.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptCharsetHeaderDelegate.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptCharsetHeaderDelegate.java?rev=821592&view=auto
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptCharsetHeaderDelegate.java (added)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptCharsetHeaderDelegate.java Sun Oct  4 19:46:08 2009
@@ -0,0 +1,102 @@
+/*
+ * 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.wink.common.internal.providers.header;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
+
+import org.apache.wink.common.internal.http.AcceptCharset;
+
+public class AcceptCharsetHeaderDelegate implements HeaderDelegate<AcceptCharset> {
+
+    public AcceptCharset fromString(String value) throws IllegalArgumentException {
+        List<String> acceptable = new LinkedList<String>();
+        List<String> banned = new LinkedList<String>();
+        boolean anyAllowed = (value == null);
+
+        // parse the Accept-Encoding header
+        List<AcceptCharset.ValuedCharset> vCharsets = parseAcceptCharset(value);
+
+        for (AcceptCharset.ValuedCharset qCharset : vCharsets) {
+            if (anyAllowed) {
+                if (qCharset.qValue == 0 && !qCharset.isWildcard()) {
+                    banned.add(qCharset.charset);
+                }
+            } else {
+                if (qCharset.qValue == 0) {
+                    break; // gone through all acceptable languages
+                }
+                if (qCharset.isWildcard()) {
+                    anyAllowed = true;
+                } else {
+                    acceptable.add(qCharset.charset);
+                }
+            }
+        }
+        return new AcceptCharset(value, acceptable, banned, anyAllowed, vCharsets);
+    }
+
+    private List<AcceptCharset.ValuedCharset> parseAcceptCharset(String acceptableCharsetValue) {
+        List<AcceptCharset.ValuedCharset> qCharsets = new LinkedList<AcceptCharset.ValuedCharset>();
+        if (acceptableCharsetValue == null) {
+            return qCharsets;
+        }
+
+        for (String charsetRange : acceptableCharsetValue.split(",")) {
+            int semicolonIndex = charsetRange.indexOf(';');
+            double qValue;
+            String charsetSpec;
+            if (semicolonIndex == -1) {
+                qValue = 1.0d;
+                charsetSpec = charsetRange;
+            } else {
+                charsetSpec = charsetRange.substring(0, semicolonIndex);
+                int equalsIndex = charsetRange.indexOf('=', semicolonIndex + 1);
+                String qString =
+                    charsetRange.substring(equalsIndex != -1 ? equalsIndex + 1 : charsetRange
+                        .length());
+                try {
+                    qValue = Double.parseDouble(qString.trim());
+                } catch (NumberFormatException nfe) {
+                    // silently ignore incorrect q-specification and assume 1
+                    qValue = 1.0d;
+                }
+            }
+            charsetSpec = charsetSpec.trim();
+            if (charsetSpec.length() == 0) {
+                // ignore empty encoding specifications
+                continue;
+            } else if (charsetSpec.equals("*")) {
+                qCharsets.add(new AcceptCharset.ValuedCharset(qValue, null));
+            } else {
+                qCharsets.add(new AcceptCharset.ValuedCharset(qValue, charsetSpec));
+            }
+        }
+        Collections.sort(qCharsets, Collections.reverseOrder());
+        return qCharsets;
+    }
+
+    public String toString(AcceptCharset value) {
+        return value.getAcceptCharsetHeader();
+    }
+}

Propchange: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptCharsetHeaderDelegate.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/runtime/RuntimeDelegateImpl.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/runtime/RuntimeDelegateImpl.java?rev=821592&r1=821591&r2=821592&view=diff
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/runtime/RuntimeDelegateImpl.java (original)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/runtime/RuntimeDelegateImpl.java Sun Oct  4 19:46:08 2009
@@ -38,10 +38,12 @@
 import org.apache.wink.common.internal.UriBuilderImpl;
 import org.apache.wink.common.internal.VariantListBuilderImpl;
 import org.apache.wink.common.internal.http.Accept;
+import org.apache.wink.common.internal.http.AcceptCharset;
 import org.apache.wink.common.internal.http.AcceptEncoding;
 import org.apache.wink.common.internal.http.AcceptLanguage;
 import org.apache.wink.common.internal.http.ContentDispositionHeader;
 import org.apache.wink.common.internal.http.EntityTagMatchHeader;
+import org.apache.wink.common.internal.providers.header.AcceptCharsetHeaderDelegate;
 import org.apache.wink.common.internal.providers.header.AcceptEncodingHeaderDelegate;
 import org.apache.wink.common.internal.providers.header.AcceptHeaderDelegate;
 import org.apache.wink.common.internal.providers.header.AcceptLanguageHeaderDelegate;
@@ -68,6 +70,7 @@
         headerDelegates.put(EntityTag.class, new EntityTagHeaderDelegate());
         headerDelegates.put(EntityTagMatchHeader.class, new EntityTagMatchHeaderDelegate());
         headerDelegates.put(Accept.class, new AcceptHeaderDelegate());
+        headerDelegates.put(AcceptCharset.class, new AcceptCharsetHeaderDelegate());
         headerDelegates.put(AcceptLanguage.class, new AcceptLanguageHeaderDelegate());
         headerDelegates.put(AcceptEncoding.class, new AcceptEncodingHeaderDelegate());
         headerDelegates.put(ContentDispositionHeader.class, new ContentDispositionHeaderDelegate());

Modified: incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/contexts/RequestImpl.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/contexts/RequestImpl.java?rev=821592&r1=821591&r2=821592&view=diff
==============================================================================
--- incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/contexts/RequestImpl.java (original)
+++ incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/contexts/RequestImpl.java Sun Oct  4 19:46:08 2009
@@ -38,9 +38,11 @@
 import javax.ws.rs.ext.RuntimeDelegate;
 import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
 
+import org.apache.wink.common.internal.http.AcceptCharset;
 import org.apache.wink.common.internal.http.AcceptEncoding;
 import org.apache.wink.common.internal.http.AcceptLanguage;
 import org.apache.wink.common.internal.http.EntityTagMatchHeader;
+import org.apache.wink.common.utils.ProviderUtils;
 import org.apache.wink.server.handlers.MessageContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -282,6 +284,20 @@
             encodings = AcceptEncoding.valueOf(acceptEncodings);
         }
 
+        List<String> acceptableCharsets =
+            msgContext.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT_CHARSET);
+        AcceptCharset charsets = null;
+        if (acceptableCharsets != null) {
+            StringBuilder acceptCharsetsTemp = new StringBuilder();
+            acceptCharsetsTemp.append(acceptableCharsets.get(0));
+            for (int c = 1; c < acceptableCharsets.size(); ++c) {
+                acceptCharsetsTemp.append(",");
+                acceptCharsetsTemp.append(acceptableCharsets.get(c));
+            }
+            String acceptCharsets = acceptCharsetsTemp.toString();
+            charsets = AcceptCharset.valueOf(acceptCharsets);
+        }
+
         VariantQChecked bestVariant = null;
         boolean isIdentityEncodingChecked = false;
 
@@ -325,7 +341,6 @@
                         logger.debug("Accept Media Type: {} is compatible with q-factor {}",
                                      mt,
                                      acceptQFactor);
-                        break;
                     }
                 }
                 if (!isCompatible || !isAcceptable) {
@@ -381,6 +396,52 @@
                 }
             }
 
+            double acceptCharsetQFactor = -1.0d;
+            String vCharset = ProviderUtils.getCharsetOrNull(v.getMediaType());
+            boolean hasCharSet = true;
+
+            if (vCharset == null) {
+                hasCharSet = false;
+            } else if (vCharset != null && charsets != null) {
+                boolean isCompatible = false;
+                logger.debug("Checking variant charset: {}", vCharset);
+                if (charsets.getBannedCharsets().contains(vCharset)) {
+                    logger.debug("Variant charset {} was in unacceptable charsets", vCharset);
+                    continue;
+                }
+                for (AcceptCharset.ValuedCharset charset : charsets.getValuedCharsets()) {
+                    logger
+                        .debug("Checking against Accept-Charset charset {} with quality factor {}",
+                               charset.charset,
+                               charset.qValue);
+                    if (charset.isWildcard() || vCharset.equalsIgnoreCase(charset.charset)) {
+                        logger.debug("Charset is compatible with {}", charset.charset);
+                        isCompatible = true;
+                        acceptCharsetQFactor = charset.qValue;
+                        break;
+                    }
+                }
+
+                if (!isCompatible) {
+                    logger.debug("Variant charset is not compatible {}", vCharset);
+                    /*
+                     * do not remove this from the acceptable list even if not
+                     * compatible but set to -1.0d for now. according to HTTP
+                     * spec, it is "ok" to send
+                     */
+                }
+            }
+
+            if (bestVariant != null) {
+                if (acceptCharsetQFactor < bestVariant.acceptCharsetQFactor && hasCharSet) {
+                    logger
+                        .debug("Best variant's charset {} q-factor {} is greater than current variant {} q-factor {}",
+                               new Object[] {bestVariant.variant, bestVariant.acceptCharsetQFactor,
+                                   v, acceptCharsetQFactor});
+                    continue;
+                }
+            }
+
             double acceptEncodingQFactor = -1.0d;
             String vEncoding = v.getEncoding();
             if (vEncoding != null) {
@@ -441,8 +502,13 @@
                 }
             }
 
-            bestVariant =
-                new VariantQChecked(v, acceptQFactor, acceptLanguageQFactor, acceptEncodingQFactor);
+            if (bestVariant == null || (acceptQFactor > bestVariant.acceptMediaTypeQFactor || acceptLanguageQFactor > bestVariant.acceptLanguageQFactor
+                || acceptEncodingQFactor > bestVariant.acceptEncodingQFactor
+                || (hasCharSet && acceptCharsetQFactor > bestVariant.acceptCharsetQFactor) || (hasCharSet && !bestVariant.hasCharset))) {
+                bestVariant =
+                    new VariantQChecked(v, acceptQFactor, acceptLanguageQFactor,
+                                        acceptEncodingQFactor, acceptCharsetQFactor, hasCharSet);
+            }
         }
 
         if (bestVariant == null) {
@@ -469,6 +535,13 @@
             varyHeaderValue.append(HttpHeaders.ACCEPT_ENCODING);
             isValueWritten = true;
         }
+        if (bestVariant.acceptCharsetQFactor > 0) {
+            if (isValueWritten) {
+                varyHeaderValue.append(", ");
+            }
+            varyHeaderValue.append(HttpHeaders.ACCEPT_CHARSET);
+            isValueWritten = true;
+        }
         String varyHeaderValueStr = varyHeaderValue.toString().trim();
         logger.debug("Vary Header value should be set to {}", varyHeaderValueStr);
         msgContext.setAttribute(RequestImpl.VaryHeader.class, new VaryHeader(varyHeaderValueStr));
@@ -480,15 +553,21 @@
         final double  acceptMediaTypeQFactor;
         final double  acceptLanguageQFactor;
         final double  acceptEncodingQFactor;
+        final double  acceptCharsetQFactor;
+        final boolean hasCharset;
 
         public VariantQChecked(Variant v,
                                double acceptMediaType,
                                double acceptLanguage,
-                               double acceptEncoding) {
+                               double acceptEncoding,
+                               double acceptCharset,
+                               boolean hasCharset) {
             this.variant = v;
             this.acceptMediaTypeQFactor = acceptMediaType;
             this.acceptLanguageQFactor = acceptLanguage;
             this.acceptEncodingQFactor = acceptEncoding;
+            this.acceptCharsetQFactor = acceptCharset;
+            this.hasCharset = hasCharset;
         }
     }
 

Modified: incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/ServerMessageContext.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/ServerMessageContext.java?rev=821592&r1=821591&r2=821592&view=diff
==============================================================================
--- incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/ServerMessageContext.java (original)
+++ incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/ServerMessageContext.java Sun Oct  4 19:46:08 2009
@@ -41,7 +41,6 @@
 import org.apache.wink.common.internal.contexts.ProvidersImpl;
 import org.apache.wink.common.internal.registry.ProvidersRegistry;
 import org.apache.wink.common.internal.runtime.AbstractRuntimeContext;
-import org.apache.wink.common.internal.runtime.RuntimeContextTLS;
 import org.apache.wink.server.handlers.MessageContext;
 import org.apache.wink.server.internal.DeploymentConfiguration;
 import org.apache.wink.server.internal.MediaTypeMapper;

Modified: incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java?rev=821592&r1=821591&r2=821592&view=diff
==============================================================================
--- incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java (original)
+++ incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java Sun Oct  4 19:46:08 2009
@@ -99,6 +99,21 @@
         }
 
         @GET
+        @Path("charset")
+        public Response getOnlyCharset(@Context Request request) {
+            List<Variant> responseVariants =
+                Variant.mediaTypes(MediaType.valueOf(MediaType.TEXT_PLAIN + ";charset=iso-8859-1"),
+                                   MediaType.valueOf(MediaType.TEXT_PLAIN + ";charset=UTF-8"),
+                                   MediaType.valueOf(MediaType.TEXT_PLAIN + ";charset=shift_jis"))
+                    .add().build();
+            Variant bestResponseVariant = request.selectVariant(responseVariants);
+            if (bestResponseVariant != null) {
+                return Response.ok("Hello world!").variant(bestResponseVariant).build();
+            }
+            return Response.notAcceptable(responseVariants).build();
+        }
+
+        @GET
         @Path("multipleheaders")
         public Response getMultipleAcceptHeaders(@Context Request request) {
             List<Variant> responseVariants =
@@ -114,6 +129,27 @@
             }
             return Response.notAcceptable(responseVariants).build();
         }
+
+        @GET
+        @Path("moremultipleheaders")
+        public Response getMoreMultipleAcceptHeaders(@Context Request request) {
+            List<Variant> responseVariants =
+                Collections.unmodifiableList(Variant
+                    .mediaTypes(MediaType.valueOf(MediaType.APPLICATION_JSON + ";charset=utf-8"),
+                                MediaType.valueOf(MediaType.TEXT_PLAIN + ";charset=shift_jis"),
+                                MediaType.valueOf(MediaType.APPLICATION_XML),
+                                MediaType
+                                    .valueOf(MediaType.APPLICATION_JSON + ";charset=iso-8859-1"),
+                                MediaType.valueOf(MediaType.TEXT_PLAIN + ";charset=iso-8859-1"))
+                    .encodings("gzip", "identity", "deflate").languages(Locale.ENGLISH,
+                                                                        Locale.FRENCH,
+                                                                        Locale.US).add().build());
+            Variant bestResponseVariant = request.selectVariant(responseVariants);
+            if (bestResponseVariant != null) {
+                return Response.ok("Hello world!").variant(bestResponseVariant).build();
+            }
+            return Response.notAcceptable(responseVariants).build();
+        }
     }
 
     public void testSimpleMediaTypeSelect() throws Exception {
@@ -361,6 +397,78 @@
         assertEquals(HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
     }
 
+    public void testCharset() throws Exception {
+        // test that a null Accept-Charset means iso-8859-1 is automatically
+        // chosen
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/charset",
+                                                        MediaType.TEXT_PLAIN);
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=iso-8859-1", response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+
+        /*
+         * due to not mentioning of iso-8859-1 and no wildcard, iso-8859-1 is
+         * given a q-factor of 1.0 and since it is the first in the list of
+         * variants, should get chosen
+         */
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root/charset", MediaType.WILDCARD);
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "shift_jis");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=iso-8859-1", response.getContentType());
+        assertEquals("Accept, Accept-Charset", response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root/charset", MediaType.WILDCARD);
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "shift_jis, *;q=0.5");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=shift_jis", response.getContentType());
+        assertEquals("Accept, Accept-Charset", response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root/charset", MediaType.WILDCARD);
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "SHIFT_jis, *;q=0.5");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=shift_jis", response.getContentType());
+        assertEquals("Accept, Accept-Charset", response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root/charset", MediaType.WILDCARD);
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "shift_jis, iso-8859-1;q=0.5");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=shift_jis", response.getContentType());
+        assertEquals("Accept, Accept-Charset", response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root/charset", MediaType.WILDCARD);
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "iso-8859-1");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=iso-8859-1", response.getContentType());
+        assertEquals("Accept, Accept-Charset", response.getHeader(HttpHeaders.VARY));
+
+        /*
+         * due to not mentioning of iso-8859-1 and no wildcard, iso-8859-1 is
+         * given a q-factor of 1.0
+         */
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/charset",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "abcd");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=iso-8859-1", response.getContentType());
+        assertEquals("Accept, Accept-Charset", response.getHeader(HttpHeaders.VARY));
+    }
+
     public void testSimpleMultipleAcceptHeaders() throws Exception {
         MockHttpServletRequest request =
             MockRequestConstructor.constructMockRequest("GET",
@@ -413,4 +521,114 @@
             + ", "
             + HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
     }
+
+    public void testMoreSimpleMultipleAcceptHeaders() throws Exception {
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/moremultipleheaders",
+                                                        MediaType.TEXT_PLAIN + ";q=1.0,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=0.8");
+        request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip;q=0.8,deflate;q=0.7");
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "en-us");
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=shift_jis", response.getContentType());
+        assertEquals("gzip", response.getHeader(HttpHeaders.CONTENT_ENCODING));
+        assertEquals("en_US", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT + ", "
+            + HttpHeaders.ACCEPT_LANGUAGE
+            + ", "
+            + HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/moremultipleheaders",
+                                                        MediaType.TEXT_PLAIN + ";q=0.9,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=1.0");
+        request.addHeader(HttpHeaders.ACCEPT_ENCODING, "deflate;q=0.8,gzip;q=0.7");
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "en-us;q=0.9,fr;q=1.0");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_XML, response.getContentType());
+        assertEquals("deflate", response.getHeader(HttpHeaders.CONTENT_ENCODING));
+        assertEquals("fr", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT + ", "
+            + HttpHeaders.ACCEPT_LANGUAGE
+            + ", "
+            + HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/moremultipleheaders",
+                                                        "text/*" + ";q=0.9,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=1.0");
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "fr;q=1.0, en-us;q=0.9");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_XML, response.getContentType());
+        assertEquals("fr", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT + ", "
+            + HttpHeaders.ACCEPT_LANGUAGE
+            + ", "
+            + HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/moremultipleheaders",
+                                                        "text/*" + ";q=0.9,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=1.0");
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "fr;q=1.0, en-us;q=0.9");
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "shift_jis, *;q=0.8");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_XML, response.getContentType());
+        assertEquals("fr", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT + ", "
+            + HttpHeaders.ACCEPT_LANGUAGE
+            + ", "
+            + HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/moremultipleheaders",
+                                                        "text/*" + ";q=0.9,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=0.8");
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "fr;q=1.0, en-us;q=0.9");
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "shift_jis, *;q=0.8");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=shift_jis", response.getContentType());
+        assertEquals("fr", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT + ", "
+            + HttpHeaders.ACCEPT_LANGUAGE
+            + ", "
+            + HttpHeaders.ACCEPT_ENCODING
+            + ", "
+            + HttpHeaders.ACCEPT_CHARSET, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/moremultipleheaders",
+                                                        "text/*" + ";q=0.9,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=0.8");
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "fr;q=1.0, en-us;q=0.9");
+        request.addHeader(HttpHeaders.ACCEPT_CHARSET, "shift_jis;q=0.7");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN + ";charset=iso-8859-1", response.getContentType());
+        assertEquals("fr", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT + ", "
+            + HttpHeaders.ACCEPT_LANGUAGE
+            + ", "
+            + HttpHeaders.ACCEPT_ENCODING
+            + ", "
+            + HttpHeaders.ACCEPT_CHARSET, response.getHeader(HttpHeaders.VARY));
+    }
+
 }