You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by bi...@apache.org on 2011/12/01 19:54:12 UTC

svn commit: r1209182 - in /cxf/trunk: common/common/src/main/java/org/apache/cxf/common/util/ rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/ rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/wadl/ rt/frontend/jaxrs/src/main/java/or...

Author: bimargulies
Date: Thu Dec  1 18:54:10 2011
New Revision: 1209182

URL: http://svn.apache.org/viewvc?rev=1209182&view=rev
Log:
CXF-3943: add an annotation to allow finer control than global params in the spring configuration.

Added:
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharing.java   (with props)
Modified:
    cxf/trunk/common/common/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/wadl/Description.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java
    cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java
    cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java

Modified: cxf/trunk/common/common/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java
URL: http://svn.apache.org/viewvc/cxf/trunk/common/common/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java?rev=1209182&r1=1209181&r2=1209182&view=diff
==============================================================================
--- cxf/trunk/common/common/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java (original)
+++ cxf/trunk/common/common/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java Thu Dec  1 18:54:10 2011
@@ -23,6 +23,7 @@ import java.beans.BeanInfo;
 import java.beans.PropertyDescriptor;
 import java.io.File;
 import java.io.IOException;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -265,4 +266,22 @@ public final class ReflectionUtil {
         }
         return m;
     }
+    
+
+    /**
+     * Look for a specified annotation on a method. If there, return it. If not, search it's containing class.
+     * Assume that the annotation is marked @Inherited.
+     * 
+     * @param m method to examine
+     * @param annotationType the annotation type to look for.
+     * @return
+     */
+    public static <T extends Annotation> T getAnnotationForMethodOrContainingClass(Method m,
+                                                                                   Class<T> annotationType) {
+        T annotation = m.getAnnotation(annotationType);
+        if (annotation != null) {
+            return annotation;
+        }
+        return m.getDeclaringClass().getAnnotation(annotationType);
+    }
 }

Added: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharing.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharing.java?rev=1209182&view=auto
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharing.java (added)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharing.java Thu Dec  1 18:54:10 2011
@@ -0,0 +1,92 @@
+/**
+ * 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.cxf.jaxrs.cors;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Attach <a href="http://www.w3.org/TR/cors/">CORS</a> information
+ * to a resource. This annotation is read by {@link CrossOriginResourceSharingFilter}.
+ * If this annotation is present on a method, or 
+ * on the method's class (or its superclasses), then it completely
+ * overrides any parameters set in {@link CrossOriginResourceSharingFilter}. 
+ * If a particular parameter of this annotation is not specified, then the
+ * default value is used, <em>not</em> the parameters of the filter. 
+ * 
+ * Note that the CORS specification censors the headers on a 
+ * preflight OPTIONS request. As a result, the filter cannot determine
+ * exactly which method corresponds to the request, and so uses only 
+ * class-level annotations to set policies.
+ */
+@Target({ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CrossOriginResourceSharing {
+    /**
+     * If true, this resource will return
+     * <pre>Access-Control-Allow-Origin: *</pre>
+     * for a valid request.
+     */
+    boolean allowAllOrigins() default false;
+    /**
+     * A list of permitted origins. This is ignored 
+     * if {@link #allowAllOrigins()} is true.
+     */
+    String[] allowOrigins();
+    /**
+     * A list of HTTP methods. This is used only for preflight,
+     * and is only valid on a class.
+     */
+    String[] allowMethods();
+    /**
+     * A list of headers that the client may include
+     * in an actual request.
+     */
+    String[] allowHeaders();
+    /**
+     * If true, this resource will return 
+     * <pre>Access-Control-Allow-Credentials: true</pre>
+     */
+    boolean allowCredentials() default false;
+    /**
+     * A list of headers to return in <tt>
+     * Access-Control-Expose-Headers</tt>. 
+     */
+    String[] exposeHeaders();
+    /**
+     * The value to return in <tt>Access-Control-Max-Age</tt>.
+     * If this is negative, then no header is returned. The default
+     * value is -1.
+     */
+    int maxAge() default -1;
+    /**
+     * Controls the implementation of preflight processing 
+     * on an OPTIONS method.
+     * If the current method is OPTIONS, and this method wants to 
+     * handle the preflight process for itself, set this value to 
+     * <tt>true</tt>. In the default, false, case, the filter
+     * performs preflight processing.
+     */
+    boolean localPreflight() default false;
+}

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharing.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharing.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java?rev=1209182&r1=1209181&r2=1209182&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java Thu Dec  1 18:54:10 2011
@@ -22,12 +22,14 @@ package org.apache.cxf.jaxrs.cors;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 
+import org.apache.cxf.common.util.ReflectionUtil;
 import org.apache.cxf.jaxrs.ext.RequestHandler;
 import org.apache.cxf.jaxrs.ext.ResponseHandler;
 import org.apache.cxf.jaxrs.model.ClassResourceInfo;
@@ -35,18 +37,14 @@ import org.apache.cxf.jaxrs.model.Operat
 import org.apache.cxf.message.Message;
 
 /**
- * An single class that provides both an input
- * and an output filter for CORS, following http://www.w3.org/TR/cors/. 
- * The input examines the input headers. If the request is valid, it stores
- * the information in the Exchange to allow the response handler
- * to add the appropriate headers to the response.
- * 
- *  If you need complex or subtle control of the behavior here (e.g. clearing
- *  the prefight cache) you might be better off reading the source of this
- *  and implementing this inside your service.
+ * An single class that provides both an input and an output filter for CORS, following
+ * http://www.w3.org/TR/cors/. The input examines the input headers. If the request is valid, it stores the
+ * information in the Exchange to allow the response handler to add the appropriate headers to the response.
+ * If you need complex or subtle control of the behavior here (e.g. clearing the prefight cache) you might be
+ * better off reading the source of this and implementing this inside your service.
  */
 public class CrossOriginResourceSharingFilter implements RequestHandler, ResponseHandler {
-    
+
     @Context
     private HttpHeaders headers;
 
@@ -54,41 +52,47 @@ public class CrossOriginResourceSharingF
      * This would be a rather painful list to maintain for real, since it's entirely dependent on the
      * deployment.
      */
-    private List<String> allowedOrigins = Collections.emptyList();
-    private List<String> allowedMethods = Collections.emptyList();
-    private List<String> allowedHeaders = Collections.emptyList();
+    private List<String> allowOrigins = Collections.emptyList();
+    private List<String> allowMethods = Collections.emptyList();
+    private List<String> allowHeaders = Collections.emptyList();
     private boolean allowAllOrigins;
-
     private boolean allowCredentials;
-
-    private List<String> exposeHeaders;
-
+    private List<String> exposeHeaders = Collections.emptyList();
     private Integer maxAge;
 
-    public CrossOriginResourceSharingFilter() {
-        exposeHeaders = Collections.emptyList();
+    private CrossOriginResourceSharing getAnnotation(OperationResourceInfo ori) {
+        return ReflectionUtil.getAnnotationForMethodOrContainingClass(ori.getAnnotatedMethod(),
+                                                                      CrossOriginResourceSharing.class);
     }
 
     public Response handleRequest(Message m, ClassResourceInfo resourceClass) {
+        OperationResourceInfo opResInfo = m.getExchange().get(OperationResourceInfo.class);
+        CrossOriginResourceSharing annotation = getAnnotation(opResInfo);
+
         if ("OPTIONS".equals(m.get(Message.HTTP_REQUEST_METHOD))) {
-            OperationResourceInfo opResInfo = m.getExchange().get(OperationResourceInfo.class);
-            if (opResInfo != null) { // OPTIONS method defined in service bean
+            // what if someone wants to use options for something else, and also for preflight?
+            // in that case, they set the localPreflight flag, and we bow out.
+            if (opResInfo != null && (annotation == null || annotation.localPreflight())) {
                 return null; // continue handling
             }
-            return preflight(m, resourceClass);
+            return preflightRequest(m, annotation, resourceClass);
         }
+        return simpleRequest(m, annotation);
+    }
+
+    private Response simpleRequest(Message m, CrossOriginResourceSharing ann) {
         List<String> values = headers.getRequestHeader(CorsHeaderConstants.HEADER_ORIGIN);
         // 5.1.1 there has to be an origin
         if (values == null || values.size() == 0) {
             return null;
         }
         // 5.1.2 check all the origins
-        if (!allowAllOrigins && !allowedOrigins.containsAll(values)) {
+        if (!effectiveAllowAllOrigins(ann) && !effectiveAllowOrigins(ann).containsAll(values)) {
             return null;
         }
         // 5.1.3 credentials lives in the output filter
         // in any case
-        if (allowAllOrigins) {
+        if (effectiveAllowAllOrigins(ann)) {
             m.getExchange().put(CorsHeaderConstants.HEADER_ORIGIN, Arrays.asList(new String[] {
                 "*"
             }));
@@ -97,14 +101,31 @@ public class CrossOriginResourceSharingF
         }
 
         // 5.1.4 expose headers lives on the output side.
-        
+
         // note what kind of processing we're doing.
         m.getExchange().put(CrossOriginResourceSharingFilter.class.getName(), "simple");
         return null;
     }
 
-    private Response preflight(Message m, ClassResourceInfo resourceClass) {
-
+    /**
+     * handle preflight.
+     * 
+     * @param m the incoming message.
+     * @param ann the annotation, if any, derived from a method that matched the OPTIONS request for the
+     *            preflight. probably completely useless.
+     * @param resourceClass the resource class passed into the filter.
+     * @return
+     */
+    private Response preflightRequest(Message m, CrossOriginResourceSharing optionAnn,
+                                      ClassResourceInfo resourceClass) {
+        /*
+         * CORS doesn't send enough information with a preflight to accurately identity the single method
+         * that will handle the request. So the code uses annotations from the containing class,
+         * only. 
+         */
+        CrossOriginResourceSharing ann 
+            = resourceClass.getResourceClass().getAnnotation(CrossOriginResourceSharing.class);
+        
         List<String> values = headers.getRequestHeader(CorsHeaderConstants.HEADER_ORIGIN);
         String origin;
         // 5.2.1 -- must have origin, must have one origin.
@@ -113,7 +134,8 @@ public class CrossOriginResourceSharingF
         }
         origin = values.get(0);
         // 5.2.2 must be on the list or we must be matching *.
-        if (!allowAllOrigins && !allowedOrigins.contains(origin)) {
+        boolean effectiveAllowAllOrigins = effectiveAllowAllOrigins(ann);
+        if (!effectiveAllowAllOrigins && !effectiveAllowOrigins(ann).contains(origin)) {
             return null;
         }
 
@@ -131,17 +153,19 @@ public class CrossOriginResourceSharingF
         List<String> requestHeaders = headers.getRequestHeader(CorsHeaderConstants.HEADER_AC_REQUEST_HEADERS);
 
         // 5.2.5 reject if the method is not on the list.
-        if (allowedMethods.size() != 0 && !allowedMethods.contains(requestMethod)) {
+        List<String> effectiveAllowMethods = effectiveAllowMethods(ann);
+
+        if (!effectiveAllowMethods.contains(requestMethod)) {
             return null;
         }
 
         // 5.2.6 reject if the header is not listed.
-        if (allowedHeaders.size() != 0 && !allowedHeaders.containsAll(requestHeaders)) {
+        if (!effectiveAllowHeaders(ann).containsAll(requestHeaders)) {
             return null;
         }
 
         // 5.2.7: add allow credentials and allow-origin as required: this lives in the Output filter
-        if (allowAllOrigins) {
+        if (effectiveAllowAllOrigins(ann)) {
             m.getExchange().put(CorsHeaderConstants.HEADER_ORIGIN, Arrays.asList(new String[] {
                 "*"
             }));
@@ -160,119 +184,202 @@ public class CrossOriginResourceSharingF
         return Response.ok().build();
     }
 
-    /**
-     * The origin strings to allow. Call {@link #setAllowAllOrigins(boolean)} to enable '*'.
-     * @param allowedOrigins a list of case-sensitive origin strings. 
-     */
-    public void setAllowedOrigins(List<String> allowedOrigins) {
-        this.allowedOrigins = allowedOrigins;
-    }
-    
-    public List<String> getAllowedOrigins() {
-        return allowedOrigins;
-    }
-
-    /**
-     * Whether to implement Access-Control-Allow-Origin: *
-     * @param allowAllOrigins if true, all origins are accepted and * is returned in the header.
-     * Sections 5.1.1 and 5.1.2, and 5.2.1 and 5.2.2.
-     * If false, then the list of allowed origins must be 
-     */
-    public void setAllowAllOrigins(boolean allowAllOrigins) {
-        this.allowAllOrigins = allowAllOrigins;
-    }
-
-    public List<String> getAllowedMethods() {
-        return allowedMethods;
-    }
-
-    /**
-     * The list of allowed non-simple methods for preflight checks.
-     * Section 5.2.3.
-     * @param allowedMethods a list of case-sensitive HTTP method names.
-     */
-    public void setAllowedMethods(List<String> allowedMethods) {
-        this.allowedMethods = allowedMethods;
-    }
-
-    public List<String> getAllowedHeaders() {
-        return allowedHeaders;
-    }
-
-    /**
-     * The list of allowed headers for preflight checks.
-     * Section 5.2.6
-     * @param allowedHeaders a list of permitted headers.
-     */
-    public void setAllowedHeaders(List<String> allowedHeaders) {
-        this.allowedHeaders = allowedHeaders;
-    }
-
-    public List<String> getExposeHeaders() {
-        return exposeHeaders;
-    }
-
-    public Integer getMaxAge() {
-        return maxAge;
-    }
-
     public Response handleResponse(Message m, OperationResourceInfo ori, Response response) {
         String op = (String)m.getExchange().get(CrossOriginResourceSharingFilter.class.getName());
         if (op == null) {
             return response; // we're not here.
         }
+        CrossOriginResourceSharing annotation;
+
         List<String> originHeader = getHeadersFromInput(m, CorsHeaderConstants.HEADER_ORIGIN);
         ResponseBuilder rbuilder = Response.fromResponse(response);
         if ("simple".equals(op)) {
+            annotation = getAnnotation(ori);
             // 5.1.3: add Allow-Origin supplied from the input side, plus allow-credentials as requested
             addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN, originHeader);
-            rbuilder.header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS, 
-                                Boolean.toString(allowCredentials));
+            rbuilder.header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS,
+                            Boolean.toString(effectiveAllowCredentials(annotation)));
             // 5.1.4 add allowed headers
-            List<String> rqAllowedHeaders 
-                = getHeadersFromInput(m, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS);
+            List<String> rqAllowedHeaders = getHeadersFromInput(m,
+                                                                CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS);
             if (rqAllowedHeaders != null) {
                 addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS, rqAllowedHeaders);
             }
-            if (exposeHeaders.size() > 0) {
-                addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_EXPOSE_HEADERS, exposeHeaders);
+            
+            List<String> effectiveExposeHeaders = effectiveExposeHeaders(annotation);
+            if (effectiveExposeHeaders.size() > 0) {
+                addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_EXPOSE_HEADERS, effectiveExposeHeaders);
             }
             // if someone wants to clear the cache, we can't help them.
             return rbuilder.build();
         } else {
+            annotation = ori.getAnnotatedMethod().getDeclaringClass()
+                    .getAnnotation(CrossOriginResourceSharing.class);
             // preflight
             // 5.2.7 add Allow-Origin supplied from the input side, plus allow-credentials as requested
             addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN, originHeader);
-            rbuilder.header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS, 
-                                Boolean.toString(allowCredentials));
+            rbuilder.header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS,
+                            Boolean.toString(allowCredentials));
             // 5.2.8 max-age
-            if (maxAge != null) {
-                rbuilder.header(CorsHeaderConstants.HEADER_AC_MAX_AGE, maxAge.toString());
+            if (effectiveMaxAge(annotation) != null) {
+                rbuilder.header(CorsHeaderConstants.HEADER_AC_MAX_AGE, 
+                                effectiveMaxAge(annotation).toString());
             }
             // 5.2.9 add allowed methods
             /*
              * Currently, input side just lists the one requested method, and spec endorses that.
              */
-            addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS, 
-                            getHeadersFromInput(m, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS));
+            addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS,
+                       getHeadersFromInput(m, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS));
             // 5.2.10 add allowed headers
-            List<String> rqAllowedHeaders 
-                = getHeadersFromInput(m, CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS);
+            List<String> rqAllowedHeaders = getHeadersFromInput(m,
+                                                                CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS);
             if (rqAllowedHeaders != null) {
                 addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS, rqAllowedHeaders);
             }
             return rbuilder.build();
-            
+
+        }
+    }
+
+    private boolean effectiveAllowAllOrigins(CrossOriginResourceSharing ann) {
+        if (ann != null) {
+            return ann.allowAllOrigins();
+        } else {
+            return allowAllOrigins;
+        }
+    }
+
+    private boolean effectiveAllowCredentials(CrossOriginResourceSharing ann) {
+        if (ann != null) {
+            return ann.allowCredentials();
+        } else {
+            return allowCredentials;
+        }
+    }
+
+    private List<String> effectiveAllowOrigins(CrossOriginResourceSharing ann) {
+        if (ann != null) {
+            if (ann.allowOrigins() == null) {
+                return Collections.emptyList();
+            }
+            return Arrays.asList(ann.allowOrigins());
+        } else {
+            return allowOrigins;
+        }
+    }
+    
+    private List<String> effectiveAllowMethods(CrossOriginResourceSharing ann) {
+        if (ann != null) {
+            if (ann.allowMethods() == null) {
+                return Collections.emptyList();
+            }
+            return Arrays.asList(ann.allowMethods());
+        } else {
+            return allowMethods;
+        }
+    }
+
+    private List<String> effectiveAllowHeaders(CrossOriginResourceSharing ann) {
+        if (ann != null) {
+            if (ann.allowHeaders() == null) {
+                return Collections.emptyList();
+            }
+            return Arrays.asList(ann.allowHeaders());
+        } else {
+            return allowHeaders;
         }
     }
 
+    private List<String> effectiveExposeHeaders(CrossOriginResourceSharing ann) {
+        if (ann != null) {
+            if (ann.exposeHeaders() == null) {
+                return Collections.emptyList();
+            }
+            return Arrays.asList(ann.exposeHeaders());
+        } else {
+            return exposeHeaders;
+        }
+    }
+
+    private Integer effectiveMaxAge(CrossOriginResourceSharing ann) {
+        if (ann != null) {
+            int ma = ann.maxAge();
+            if (ma < 0) {
+                return null;
+            } else {
+                return Integer.valueOf(ma);
+            }
+        } else {
+            return maxAge;
+        }
+    }
+
+    /**
+     * The origin strings to allow. Call {@link #setAllowAllOrigins(boolean)} to enable '*'.
+     * 
+     * @param allowedOrigins a list of case-sensitive origin strings.
+     */
+    public void setAllowOrigins(List<String> allowedOrigins) {
+        this.allowOrigins = allowedOrigins;
+    }
+
+    public List<String> getAllowOrigins() {
+        return allowOrigins;
+    }
+
+    /**
+     * Whether to implement Access-Control-Allow-Origin: *
+     * 
+     * @param allowAllOrigins if true, all origins are accepted and * is returned in the header. Sections
+     *            5.1.1 and 5.1.2, and 5.2.1 and 5.2.2. If false, then the list of allowed origins must be
+     */
+    public void setAllowAllOrigins(boolean allowAllOrigins) {
+        this.allowAllOrigins = allowAllOrigins;
+    }
+
+    public List<String> getAllowMethods() {
+        return allowMethods;
+    }
+
+    /**
+     * The list of allowed non-simple methods for preflight checks. Section 5.2.3.
+     * 
+     * @param allowedMethods a list of case-sensitive HTTP method names.
+     */
+    public void setAllowMethods(List<String> allowedMethods) {
+        this.allowMethods = allowedMethods;
+    }
+
+    public List<String> getAllowHeaders() {
+        return allowHeaders;
+    }
+
+    /**
+     * The list of allowed headers for preflight checks. Section 5.2.6
+     * 
+     * @param allowedHeaders a list of permitted headers.
+     */
+    public void setAllowHeaders(List<String> allowedHeaders) {
+        this.allowHeaders = allowedHeaders;
+    }
+
+    public List<String> getExposeHeaders() {
+        return exposeHeaders;
+    }
+
+    public Integer getMaxAge() {
+        return maxAge;
+    }
+
     public boolean isAllowCredentials() {
         return allowCredentials;
     }
 
     /**
-     * The value for the Access-Control-Allow-Credentials header. If false, no header is added.
-     * If true, the header is added with the value 'true'.
+     * The value for the Access-Control-Allow-Credentials header. If false, no header is added. If true, the
+     * header is added with the value 'true'.
+     * 
      * @param allowCredentials
      */
     public void setAllowCredentials(boolean allowCredentials) {
@@ -281,6 +388,7 @@ public class CrossOriginResourceSharingF
 
     /**
      * A list of non-simple headers to be exposed via Access-Control-Expose-Headers.
+     * 
      * @param exposeHeaders the list of (case-sensitive) header names.
      */
     public void setExposeHeaders(List<String> exposeHeaders) {
@@ -289,6 +397,7 @@ public class CrossOriginResourceSharingF
 
     /**
      * The value for Access-Control-Max-Age.
+     * 
      * @param maxAge An integer 'delta-seconds' or null. If null, no header is added.
      */
     public void setMaxAge(Integer maxAge) {

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/wadl/Description.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/wadl/Description.java?rev=1209182&r1=1209181&r2=1209182&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/wadl/Description.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/wadl/Description.java Thu Dec  1 18:54:10 2011
@@ -27,7 +27,7 @@ import java.lang.annotation.Target;
 /**
  * Can be used to document resource classes and methods
  * 
- * See {@link <a href="http://www.w3.org/Submission/wadl/#x3-80002.3">WADL Documentation</a>}.
+ * See <a href="http://www.w3.org/Submission/wadl/#x3-80002.3">WADL Documentation</a>.
  */
 @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER })
 @Retention(RetentionPolicy.RUNTIME)

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java?rev=1209182&r1=1209181&r2=1209182&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java Thu Dec  1 18:54:10 2011
@@ -55,7 +55,7 @@ import org.apache.cxf.common.logging.Log
 import org.apache.cxf.jaxrs.ext.MessageContext;
 
 public final class AnnotationUtils {
-    
+
     private static final Logger LOG = LogUtils.getL7dLogger(AnnotationUtils.class);
     private static final ResourceBundle BUNDLE = BundleUtils.getBundle(AnnotationUtils.class);
 
@@ -67,13 +67,12 @@ public final class AnnotationUtils {
         PARAM_ANNOTATION_CLASSES = initParamAnnotationClasses();
         METHOD_ANNOTATION_CLASSES = initMethodAnnotationClasses();
     }
-    
-    
+
     private AnnotationUtils() {
-        
+
     }
-    
-    private static Set<Class> initContextClasses() { 
+
+    private static Set<Class> initContextClasses() {
         Set<Class> classes = new HashSet<Class>();
         classes.add(UriInfo.class);
         classes.add(SecurityContext.class);
@@ -90,17 +89,16 @@ public final class AnnotationUtils {
             classes.add(ServletContext.class);
         } catch (Throwable ex) {
             // it is not a problem on the client side and the exception will be
-            // thrown later on if the injection of one of these contexts will be 
+            // thrown later on if the injection of one of these contexts will be
             // attempted on the server side
-            LOG.fine(new org.apache.cxf.common.i18n.Message("NO_SERVLET_API", 
-                                                            BUNDLE).toString());
+            LOG.fine(new org.apache.cxf.common.i18n.Message("NO_SERVLET_API", BUNDLE).toString());
         }
         // CXF-specific
         classes.add(MessageContext.class);
         return classes;
     }
-    
-    private static Set<Class> initParamAnnotationClasses() { 
+
+    private static Set<Class> initParamAnnotationClasses() {
         Set<Class> classes = new HashSet<Class>();
         classes.add(PathParam.class);
         classes.add(QueryParam.class);
@@ -110,8 +108,8 @@ public final class AnnotationUtils {
         classes.add(FormParam.class);
         return classes;
     }
-    
-    private static Set<Class> initMethodAnnotationClasses() { 
+
+    private static Set<Class> initMethodAnnotationClasses() {
         Set<Class> classes = new HashSet<Class>();
         classes.add(HttpMethod.class);
         classes.add(Path.class);
@@ -119,20 +117,19 @@ public final class AnnotationUtils {
         classes.add(Consumes.class);
         return classes;
     }
-    
-    public static boolean isContextClass(Class<?> contextClass) { 
+
+    public static boolean isContextClass(Class<?> contextClass) {
         return CONTEXT_CLASSES.contains(contextClass);
     }
-    
-    public static boolean isParamAnnotationClass(Class<?> annotationClass) { 
+
+    public static boolean isParamAnnotationClass(Class<?> annotationClass) {
         return PARAM_ANNOTATION_CLASSES.contains(annotationClass);
     }
-    
-    public static boolean isValidParamAnnotationClass(Class<?> annotationClass) { 
-        return PARAM_ANNOTATION_CLASSES.contains(annotationClass)
-               || Context.class == annotationClass;
+
+    public static boolean isValidParamAnnotationClass(Class<?> annotationClass) {
+        return PARAM_ANNOTATION_CLASSES.contains(annotationClass) || Context.class == annotationClass;
     }
-    
+
     public static boolean isValidParamAnnotations(Annotation[] paramAnnotations) {
         for (Annotation a : paramAnnotations) {
             if (AnnotationUtils.isValidParamAnnotationClass(a.annotationType())) {
@@ -141,12 +138,12 @@ public final class AnnotationUtils {
         }
         return false;
     }
-    
-    public static boolean isMethodAnnotation(Annotation a) { 
+
+    public static boolean isMethodAnnotation(Annotation a) {
         return METHOD_ANNOTATION_CLASSES.contains(a.annotationType())
                || a.annotationType().getAnnotation(HttpMethod.class) != null;
     }
-    
+
     public static String getAnnotationValue(Annotation a) {
         String value = null;
         if (a.annotationType() == PathParam.class) {
@@ -164,41 +161,41 @@ public final class AnnotationUtils {
         }
         return value;
     }
-    
-    public static <T> T getAnnotation(Annotation[] anns, Class<T> type) { 
+
+    public static <T> T getAnnotation(Annotation[] anns, Class<T> type) {
         if (anns == null) {
             return null;
         }
-        for (Annotation a : anns) {    
+        for (Annotation a : anns) {
             if (a.annotationType() == type) {
                 return type.cast(a);
             }
         }
         return null;
     }
-    
+
     public static Method getAnnotatedMethod(Method m) {
         Method annotatedMethod = doGetAnnotatedMethod(m);
         return annotatedMethod == null ? m : annotatedMethod;
     }
-    
+
     private static Method doGetAnnotatedMethod(Method m) {
-        
+
         if (m == null) {
             return m;
         }
-        
+
         for (Annotation a : m.getAnnotations()) {
             if (AnnotationUtils.isMethodAnnotation(a)) {
                 return m;
-            }        
+            }
         }
         for (Annotation[] paramAnnotations : m.getParameterAnnotations()) {
             if (isValidParamAnnotations(paramAnnotations)) {
                 return m;
             }
         }
-        
+
         Class<?> superC = m.getDeclaringClass().getSuperclass();
         if (superC != null && Object.class != superC) {
             try {
@@ -220,12 +217,10 @@ public final class AnnotationUtils {
                 // ignore
             }
         }
-        
+
         return null;
     }
-    
-   
-    
+
     public static String getHttpMethodValue(Method m) {
         for (Annotation a : m.getAnnotations()) {
             HttpMethod httpM = a.annotationType().getAnnotation(HttpMethod.class);
@@ -235,14 +230,12 @@ public final class AnnotationUtils {
         }
         return null;
     }
-    
-    public static <A extends Annotation> A getMethodAnnotation(Method m,
-                                                 Class<A> aClass) {
+
+    public static <A extends Annotation> A getMethodAnnotation(Method m, Class<A> aClass) {
         return m == null ? null : m.getAnnotation(aClass);
     }
-    
-    public static <A extends Annotation> A getClassAnnotation(Class<?> c,
-                                                              Class<A> aClass) { 
+
+    public static <A extends Annotation> A getClassAnnotation(Class<?> c, Class<A> aClass) {
         if (c == null) {
             return null;
         }
@@ -250,12 +243,12 @@ public final class AnnotationUtils {
         if (p != null) {
             return p;
         }
-        
+
         p = getClassAnnotation(c.getSuperclass(), aClass);
         if (p != null) {
             return p;
         }
-        
+
         // finally try the first one on the interface
         for (Class<?> i : c.getInterfaces()) {
             p = getClassAnnotation(i, aClass);
@@ -265,11 +258,11 @@ public final class AnnotationUtils {
         }
         return null;
     }
-    
+
     public static String getDefaultParameterValue(Annotation[] anns) {
-        
+
         DefaultValue dv = AnnotationUtils.getAnnotation(anns, DefaultValue.class);
         return dv != null ? dv.value() : null;
-        
     }
+
 }

Modified: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java?rev=1209182&r1=1209181&r2=1209182&view=diff
==============================================================================
--- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java (original)
+++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java Thu Dec  1 18:54:10 2011
@@ -29,6 +29,7 @@ import org.apache.cxf.jaxrs.cors.CorsHea
 import org.apache.cxf.systest.jaxrs.AbstractSpringServer;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 import org.apache.http.Header;
+import org.apache.http.HeaderElement;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.ClientProtocolException;
@@ -73,6 +74,16 @@ public class BasicCrossOriginTest extend
         assertEquals(1, aaoHeaders.length);
         assertEquals("*", aaoHeaders[0].getValue());
     }
+    
+    private List<String> headerValues(Header[] headers) {
+        List<String> values = new ArrayList<String>();
+        for (Header h : headers) {
+            for (HeaderElement e : h.getElements()) {
+                values.add(e.getName());
+            }
+        }
+        return values;
+    }
 
     private void assertAllOrigin(boolean allOrigins, String[] originList, String[] requestOrigins,
                                  boolean permitted) throws ClientProtocolException, IOException {
@@ -103,9 +114,10 @@ public class BasicCrossOriginTest extend
                 assertEquals(1, aaoHeaders.length);
                 assertEquals("*", aaoHeaders[0].getValue());
             } else {
-                assertEquals(requestOrigins.length, aaoHeaders.length);
+                List<String> ovalues = headerValues(aaoHeaders);
+                assertEquals(requestOrigins.length, ovalues.size());
                 for (int x = 0; x < requestOrigins.length; x++) {
-                    assertEquals(requestOrigins[x], aaoHeaders[x].getValue());
+                    assertEquals(requestOrigins[x], ovalues.get(x));
                 }
             }
         } else {
@@ -158,7 +170,6 @@ public class BasicCrossOriginTest extend
         }, true);
     }
     
-    @org.junit.Ignore
     @Test
     public void allowTwoPassTwo() throws Exception {
         // allow two, pass two

Modified: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java?rev=1209182&r1=1209181&r2=1209182&view=diff
==============================================================================
--- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java (original)
+++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java Thu Dec  1 18:54:10 2011
@@ -43,7 +43,7 @@ public class ConfigServer {
             inputFilter.setAllowAllOrigins(true);
         } else {
             inputFilter.setAllowAllOrigins(false);
-            inputFilter.setAllowedOrigins(Arrays.asList(origins));
+            inputFilter.setAllowOrigins(Arrays.asList(origins));
         }
         return "ok";
     }