package com.plexobject.rbac.http.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.SocketException;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.validator.GenericValidator;
import org.apache.log4j.Logger;
import com.plexobject.rbac.Configuration;
import com.plexobject.rbac.domain.Tuple;
import com.plexobject.rbac.http.RestClient;
import com.plexobject.rbac.http.RestException;
import com.plexobject.rbac.metric.Metric;
import com.plexobject.rbac.metric.Timing;
/**
* This class provides APIs to call remote Web service with JSON payload
*
*/
public class RestClientImpl implements RestClient {
private static final Logger LOGGER = Logger.getLogger(RestClientImpl.class);
private static final int CONNECTION_TIMEOUT_MILLIS = Configuration
.getInstance().getInteger("rest.connection.timeout.millis", 10000);
private HttpClient httpClient = new HttpClient();
private final String url;
public RestClientImpl(final String url) {
this(url, null, null);
}
public RestClientImpl(final String url, final String subjectName,
final String credentials) {
if (GenericValidator.isBlankOrNull(url)) {
throw new IllegalArgumentException("url not specified");
}
this.url = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
httpClient.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
httpClient.getParams().setSoTimeout(CONNECTION_TIMEOUT_MILLIS);
httpClient.getParams().setParameter("http.socket.timeout",
Integer.valueOf(CONNECTION_TIMEOUT_MILLIS));
httpClient.getParams().setParameter("http.connection.timeout",
Integer.valueOf(CONNECTION_TIMEOUT_MILLIS));
if (!GenericValidator.isBlankOrNull(subjectName)
&& !GenericValidator.isBlankOrNull(credentials)) {
httpClient.getParams().setAuthenticationPreemptive(true);
final Credentials creds = new UsernamePasswordCredentials(
subjectName, credentials);
httpClient.getState().setCredentials(AuthScope.ANY, creds);
}
}
/*
* @see com.peak6.weseed.search.http.RestClient#get(java.lang.String)
*/
public Tuple get(final String path) throws IOException {
return execute(new GetMethod(url(path)));
}
/*
* @see com.peak6.weseed.search.http.RestClient#put(java.lang.String,
* java.lang.String)
*/
public Tuple put(final String path, final String body) throws IOException {
final PutMethod method = new PutMethod(url(path));
if (body != null) {
method.setRequestEntity(new StringRequestEntity(body,
"application/json", "UTF-8"));
}
try {
return execute(method);
} finally {
method.releaseConnection();
}
}
/*
* @see com.peak6.weseed.search.http.RestClient#delete(java.lang.String)
*/
public int delete(final String path) throws IOException {
final DeleteMethod method = new DeleteMethod(url(path));
try {
return httpClient.executeMethod(method);
} catch (SocketException e) {
throw new IOException("Failed to connet to " + url(path), e);
} finally {
method.releaseConnection();
}
}
private String url(final String path) {
return String.format("%s/%s", url, path);
}
/*
* @see com.peak6.weseed.search.http.RestClient#post(java.lang.String,
* java.lang.String)
*/
public Tuple post(final String path, final String body) throws IOException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Posting to " + path + ": " + body);
}
final PostMethod post = new PostMethod(url(path));
post.setRequestEntity(new StringRequestEntity(body, "application/json",
"UTF-8"));
return execute(post);
}
private Tuple execute(final HttpMethodBase method) throws IOException {
return execute(method, 0);
}
private Tuple execute(final HttpMethodBase method, int numTries)
throws IOException {
final Timing timer = Metric.newTiming("RestClientImpl.execute");
try {
final int sc = httpClient.executeMethod(method);
if (sc < OK_MIN || sc > OK_MAX) {
throw new RestException("Unexpected status code: " + sc + ": "
+ method.getStatusText() + " -- " + method, sc);
}
final InputStream in = method.getResponseBodyAsStream();
try {
final StringWriter writer = new StringWriter(2048);
IOUtils.copy(in, writer, method.getResponseCharSet());
return new Tuple(sc, writer.toString());
} finally {
in.close();
}
} catch (NullPointerException e) {
if (numTries < 3) {
try {
Thread.sleep(200);
} catch (InterruptedException ie) {
Thread.interrupted();
}
return execute(method, numTries + 1);
}
throw new IOException("Failed to connet to " + url + " [" + method
+ "]", e);
} catch (SocketException e) {
if (numTries < 3) {
try {
Thread.sleep(200);
} catch (InterruptedException ie) {
Thread.interrupted();
}
return execute(method, numTries + 1);
}
throw new IOException("Failed to connet to " + url + " [" + method
+ "]", e);
} catch (IOException e) {
if (numTries < 3) {
try {
Thread.sleep(200);
} catch (InterruptedException ie) {
Thread.interrupted();
}
return execute(method, numTries + 1);
}
throw e;
} finally {
method.releaseConnection();
timer.stop();
}
}
/**
* @see java.lang.Object#equals(Object)
*/
@Override
public boolean equals(Object object) {
if (!(object instanceof RestClientImpl)) {
return false;
}
RestClientImpl rhs = (RestClientImpl) object;
return new EqualsBuilder().append(this.url, rhs.url).isEquals();
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return new HashCodeBuilder(786529047, 1924536713).append(this.url)
.toHashCode();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return new ToStringBuilder(this).append("url", this.url).toString();
}
}