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/08/24 20:48:32 UTC

svn commit: r807336 - 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: Mon Aug 24 18:48:31 2009
New Revision: 807336

URL: http://svn.apache.org/viewvc?rev=807336&view=rev
Log:
Request#selectVariant use Accept* request headers

See [WINK-107]

Added:
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptEncoding.java   (with props)
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptEncodingHeaderDelegate.java   (with props)
    incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java   (with props)
Modified:
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptLanguage.java
    incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptLanguageHeaderDelegate.java
    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/FlushResultHandler.java

Added: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptEncoding.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptEncoding.java?rev=807336&view=auto
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptEncoding.java (added)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptEncoding.java Mon Aug 24 18:48:31 2009
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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.Collections;
+import java.util.List;
+
+import javax.ws.rs.ext.RuntimeDelegate;
+import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
+
+/**
+ * Represent HTTP Accept-Language header.
+ * <p>
+ * This version of the API does not support construction.
+ * 
+ * @see <a href='http://tools.ietf.org/html/rfc2616#section-14.3'>RFC 2616
+ *      14.3</a>
+ */
+public class AcceptEncoding {
+
+    public static final class ValuedEncoding implements Comparable<ValuedEncoding> {
+        public final double qValue;
+        public final String encoding;
+
+        public ValuedEncoding(double qValue, String encoding) {
+            this.qValue = qValue;
+            this.encoding = encoding;
+        }
+
+        public int compareTo(ValuedEncoding other) {
+            return Double.compare(qValue, other.qValue);
+        }
+
+        public boolean isWildcard() {
+            return encoding == null;
+        }
+    }
+
+    private static final HeaderDelegate<AcceptEncoding> delegate =
+                                                                     RuntimeDelegate
+                                                                         .getInstance()
+                                                                         .createHeaderDelegate(AcceptEncoding.class);
+
+    private final String                                acceptEncodingHeader;
+    private final boolean                               anyAllowed;
+    private final List<String>                          acceptable;
+    private final List<String>                          banned;
+    private final List<AcceptEncoding.ValuedEncoding>   valuedEncodings;
+
+    public AcceptEncoding(String acceptEncodingValue,
+                          List<String> acceptableEncodings,
+                          List<String> bannedEncodings,
+                          boolean anyEncodingAllowed,
+                          List<AcceptEncoding.ValuedEncoding> encodings) {
+        this.acceptEncodingHeader = acceptEncodingValue;
+        this.anyAllowed = anyEncodingAllowed;
+        this.acceptable = Collections.unmodifiableList(acceptableEncodings);
+        this.banned = Collections.unmodifiableList(bannedEncodings);
+        this.valuedEncodings = Collections.unmodifiableList(encodings);
+    }
+
+    public List<String> getAcceptableEncodings() {
+        return acceptable;
+    }
+
+    /**
+     * Is any encoding acceptable? Note that expresions are listed by
+     * {@link #getBannedLanguages()}. This means that the value contains
+     * wildcard (with non-zero priority) or the header is not present at all.
+     * 
+     * @return <code>true</code> if any encoding is acceptable
+     */
+    public boolean isAnyEncodingAllowed() {
+        return anyAllowed;
+    }
+
+    public List<String> getBannedEncodings() {
+        return banned;
+    }
+
+    public static AcceptEncoding valueOf(String value) throws IllegalArgumentException {
+        return delegate.fromString(value);
+    }
+
+    public String getAcceptEncodingHeader() {
+        return acceptEncodingHeader;
+    }
+
+    public List<AcceptEncoding.ValuedEncoding> getValuedEncodings() {
+        return valuedEncodings;
+    }
+
+    @Override
+    public String toString() {
+        return delegate.toString(this);
+    }
+}

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

Modified: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptLanguage.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptLanguage.java?rev=807336&r1=807335&r2=807336&view=diff
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptLanguage.java (original)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/http/AcceptLanguage.java Mon Aug 24 18:48:31 2009
@@ -37,6 +37,24 @@
  */
 public class AcceptLanguage {
 
+    public static final class ValuedLocale implements Comparable<ValuedLocale> {
+        public final double qValue;
+        public final Locale locale;
+
+        public ValuedLocale(double qValue, Locale locale) {
+            this.qValue = qValue;
+            this.locale = locale;
+        }
+
+        public int compareTo(ValuedLocale other) {
+            return Double.compare(qValue, other.qValue);
+        }
+
+        public boolean isWildcard() {
+            return locale == null;
+        }
+    }
+
     private static final HeaderDelegate<AcceptLanguage> delegate =
                                                                      RuntimeDelegate
                                                                          .getInstance()
@@ -46,15 +64,18 @@
     private final boolean                               anyAllowed;
     private final List<Locale>                          acceptable;
     private final List<Locale>                          banned;
+    private final List<AcceptLanguage.ValuedLocale>     valuedLocales;
 
     public AcceptLanguage(String acceptLanguageValue,
                           List<Locale> acceptableLanguages,
                           List<Locale> bannedLanguages,
-                          boolean anyLanguageAllowed) {
+                          boolean anyLanguageAllowed,
+                          List<AcceptLanguage.ValuedLocale> valuedLocales) {
         this.acceptLanguageHeader = acceptLanguageValue;
         this.anyAllowed = anyLanguageAllowed;
         this.acceptable = Collections.unmodifiableList(acceptableLanguages);
         this.banned = Collections.unmodifiableList(bannedLanguages);
+        this.valuedLocales = Collections.unmodifiableList(valuedLocales);
     }
 
     // /**
@@ -203,6 +224,10 @@
         return acceptLanguageHeader;
     }
 
+    public List<AcceptLanguage.ValuedLocale> getValuedLocales() {
+        return valuedLocales;
+    }
+
     @Override
     public String toString() {
         return delegate.toString(this);

Added: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptEncodingHeaderDelegate.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptEncodingHeaderDelegate.java?rev=807336&view=auto
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptEncodingHeaderDelegate.java (added)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptEncodingHeaderDelegate.java Mon Aug 24 18:48:31 2009
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * 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.AcceptEncoding;
+
+public class AcceptEncodingHeaderDelegate implements HeaderDelegate<AcceptEncoding> {
+
+    public AcceptEncoding 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<AcceptEncoding.ValuedEncoding> vEncodings = parseAcceptEncoding(value);
+
+        for (AcceptEncoding.ValuedEncoding qEncoding : vEncodings) {
+            if (anyAllowed) {
+                if (qEncoding.qValue == 0 && !qEncoding.isWildcard()) {
+                    banned.add(qEncoding.encoding);
+                }
+            } else {
+                if (qEncoding.qValue == 0) {
+                    break; // gone through all acceptable languages
+                }
+                if (qEncoding.isWildcard()) {
+                    anyAllowed = true;
+                } else {
+                    acceptable.add(qEncoding.encoding);
+                }
+            }
+        }
+        return new AcceptEncoding(value, acceptable, banned, anyAllowed, vEncodings);
+    }
+
+    private List<AcceptEncoding.ValuedEncoding> parseAcceptEncoding(String acceptableEncodingValue) {
+        List<AcceptEncoding.ValuedEncoding> qEncodings =
+            new LinkedList<AcceptEncoding.ValuedEncoding>();
+        if (acceptableEncodingValue == null) {
+            return qEncodings;
+        }
+
+        for (String encodingRange : acceptableEncodingValue.split(",")) {
+            int semicolonIndex = encodingRange.indexOf(';');
+            double qValue;
+            String encodingSpec;
+            if (semicolonIndex == -1) {
+                qValue = 1.0d;
+                encodingSpec = encodingRange;
+            } else {
+                encodingSpec = encodingRange.substring(0, semicolonIndex);
+                int equalsIndex = encodingRange.indexOf('=', semicolonIndex + 1);
+                String qString =
+                    encodingRange.substring(equalsIndex != -1 ? equalsIndex + 1 : encodingRange
+                        .length());
+                try {
+                    qValue = Double.parseDouble(qString.trim());
+                } catch (NumberFormatException nfe) {
+                    // silently ignore incorrect q-specification and assume 1
+                    qValue = 1.0d;
+                }
+            }
+            encodingSpec = encodingSpec.trim();
+            if (encodingSpec.length() == 0) {
+                // ignore empty encoding specifications
+                continue;
+            } else if (encodingSpec.equals("*")) {
+                qEncodings.add(new AcceptEncoding.ValuedEncoding(qValue, null));
+            } else {
+                qEncodings.add(new AcceptEncoding.ValuedEncoding(qValue, encodingSpec));
+            }
+        }
+        Collections.sort(qEncodings, Collections.reverseOrder());
+        return qEncodings;
+    }
+
+    public String toString(AcceptEncoding value) {
+        return value.getAcceptEncodingHeader();
+    }
+}

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

Modified: incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptLanguageHeaderDelegate.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptLanguageHeaderDelegate.java?rev=807336&r1=807335&r2=807336&view=diff
==============================================================================
--- incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptLanguageHeaderDelegate.java (original)
+++ incubator/wink/trunk/wink-common/src/main/java/org/apache/wink/common/internal/providers/header/AcceptLanguageHeaderDelegate.java Mon Aug 24 18:48:31 2009
@@ -38,9 +38,9 @@
         boolean anyAllowed = (value == null);
 
         // parse the Accept-Language header
-        List<ValuedLocale> qLocales = parseAcceptLanguage(value);
+        List<AcceptLanguage.ValuedLocale> qLocales = parseAcceptLanguage(value);
 
-        for (ValuedLocale qLocale : qLocales) {
+        for (AcceptLanguage.ValuedLocale qLocale : qLocales) {
             if (anyAllowed) {
                 if (qLocale.qValue == 0 && !qLocale.isWildcard()) {
                     banned.add(qLocale.locale);
@@ -56,11 +56,11 @@
                 }
             }
         }
-        return new AcceptLanguage(value, acceptable, banned, anyAllowed);
+        return new AcceptLanguage(value, acceptable, banned, anyAllowed, qLocales);
     }
 
-    private List<ValuedLocale> parseAcceptLanguage(String acceptLanguageValue) {
-        List<ValuedLocale> qLocales = new LinkedList<ValuedLocale>();
+    private List<AcceptLanguage.ValuedLocale> parseAcceptLanguage(String acceptLanguageValue) {
+        List<AcceptLanguage.ValuedLocale> qLocales = new LinkedList<AcceptLanguage.ValuedLocale>();
         if (acceptLanguageValue == null) {
             return qLocales;
         }
@@ -90,34 +90,16 @@
                 // ignore empty language specifications
                 continue;
             } else if (languageSpec.equals("*")) {
-                qLocales.add(new ValuedLocale(qValue, null));
+               qLocales.add(new AcceptLanguage.ValuedLocale(qValue, null));
             } else {
                 Locale newLocale = HeaderUtils.languageToLocale(languageSpec);
-                qLocales.add(new ValuedLocale(qValue, newLocale));
+                qLocales.add(new AcceptLanguage.ValuedLocale(qValue, newLocale));
             }
         }
         Collections.sort(qLocales, Collections.reverseOrder());
         return qLocales;
     }
 
-    private static final class ValuedLocale implements Comparable<ValuedLocale> {
-        final double qValue;
-        final Locale locale;
-
-        ValuedLocale(double qValue, Locale locale) {
-            this.qValue = qValue;
-            this.locale = locale;
-        }
-
-        public int compareTo(ValuedLocale other) {
-            return Double.compare(qValue, other.qValue);
-        }
-
-        boolean isWildcard() {
-            return locale == null;
-        }
-    }
-
     public String toString(AcceptLanguage value) {
         return value.getAcceptLanguageHeader();
     }

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=807336&r1=807335&r2=807336&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 Mon Aug 24 18:48:31 2009
@@ -38,9 +38,11 @@
 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.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.AcceptEncodingHeaderDelegate;
 import org.apache.wink.common.internal.providers.header.AcceptHeaderDelegate;
 import org.apache.wink.common.internal.providers.header.AcceptLanguageHeaderDelegate;
 import org.apache.wink.common.internal.providers.header.CacheControlHeaderDelegate;
@@ -67,6 +69,7 @@
         headerDelegates.put(EntityTagMatchHeader.class, new EntityTagMatchHeaderDelegate());
         headerDelegates.put(Accept.class, new AcceptHeaderDelegate());
         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=807336&r1=807335&r2=807336&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 Mon Aug 24 18:48:31 2009
@@ -21,10 +21,11 @@
 package org.apache.wink.server.internal.contexts;
 
 import java.util.Date;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
-import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.EntityTag;
@@ -37,6 +38,8 @@
 import javax.ws.rs.ext.RuntimeDelegate;
 import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
 
+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.server.handlers.MessageContext;
 
@@ -191,57 +194,225 @@
     }
 
     public Variant selectVariant(List<Variant> variants) throws IllegalArgumentException {
-        MediaType inputMediaType = msgContext.getHttpHeaders().getMediaType();
-        String inputEncoding =
-            msgContext.getAttribute(HttpServletRequest.class).getCharacterEncoding();
-        String inputLanguage = getHeaderValue("Content-Language");
-        for (Variant variant : variants) {
-            String variantEncoding = variant.getEncoding();
-            Locale variantLanguage = variant.getLanguage();
-            javax.ws.rs.core.MediaType variantMediaType = variant.getMediaType();
-            if (isEncodingEqual(inputEncoding, variantEncoding) && isLanguageEqual(inputLanguage,
-                                                                                   variantLanguage)
-                && isMediaTypeEqual(inputMediaType, variantMediaType)) {
-                return variant;
-            }
+        if (variants == null) {
+            throw new IllegalArgumentException();
         }
-        return null;
-    }
 
-    private boolean isEncodingEqual(String inputEncoding, String variantEncoding) {
-        if (inputEncoding == null && variantEncoding == null) {
-            return true;
+        if (variants.size() == 0) {
+            return null;
         }
 
-        if ((inputEncoding == null && variantEncoding != null) || (inputEncoding != null && variantEncoding == null)) {
-            return false;
+        // algorithm is based on Apache Content Negotiation algorithm with
+        // slight modifications
+        // http://httpd.apache.org/docs/2.0/content-negotiation.html
+
+        // eliminate all Accept* variants that are not acceptable
+        List<MediaType> acceptableMediaTypes =
+            msgContext.getHttpHeaders().getAcceptableMediaTypes();
+
+        List<String> acceptableLanguages =
+            msgContext.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT_LANGUAGE);
+        AcceptLanguage languages = null;
+        if (acceptableLanguages != null) {
+            StringBuilder acceptLanguageTemp = new StringBuilder();
+            acceptLanguageTemp.append(acceptableLanguages.get(0));
+            for (int c = 1; c < acceptableLanguages.size(); ++c) {
+                acceptLanguageTemp.append(",");
+                acceptLanguageTemp.append(acceptableLanguages.get(c));
+            }
+            String acceptLanguage = acceptLanguageTemp.toString();
+            languages = AcceptLanguage.valueOf(acceptLanguage);
         }
 
-        return variantEncoding.equals(inputEncoding.toString());
-    }
+        List<String> acceptableEncodings =
+            msgContext.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT_ENCODING);
+        AcceptEncoding encodings = null;
+        if (acceptableEncodings != null) {
+            StringBuilder acceptEncodingsTemp = new StringBuilder();
+            acceptEncodingsTemp.append(acceptableEncodings.get(0));
+            for (int c = 1; c < acceptableEncodings.size(); ++c) {
+                acceptEncodingsTemp.append(",");
+                acceptEncodingsTemp.append(acceptableEncodings.get(c));
+            }
+            String acceptEncodings = acceptEncodingsTemp.toString();
+            encodings = AcceptEncoding.valueOf(acceptEncodings);
+        }
+
+        VariantQChecked bestVariant = null;
+        boolean isIdentityEncodingChecked = false;
+
+        for (Iterator<Variant> iter = variants.iterator(); iter.hasNext();) {
+            double acceptQFactor = -1.0d;
+            Variant v = iter.next();
+            MediaType vMediaType = v.getMediaType();
+            if (vMediaType != null && acceptableMediaTypes != null) {
+                boolean isCompatible = false;
+                boolean isAcceptable = true; // explicitly denied by the client
+                for (MediaType mt : acceptableMediaTypes) {
+                    if (mt.isCompatible(vMediaType)) {
+                        Map<String, String> params = mt.getParameters();
+                        String q = params.get("q");
+                        if (q != null) {
+                            try {
+                                Double qAsDouble = Double.valueOf(q);
+                                if (qAsDouble.equals(0.0)) {
+                                    isAcceptable = false;
+                                    break;
+                                }
+                                acceptQFactor = qAsDouble;
+                            } catch (NumberFormatException e) {
+                                // silently ignore
+                                e.printStackTrace();
+                            }
+                        } else {
+                            acceptQFactor = 1.0d;
+                        }
+                        isCompatible = true;
+                        break;
+                    }
+                }
+                if (!isCompatible || !isAcceptable) {
+                    continue;
+                }
+            }
+
+            if (bestVariant != null) {
+                if (acceptQFactor < bestVariant.acceptMediaTypeQFactor) {
+                    continue;
+                }
+            }
+
+            double acceptLanguageQFactor = -1.0d;
+            Locale vLocale = v.getLanguage();
+            if (vLocale != null && languages != null) {
+                boolean isCompatible = false;
+                if (languages.getBannedLanguages().contains(vLocale)) {
+                    continue;
+                }
+                for (AcceptLanguage.ValuedLocale locale : languages.getValuedLocales()) {
+                    if (locale.isWildcard() || vLocale.equals(locale.locale)) {
+                        isCompatible = true;
+                        acceptLanguageQFactor = locale.qValue;
+                        break;
+                    }
+                }
+                if (!isCompatible) {
+                    continue;
+                }
+            }
+
+            if (bestVariant != null) {
+                if (acceptLanguageQFactor < bestVariant.acceptLanguageQFactor) {
+                    continue;
+                }
+            }
 
-    private boolean isLanguageEqual(String inputLanguage, Locale variantLanguage) {
-        if (inputLanguage == null && variantLanguage == null) {
-            return true;
+            double acceptEncodingQFactor = -1.0d;
+            String vEncoding = v.getEncoding();
+            if (vEncoding != null) {
+                if (encodings == null || encodings.isAnyEncodingAllowed()) {
+                    if (!v.getEncoding().equalsIgnoreCase("identity")) {
+                        // if there is no Accept Encoding, only identity is
+                        // acceptable
+                        isIdentityEncodingChecked = true;
+                        continue;
+                    }
+                } else {
+                    boolean isAcceptable = true;
+                    for (String encoding : encodings.getBannedEncodings()) {
+                        if (encoding.equalsIgnoreCase(vEncoding)) {
+                            isAcceptable = false;
+                            break;
+                        }
+                    }
+                    if (!isAcceptable) {
+                        continue;
+                    }
+
+                    boolean isCompatible = false;
+                    for (AcceptEncoding.ValuedEncoding encoding : encodings.getValuedEncodings()) {
+                        if (encoding.isWildcard() || encoding.encoding.equalsIgnoreCase(vEncoding)) {
+                            isCompatible = true;
+                            acceptEncodingQFactor = encoding.qValue;
+                            break;
+                        }
+                    }
+                    if (!isCompatible) {
+                        continue;
+                    }
+                }
+            }
+
+            if (bestVariant != null) {
+                if (acceptEncodingQFactor < bestVariant.acceptEncodingQFactor) {
+                    continue;
+                }
+            }
+
+            bestVariant =
+                new VariantQChecked(v, acceptQFactor, acceptLanguageQFactor, acceptEncodingQFactor);
         }
 
-        if ((inputLanguage == null && variantLanguage != null) || (inputLanguage != null && variantLanguage == null)) {
-            return false;
+        if (bestVariant == null) {
+            return null;
         }
 
-        return variantLanguage.getLanguage().equalsIgnoreCase(inputLanguage.toString());
-    }
+        StringBuilder varyHeaderValue = new StringBuilder();
+        boolean isValueWritten = false;
+        if (bestVariant.acceptMediaTypeQFactor > 0) {
+            varyHeaderValue.append(HttpHeaders.ACCEPT);
+            isValueWritten = true;
+        }
+        if (bestVariant.acceptLanguageQFactor > 0) {
+            if (isValueWritten) {
+                varyHeaderValue.append(", ");
+            }
+            varyHeaderValue.append(HttpHeaders.ACCEPT_LANGUAGE);
+            isValueWritten = true;
+        }
+        if (isIdentityEncodingChecked || bestVariant.acceptEncodingQFactor > 0) {
+            if (isValueWritten) {
+                varyHeaderValue.append(", ");
+            }
+            varyHeaderValue.append(HttpHeaders.ACCEPT_ENCODING);
+            isValueWritten = true;
+        }
 
-    private boolean isMediaTypeEqual(MediaType inputMediaType, MediaType variantMediaType) {
-        if (inputMediaType == null && variantMediaType == null) {
-            return true;
+        msgContext.setAttribute(RequestImpl.VaryHeader.class, new VaryHeader(varyHeaderValue
+            .toString().trim()));
+        return bestVariant.variant;
+    }
+
+    private static class VariantQChecked {
+        final Variant variant;
+        final double  acceptMediaTypeQFactor;
+        final double  acceptLanguageQFactor;
+        final double  acceptEncodingQFactor;
+
+        public VariantQChecked(Variant v,
+                               double acceptMediaType,
+                               double acceptLanguage,
+                               double acceptEncoding) {
+            this.variant = v;
+            this.acceptMediaTypeQFactor = acceptMediaType;
+            this.acceptLanguageQFactor = acceptLanguage;
+            this.acceptEncodingQFactor = acceptEncoding;
         }
+    }
+
+    /**
+     * Stores the Vary header value created from the
+     * {@link RequestImpl#selectVariant(List)} method call.
+     */
+    public static class VaryHeader {
+        final private String varyHeaderValue;
 
-        if ((inputMediaType == null && variantMediaType != null) || (inputMediaType != null && variantMediaType == null)) {
-            return false;
+        private VaryHeader(String varyHeaderValue) {
+            this.varyHeaderValue = varyHeaderValue;
         }
 
-        return variantMediaType.toString().equals(inputMediaType.toString());
+        public String getVaryHeaderValue() {
+            return varyHeaderValue;
+        }
     }
-
 }

Modified: incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/FlushResultHandler.java
URL: http://svn.apache.org/viewvc/incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/FlushResultHandler.java?rev=807336&r1=807335&r2=807336&view=diff
==============================================================================
--- incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/FlushResultHandler.java (original)
+++ incubator/wink/trunk/wink-server/src/main/java/org/apache/wink/server/internal/handlers/FlushResultHandler.java Mon Aug 24 18:48:31 2009
@@ -46,8 +46,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.wink.common.internal.MultivaluedMapImpl;
+import org.apache.wink.common.internal.runtime.RuntimeContextTLS;
 import org.apache.wink.server.handlers.AbstractHandler;
 import org.apache.wink.server.handlers.MessageContext;
+import org.apache.wink.server.internal.contexts.RequestImpl;
 
 public class FlushResultHandler extends AbstractHandler {
 
@@ -200,6 +202,14 @@
     @SuppressWarnings("unchecked")
     private static void flushHeaders(final HttpServletResponse httpResponse,
                                      final MultivaluedMap<String, Object> headers) {
+        if (headers.get(HttpHeaders.VARY) == null) {
+            RequestImpl.VaryHeader varyHeader =
+                RuntimeContextTLS.getRuntimeContext().getAttribute(RequestImpl.VaryHeader.class);
+            if (varyHeader != null) {
+                headers.putSingle(HttpHeaders.VARY, varyHeader.getVaryHeaderValue());
+            }
+        }
+
         for (Entry<String, List<Object>> entry : headers.entrySet()) {
             String key = entry.getKey();
             List<Object> values = entry.getValue();

Added: 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=807336&view=auto
==============================================================================
--- incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java (added)
+++ incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java Mon Aug 24 18:48:31 2009
@@ -0,0 +1,416 @@
+/*******************************************************************************
+ * 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.server.internal;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+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 org.apache.wink.server.internal.servlet.MockServletInvocationTest;
+import org.apache.wink.test.mock.MockRequestConstructor;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+public class RequestsTest extends MockServletInvocationTest {
+
+    @Override
+    protected Class<?>[] getClasses() {
+        return new Class[] {RequestRootResource.class};
+    }
+
+    @Path("/root")
+    public static class RequestRootResource {
+
+        @GET
+        public Response getOnlyMediaType(@Context Request request) {
+            List<Variant> responseVariants =
+                Variant.mediaTypes(MediaType.TEXT_PLAIN_TYPE,
+                                   MediaType.APPLICATION_XML_TYPE,
+                                   MediaType.APPLICATION_JSON_TYPE).add().build();
+            Variant bestResponseVariant = request.selectVariant(responseVariants);
+            if (bestResponseVariant != null) {
+                return Response.ok("Hello world!").variant(request.selectVariant(responseVariants))
+                    .build();
+            }
+            return Response.notAcceptable(responseVariants).build();
+        }
+
+        @GET
+        @Path("language")
+        public Response getOnlyLanguage(@Context Request request) {
+            List<Variant> responseVariants =
+                Variant.languages(Locale.ENGLISH, Locale.FRENCH, Locale.KOREAN).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("encoding")
+        public Response getOnlyEncoding(@Context Request request) {
+            List<Variant> responseVariants =
+                Collections.unmodifiableList(Variant.encodings("gzip", "identity", "deflate").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("noidentityencoding")
+        public Response getNoIdentityEncoding(@Context Request request) {
+            List<Variant> responseVariants =
+                Collections.unmodifiableList(Variant.encodings("gzip", "deflate").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 =
+                Collections.unmodifiableList(Variant.mediaTypes(MediaType.APPLICATION_XML_TYPE,
+                                                                MediaType.APPLICATION_JSON_TYPE,
+                                                                MediaType.TEXT_PLAIN_TYPE)
+                    .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 {
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET", "/root", MediaType.TEXT_PLAIN);
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+
+        request = MockRequestConstructor.constructMockRequest("GET", "/root", "text/*");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root", MediaType.APPLICATION_XML);
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_XML, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root", MediaType.APPLICATION_JSON);
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_JSON, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root",
+                                                        MediaType.APPLICATION_ATOM_XML);
+        response = invoke(request);
+        assertEquals(406, response.getStatus());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+    }
+
+    public void testMultipleMediaTypeSelect() throws Exception {
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root",
+                                                        MediaType.TEXT_PLAIN + ";q=1.0,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=0.9");
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(response.getContentType(), MediaType.TEXT_PLAIN);
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root",
+                                                        MediaType.APPLICATION_XML + ";q=0.9,"
+                                                            + "text/*"
+                                                            + ";q=1.0");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root",
+                                                        MediaType.TEXT_PLAIN + ";q=0.9,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=1.0");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_XML, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET", "/root", MediaType.WILDCARD + ","
+                + MediaType.TEXT_PLAIN
+                + ";q=0.0,"
+                + MediaType.APPLICATION_XML
+                + ";q=0.0");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_JSON, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root",
+                                                        MediaType.APPLICATION_ATOM_XML);
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root",
+                                                        MediaType.APPLICATION_ATOM_XML + ";q=1.0,"
+                                                            + MediaType.TEXT_PLAIN
+                                                            + ";q=0.0,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=0.0");
+        response = invoke(request);
+        assertEquals(406, response.getStatus());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root",
+                                                        MediaType.WILDCARD + ";q=0.1 ,"
+                                                            + MediaType.APPLICATION_ATOM_XML
+                                                            + ";q=1.0,"
+                                                            + MediaType.TEXT_PLAIN
+                                                            + ";q=0.0,"
+                                                            + MediaType.APPLICATION_XML
+                                                            + ";q=0.0");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.APPLICATION_JSON, response.getContentType());
+        assertEquals(HttpHeaders.ACCEPT, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+    }
+
+    public void testSimpleLanguagesSelect() throws Exception {
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/language",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "en");
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("en", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT_LANGUAGE, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/language",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "fr");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("fr", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT_LANGUAGE, response.getHeader(HttpHeaders.VARY));
+        assertNull(response.getHeader(HttpHeaders.CONTENT_ENCODING));
+    }
+
+    public void testMultipleLanguagesSelect() throws Exception {
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/language",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "en;q=0.6,fr;q=0.5");
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("en", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT_LANGUAGE, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/language",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "en;q=0.6,fr;q=0.7");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("fr", response.getHeader(HttpHeaders.CONTENT_LANGUAGE));
+        assertEquals(HttpHeaders.ACCEPT_LANGUAGE, response.getHeader(HttpHeaders.VARY));
+    }
+
+    public void testSimpleEncoding() throws Exception {
+        // test that a null Accept-Encoding means the only thing that can come
+        // back is identity
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/encoding",
+                                                        MediaType.TEXT_PLAIN);
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("identity", response.getHeader(HttpHeaders.CONTENT_ENCODING));
+        assertEquals(HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/encoding",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("gzip", response.getHeader(HttpHeaders.CONTENT_ENCODING));
+        assertEquals(HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/noidentityencoding",
+                                                        MediaType.TEXT_PLAIN);
+        response = invoke(request);
+        assertEquals(406, response.getStatus());
+        assertEquals(HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/noidentityencoding",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("gzip", response.getHeader(HttpHeaders.CONTENT_ENCODING));
+        assertEquals(HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+    }
+
+    public void testMultipleEncoding() throws Exception {
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/encoding",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip;q=0.8,deflate;q=0.7");
+        MockHttpServletResponse response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("gzip", response.getHeader(HttpHeaders.CONTENT_ENCODING));
+        assertEquals(HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+
+        request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/encoding",
+                                                        MediaType.TEXT_PLAIN);
+        request.addHeader(HttpHeaders.ACCEPT_ENCODING, "deflate;q=0.8,gzip;q=0.7");
+        response = invoke(request);
+        assertEquals(200, response.getStatus());
+        assertEquals(MediaType.TEXT_PLAIN, response.getContentType());
+        assertEquals("deflate", response.getHeader(HttpHeaders.CONTENT_ENCODING));
+        assertEquals(HttpHeaders.ACCEPT_ENCODING, response.getHeader(HttpHeaders.VARY));
+    }
+
+    public void testSimpleMultipleAcceptHeaders() throws Exception {
+        MockHttpServletRequest request =
+            MockRequestConstructor.constructMockRequest("GET",
+                                                        "/root/multipleheaders",
+                                                        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, 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/multipleheaders",
+                                                        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/multipleheaders",
+                                                        "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));
+    }
+}

Propchange: incubator/wink/trunk/wink-server/src/test/java/org/apache/wink/server/internal/RequestsTest.java
------------------------------------------------------------------------------
    svn:eol-style = native