cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From serg...@apache.org
Subject cxf git commit: [CXF-5996] Initial support for client-side caching, patch from Romain Manni-Bucau applied
Date Tue, 25 Nov 2014 21:45:20 GMT
Repository: cxf
Updated Branches:
  refs/heads/master 9283d10ad -> 2200660aa


[CXF-5996] Initial support for client-side caching, patch from Romain Manni-Bucau applied


Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/2200660a
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/2200660a
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/2200660a

Branch: refs/heads/master
Commit: 2200660aa3ec40942b50161abee6ebf776750ed3
Parents: 9283d10
Author: Sergey Beryozkin <sberyozkin@talend.com>
Authored: Tue Nov 25 21:35:18 2014 +0000
Committer: Sergey Beryozkin <sberyozkin@talend.com>
Committed: Tue Nov 25 21:44:58 2014 +0000

----------------------------------------------------------------------
 rt/rs/client/pom.xml                            |  19 +++
 .../CacheControlClientReaderInterceptor.java    | 118 ++++++++++++++++++
 .../cache/CacheControlClientRequestFilter.java  |  78 ++++++++++++
 .../jaxrs/client/cache/CacheControlFeature.java | 123 +++++++++++++++++++
 .../cxf/jaxrs/client/cache/ClientCache.java     |  32 +++++
 .../apache/cxf/jaxrs/client/cache/Entry.java    |  94 ++++++++++++++
 .../org/apache/cxf/jaxrs/client/cache/Key.java  |  79 ++++++++++++
 .../cxf/jaxrs/client/cache/ClientCacheTest.java |  98 +++++++++++++++
 8 files changed, 641 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/pom.xml
----------------------------------------------------------------------
diff --git a/rt/rs/client/pom.xml b/rt/rs/client/pom.xml
index ee50a8f..b503b25 100644
--- a/rt/rs/client/pom.xml
+++ b/rt/rs/client/pom.xml
@@ -78,6 +78,13 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+        <groupId>org.apache.geronimo.specs</groupId>
+	      <artifactId>geronimo-jcache_1.0_spec</artifactId>
+	      <version>1.0-alpha-1</version>
+	      <scope>provided</scope>
+	      <optional>true</optional>
+	    </dependency>
+        <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-core</artifactId>
             <scope>provided</scope>
@@ -104,5 +111,17 @@
             <artifactId>slf4j-jdk14</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+      <groupId>org.apache.commons</groupId>
+        <artifactId>commons-jcs-jcache</artifactId>
+        <version>2.0-SNAPSHOT</version>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
+    <repositories>
+      <repository>
+        <id>jboss</id>
+        <url>http://repository.jboss.org/nexus/content/groups/public/</url> 

+      </repository>
+    </repositories>
 </project>

http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
----------------------------------------------------------------------
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
new file mode 100644
index 0000000..8c9e1f6
--- /dev/null
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientReaderInterceptor.java
@@ -0,0 +1,118 @@
+/**
+ * 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.client.cache;
+
+import java.io.IOException;
+import java.net.URI;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.ReaderInterceptor;
+import javax.ws.rs.ext.ReaderInterceptorContext;
+
+import org.apache.cxf.transport.http.Headers;
+
+@ClientCache
+@Priority(Priorities.USER - 1)
+public class CacheControlClientReaderInterceptor implements ReaderInterceptor {
+    private Cache<Key, Entry> cache;
+
+    @Context
+    private UriInfo uriInfo;
+
+    public CacheControlClientReaderInterceptor(final Cache<Key, Entry> cache) {
+        setCache(cache);
+    }
+
+    public CacheControlClientReaderInterceptor() {
+        // no-op: use setCache then
+    }
+
+    public CacheControlClientReaderInterceptor setCache(final Cache<Key, Entry> c)
{
+        this.cache = c;
+        return this;
+    }
+
+    @Override
+    public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException,
WebApplicationException {
+        if (Boolean.parseBoolean((String)context.getProperty("no_client_cache"))) {
+            return context.proceed();
+        }
+        final MultivaluedMap<String, String> headers = context.getHeaders(); 
+        final String cacheControlHeader = headers.getFirst(HttpHeaders.CACHE_CONTROL);
+        String expiresHeader = headers.getFirst(HttpHeaders.EXPIRES);
+        
+        long expiry = -1;
+        if (cacheControlHeader != null) {
+            final CacheControl value = CacheControl.valueOf(cacheControlHeader.toString());
+            if (value.isNoCache()) {
+                return context.proceed();
+            }
+            expiry = value.getMaxAge();
+        } else if (expiresHeader != null) {
+            if (expiresHeader.startsWith("'") && expiresHeader.endsWith("'")) {
+                expiresHeader = expiresHeader.substring(1, expiresHeader.length() - 1);
+            }
+            try {
+                expiry = (Headers.getHttpDateFormat().parse(expiresHeader).getTime() 
+                    - System.currentTimeMillis()) / 1000;
+            } catch (final ParseException e) {
+                // try next
+            }
+            
+        } else { // no cache
+            return context.proceed();
+        }
+
+        final Object proceed = context.proceed();
+        
+        final Entry entry = new Entry(((String)proceed).getBytes(), context.getHeaders(),

+                                      computeCacheHeaders(context.getHeaders()), expiry);
+        final URI uri = uriInfo.getRequestUri();
+        final String accepts = headers.getFirst(HttpHeaders.ACCEPT);
+        cache.put(new Key(uri, accepts), entry);
+
+        return proceed;
+    }
+
+    private Map<String, String> computeCacheHeaders(final MultivaluedMap<String,
String> httpHeaders) {
+        final Map<String, String> cacheHeaders = new HashMap<String, String>(2);
+
+        final String etagHeader = httpHeaders.getFirst(HttpHeaders.ETAG);
+        if (etagHeader != null) {
+            cacheHeaders.put("If-None-Match", etagHeader);
+        }
+        final String lastModifiedHeader = httpHeaders.getFirst(HttpHeaders.LAST_MODIFIED);
+        if (lastModifiedHeader != null) {
+            cacheHeaders.put("If-Modified-Since", lastModifiedHeader);
+        }
+
+        return cacheHeaders;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientRequestFilter.java
----------------------------------------------------------------------
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientRequestFilter.java
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientRequestFilter.java
new file mode 100644
index 0000000..3656c9f
--- /dev/null
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlClientRequestFilter.java
@@ -0,0 +1,78 @@
+/**
+ * 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.client.cache;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+@ClientCache
+@Priority(Priorities.USER - 1)
+public class CacheControlClientRequestFilter implements ClientRequestFilter {
+    private Cache<Key, Entry> cache;
+
+    public CacheControlClientRequestFilter(final Cache<Key, Entry> cache) {
+        setCache(cache);
+    }
+
+    public CacheControlClientRequestFilter() {
+        // no-op: use setCache then
+    }
+
+    @Override
+    public void filter(final ClientRequestContext request) throws IOException {
+        if (!"GET".equals(request.getMethod())) {
+            request.setProperty("no_client_cache", "true");
+            return;
+        }
+        final URI uri = request.getUri();
+        final String accepts = request.getHeaderString(HttpHeaders.ACCEPT);
+        final Key key = new Key(uri, accepts);
+        Entry entry = cache.get(key);
+        if (entry != null) {
+            if (entry.isOutDated()) {
+                cache.remove(key, entry);
+            } else {
+                Response.ResponseBuilder ok = Response.ok(new String(entry.getData()));
+                if (entry.getHeaders() != null) {
+                    for (Map.Entry<String, List<String>> h : entry.getHeaders().entrySet())
{
+                        for (final Object instance : h.getValue()) {
+                            ok = ok.header(h.getKey(), instance);
+                        }
+                    }
+                }
+                request.abortWith(ok.build());
+            }
+        }
+    }
+
+    public CacheControlClientRequestFilter setCache(final Cache<Key, Entry> c) {
+        this.cache = c;
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
----------------------------------------------------------------------
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
new file mode 100644
index 0000000..9b626d5
--- /dev/null
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/CacheControlFeature.java
@@ -0,0 +1,123 @@
+/**
+ * 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.client.cache;
+
+import java.io.Closeable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.annotation.PreDestroy;
+import javax.annotation.Priority;
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.Factory;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.spi.CachingProvider;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+
+
+@Priority(Priorities.HEADER_DECORATOR)
+public class CacheControlFeature implements Feature {
+    private CachingProvider provider;
+    private CacheManager manager;
+    private Cache<Key, Entry> cache;
+
+    @Override
+    public boolean configure(final FeatureContext context) {
+        // TODO: read context properties to exclude some patterns?
+        final Cache<Key, Entry> entryCache = createCache(context.getConfiguration().getProperties());
+        context.register(new CacheControlClientRequestFilter(entryCache));
+        context.register(new CacheControlClientReaderInterceptor(entryCache));
+        return true;
+    }
+
+    @PreDestroy // TODO: check it is called
+    public void close() {
+        for (final Closeable c : Arrays.asList(cache, manager, provider)) {
+            try {
+                if (c != null) {
+                    c.close();
+                }
+            } catch (final Exception e) {
+                // no-op
+            }
+        }
+    }
+
+    private Cache<Key, Entry> createCache(final Map<String, Object> properties)
{
+        final Properties props = new Properties();
+        props.putAll(properties);
+
+        final String prefix = ClientCache.class.getName() + ".";
+        final String uri = props.getProperty(prefix + "config-uri");
+        final String name = props.getProperty(prefix + "name", ClientCache.class.getName());
+
+        final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+
+        provider = Caching.getCachingProvider();
+        try {
+            manager = provider.getCacheManager(
+                    uri == null ? provider.getDefaultURI() : new URI(uri),
+                    contextClassLoader,
+                    props);
+
+            final MutableConfiguration<Key, Entry> configuration = new MutableConfiguration<Key,
Entry>()
+                .setReadThrough("true".equalsIgnoreCase(props.getProperty(prefix + "readThrough",
"false")))
+                .setWriteThrough("true".equalsIgnoreCase(props.getProperty(prefix + "writeThrough",
"false")))
+                .setManagementEnabled(
+                    "true".equalsIgnoreCase(props.getProperty(prefix + "managementEnabled",
"false")))
+                .setStatisticsEnabled(
+                    "true".equalsIgnoreCase(props.getProperty(prefix + "statisticsEnabled",
"false")))
+                .setStoreByValue("true".equalsIgnoreCase(props.getProperty(prefix + "storeByValue",
"false")));
+
+            final String loader = props.getProperty(prefix + "loaderFactory");
+            if (loader != null) {
+                configuration.setCacheLoaderFactory(newInstance(contextClassLoader, loader,
Factory.class));
+            }
+            final String writer = props.getProperty(prefix + "writerFactory");
+            if (writer != null) {
+                configuration.setCacheWriterFactory(newInstance(contextClassLoader, writer,
Factory.class));
+            }
+            final String expiry = props.getProperty(prefix + "expiryFactory");
+            if (expiry != null) {
+                configuration.setExpiryPolicyFactory(newInstance(contextClassLoader, expiry,
Factory.class));
+            }
+
+            cache = manager.createCache(name, configuration);
+            return cache;
+        } catch (final URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private static <T> T newInstance(final ClassLoader contextClassLoader, final String
clazz, final Class<T> cast) {
+        try {
+            return (T) contextClassLoader.loadClass(clazz).newInstance();
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/ClientCache.java
----------------------------------------------------------------------
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/ClientCache.java
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/ClientCache.java
new file mode 100644
index 0000000..45f629a
--- /dev/null
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/ClientCache.java
@@ -0,0 +1,32 @@
+/**
+ * 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.client.cache;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.ws.rs.NameBinding;
+
+@NameBinding
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface ClientCache {
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Entry.java
----------------------------------------------------------------------
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Entry.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Entry.java
new file mode 100644
index 0000000..4f01713
--- /dev/null
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Entry.java
@@ -0,0 +1,94 @@
+/**
+ * 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.client.cache;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+public class Entry implements Serializable {
+    private static final long serialVersionUID = -3551501551331222546L;
+    private Map<String, String> cacheHeaders = Collections.emptyMap();
+    private byte[] data;
+    private MultivaluedMap<String, String> headers;
+    private long expiresValue;
+    private long initialTimestamp = now();
+
+    public Entry(final byte[] bytes, final MultivaluedMap<String, String> headers,
+                 final Map<String, String> cacheHeaders, final long expiresHeaderValue)
{
+        this.data = bytes;
+        this.headers = headers;
+        this.cacheHeaders = cacheHeaders;
+        this.expiresValue = expiresHeaderValue;
+    }
+
+    public Entry() {
+        // no-op
+    }
+
+    public boolean isOutDated() {
+        return now() - initialTimestamp > expiresValue * 1000;
+    }
+
+    public Map<String, String> getCacheHeaders() {
+        return cacheHeaders;
+    }
+
+    public void setCacheHeaders(final Map<String, String> cacheHeaders) {
+        this.cacheHeaders = cacheHeaders;
+    }
+
+    public byte[] getData() {
+        return data;
+    }
+
+    public void setData(final byte[] data) {
+        this.data = data;
+    }
+
+    public MultivaluedMap<String, String> getHeaders() {
+        return headers;
+    }
+
+    public void setHeaders(final MultivaluedMap<String, String> headers) {
+        this.headers = headers;
+    }
+
+    public long getExpiresValue() {
+        return expiresValue;
+    }
+
+    public void setExpiresValue(final long expiresValue) {
+        this.expiresValue = expiresValue;
+    }
+
+    public long getInitialTimestamp() {
+        return initialTimestamp;
+    }
+
+    public void setInitialTimestamp(final long initialTimestamp) {
+        this.initialTimestamp = initialTimestamp;
+    }
+
+    private static long now() {
+        return System.currentTimeMillis();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Key.java
----------------------------------------------------------------------
diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Key.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Key.java
new file mode 100644
index 0000000..f794a82
--- /dev/null
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/Key.java
@@ -0,0 +1,79 @@
+/**
+ * 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.client.cache;
+
+import java.io.Serializable;
+import java.net.URI;
+
+public class Key implements Serializable {
+    private static final long serialVersionUID = 400974121100289840L;
+
+    private int hash;
+
+    private URI uri;
+    private String accept;
+
+    public Key(final URI uri, final String accept) {
+        this.uri = uri;
+        this.accept = accept;
+
+        int result = uri.hashCode();
+        result = 31 * result + (accept != null ? accept.hashCode() : 0);
+        this.hash = result;
+    }
+
+    public Key() {
+        // no-op
+    }
+
+    public URI getUri() {
+        return uri;
+    }
+
+    public void setUri(final URI uri) {
+        this.uri = uri;
+    }
+
+    public String getAccept() {
+        return accept;
+    }
+
+    public void setAccept(final String accept) {
+        this.accept = accept;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || Key.class != o.getClass()) {
+            return false;
+        }
+
+        final Key key = Key.class.cast(o);
+        return !(accept != null ? !accept.equals(key.accept) : key.accept != null) &&
uri.equals(key.uri);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return hash;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/2200660a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java
b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java
new file mode 100644
index 0000000..8b141f0
--- /dev/null
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/cache/ClientCacheTest.java
@@ -0,0 +1,98 @@
+/**
+ * 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.client.cache;
+
+
+import java.net.HttpURLConnection;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.client.spec.InvocationBuilderImpl;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.transport.local.LocalConduit;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ClientCacheTest extends Assert {
+    public static final String ADDRESS = "local://transport";
+    private static Server server;
+
+    @BeforeClass
+    public static void bind() throws Exception {
+        final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+        sf.setResourceClasses(TheServer.class);
+        sf.setResourceProvider(TheServer.class, new SingletonResourceProvider(new TheServer(),
false));
+        sf.setAddress(ADDRESS);
+        server = sf.create();
+    }
+
+    @AfterClass
+    public static void unbind() throws Exception {
+        server.stop();
+        server.destroy();
+    }
+
+    @Test
+    public void testCache() {
+        final WebTarget base = ClientBuilder.newBuilder().register(CacheControlFeature.class).build().target(ADDRESS);
+        final Invocation.Builder cached = setAsLocal(base.request()).header(HttpHeaders.CACHE_CONTROL,
"public");
+        final Response r = cached.get();
+        assertEquals(r.getStatus(), HttpURLConnection.HTTP_OK);
+        final String r1 = r.readEntity(String.class);
+        waitABit();
+        assertEquals(r1, cached.get().readEntity(String.class));
+    }
+
+    private static Invocation.Builder setAsLocal(final Invocation.Builder client) {
+        WebClient.getConfig(InvocationBuilderImpl.class.cast(client).getWebClient())
+            .getRequestContext().put(LocalConduit.DIRECT_DISPATCH, Boolean.TRUE);
+        return client;
+    }
+
+    private static void waitABit() {
+        try { // just to be sure
+            Thread.sleep(150);
+        } catch (final InterruptedException e) {
+            Thread.interrupted();
+        }
+    }
+
+    @Path("/")
+    public static class TheServer {
+        @GET
+        @ClientCache
+        public Response array() {
+            return Response.ok(Long.toString(System.currentTimeMillis()))
+                .tag("123").cacheControl(CacheControl.valueOf("max-age=50000")).build();
+        }
+    }
+}


Mime
View raw message