You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@abdera.apache.org by jm...@apache.org on 2006/08/08 04:00:28 UTC

svn commit: r429538 - in /incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol: cache/ client/ util/

Author: jmsnell
Date: Mon Aug  7 19:00:27 2006
New Revision: 429538

URL: http://svn.apache.org/viewvc?rev=429538&view=rev
Log:
A number of general implementation clean ups to make it easier to manage.
Also, base the decision on what responses to cache based on the status code 
(per RFC2616 13.4) and cache-control headers.

Added:
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/util/MethodHelper.java
Removed:
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/util/ExtensionMethod.java
Modified:
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/Cache.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/CacheBase.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/InMemoryCachedResponse.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Client.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsClient.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsResponse.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/RequestOptions.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Response.java
    incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/ResponseBase.java

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/Cache.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/Cache.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/Cache.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/Cache.java Mon Aug  7 19:00:27 2006
@@ -51,9 +51,8 @@
     CacheKey key);
   
   Response update(
-    String method,
-    String uri, 
     RequestOptions options, 
-    Response response);
+    Response response,
+    Response cached_response);
   
 }

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/CacheBase.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/CacheBase.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/CacheBase.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/CacheBase.java Mon Aug  7 19:00:27 2006
@@ -117,29 +117,71 @@
       }
       return response;
   }
+
+  private boolean shouldUpdateCache(
+    Response response,
+    boolean allowedByDefault) {
+    // TODO: we should probably include pragma: no-cache headers in here 
+      if (allowedByDefault) {
+        return !response.isNoCache() &&
+               !response.isNoStore() &&
+               response.getMaxAge() != 0;
+      } else {
+        return response.getExpires() != null ||
+               response.getMaxAge() > 0 ||
+               response.isMustRevalidate() ||
+               response.isPublic() ||
+               response.isPrivate();
+      }
+  }
   
   public Response update(
-    String method,
-    String uri, 
+    RequestOptions options,
+    Response response,
+    Response cached_response) {
+      int status = response.getStatus();  
+      String uri = response.getUri();
+      String method = response.getMethod();
+      // if the method changes state on the server, don't cache and 
+      // clear what we already have
+      if (!CacheControlUtil.isIdempotent(method)) {
+        remove(uri,options);
+        return response;
+      }
+      // otherwise, base the decision on the response status code
+      switch(status) {
+        case 200: case 203: case 300: case 301: case 410:
+          // rfc2616 says these are cacheable unless otherwise noted
+          if (shouldUpdateCache(response,true))
+            return update(options, response);
+          else remove(uri, options);
+          break;
+        case 304: case 412:
+          // if not revalidated, fall through
+          if (cached_response != null) 
+            return cached_response;
+        default:
+          // rfc2616 says are *not* cacheable unless otherwise noted
+          if (shouldUpdateCache(response,false))
+            return update(options, response);
+          else remove(uri, options);
+          break;
+      }
+      return response;
+  }
+   
+  private Response update(
     RequestOptions options,
     Response response) {
-    CacheKey key = getCacheKey(uri, options,response);
-    if ((response != null && response.isNoStore()) || 
-        !CacheControlUtil.isIdempotent(method)) {
-// TODO: Need to get clarification on this.. if the request is no-store, can
-//       the response be cached.
-//    if ((response != null && response.isNoStore()) ||
-//        options != null && options.getNoStore()) {
-     remove(key);
-   } else {
-     try {
-       CachedResponse cachedResponse = createCachedResponse(response, key);
-       add(key, cachedResponse);
-     } catch (IOException e) {
-       throw new CacheException(e);
-     }
-   }
-   return response;
+      String uri = response.getUri();
+      CacheKey key = getCacheKey(uri, options,response);
+      try {
+        CachedResponse cachedResponse = createCachedResponse(response, key);
+        add(key, cachedResponse);
+        return cachedResponse;
+       } catch (IOException e) {
+        throw new CacheException(e);
+      }
   }
 
 }

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/InMemoryCachedResponse.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/InMemoryCachedResponse.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/InMemoryCachedResponse.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/cache/InMemoryCachedResponse.java Mon Aug  7 19:00:27 2006
@@ -21,17 +21,18 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.abdera.protocol.client.Response;
+import org.apache.abdera.protocol.util.MethodHelper;
 
 public class InMemoryCachedResponse 
   extends CachedResponseBase
   implements CachedResponse {
 
+  private String method = null;
   private int status = 0;
   private String status_text = null;
   private String uri = null;
@@ -44,18 +45,11 @@
     Response response) 
       throws IOException {
     super(key,cache);
+    this.method = response.getMethod();
     this.status = response.getStatus();
     this.status_text = response.getStatusText();
     this.uri = response.getUri();
-    String[] headers = response.getHeaderNames();
-    for (String header : headers) {
-      if (!isNoCacheOrPrivate(header, response) &&
-          !isHopByHop(header)) {
-        String[] values = response.getHeaders(header);
-        List<String> list = Arrays.asList(values);
-        getHeaders().put(header, list);
-      }
-    }
+    this.headers = MethodHelper.getCacheableHeaders(response);
     getServerDate();
     getInitialAge();
     cacheStream(response.getInputStream());
@@ -63,23 +57,6 @@
   }
 
   /**
-   * We don't cache hop-by-hop headers
-   * TODO: There may actually be other hop-by-hop headers we need to filter 
-   *       out.  They'll be listed in the Connection header. see Section 14.10
-   *       of RFC2616 (last paragraph)
-   */
-  private boolean isHopByHop(String header) {
-    return (header.equalsIgnoreCase("Connection") ||
-            header.equalsIgnoreCase("Keep-Alive") ||
-            header.equalsIgnoreCase("Proxy-Authenticate") ||
-            header.equalsIgnoreCase("Proxy-Authorization") ||
-            header.equalsIgnoreCase("TE") ||
-            header.equalsIgnoreCase("Trailers") ||
-            header.equalsIgnoreCase("Transfer-Encoding") ||
-            header.equalsIgnoreCase("Upgrade"));
-  }
-  
-  /**
    * This is terribly inefficient, but it is an in-memory cache
    * that is being used by parsers that incrementally consume 
    * InputStreams at different rates.  There's really no other
@@ -101,22 +78,8 @@
     return headers;
   }
   
-  private boolean isNoCacheOrPrivate(
-    String header, 
-    Response response) {
-      String[] no_cache_headers = response.getNoCacheHeaders();
-      String[] private_headers = response.getPrivateHeaders();
-      return contains(no_cache_headers,header) ||
-             contains(private_headers,header);
-  }
-
-  private boolean contains(String[] headers, String header) {
-    if (headers != null) {
-      for (String h : headers) {
-        if (h.equals(header)) return true;
-      }
-    } 
-    return false;
+  public String getMethod() {
+    return method;
   }
   
   public String getHeader(String header) {

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Client.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Client.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Client.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Client.java Mon Aug  7 19:00:27 2006
@@ -23,6 +23,7 @@
 import org.apache.abdera.model.Base;
 import org.apache.abdera.protocol.cache.Cache;
 import org.apache.abdera.protocol.cache.CacheFactory;
+import org.apache.abdera.protocol.cache.lru.LRUCache;
 import org.apache.commons.httpclient.Credentials;
 import org.apache.commons.httpclient.auth.AuthPolicy;
 import org.apache.commons.httpclient.auth.AuthScheme;
@@ -53,6 +54,8 @@
       CacheFactory factory = CacheFactory.INSTANCE;
       if (factory != null)
         cache = factory.getCache();
+      if (cache == null) 
+        cache = new LRUCache();
     }
     return cache;
   }

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsClient.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsClient.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsClient.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsClient.java Mon Aug  7 19:00:27 2006
@@ -25,22 +25,14 @@
 import org.apache.abdera.protocol.cache.CacheDisposition;
 import org.apache.abdera.protocol.cache.CachedResponse;
 import org.apache.abdera.protocol.util.CacheControlUtil;
-import org.apache.abdera.protocol.util.ExtensionMethod;
+import org.apache.abdera.protocol.util.MethodHelper;
 import org.apache.abdera.util.Version;
 import org.apache.commons.httpclient.Credentials;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.HttpMethod;
 import org.apache.commons.httpclient.auth.AuthPolicy;
 import org.apache.commons.httpclient.auth.AuthScope;
-import org.apache.commons.httpclient.methods.DeleteMethod;
-import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.HeadMethod;
-import org.apache.commons.httpclient.methods.OptionsMethod;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.methods.PutMethod;
 import org.apache.commons.httpclient.methods.RequestEntity;
-import org.apache.commons.httpclient.methods.TraceMethod;
 import org.apache.commons.httpclient.params.HttpClientParams;
 
 public class CommonsClient extends Client {
@@ -69,6 +61,15 @@
       HttpClientParams.USE_EXPECT_CONTINUE, true);    
   }
   
+  private boolean useCache(
+    String method, 
+    RequestOptions options) {
+      return (CacheControlUtil.isIdempotent(method)) &&
+        !options.getNoCache() &&
+        !options.getNoStore() &&
+        options.getUseLocalCache();
+  }
+  
   @Override
   public Response execute(
     String method, 
@@ -77,114 +78,38 @@
     RequestOptions options) {
       try {
         if (options == null) options = getDefaultRequestOptions();
-        Response response = null;
-        CachedResponse cached_response = null;
         Cache cache = getCache();
-        CacheDisposition disp = CacheDisposition.TRANSPARENT;
-        if (CacheControlUtil.isIdempotent(method) &&
-            cache != null && 
-            options.getNoCache() == false && 
-            options.getNoStore() == false &&
-            options.getUseLocalCache()) {
-          disp = cache.getDisposition(uri,options);
-          cached_response = cache.get(uri, options);
-          switch(disp) {
-            case FRESH:
-              //System.out.println("____ CACHE HIT: FRESH");
-              response = cached_response;
-              break;
-            case STALE:
-              //System.out.println("____ CACHE HIT: STALE, Need to revalidate");
-              if (options == null) 
-                options = getDefaultRequestOptions();
-              if (cached_response.getLastModified() != null)
-                options.setIfModifiedSince(cached_response.getLastModified());
-              if (cached_response.getEntityTag() != null)
-                options.setIfNoneMatch(cached_response.getEntityTag());
-              break;
-            default:
-              //System.out.println("____ CACHE MISS: TRANSPARENT");
-          }          
-        }
-        if (response == null) {
-          HttpMethod httpMethod = createMethod(method, uri, entity);
-          String[] headers = options.getHeaderNames();
-          for (String header : headers) {
-            String[] values = options.getHeaders(header);
-            for (String value : values) {
-              httpMethod.addRequestHeader(header, value);
-            }
-          }
-          String cc = options.getCacheControl();
-          if (cc != null && cc.length() != 0)
-            httpMethod.setRequestHeader("Cache-Control", cc);
-          int n = client.executeMethod(httpMethod);
-          if (n == 304 || n == 412 && 
-              cached_response != null &&
-              disp.equals(CacheDisposition.STALE)) {
-            response = cached_response;
-          } else {
-            response = new CommonsResponse(httpMethod);
-            if (cache != null) 
-              response = cache.update(
-                method, uri, options, response);
-          }
+        CacheDisposition disp = 
+          (useCache(method,options)) ? 
+            cache.getDisposition(uri, options) : 
+            CacheDisposition.TRANSPARENT;
+        CachedResponse cached_response = cache.get(uri, options);
+        switch(disp) {
+          case FRESH:                                                            // CACHE HIT: FRESH
+            if (cached_response != null)
+              return cached_response;
+          case STALE:                                                            // CACHE HIT: STALE
+            // revalidate the cached entry
+            options.setIfModifiedSince(cached_response.getLastModified());
+            options.setIfNoneMatch(cached_response.getEntityTag());
+          default:                                                               // CACHE MISS
+            HttpMethod httpMethod = 
+              MethodHelper.createMethod(
+                method, uri, entity, options);
+            client.executeMethod(httpMethod);
+            Response response = new CommonsResponse(httpMethod);
+            return (options.getUseLocalCache()) ?
+              response = cache.update(options, response, cached_response) : 
+              response;
         }
-        return response;
       } catch (Throwable t) {
         throw new ClientException(t);
       }
   }
 
-  private HttpMethod createMethod(
-    String method, 
-    String uri,
-    RequestEntity entity) {
-      if (method == null) return null;
-      if (method.equalsIgnoreCase("GET")) {
-        return new GetMethod(uri);
-      } else if (method.equalsIgnoreCase("POST")) {
-        EntityEnclosingMethod m = new PostMethod(uri);
-        if (entity != null)
-          m.setRequestEntity(entity);
-        return m;
-      } else if (method.equalsIgnoreCase("PUT")) {
-        EntityEnclosingMethod m = new PutMethod(uri);
-        if (entity != null)
-          m.setRequestEntity(entity);
-        return m;
-      } else if (method.equalsIgnoreCase("DELETE")) {
-        return new DeleteMethod(uri);
-      } else if (method.equalsIgnoreCase("HEAD")) {
-        return new HeadMethod(uri);
-      } else if (method.equalsIgnoreCase("OPTIONS")) {
-        return new OptionsMethod(uri);
-      } else if (method.equalsIgnoreCase("TRACE")) {
-        return new TraceMethod(uri);
-      } else {
-        EntityEnclosingMethod m = new ExtensionMethod(method.toUpperCase());
-        if (entity != null)
-          m.setRequestEntity(entity);
-        return m;
-      }
-  }
-  
   @Override
   public RequestOptions getDefaultRequestOptions() {
-    RequestOptions options = new RequestOptions();
-    options.setAcceptEncoding(
-      "gzip;q=1.0", 
-      "deflate;q=1.0", 
-      "zip;q=0.5");
-    options.setAccept(
-      "application/atom+xml",
-      "application/atomserv+xml",
-      "application/xml;q=0.8",
-      "text/xml;q=0.5",
-      "*/*;q=0.1");
-    options.setAcceptCharset(
-      "utf-8", "*;q=0.5");
-    return options;
+    return MethodHelper.createDefaultRequestOptions();
   }
 
   @Override

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsResponse.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsResponse.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsResponse.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/CommonsResponse.java Mon Aug  7 19:00:27 2006
@@ -44,8 +44,12 @@
     getServerDate();
   }
 
-  public HttpMethod getMethod() {
+  public HttpMethod getHttpMethod() {
     return method;
+  }
+  
+  public String getMethod() {
+    return method.getName();
   }
   
   public int getStatus() {

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/RequestOptions.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/RequestOptions.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/RequestOptions.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/RequestOptions.java Mon Aug  7 19:00:27 2006
@@ -93,23 +93,34 @@
   }
   
   public void setHeader(String header, String value) {
-    setHeader(header, new String[] {value});
+    if (value != null)
+      setHeader(header, new String[] {value});
+    else
+      removeHeaders(header);
   }
 
   public void setHeader(String header, String... values) {
-    List<String> list = Arrays.asList(new String[] {combine(values)});
-    getHeaders().put(header, list);
+    if (values != null && values.length > 0) {
+      List<String> list = Arrays.asList(new String[] {combine(values)});
+      getHeaders().put(header, list);
+    } else {
+      removeHeaders(header);
+    }
   }
   
   public void setDateHeader(String header, Date value) {
-    setHeader(header, DateUtil.formatDate(value));
+    if (value != null) 
+      setHeader(header, DateUtil.formatDate(value));
+    removeHeaders(header);
   }
   
   public void addHeader(String header, String value) {
-    addHeader(header, new String[] {value});
+    if (value != null)
+      addHeader(header, new String[] {value});
   }
   
   public void addHeader(String header, String... values) {
+    if (values == null || values.length == 0) return;
     List<String> list = getHeaders().get(header);
     String value = combine(values);
     if (list != null) {
@@ -121,6 +132,7 @@
   }
 
   public void addDateHeader(String header, Date value) {
+    if (value == null) return;
     addHeader(header, DateUtil.formatDate(value));
   }
   
@@ -300,6 +312,10 @@
   
   public void setCacheControl(String cc) {
     CacheControlUtil.parseCacheControl(cc, this);
+  }
+  
+  public void removeHeaders(String name) {
+    getHeaders().remove(name);
   }
   
   public String getCacheControl() {

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Response.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Response.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Response.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/Response.java Mon Aug  7 19:00:27 2006
@@ -31,6 +31,8 @@
 
   ResponseType getResponseClass();
   
+  String getMethod();
+  
   int getStatus();
   
   String getUri();

Modified: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/ResponseBase.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/ResponseBase.java?rev=429538&r1=429537&r2=429538&view=diff
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/ResponseBase.java (original)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/client/ResponseBase.java Mon Aug  7 19:00:27 2006
@@ -41,7 +41,7 @@
   protected long max_age = -1;
   protected InputStream in = null;
   protected Date response_date = null;
-  protected Date now = new Date();
+  protected Date now = new Date(); 
   
   public InputStream getInputStream() throws IOException {
     return in;
@@ -51,8 +51,7 @@
     this.in = in;
   }
 
-  public ResponseType getResponseClass() {
-    int status = getStatus();
+  public static ResponseType getResponseClass(int status) {
     if (status >= 200 && status < 300) return ResponseType.SUCCESS;
     if (status >= 300 && status < 400) return ResponseType.REDIRECTION;
     if (status >= 400 && status < 500) return ResponseType.CLIENT_ERROR;
@@ -60,6 +59,10 @@
     return ResponseType.UNKNOWN;
   }
   
+  public ResponseType getResponseClass() {
+    return getResponseClass(getStatus());
+  }
+  
   public Date getDateHeader(String header) {
     try {
       String value = getHeader(header);
@@ -79,7 +82,7 @@
     } catch (Exception e) {}
     return null;  // shouldn't happen
   } 
-
+  
   public String getEntityTag() {
     return getHeader("ETag");
   }

Added: incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/util/MethodHelper.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/util/MethodHelper.java?rev=429538&view=auto
==============================================================================
--- incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/util/MethodHelper.java (added)
+++ incubator/abdera/java/trunk/client/src/main/java/org/apache/abdera/protocol/util/MethodHelper.java Mon Aug  7 19:00:27 2006
@@ -0,0 +1,185 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.protocol.util;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.abdera.protocol.client.RequestOptions;
+import org.apache.abdera.protocol.client.Response;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.HeadMethod;
+import org.apache.commons.httpclient.methods.OptionsMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
+import org.apache.commons.httpclient.methods.TraceMethod;
+
+public class MethodHelper {
+
+  public static enum HopByHop {
+    Connection, 
+    KeepAlive, 
+    ProxyAuthenticate,
+    ProxyAuthorization, 
+    TE, 
+    Trailers, 
+    TransferEncoding,
+    Upgrade;
+  }
+  
+  public static enum Method {
+    GET, POST, PUT, DELETE, OPTIONS, TRACE, HEAD, OTHER;
+    
+    public static Method fromString(String method) {
+      try {
+        return Method.valueOf(method.toUpperCase());
+      } catch (Exception e) {
+        return OTHER;
+      }
+    }
+  }
+
+  public static Map<String,List<String>> getCacheableHeaders(Response response) {
+    Map<String,List<String>> map = new HashMap<String,List<String>>();
+    String[] headers = response.getHeaderNames();
+    for (String header : headers) {
+      if (MethodHelper.isCacheableHeader(header, response)) {
+        String[] values = response.getHeaders(header);
+        List<String> list = Arrays.asList(values);
+        map.put(header, list);
+      }
+    }
+    return map;
+  }
+  
+  public static boolean isCacheableHeader(
+    String header, Response response) {
+      return !isNoCacheOrPrivate(header, response) &&
+             !isHopByHop(header);
+  }
+  
+  public static boolean isNoCacheOrPrivate(
+    String header, 
+    Response response) {
+      String[] no_cache_headers = response.getNoCacheHeaders();
+      String[] private_headers = response.getPrivateHeaders();
+      return contains(no_cache_headers,header) ||
+             contains(private_headers,header);
+  }
+
+  private static boolean contains(String[] headers, String header) {
+    if (headers != null) {
+      for (String h : headers) {
+        if (h.equals(header)) return true;
+      }
+    } 
+    return false;
+  }
+     
+  /**
+   * We don't cache hop-by-hop headers
+   * TODO: There may actually be other hop-by-hop headers we need to filter 
+   *       out.  They'll be listed in the Connection header. see Section 14.10
+   *       of RFC2616 (last paragraph)
+   */
+  public static boolean isHopByHop(String header) {
+    try {
+      HopByHop.valueOf(header.replaceAll("-", ""));
+      return true;
+    } catch (Exception e) {
+      return false;
+    }
+  }
+  
+  private static EntityEnclosingMethod getMethod(
+    EntityEnclosingMethod method, RequestEntity entity) {
+      if (entity != null) method.setRequestEntity(entity);
+      return method;
+  }
+  
+  public static HttpMethod createMethod(
+    String method, 
+    String uri,
+    RequestEntity entity,
+    RequestOptions options) {
+      if (method == null) return null;
+      Method m = Method.fromString(method);
+      HttpMethod httpMethod = null;
+      switch(m) {
+        case GET:     httpMethod = new GetMethod(uri); break;
+        case POST:    httpMethod = getMethod(new PostMethod(), entity); break;
+        case PUT:     httpMethod = getMethod(new PutMethod(), entity); break;
+        case DELETE:  httpMethod = new DeleteMethod(uri); break;
+        case HEAD:    httpMethod = new HeadMethod(uri); break;
+        case OPTIONS: httpMethod = new OptionsMethod(uri); break;
+        case TRACE:   httpMethod = new TraceMethod(uri); break;
+        default:      httpMethod = getMethod(new ExtensionMethod(method), entity);
+      }
+      initHeaders(options, httpMethod);
+      return httpMethod;
+  }
+
+  private static void initHeaders(RequestOptions options, HttpMethod method) {
+    String[] headers = options.getHeaderNames();
+    for (String header : headers) {
+      String[] values = options.getHeaders(header);
+      for (String value : values) {
+        method.addRequestHeader(header, value);
+      }
+    }
+    String cc = options.getCacheControl();
+    if (cc != null && cc.length() != 0)
+      method.setRequestHeader("Cache-Control", cc);
+  }
+  
+  public static final class ExtensionMethod 
+    extends EntityEnclosingMethod {
+    private String method = null;
+    public ExtensionMethod(String method) {
+      super(method);
+      this.method = method;
+    }
+    @Override
+    public String getName() {
+      return method;
+    }
+  }
+
+  public static RequestOptions createDefaultRequestOptions() {
+    RequestOptions options = new RequestOptions();
+    options.setAcceptEncoding(
+      "gzip;q=1.0", 
+      "deflate;q=1.0", 
+      "zip;q=0.5");
+    options.setAccept(
+      "application/atom+xml",
+      "application/atomserv+xml",
+      "application/xml;q=0.8",
+      "text/xml;q=0.5",
+      "*/*;q=0.1");
+    options.setAcceptCharset(
+      "utf-8", "*;q=0.5");
+    return options;
+  }
+}