package org.radargun.http.service;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.resteasy.client.jaxrs.BasicAuthentication;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder.HostnameVerificationPolicy;
import org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine;
import org.radargun.Service;
import org.radargun.config.Property;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.traits.Lifecycle;
import org.radargun.traits.ProvidesTrait;
import org.radargun.utils.RESTAddressListConverter;
import org.radargun.utils.TimeConverter;
/**
* REST client service using the JAX-RS 2.0 client api with the RESTEasy engine based on
* java.net.HttpURLConnection
*
* @author Alan Field <afield@redhat.com>
*/
@Service(doc = "RestEasy REST client for Cache")
public class RESTEasyCacheService implements Lifecycle {
private static final Log log = LogFactory.getLog(RESTEasyCacheService.class);
private ResteasyClient httpClient = null;
@Property(doc = "The default path on the server for the REST service. Defaults to 'rest'.")
private String rootPath = "rest";
@Property(doc = "The username to use on an authenticated server. Defaults to null.")
private String username;
@Property(doc = "The password of the username to use on an authenticated server. Defaults to null.")
private String password;
@Property(doc = "The content type used for put and get operations. Defaults to application/octet-stream.")
private String contentType = "application/octet-stream";
@Property(doc = "Expected cache name. Requests for other caches will fail. Defaults to 'default'.")
protected String cacheName = "default";
@Property(doc = "Semicolon-separated list of server addresses.", converter = RESTAddressListConverter.class)
protected List<InetSocketAddress> servers;
@Property(doc = "Timeout for socket. Default is 30 seconds.", converter = TimeConverter.class)
protected long socketTimeout = 30000;
@Property(doc = "Timeout for connection. Default is 30 seconds.", converter = TimeConverter.class)
protected long connectionTimeout = 30000;
@Property(doc = "The size of the connection pool. Default is unlimited.")
protected int maxConnections = 0;
@Property(doc = "The number of connections to pool per url. Default is equal to <code>maxConnections</code>.")
protected int maxConnectionsPerHost = 0;
// Used to load balance requests across servers
protected AtomicInteger nextIndex = new AtomicInteger(0);
@ProvidesTrait
public RESTEasyCacheInfo createCacheInfo() {
return new RESTEasyCacheInfo(this);
}
@ProvidesTrait
public RESTEasyCacheOperations createOperations() {
return new RESTEasyCacheOperations(this);
}
@ProvidesTrait
public Lifecycle getLifecycle() {
return this;
}
@Override
public synchronized void start() {
if (httpClient != null) {
log.warn("Service already started");
return;
}
httpClient = new ResteasyClientBuilder().httpEngine(new URLConnectionEngine())
.establishConnectionTimeout(connectionTimeout, TimeUnit.MILLISECONDS)
.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS).connectionPoolSize(maxConnections)
.maxPooledPerRoute(maxConnectionsPerHost).hostnameVerification(HostnameVerificationPolicy.ANY).build();
if (username != null) {
BasicAuthentication auth = new BasicAuthentication(username, password);
httpClient.register(auth);
}
}
public String getContentType() {
return contentType;
}
public String getRootPath() {
return rootPath;
}
public ResteasyClient getHttpClient() {
return httpClient;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
@Override
public synchronized void stop() {
if (httpClient == null) {
log.warn("Service not started");
return;
}
httpClient.close();
httpClient = null;
}
@Override
public synchronized boolean isRunning() {
return httpClient != null;
}
/**
* Distributes HTTP requests among servers.
*
* @return InetSocketAddress of the next server to use.
*/
public InetSocketAddress nextServer() {
return servers.get((nextIndex.getAndIncrement() & Integer.MAX_VALUE) % servers.size());
}
String buildUrl(Object key) {
StringBuilder str = new StringBuilder(buildCacheUrl(cacheName));
if (key != null) {
str.append("/").append(key);
}
log.trace("buildUrl(Object key) = " + str);
return str.toString();
}
String buildCacheUrl(String cache) {
InetSocketAddress node = nextServer();
StringBuilder s = new StringBuilder("http://");
if (getUsername() != null) {
try {
s.append(URLEncoder.encode(getUsername(), "UTF-8")).append(":")
.append(URLEncoder.encode(getPassword(), "UTF-8")).append("@");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Could not encode the supplied username and password", e);
}
}
s.append(node.getHostName()).append(":").append(node.getPort()).append("/");
if (getRootPath() != null) {
s.append(getRootPath()).append("/");
}
s.append(cache);
log.trace("buildCacheUrl(String cache) = " + s.toString());
return s.toString();
}
Object decodeByteArray(byte[] bytes) throws IOException, ClassNotFoundException {
if (bytes != null) {
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bin);
try {
return ois.readObject();
} finally {
if (bin != null) {
bin.close();
}
}
}
return null;
}
}