Repository: cxf Updated Branches: refs/heads/3.0.x-fixes 2b3a7b173 -> a77eeb984
[CXF-5996] Better support for reading caching various entity types Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/a77eeb98 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/a77eeb98 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/a77eeb98 Branch: refs/heads/3.0.x-fixes Commit: a77eeb9848dce575079a76a8a8a12981679dbc6b Parents: 2b3a7b1 Author: Sergey Beryozkin <[email protected]> Authored: Wed Nov 26 15:06:34 2014 +0000 Committer: Sergey Beryozkin <[email protected]> Committed: Wed Nov 26 17:48:05 2014 +0000 ---------------------------------------------------------------------- rt/rs/client/pom.xml | 15 +- .../cxf/jaxrs/client/cache/BytesEntity.java | 47 ++++++ .../CacheControlClientReaderInterceptor.java | 109 +++++++++--- .../cache/CacheControlClientRequestFilter.java | 14 +- .../jaxrs/client/cache/CacheControlFeature.java | 34 ++-- .../cxf/jaxrs/client/cache/ClientCache.java | 32 ---- .../apache/cxf/jaxrs/client/cache/Entry.java | 10 +- .../cxf/jaxrs/client/cache/ClientCacheTest.java | 165 +++++++++++++++++-- 8 files changed, 335 insertions(+), 91 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/a77eeb98/rt/rs/client/pom.xml ---------------------------------------------------------------------- diff --git a/rt/rs/client/pom.xml b/rt/rs/client/pom.xml index 9552789..68d3010 100644 --- a/rt/rs/client/pom.xml +++ b/rt/rs/client/pom.xml @@ -83,7 +83,7 @@ <version>1.0-alpha-1</version> <scope>provided</scope> <optional>true</optional> - </dependency> + </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> @@ -102,6 +102,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>${cxf.servlet-api.group}</groupId> + <artifactId>${cxf.servlet-api.artifact}</artifactId> + <scope>test</scope> + </dependency> + + <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <scope>test</scope> @@ -111,6 +117,13 @@ <artifactId>slf4j-jdk14</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-databinding-jaxb</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <!-- <dependency> <groupId>org.apache.commons</groupId> http://git-wip-us.apache.org/repos/asf/cxf/blob/a77eeb98/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/BytesEntity.java ---------------------------------------------------------------------- diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/BytesEntity.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/BytesEntity.java new file mode 100644 index 0000000..91e8c6c --- /dev/null +++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/BytesEntity.java @@ -0,0 +1,47 @@ +/** + * 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; + +public class BytesEntity implements Serializable { + + private static final long serialVersionUID = -6010007172900653981L; + private byte[] entity; + private boolean fromStream; + public BytesEntity() { + + } + public BytesEntity(byte[] entity, boolean fromStream) { + this.entity = entity; + this.setFromStream(fromStream); + } + public byte[] getEntity() { + return entity; + } + public void setEntity(byte[] entity) { + this.entity = entity; + } + public boolean isFromStream() { + return fromStream; + } + public void setFromStream(boolean fromStream) { + this.fromStream = fromStream; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/a77eeb98/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 index 8c9e1f6..1cba11f 100644 --- 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 @@ -18,7 +18,10 @@ */ package org.apache.cxf.jaxrs.client.cache; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; import java.net.URI; import java.text.ParseException; import java.util.HashMap; @@ -36,16 +39,17 @@ import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.ReaderInterceptor; import javax.ws.rs.ext.ReaderInterceptorContext; +import org.apache.cxf.helpers.IOUtils; 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; - + private boolean cacheResponseInputStream; + public CacheControlClientReaderInterceptor(final Cache<Key, Entry> cache) { setCache(cache); } @@ -61,21 +65,50 @@ public class CacheControlClientReaderInterceptor implements ReaderInterceptor { @Override public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException { - if (Boolean.parseBoolean((String)context.getProperty("no_client_cache"))) { + Object cachedEntity = context.getProperty(CacheControlClientRequestFilter.CACHED_ENTITY_PROPERTY); + if (cachedEntity != null) { + if (cachedEntity instanceof BytesEntity) { + // InputStream or byte[] + BytesEntity bytesEntity = (BytesEntity)cachedEntity; + byte[] bytes = bytesEntity.getEntity(); + cachedEntity = bytesEntity.isFromStream() ? new ByteArrayInputStream(bytes) : bytes; + if (cacheResponseInputStream) { + InputStream is = bytesEntity.isFromStream() ? (InputStream)cachedEntity + : new ByteArrayInputStream((byte[])cachedEntity); + context.setInputStream(is); + return context.proceed(); + } + } + return cachedEntity; + } + + if (Boolean.parseBoolean((String)context.getProperty(CacheControlClientRequestFilter.NO_CACHE_PROPERTY))) { + // non GET HTTP method or other restriction applies return context.proceed(); } final MultivaluedMap<String, String> headers = context.getHeaders(); final String cacheControlHeader = headers.getFirst(HttpHeaders.CACHE_CONTROL); - String expiresHeader = headers.getFirst(HttpHeaders.EXPIRES); + final CacheControl cacheControl = CacheControl.valueOf(cacheControlHeader.toString()); - 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) { + byte[] cachedBytes = null; + if (cacheControl != null && !cacheControl.isNoCache() && !cacheControl.isNoStore() + && cacheResponseInputStream) { + // if Cache-Control is set and the stream needs to be cached then do it + cachedBytes = IOUtils.readBytesFromStream((InputStream)context.getInputStream()); + context.setInputStream(new ByteArrayInputStream(cachedBytes)); + } + // Read the stream and get the actual entity + Object responseEntity = context.proceed(); + + if (cacheControl == null || cacheControl.isNoCache() || cacheControl.isNoStore()) { + // TODO: apparently no-cache also means the local cache has to be revalidated + return responseEntity; + } + // if a max-age property is set then it overrides Expires + long expiry = cacheControl.getMaxAge(); + if (expiry == -1) { + //TODO: Review if Expires can be supported as an alternative to Cache-Control + String expiresHeader = headers.getFirst(HttpHeaders.EXPIRES); if (expiresHeader.startsWith("'") && expiresHeader.endsWith("'")) { expiresHeader = expiresHeader.substring(1, expiresHeader.length() - 1); } @@ -83,22 +116,33 @@ public class CacheControlClientReaderInterceptor implements ReaderInterceptor { expiry = (Headers.getHttpDateFormat().parse(expiresHeader).getTime() - System.currentTimeMillis()) / 1000; } catch (final ParseException e) { - // try next + // TODO: Revisit the possibility of supporting multiple formats } - - } else { // no cache - return context.proceed(); + } + Serializable ser = null; + if (cachedBytes != null) { + // store the cached bytes - they will be parsed again when a client cache will return them + ser = new BytesEntity(cachedBytes, responseEntity instanceof InputStream); + } else if (responseEntity instanceof Serializable) { + // store the entity directly + ser = (Serializable)responseEntity; + } else if (responseEntity instanceof InputStream) { + // read the stream, cache it, the cached bytes will be returned immediately + // when a client cache will return them + byte[] bytes = IOUtils.readBytesFromStream((InputStream)responseEntity); + ser = new BytesEntity(bytes, true); + responseEntity = new ByteArrayInputStream(bytes); + } else if (responseEntity instanceof byte[]) { + // the cached bytes will be returned immediately when a client cache will return them + ser = new BytesEntity((byte[])responseEntity, false); } - final Object proceed = context.proceed(); - - final Entry entry = new Entry(((String)proceed).getBytes(), context.getHeaders(), - computeCacheHeaders(context.getHeaders()), expiry); + final Entry entry = new Entry(ser, context.getHeaders(), computeCacheHeaders(context.getHeaders()), expiry); final URI uri = uriInfo.getRequestUri(); - final String accepts = headers.getFirst(HttpHeaders.ACCEPT); + final String accepts = (String)context.getProperty(CacheControlClientRequestFilter.CLIENT_ACCEPTS); cache.put(new Key(uri, accepts), entry); - return proceed; + return responseEntity; } private Map<String, String> computeCacheHeaders(final MultivaluedMap<String, String> httpHeaders) { @@ -106,13 +150,30 @@ public class CacheControlClientReaderInterceptor implements ReaderInterceptor { final String etagHeader = httpHeaders.getFirst(HttpHeaders.ETAG); if (etagHeader != null) { - cacheHeaders.put("If-None-Match", etagHeader); + cacheHeaders.put(HttpHeaders.IF_NONE_MATCH, etagHeader); } final String lastModifiedHeader = httpHeaders.getFirst(HttpHeaders.LAST_MODIFIED); if (lastModifiedHeader != null) { - cacheHeaders.put("If-Modified-Since", lastModifiedHeader); + cacheHeaders.put(HttpHeaders.IF_MODIFIED_SINCE, lastModifiedHeader); } return cacheHeaders; } + + public boolean isCacheInputStream() { + return cacheResponseInputStream; + } + /** + * Enforce the caching of the response stream. + * This is not recommended if the client code expects Serializable data, + * example, String or custom JAXB beans marked as Serializable, + * which can be stored in the cache directly. + * Use this property only if returning a cached entity does require a + * repeated stream parsing. + * + * @param cacheInputStream + */ + public void setCacheResponseInputStream(boolean cacheInputStream) { + this.cacheResponseInputStream = cacheInputStream; + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/a77eeb98/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 index 3656c9f..0e46d17 100644 --- 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 @@ -25,15 +25,18 @@ import java.util.Map; import javax.annotation.Priority; import javax.cache.Cache; +import javax.ws.rs.HttpMethod; 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 { + public static final String NO_CACHE_PROPERTY = "no_client_cache"; + public static final String CACHED_ENTITY_PROPERTY = "client_cached_entity"; + public static final String CLIENT_ACCEPTS = "client_accepts"; private Cache<Key, Entry> cache; public CacheControlClientRequestFilter(final Cache<Key, Entry> cache) { @@ -46,8 +49,8 @@ public class CacheControlClientRequestFilter implements ClientRequestFilter { @Override public void filter(final ClientRequestContext request) throws IOException { - if (!"GET".equals(request.getMethod())) { - request.setProperty("no_client_cache", "true"); + if (!HttpMethod.GET.equals(request.getMethod())) { + request.setProperty(NO_CACHE_PROPERTY, "true"); return; } final URI uri = request.getUri(); @@ -58,7 +61,8 @@ public class CacheControlClientRequestFilter implements ClientRequestFilter { if (entry.isOutDated()) { cache.remove(key, entry); } else { - Response.ResponseBuilder ok = Response.ok(new String(entry.getData())); + Object cachedEntity = entry.getData(); + Response.ResponseBuilder ok = Response.ok(cachedEntity); if (entry.getHeaders() != null) { for (Map.Entry<String, List<String>> h : entry.getHeaders().entrySet()) { for (final Object instance : h.getValue()) { @@ -66,9 +70,11 @@ public class CacheControlClientRequestFilter implements ClientRequestFilter { } } } + request.setProperty(CACHED_ENTITY_PROPERTY, cachedEntity); request.abortWith(ok.build()); } } + request.setProperty(CLIENT_ACCEPTS, accepts); } public CacheControlClientRequestFilter setCache(final Cache<Key, Entry> c) { http://git-wip-us.apache.org/repos/asf/cxf/blob/a77eeb98/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 index 9b626d5..db8094d 100644 --- 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 @@ -26,31 +26,34 @@ 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.expiry.ExpiryPolicy; +import javax.cache.integration.CacheLoader; +import javax.cache.integration.CacheWriter; 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; - + private boolean cacheResponseInputStream; + @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)); + CacheControlClientReaderInterceptor reader = new CacheControlClientReaderInterceptor(entryCache); + reader.setCacheResponseInputStream(cacheResponseInputStream); + context.register(reader); return true; } @@ -71,9 +74,9 @@ public class CacheControlFeature implements Feature { final Properties props = new Properties(); props.putAll(properties); - final String prefix = ClientCache.class.getName() + "."; + final String prefix = this.getClass().getName() + "."; final String uri = props.getProperty(prefix + "config-uri"); - final String name = props.getProperty(prefix + "name", ClientCache.class.getName()); + final String name = props.getProperty(prefix + "name", this.getClass().getName()); final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); @@ -95,15 +98,21 @@ public class CacheControlFeature implements Feature { final String loader = props.getProperty(prefix + "loaderFactory"); if (loader != null) { - configuration.setCacheLoaderFactory(newInstance(contextClassLoader, loader, Factory.class)); + @SuppressWarnings("unchecked") + Factory<? extends CacheLoader<Key, Entry>> f = newInstance(contextClassLoader, loader, Factory.class); + configuration.setCacheLoaderFactory(f); } final String writer = props.getProperty(prefix + "writerFactory"); if (writer != null) { - configuration.setCacheWriterFactory(newInstance(contextClassLoader, writer, Factory.class)); + @SuppressWarnings("unchecked") + Factory<? extends CacheWriter<Key, Entry>> f = newInstance(contextClassLoader, writer, Factory.class); + configuration.setCacheWriterFactory(f); } final String expiry = props.getProperty(prefix + "expiryFactory"); if (expiry != null) { - configuration.setExpiryPolicyFactory(newInstance(contextClassLoader, expiry, Factory.class)); + @SuppressWarnings("unchecked") + Factory<? extends ExpiryPolicy> f = newInstance(contextClassLoader, expiry, Factory.class); + configuration.setExpiryPolicyFactory(f); } cache = manager.createCache(name, configuration); @@ -113,6 +122,7 @@ public class CacheControlFeature implements Feature { } } + @SuppressWarnings("unchecked") private static <T> T newInstance(final ClassLoader contextClassLoader, final String clazz, final Class<T> cast) { try { return (T) contextClassLoader.loadClass(clazz).newInstance(); @@ -120,4 +130,8 @@ public class CacheControlFeature implements Feature { throw new IllegalArgumentException(e); } } + + public void setCacheResponseInputStream(boolean cacheStream) { + this.cacheResponseInputStream = cacheStream; + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/a77eeb98/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 deleted file mode 100644 index 45f629a..0000000 --- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/cache/ClientCache.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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/a77eeb98/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 index 4f01713..a93df67 100644 --- 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 @@ -27,14 +27,14 @@ 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 Serializable data; private MultivaluedMap<String, String> headers; private long expiresValue; private long initialTimestamp = now(); - public Entry(final byte[] bytes, final MultivaluedMap<String, String> headers, + public Entry(final Serializable data, final MultivaluedMap<String, String> headers, final Map<String, String> cacheHeaders, final long expiresHeaderValue) { - this.data = bytes; + this.data = data; this.headers = headers; this.cacheHeaders = cacheHeaders; this.expiresValue = expiresHeaderValue; @@ -56,11 +56,11 @@ public class Entry implements Serializable { this.cacheHeaders = cacheHeaders; } - public byte[] getData() { + public Serializable getData() { return data; } - public void setData(final byte[] data) { + public void setData(final Serializable data) { this.data = data; } http://git-wip-us.apache.org/repos/asf/cxf/blob/a77eeb98/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 index 027c7aa..1a9ee54 100644 --- 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 @@ -19,23 +19,27 @@ package org.apache.cxf.jaxrs.client.cache; -import java.net.HttpURLConnection; +import java.io.InputStream; +import java.io.Serializable; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.Produces; 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 javax.xml.bind.annotation.XmlRootElement; import org.apache.cxf.endpoint.Server; +import org.apache.cxf.helpers.IOUtils; 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.apache.cxf.transport.local.LocalTransportFactory; import org.junit.AfterClass; import org.junit.Assert; @@ -52,6 +56,7 @@ public class ClientCacheTest extends Assert { final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean(); sf.setResourceClasses(TheServer.class); sf.setResourceProvider(TheServer.class, new SingletonResourceProvider(new TheServer(), false)); + sf.setTransportId(LocalTransportFactory.TRANSPORT_ID); sf.setAddress(ADDRESS); server = sf.create(); } @@ -64,19 +69,104 @@ public class ClientCacheTest extends Assert { @Test @Ignore - 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)); + public void testGetTimeString() { + CacheControlFeature feature = new CacheControlFeature(); + try { + final WebTarget base = ClientBuilder.newBuilder().register(feature).build().target(ADDRESS); + final Invocation.Builder cached = base.request("text/plain").header(HttpHeaders.CACHE_CONTROL, "public"); + final Response r = cached.get(); + assertEquals(Response.Status.OK.getStatusCode(), r.getStatus()); + final String r1 = r.readEntity(String.class); + waitABit(); + assertEquals(r1, cached.get().readEntity(String.class)); + } finally { + feature.close(); + } } - + + @Test + @Ignore + public void testGetTimeStringAsInputStream() throws Exception { + CacheControlFeature feature = new CacheControlFeature(); + try { + final WebTarget base = ClientBuilder.newBuilder().register(feature).build().target(ADDRESS); + final Invocation.Builder cached = base.request("text/plain").header(HttpHeaders.CACHE_CONTROL, "public"); + final Response r = cached.get(); + assertEquals(Response.Status.OK.getStatusCode(), r.getStatus()); + InputStream is = r.readEntity(InputStream.class); + final String r1 = IOUtils.readStringFromStream(is); + waitABit(); + is = cached.get().readEntity(InputStream.class); + final String r2 = IOUtils.readStringFromStream(is); + assertEquals(r1, r2); + } finally { + feature.close(); + } + } + + @Test + @Ignore + public void testGetTimeStringAsInputStreamAndString() throws Exception { + CacheControlFeature feature = new CacheControlFeature(); + try { + feature.setCacheResponseInputStream(true); + final WebTarget base = ClientBuilder.newBuilder().register(feature).build().target(ADDRESS); + final Invocation.Builder cached = base.request("text/plain").header(HttpHeaders.CACHE_CONTROL, "public"); + final Response r = cached.get(); + assertEquals(Response.Status.OK.getStatusCode(), r.getStatus()); + InputStream is = r.readEntity(InputStream.class); + final String r1 = IOUtils.readStringFromStream(is); + waitABit(); + // CassCastException would occur without a cached stream support + final String r2 = cached.get().readEntity(String.class); + assertEquals(r1, r2); + } finally { + feature.close(); + } + } + @Test + @Ignore + public void testGetTimeStringAsStringAndInputStream() throws Exception { + CacheControlFeature feature = new CacheControlFeature(); + try { + feature.setCacheResponseInputStream(true); + final WebTarget base = ClientBuilder.newBuilder().register(feature).build().target(ADDRESS); + final Invocation.Builder cached = base.request("text/plain").header(HttpHeaders.CACHE_CONTROL, "public"); + final Response r = cached.get(); + assertEquals(Response.Status.OK.getStatusCode(), r.getStatus()); + final String r1 = r.readEntity(String.class); + waitABit(); + // CassCastException would occur without a cached stream support + InputStream is = cached.get().readEntity(InputStream.class); + final String r2 = IOUtils.readStringFromStream(is); + assertEquals(r1, r2); + } finally { + feature.close(); + } + } + + @Test + @Ignore + public void testGetJaxbBookCache() { + CacheControlFeature feature = new CacheControlFeature(); + try { + final WebTarget base = ClientBuilder.newBuilder().register(feature).build().target(ADDRESS); + final Invocation.Builder cached = + setAsLocal(base.request("application/xml")).header(HttpHeaders.CACHE_CONTROL, "public"); + final Response r = cached.get(); + assertEquals(Response.Status.OK.getStatusCode(), r.getStatus()); + final Book b1 = r.readEntity(Book.class); + assertEquals("JCache", b1.getName()); + assertNotNull(b1.getId()); + waitABit(); + assertEquals(b1, cached.get().readEntity(Book.class)); + } finally { + feature.close(); + } + } + private static Invocation.Builder setAsLocal(final Invocation.Builder client) { - WebClient.getConfig(InvocationBuilderImpl.class.cast(client).getWebClient()) - .getRequestContext().put(LocalConduit.DIRECT_DISPATCH, Boolean.TRUE); + WebClient.getConfig(client).getRequestContext().put(LocalConduit.DIRECT_DISPATCH, Boolean.TRUE); return client; } @@ -91,10 +181,55 @@ public class ClientCacheTest extends Assert { @Path("/") public static class TheServer { @GET - @ClientCache - public Response array() { + @Produces("text/plain") + public Response getString() { return Response.ok(Long.toString(System.currentTimeMillis())) .tag("123").cacheControl(CacheControl.valueOf("max-age=50000")).build(); } + @GET + @Produces("application/xml") + public Response getJaxbBook() { + Book b = new Book(); + b.setId(System.currentTimeMillis()); + b.setName("JCache"); + return Response.ok(b).tag("123").cacheControl(CacheControl.valueOf("max-age=50000")).build(); + } + } + @XmlRootElement + public static class Book implements Serializable { + private static final long serialVersionUID = 4924824780883333782L; + private String name; + private Long id; + public Book() { + + } + public Book(String name, long id) { + this.name = name; + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public int hashCode() { + return id.hashCode() + 37 * name.hashCode(); + } + public boolean equals(Object o) { + if (o instanceof Book) { + Book other = (Book)o; + return other.id.equals(id) && other.name.equals(name); + } else { + return false; + } + } } + }
