package org.radargun.http.service; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import javax.ws.rs.HttpMethod; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.Invocation.Builder; import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.radargun.logging.Log; import org.radargun.logging.LogFactory; import org.radargun.traits.BasicOperations; /** * Implementation of {@link BasicOperations} through the HTTP protocol, using the JAX-RS 2.0 client * api with RESTEasy implementation. * * @author Alan Field <afield@redhat.com> */ public class RESTEasyCacheOperations implements BasicOperations { private static final Log log = LogFactory.getLog(RESTEasyCacheOperations.class); private final RESTEasyCacheService service; public RESTEasyCacheOperations(RESTEasyCacheService service) { this.service = service; } @Override public <K, V> HTTPCache<K, V> getCache(String cacheName) { if (service.isRunning()) { if (cacheName != null && (service.cacheName == null || !service.cacheName.equals(cacheName))) { throw new UnsupportedOperationException(); } return new HTTPCache<K, V>(); } return null; } protected class HTTPCache<K, V> implements BasicOperations.Cache<K, V> { @Override public V get(K key) { return getInternal(key).value; } private WrappedValue<V> getInternal(K key) { V value = null; EntityTag eTag = null; if (service.isRunning()) { String target = service.buildUrl(key); Response response = null; try { Invocation get = service.getHttpClient().target(target).request().accept(service.getContentType()) .buildGet(); response = get.invoke(); if (response.getStatus() == Status.NOT_FOUND.getStatusCode()) { log.warn("RESTEasyCacheOperations.getInternal::Key: " + key + " does not exist in cache: " + service.cacheName); } else { eTag = response.getEntityTag(); value = (V) service.decodeByteArray(response.readEntity(byte[].class)); } } catch (Exception e) { throw new RuntimeException("RESTEasyCacheOperations::get request threw exception: " + target, e); } finally { if (response != null) { response.close(); } } } return new WrappedValue<V>(eTag, value); } private class WrappedValue<W> { EntityTag eTag; W value; public WrappedValue(EntityTag eTag, W value) { super(); this.eTag = eTag; this.value = value; } } @Override public boolean containsKey(K key) { if (service.isRunning()) { String target = service.buildUrl(key); Response response = null; response = service.getHttpClient().target(target).request().build(HttpMethod.HEAD).invoke(); if (response.getStatus() == Status.OK.getStatusCode()) { return true; } if (response.getStatus() == Status.NOT_FOUND.getStatusCode()) { return false; } throw new RuntimeException("RESTEasyCacheOperations.containsKey::Unexpected HttpStatus: " + response.getStatus() + " " + response.getStatusInfo().getReasonPhrase() + " for request " + target); } return false; } @Override public void put(K key, V value) { putInternal(key, value, null); } private void putInternal(K key, V value, EntityTag eTag) { if (service.isRunning()) { Response response = null; try { String target = service.buildUrl(key); Builder putBuilder = service.getHttpClient().target(target).request().accept(service.getContentType()); if (eTag != null) { // If the eTag doesn't match the current value for the key, then the put will fail putBuilder = putBuilder.header(HttpHeaders.IF_MATCH, eTag.getValue()); } response = putBuilder.buildPut(Entity.entity(encodeObject(value), service.getContentType())).invoke(); int status = response.getStatus(); String reason = response.getStatusInfo().getReasonPhrase(); response.close(); if (status != Status.OK.getStatusCode() && status != Status.CREATED.getStatusCode() && status != Status.NO_CONTENT.getStatusCode()) { throw new RuntimeException("RESTEasyCacheOperations.put::Unexpected HttpStatus: " + status + " " + reason + " for request " + target); } } catch (IOException e) { throw new RuntimeException(e); } finally { if (response != null) { response.close(); } } } } @Override public V getAndPut(K key, V value) { V prevValue = null; EntityTag eTag = null; if (service.isRunning()) { if (containsKey(key)) { HTTPCache<K, V>.WrappedValue<V> wrap = getInternal(key); eTag = wrap.eTag; prevValue = wrap.value; } if (eTag == null) { put(key, value); } else { putInternal(key, value, eTag); } } return prevValue; } @Override public boolean remove(K key) { return doDelete(service.buildUrl(key), null); } public boolean remove(K key, EntityTag eTag) { return doDelete(service.buildUrl(key), eTag); } @Override public V getAndRemove(K key) { if (service.isRunning()) { if (containsKey(key)) { HTTPCache<K, V>.WrappedValue<V> wrap = getInternal(key); if (wrap.eTag != null) { if (remove(key, wrap.eTag)) { return wrap.value; } } else { if (remove(key)) { return wrap.value; } } } } return null; } @Override public void clear() { doDelete(service.buildCacheUrl(service.cacheName), null); } private boolean doDelete(String target, EntityTag eTag) { if (service.isRunning()) { Builder deleteBuilder = service.getHttpClient().target(target).request().accept(service.getContentType()); if (eTag != null) { // If the eTag doesn't match the current value for the key, then the delete will fail deleteBuilder = deleteBuilder.header(HttpHeaders.IF_MATCH, eTag.getValue()); } Response response = deleteBuilder.buildDelete().invoke(); int status = response.getStatus(); String reason = response.getStatusInfo().getReasonPhrase(); response.close(); if (status == Status.OK.getStatusCode() || status == Status.NO_CONTENT.getStatusCode()) { return true; } if (status == Status.NOT_FOUND.getStatusCode()) { return false; } throw new RuntimeException("RESTEasyCacheOperations.doDelete::Unexpected HttpStatus: " + status + " " + reason + " for request " + target); } return false; } private byte[] encodeObject(Object object) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bout); oo.writeObject(object); oo.flush(); return bout.toByteArray(); } } }