Author: pete
Date: Thu Jun 30 17:36:17 2011
New Revision: 1141632

URL: http://svn.apache.org/viewvc?rev=1141632&view=rev
Log:
WICKET-3845 There is not a good way to use custom response headers with 
AbstractResource

Added:
    
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/HeaderCollection.java
    
wicket/trunk/wicket-request/src/test/java/org/apache/wicket/request/HeadersCollectionTest.java
Modified:
    
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/resource/AbstractResource.java

Modified: 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/resource/AbstractResource.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/resource/AbstractResource.java?rev=1141632&r1=1141631&r2=1141632&view=diff
==============================================================================
--- 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/resource/AbstractResource.java
 (original)
+++ 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/resource/AbstractResource.java
 Thu Jun 30 17:36:17 2011
@@ -19,11 +19,14 @@ package org.apache.wicket.request.resour
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.wicket.Application;
 import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.request.HeaderCollection;
 import org.apache.wicket.request.Response;
 import org.apache.wicket.request.http.WebRequest;
 import org.apache.wicket.request.http.WebResponse;
@@ -66,6 +69,9 @@ public abstract class AbstractResource i
         */
        public static class ResourceResponse
        {
+               /** header values that are managed internally and must not be 
set directly */
+               public static final Set<String> INTERNAL_HEADERS; 
+               
                private Integer errorCode;
                private String errorMessage;
                private String fileName = null;
@@ -77,7 +83,23 @@ public abstract class AbstractResource i
                private WriteCallback writeCallback;
                private Duration cacheDuration;
                private WebResponse.CacheScope cacheScope;
+               private final HeaderCollection headers;
 
+               static
+               {
+                       INTERNAL_HEADERS = new HashSet<String>();
+                       INTERNAL_HEADERS.add("server");
+                       INTERNAL_HEADERS.add("date");
+                       INTERNAL_HEADERS.add("expires");
+                       INTERNAL_HEADERS.add("last-modified");
+                       INTERNAL_HEADERS.add("content-type");
+                       INTERNAL_HEADERS.add("content-length");
+                       INTERNAL_HEADERS.add("content-disposition");
+                       INTERNAL_HEADERS.add("transfer-encoding");
+                       INTERNAL_HEADERS.add("connection");
+                       INTERNAL_HEADERS.add("content-disposition");
+               }
+               
                /**
                 * Construct.
                 */
@@ -87,6 +109,9 @@ public abstract class AbstractResource i
                        // setting it to [PUBLIC] seems to be sexy but could 
potentially cache confidential
                        // data on public proxies for users migrating to 1.5
                        cacheScope = WebResponse.CacheScope.PRIVATE;
+                       
+                       // collection of directly set response headers
+                       headers = new HeaderCollection();
                }
 
                /**
@@ -396,6 +421,86 @@ public abstract class AbstractResource i
                {
                        return writeCallback;
                }
+
+               /**
+                * set a response header
+                * 
+                * you can only set header values that or not already modified 
by
+                * the other methods of this class like 'Content-Length', 
'Last-Modified', etc.
+                *  
+                * @param name
+                *          header name
+                * @param value
+                *          header value
+                */
+               public void setHeader(String name, String value)
+               {
+                       // check if header can be directly access
+                       checkHeaderAccess(name);
+                       
+                       //set header value
+                       headers.setHeader(name, value);
+               }
+
+               /**
+                * get header value
+                * <p/>
+                * you can only get header values that or not already handled by
+                * the other methods of this class like 'Content-Length', 
'Last-Modified', etc.
+                *  
+                * @param name
+                *          header name
+                *          
+                * @return header value or <code>null</code> if not found
+                */
+               public String getHeader(final String name)
+               {
+                       // check if header can be directly access
+                       checkHeaderAccess(name);
+
+                       // get header value
+                       return headers.getValue(name);
+               }
+
+               /**
+                * remove header value
+                * 
+                * you can only access header values that or not already 
modified by
+                * the other methods of this class like 'Content-Length', 
'Last-Modified', etc.
+                *  
+                * @param name
+                *          header name
+                */
+               public void removeHeader(String name)
+               {
+                       // check if header can be directly access
+                       checkHeaderAccess(name);
+
+                       // remove header value
+                       headers.removeHeader(name);
+               }
+
+               /**
+                * check if header is directly modifyable 
+                * 
+                * @param name
+                *         header name
+                *
+                * @throws IllegalArgumentException 
+                *         if access is forbidden 
+                */
+               private void checkHeaderAccess(String name)
+               {
+                       name = Args.notEmpty(name.trim().toLowerCase(), "name");
+
+                       if (INTERNAL_HEADERS.contains(name))
+                       {
+                               throw new IllegalArgumentException(
+                                       "you are not allowed to directly access 
header [" + name + "], " +
+                                       "use one of the other specialized 
methods of " + getClass().getSimpleName() +
+                                       " to get or modify its value");
+                       }
+               }
        }
 
        /**
@@ -522,6 +627,12 @@ public abstract class AbstractResource i
                                webResponse.setContentLength(contentLength);
                        }
 
+                       // set additional response headers
+                       for (HeaderCollection.Entry entry : data.headers)
+                       {
+                               webResponse.setHeader(entry.getName(), 
entry.getValue());
+                       }
+
                        // 6. Flush the response
                        // This is necessary for firefox if this resource is an 
image, otherwise it messes up
                        // other images on page

Added: 
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/HeaderCollection.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/HeaderCollection.java?rev=1141632&view=auto
==============================================================================
--- 
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/HeaderCollection.java
 (added)
+++ 
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/HeaderCollection.java
 Thu Jun 30 17:36:17 2011
@@ -0,0 +1,180 @@
+/*
+ * 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.wicket.request;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * a collection of headers with name and value suitable for 
+ * processing request and response headers.
+ *
+ * @author Peter Ertl
+ *
+ * @since 1.5
+ */
+public class HeaderCollection implements Iterable<HeaderCollection.Entry>
+{
+       private final Map<String, String> headers;
+
+       public HeaderCollection()
+       {
+               headers = new HashMap<String, String>();
+       }
+
+       /**
+        * set header value
+        *
+        * @param name
+        *          header name
+        * @param value
+        *          header value
+        */
+       public void setHeader(String name, String value)
+       {
+               // be lenient and strip leading / trailing blanks
+               name = Args.notEmpty(name, "name").trim();
+               value = Args.notEmpty(value, "value").trim();
+
+               // remove previous value (required since headers are handled 
case-sensitive)
+               // for example adding 'Content-Type' will overwrite 
'content-type' 
+               removeHeader(name);
+
+               // add new value
+               headers.put(name, value);
+       }
+
+       /**
+        * remove header value
+        *
+        * @param name
+        *          header name
+        */
+       public void removeHeader(String name)
+       {
+               name = Args.notEmpty(name, "name").trim();
+
+               final Iterator<Map.Entry<String, String>> it = 
headers.entrySet().iterator();
+
+               while (it.hasNext())
+               {
+                       Map.Entry<String, String> header = it.next();
+
+                       if (header.getKey().equalsIgnoreCase(name))
+                       {
+                               it.remove();
+                       }
+               }
+       }
+
+       /**
+        * get header value
+        *
+        * @param name
+        *          header name
+        *
+        * @return header value or <code>null</code> if not found
+        */
+       public String getValue(String name)
+       {
+               Args.notEmpty(name, "name");
+
+               // get the header value (case might differ)
+               for (Map.Entry<String, String> header : headers.entrySet())
+               {
+                       if (header.getKey().equalsIgnoreCase(name))
+                       {
+                               return header.getValue();
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * get iterator over header values
+        *
+        * @return iterator
+        */
+       public Iterator<Entry> iterator()
+       {
+               final Iterator<Map.Entry<String, String>> iterator = 
headers.entrySet().iterator();
+
+               return new Iterator<Entry>()
+               {
+                       public boolean hasNext()
+                       {
+                               return iterator.hasNext();
+                       }
+
+                       public Entry next()
+                       {
+                               return new Entry(iterator.next());
+                       }
+
+                       public void remove()
+                       {
+                               throw new UnsupportedOperationException();
+                       }
+               };
+       }
+
+       /**
+        * check if collection is empty
+        * 
+        * @return <code>true</code> if collection is empty, <code>false</code> 
otherwise
+        */
+       public boolean isEmpty()
+       {
+               return headers.isEmpty();
+       }
+
+       /**
+        * get number of headers
+        * 
+        * @return count
+        */
+       public int getCount()
+       {
+               return headers.size();
+       }
+
+       /**
+        * read-only header entry
+        */
+       public static class Entry
+       {
+               private final Map.Entry<String, String> header;
+
+               public Entry(Map.Entry<String, String> header)
+               {
+                       this.header = header;
+               }
+
+               public String getName()
+               {
+                       return header.getKey();
+               }
+
+               public String getValue()
+               {
+                       return header.getValue();
+               }
+       }
+}

Added: 
wicket/trunk/wicket-request/src/test/java/org/apache/wicket/request/HeadersCollectionTest.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-request/src/test/java/org/apache/wicket/request/HeadersCollectionTest.java?rev=1141632&view=auto
==============================================================================
--- 
wicket/trunk/wicket-request/src/test/java/org/apache/wicket/request/HeadersCollectionTest.java
 (added)
+++ 
wicket/trunk/wicket-request/src/test/java/org/apache/wicket/request/HeadersCollectionTest.java
 Thu Jun 30 17:36:17 2011
@@ -0,0 +1,50 @@
+/*
+ * 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.wicket.request;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class HeadersCollectionTest
+{
+       @Test
+       public void testHeaderCollection()
+       {
+               HeaderCollection headers = new HeaderCollection();
+               assertTrue(headers.isEmpty());
+               
+               headers.setHeader("Content-Type", "text/html");
+               headers.setHeader("content-type", "text/plain");
+               assertEquals("text/plain", headers.getValue("CONTENT-TYPE"));
+               
+               headers.removeHeader("content-TYPE");
+               assertTrue(headers.isEmpty());
+               
+               headers.setHeader("   Content-Type    ", "    image/jpeg     ");
+               headers.setHeader("Content-TYPE    ", "    image/gif     ");
+               assertEquals("image/gif", headers.getValue("CONTENT-TYPE"));
+               assertEquals(1, headers.getCount());
+               
+               headers.setHeader("X-Test", "123");
+               assertEquals(2, headers.getCount());
+               
+               headers.removeHeader("   content-TYPE");
+               assertEquals(1, headers.getCount());
+       }
+}


Reply via email to