issue 69: refactored http clients to not be bound to a single endpoint such that redirects can be assigned to another host

git-svn-id: http://jclouds.googlecode.com/svn/trunk@1456 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-06-24 18:25:04 +00:00
parent 39e96d041f
commit 48c3155450
26 changed files with 838 additions and 349 deletions

View File

@ -33,7 +33,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
*
* @author Adrian Cole
*/
public class FutureCommand<Q, R, T> implements Future<T> {
public class FutureCommand<E, Q extends Request<E>, R, T> implements Future<T> {
private final Q request;
private final ResponseRunnableFuture<R, T> responseRunnableFuture;

View File

@ -28,6 +28,6 @@ package org.jclouds.command;
*
* @author Adrian Cole
*/
public interface FutureCommandClient<O extends FutureCommand<?, ?, ?>> {
void submit(O operation);
public interface FutureCommandClient<O extends FutureCommand<?, ?, ?, ?>> {
void submit(O operation);
}

View File

@ -0,0 +1,35 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.jclouds.command;
/**
* A service request must have an endPoint associated with it.
*
* @author Adrian Cole
*/
public interface Request<E> {
E getEndPoint();
void setEndPoint(E endPoint);
}

View File

@ -32,76 +32,78 @@ import javax.annotation.Resource;
import org.jclouds.command.FutureCommand;
import org.jclouds.logging.Logger;
import com.google.inject.assistedinject.Assisted;
/**
* // TODO: Adrian: Document this!
* Associates a command with an open connection to a service.
*
* @author Adrian Cole
*/
public abstract class FutureCommandConnectionHandle<C, O extends FutureCommand<?, ?, ?>> {
protected final BlockingQueue<C> available;
protected final Semaphore maxConnections;
protected final Semaphore completed;
protected C conn;
protected O command;
@Resource
protected Logger logger = Logger.NULL;
public abstract class FutureCommandConnectionHandle<E, C, O extends FutureCommand<E, ?, ?, ?>> {
protected final BlockingQueue<C> available;
protected final Semaphore maxConnections;
protected final Semaphore completed;
protected final E endPoint;
protected C conn;
protected O command;
@Resource
protected Logger logger = Logger.NULL;
public FutureCommandConnectionHandle(Semaphore maxConnections,
@Assisted O command, @Assisted C conn, BlockingQueue<C> available)
throws InterruptedException {
this.maxConnections = maxConnections;
this.command = command;
this.conn = conn;
this.available = available;
this.completed = new Semaphore(1);
completed.acquire();
}
public FutureCommandConnectionHandle(Semaphore maxConnections, BlockingQueue<C> available,
E endPoint, O command, C conn) throws InterruptedException {
this.available = available;
this.maxConnections = maxConnections;
this.completed = new Semaphore(1);
this.endPoint = endPoint;
this.command = command;
this.conn = conn;
completed.acquire();
}
public O getCommand() {
return command;
}
public O getCommand() {
return command;
}
public abstract void startConnection();
public abstract void startConnection();
public boolean isCompleted() {
return (completed.availablePermits() == 1);
}
public boolean isCompleted() {
return (completed.availablePermits() == 1);
}
public void release() throws InterruptedException {
if (isCompleted()) {
return;
}
logger.trace("%1$s - %2$d - releasing to pool", conn, conn.hashCode());
available.put(conn);
conn = null;
command = null;
completed.release();
}
public void release() throws InterruptedException {
if (isCompleted() || alreadyReleased()) {
return;
}
logger.trace("%1$s - %2$d - releasing to pool", conn, conn.hashCode());
available.put(conn);
conn = null;
command = null;
completed.release();
}
public void cancel() throws IOException {
if (isCompleted()) {
return;
}
if (conn != null) {
logger.trace("%1$s - %2$d - cancelled; shutting down connection",
conn, conn.hashCode());
try {
shutdownConnection();
} finally {
conn = null;
command = null;
maxConnections.release();
}
}
completed.release();
}
private boolean alreadyReleased() {
return conn == null;
}
public abstract void shutdownConnection() throws IOException;
public void cancel() throws IOException {
if (isCompleted()) {
return;
}
if (conn != null) {
logger.trace("%1$s - %2$d - cancelled; shutting down connection", conn, conn.hashCode());
try {
shutdownConnection();
} finally {
conn = null;
command = null;
maxConnections.release();
}
}
completed.release();
}
public void waitFor() throws InterruptedException {
completed.acquire();
completed.release();
}
public abstract void shutdownConnection() throws IOException;
public void waitFor() throws InterruptedException {
completed.acquire();
completed.release();
}
}

View File

@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.jclouds.command.FutureCommand;
import org.jclouds.lifecycle.BaseLifeCycle;
import com.google.inject.Provides;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.name.Named;
/**
@ -41,130 +41,156 @@ import com.google.inject.name.Named;
*
* @author Adrian Cole
*/
public abstract class FutureCommandConnectionPool<C, O extends FutureCommand<?, ?, ?>>
extends BaseLifeCycle {
protected final Semaphore allConnections;
protected final BlockingQueue<C> available;
protected final BlockingQueue<O> commandQueue;
protected final FutureCommandConnectionHandleFactory<C, O> futureCommandConnectionHandleFactory;
protected final int maxConnectionReuse;
protected final AtomicInteger currentSessionFailures = new AtomicInteger(0);
protected volatile boolean hitBottom = false;
public abstract class FutureCommandConnectionPool<E, C, O extends FutureCommand<E, ?, ?, ?>>
extends BaseLifeCycle {
public FutureCommandConnectionPool(
ExecutorService executor,
Semaphore allConnections,
BlockingQueue<O> commandQueue,
FutureCommandConnectionHandleFactory<C, O> futureCommandConnectionHandleFactory,
@Named("maxConnectionReuse") int maxConnectionReuse,
BlockingQueue<C> available, BaseLifeCycle... dependencies) {
super(executor, dependencies);
this.allConnections = allConnections;
this.commandQueue = commandQueue;
this.futureCommandConnectionHandleFactory = futureCommandConnectionHandleFactory;
this.maxConnectionReuse = maxConnectionReuse;
this.available = available;
}
protected final Semaphore allConnections;
protected final BlockingQueue<C> available;
protected void setResponseException(Exception ex, C conn) {
O command = getHandleFromConnection(conn).getCommand();
command.getResponseFuture().setException(ex);
}
/**
* inputOnly: nothing is taken from this queue.
*/
protected final BlockingQueue<O> resubmitQueue;
protected final int maxConnectionReuse;
protected final AtomicInteger currentSessionFailures = new AtomicInteger(0);
protected volatile boolean hitBottom = false;
protected final E endPoint;
protected void cancel(C conn) {
O command = getHandleFromConnection(conn).getCommand();
command.cancel(true);
}
public E getEndPoint() {
return endPoint;
}
@Provides
public C getConnection() throws InterruptedException, TimeoutException {
exceptionIfNotActive();
if (!hitBottom) {
hitBottom = available.size() == 0
&& allConnections.availablePermits() == 0;
if (hitBottom)
logger.warn("%1$s - saturated connection pool", this);
}
logger
.debug(
"%1$s - attempting to acquire connection; %d currently available",
this, available.size());
C conn = available.poll(5, TimeUnit.SECONDS);
if (conn == null)
throw new TimeoutException(
"could not obtain a pooled connection within 5 seconds");
public static interface Factory<E, C, O extends FutureCommand<E, ?, ?, ?>> {
FutureCommandConnectionPool<E, C, O> create(E endPoint);
}
logger.trace("%1$s - %2$d - aquired", conn, conn.hashCode());
if (connectionValid(conn)) {
logger.debug("%1$s - %2$d - reusing", conn, conn.hashCode());
return conn;
} else {
logger.debug("%1$s - %2$d - unusable", conn, conn.hashCode());
shutdownConnection(conn);
allConnections.release();
return getConnection();
}
}
public FutureCommandConnectionPool(ExecutorService executor, Semaphore allConnections,
BlockingQueue<O> commandQueue, @Named("maxConnectionReuse") int maxConnectionReuse,
BlockingQueue<C> available, @Assisted E endPoint, BaseLifeCycle... dependencies) {
super(executor, dependencies);
this.allConnections = allConnections;
this.resubmitQueue = commandQueue;
this.maxConnectionReuse = maxConnectionReuse;
this.available = available;
this.endPoint = endPoint;
}
protected void fatalException(Exception ex, C conn) {
setResponseException(ex, conn);
exception.set(ex);
shutdown();
}
protected void setResponseException(Exception ex, C conn) {
O command = getHandleFromConnection(conn).getCommand();
command.getResponseFuture().setException(ex);
}
protected abstract void shutdownConnection(C conn);
protected void cancel(C conn) {
O command = getHandleFromConnection(conn).getCommand();
command.cancel(true);
}
protected abstract boolean connectionValid(C conn);
protected C getConnection() throws InterruptedException, TimeoutException {
exceptionIfNotActive();
if (!hitBottom) {
hitBottom = available.size() == 0 && allConnections.availablePermits() == 0;
if (hitBottom)
logger.warn("%1$s - saturated connection pool", this);
}
logger.debug("%s - attempting to acquire connection; %s currently available", this, available
.size());
C conn = available.poll(5, TimeUnit.SECONDS);
if (conn == null)
throw new TimeoutException("could not obtain a pooled connection within 5 seconds");
public FutureCommandConnectionHandle<C, O> getHandle(O command)
throws InterruptedException, TimeoutException {
exceptionIfNotActive();
C conn = getConnection();
FutureCommandConnectionHandle<C, O> handle = futureCommandConnectionHandleFactory
.create(command, conn);
associateHandleWithConnection(handle, conn);
return handle;
}
logger.trace("%1$s - %2$d - aquired", conn, conn.hashCode());
if (connectionValid(conn)) {
logger.debug("%1$s - %2$d - reusing", conn, conn.hashCode());
return conn;
} else {
logger.debug("%1$s - %2$d - unusable", conn, conn.hashCode());
shutdownConnection(conn);
allConnections.release();
return getConnection();
}
}
protected void resubmitIfRequestIsReplayable(C connection, Exception e) {
O command = getCommandFromConnection(connection);
if (command != null) {
if (isReplayable(command)) {
logger.info("resubmitting command: %1$s", command);
commandQueue.add(command);
} else {
command.setException(e);
}
}
}
protected void fatalException(Exception ex, C conn) {
setResponseException(ex, conn);
exception.set(ex);
shutdown();
}
protected abstract boolean isReplayable(O command);
protected abstract void shutdownConnection(C conn);
O getCommandFromConnection(C connection) {
FutureCommandConnectionHandle<C, O> handle = getHandleFromConnection(connection);
if (handle != null && handle.getCommand() != null) {
return handle.getCommand();
}
return null;
}
protected abstract boolean connectionValid(C conn);
protected void setExceptionOnCommand(C connection, Exception e) {
FutureCommand<?, ?, ?> command = getCommandFromConnection(connection);
if (command != null) {
logger.warn(e, "exception in command: %1$s", command);
command.setException(e);
}
}
public FutureCommandConnectionHandle<E, C, O> getHandle(O command) throws InterruptedException,
TimeoutException {
exceptionIfNotActive();
C conn = getConnection();
FutureCommandConnectionHandle<E, C, O> handle = createHandle(command, conn);
associateHandleWithConnection(handle, conn);
return handle;
}
protected abstract void associateHandleWithConnection(
FutureCommandConnectionHandle<C, O> handle, C connection);
protected abstract FutureCommandConnectionHandle<E, C, O> createHandle(O command, C conn);
protected abstract FutureCommandConnectionHandle<C, O> getHandleFromConnection(
C connection);
protected void resubmitIfRequestIsReplayable(C connection, Exception e) {
O command = getCommandFromConnection(connection);
if (command != null) {
if (isReplayable(command)) {
logger.info("resubmitting command: %1$s", command);
resubmitQueue.add(command);
} else {
command.setException(e);
}
}
}
protected abstract void createNewConnection() throws InterruptedException;
protected abstract boolean isReplayable(O command);
O getCommandFromConnection(C connection) {
FutureCommandConnectionHandle<E, C, O> handle = getHandleFromConnection(connection);
if (handle != null && handle.getCommand() != null) {
return handle.getCommand();
}
return null;
}
protected void setExceptionOnCommand(C connection, Exception e) {
FutureCommand<E, ?, ?, ?> command = getCommandFromConnection(connection);
if (command != null) {
logger.warn(e, "exception in command: %1$s", command);
command.setException(e);
}
}
protected abstract void associateHandleWithConnection(
FutureCommandConnectionHandle<E, C, O> handle, C connection);
protected abstract FutureCommandConnectionHandle<E, C, O> getHandleFromConnection(C connection);
protected abstract void createNewConnection() throws InterruptedException;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((endPoint == null) ? 0 : endPoint.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FutureCommandConnectionPool<?, ?, ?> other = (FutureCommandConnectionPool<?, ?, ?>) obj;
if (endPoint == null) {
if (other.endPoint != null)
return false;
} else if (!endPoint.equals(other.endPoint))
return false;
return true;
}
public interface FutureCommandConnectionHandleFactory<C, O extends FutureCommand<?, ?, ?>> {
FutureCommandConnectionHandle<C, O> create(O command, C conn);
}
}

View File

@ -24,6 +24,7 @@
package org.jclouds.command.pool;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -33,6 +34,8 @@ import org.jclouds.command.FutureCommandClient;
import org.jclouds.lifecycle.BaseLifeCycle;
import org.jclouds.util.Utils;
import com.google.common.base.Function;
import com.google.common.collect.MapMaker;
import com.google.inject.Inject;
/**
@ -40,30 +43,30 @@ import com.google.inject.Inject;
*
* @author Adrian Cole
*/
public class FutureCommandConnectionPoolClient<C, O extends FutureCommand<?, ?, ?>> extends
public class FutureCommandConnectionPoolClient<E, C, O extends FutureCommand<E, ?, ?, ?>> extends
BaseLifeCycle implements FutureCommandClient<O> {
private final FutureCommandConnectionPool<C, O> futureCommandConnectionPool;
private final ConcurrentMap<E, FutureCommandConnectionPool<E, C, O>> poolMap;
private final BlockingQueue<O> commandQueue;
private final FutureCommandConnectionPool.Factory<E, C, O> poolFactory;
@Inject
public FutureCommandConnectionPoolClient(ExecutorService executor,
FutureCommandConnectionPool<C, O> futureCommandConnectionPool,
BlockingQueue<O> commandQueue) {
super(executor, futureCommandConnectionPool);
this.futureCommandConnectionPool = futureCommandConnectionPool;
FutureCommandConnectionPool.Factory<E, C, O> pf, BlockingQueue<O> commandQueue) {
super(executor);
this.poolFactory = pf;
// TODO inject this.
poolMap = new MapMaker()
.makeComputingMap(new Function<E, FutureCommandConnectionPool<E, C, O>>() {
public FutureCommandConnectionPool<E, C, O> apply(E endPoint) {
FutureCommandConnectionPool<E, C, O> pool = poolFactory.create(endPoint);
addDependency(pool);
return pool;
}
});
this.commandQueue = commandQueue;
}
/**
* {@inheritDoc}
* <p/>
* we continue while the connection pool is active
*/
@Override
protected boolean shouldDoWork() {
return super.shouldDoWork() && futureCommandConnectionPool.getStatus().equals(Status.ACTIVE);
}
/**
* {@inheritDoc}
*
@ -72,9 +75,9 @@ public class FutureCommandConnectionPoolClient<C, O extends FutureCommand<?, ?,
*/
@Override
protected void doShutdown() {
exception.compareAndSet(null, futureCommandConnectionPool.getException());
exception.compareAndSet(null, getExceptionFromDependenciesOrNull());
while (!commandQueue.isEmpty()) {
FutureCommand<?, ?, ?> command = (FutureCommand<?, ?, ?>) commandQueue.remove();
FutureCommand<E, ?, ?, ?> command = (FutureCommand<E, ?, ?, ?>) commandQueue.remove();
if (command != null) {
if (exception.get() != null)
command.setException(exception.get());
@ -119,21 +122,29 @@ public class FutureCommandConnectionPoolClient<C, O extends FutureCommand<?, ?,
*/
protected void invoke(O command) {
exceptionIfNotActive();
FutureCommandConnectionHandle<C, O> connectionHandle = null;
FutureCommandConnectionPool<E, C, O> pool = poolMap.get(command.getRequest().getEndPoint());
if (pool == null) {
//TODO limit;
logger.warn("pool not available for command %s; retrying", command);
commandQueue.add(command);
return;
}
FutureCommandConnectionHandle<E, C, O> connectionHandle = null;
try {
connectionHandle = futureCommandConnectionPool.getHandle(command);
connectionHandle = pool.getHandle(command);
} catch (InterruptedException e) {
logger.warn(e, "Interrupted getting a connection for command %1$s; retrying", command);
logger.warn(e, "Interrupted getting a connection for command %s; retrying", command);
commandQueue.add(command);
return;
} catch (TimeoutException e) {
logger.warn(e, "Timeout getting a connection for command %1$s; retrying", command);
logger.warn(e, "Timeout getting a connection for command %s on pool %s; retrying", command, pool);
commandQueue.add(command);
return;
}
if (connectionHandle == null) {
logger.error("Failed to obtain connection for command %1$s; retrying", command);
logger.error("Failed to obtain connection for command %s; retrying", command);
commandQueue.add(command);
return;
}
@ -146,7 +157,7 @@ public class FutureCommandConnectionPoolClient<C, O extends FutureCommand<?, ?,
sb.append("FutureCommandConnectionPoolClient");
sb.append("{status=").append(status);
sb.append(", commandQueue=").append((commandQueue != null) ? commandQueue.size() : 0);
sb.append(", futureCommandConnectionPool=").append(futureCommandConnectionPool);
sb.append(", poolMap=").append(poolMap);
sb.append('}');
return sb.toString();
}

View File

@ -32,7 +32,6 @@ import org.jclouds.lifecycle.config.LifeCycleModule;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
/**
@ -47,7 +46,7 @@ public abstract class FutureCommandConnectionPoolClientModule<C> extends
}
@Provides
@Singleton
// @Singleton per uri...
public abstract BlockingQueue<C> provideAvailablePool(
@Named(PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS) int max)
throws Exception;
@ -62,7 +61,7 @@ public abstract class FutureCommandConnectionPoolClientModule<C> extends
* @throws Exception
*/
@Provides
@Singleton
// @Singleton per uri...
public Semaphore provideTotalConnectionSemaphore(
@Named(PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS) int max)
throws Exception {

View File

@ -30,9 +30,9 @@ package org.jclouds.http;
*/
public interface HttpErrorHandler {
public static final HttpErrorHandler NOOP = new HttpErrorHandler() {
public void handle(HttpFutureCommand<?> command, HttpResponse response) {
public void handleError(HttpFutureCommand<?> command, HttpResponse response) {
}
};
void handle(HttpFutureCommand<?> command, HttpResponse response);
void handleError(HttpFutureCommand<?> command, HttpResponse response);
}

View File

@ -39,7 +39,16 @@ import org.jclouds.logging.Logger;
*
* @author Adrian Cole
*/
public class HttpFutureCommand<T> extends FutureCommand<HttpRequest, HttpResponse, T> {
public class HttpFutureCommand<T> extends FutureCommand<URI, HttpRequest, HttpResponse, T> {
private volatile int redirectCount;
public int incrementRedirectCount() {
return ++redirectCount;
}
public int getRedirectCount() {
return redirectCount;
}
public HttpFutureCommand(URI endPoint, HttpMethod method, String uri,
ResponseCallable<T> responseCallable) {

View File

@ -25,65 +25,61 @@ package org.jclouds.http;
public interface HttpHeaders {
/**
* Can be used to specify caching behavior along the request/reply chain. Go
* to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.9.
*/
public static final String CACHE_CONTROL = "Cache-Control";
/**
* Specifies presentational information for the object. Go to
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html?sec19.5.1.
*/
public static final String CONTENT_DISPOSITION = "Content-Disposition";
/**
* Specifies what content encodings have been applied to the object and thus
* what decoding mechanisms must be applied in order to obtain the
* media-type referenced by the Content-Type header field. Go to
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.11.
*/
public static final String CONTENT_ENCODING = "Content-Encoding";
/**
* The size of the object, in bytes. This is required. Go to
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.13.
*/
public static final String CONTENT_LENGTH = "Content-Length";
/**
* A standard MIME type describing the format of the contents. If none is
* provided, the default is binary/octet-stream. Go to
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.17.
*/
public static final String CONTENT_TYPE = "Content-Type";
/**
* The base64 encoded 128-bit MD5 digest of the message (without the
* headers) according to RFC 1864. This header can be used as a message
* integrity check to verify that the data is the same data that was
* originally sent.
*/
public static final String CONTENT_MD5 = "Content-MD5";
/**
* A user agent that wishes to authenticate itself with a server-- usually,
* but not necessarily, after receiving a 401 response--does so by including
* an Authorization request-header field with the request. The Authorization
* field value consists of credentials containing the authentication
* information of the user agent for the realm of the resource being
* requested.
*
* Authorization = "Authorization" ":" credentials
*
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html" />
*/
public static final String AUTHORIZATION = "Authorization";
public static final String HOST = "Host";
public static final String DATE = "Date";
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
public static final String LAST_MODIFIED = "Last-Modified";
public static final String SERVER = "Server";
public static final String ETAG = "ETag";
public static final String RANGE = "Range";
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
public static final String IF_MATCH = "If-Match";
public static final String IF_NONE_MATCH = "If-None-Match";
public static final String CONTENT_RANGE = "Content-Range";
/**
* Can be used to specify caching behavior along the request/reply chain. Go to
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.9.
*/
public static final String CACHE_CONTROL = "Cache-Control";
/**
* Specifies presentational information for the object. Go to
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html?sec19.5.1.
*/
public static final String CONTENT_DISPOSITION = "Content-Disposition";
/**
* Specifies what content encodings have been applied to the object and thus what decoding
* mechanisms must be applied in order to obtain the media-type referenced by the Content-Type
* header field. Go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.11.
*/
public static final String CONTENT_ENCODING = "Content-Encoding";
/**
* The size of the object, in bytes. This is required. Go to
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.13.
*/
public static final String CONTENT_LENGTH = "Content-Length";
/**
* A standard MIME type describing the format of the contents. If none is provided, the default
* is binary/octet-stream. Go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.17.
*/
public static final String CONTENT_TYPE = "Content-Type";
/**
* The base64 encoded 128-bit MD5 digest of the message (without the headers) according to RFC
* 1864. This header can be used as a message integrity check to verify that the data is the same
* data that was originally sent.
*/
public static final String CONTENT_MD5 = "Content-MD5";
/**
* A user agent that wishes to authenticate itself with a server-- usually, but not necessarily,
* after receiving a 401 response--does so by including an Authorization request-header field
* with the request. The Authorization field value consists of credentials containing the
* authentication information of the user agent for the realm of the resource being requested.
*
* Authorization = "Authorization" ":" credentials
*
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html" />
*/
public static final String AUTHORIZATION = "Authorization";
public static final String HOST = "Host";
public static final String DATE = "Date";
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
public static final String LAST_MODIFIED = "Last-Modified";
public static final String SERVER = "Server";
public static final String ETAG = "ETag";
public static final String RANGE = "Range";
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
public static final String IF_MATCH = "If-Match";
public static final String IF_NONE_MATCH = "If-None-Match";
public static final String CONTENT_RANGE = "Content-Range";
public static final String LOCATION = "Location";
}

View File

@ -30,6 +30,7 @@ import java.net.URI;
import javax.annotation.Resource;
import org.jclouds.command.Request;
import org.jclouds.logging.Logger;
import org.jclouds.util.Utils;
@ -38,7 +39,7 @@ import org.jclouds.util.Utils;
*
* @author Adrian Cole
*/
public class HttpRequest extends HttpMessage {
public class HttpRequest extends HttpMessage implements Request<URI> {
private URI endPoint;
private final HttpMethod method;
@ -100,10 +101,16 @@ public class HttpRequest extends HttpMessage {
this.payload = content;
}
/**
* only the scheme, host, and port of the URI designates the endpoint
*/
public void setEndPoint(URI endPoint) {
this.endPoint = endPoint;
}
/**
* only the scheme, host, and port of the URI designates the endpoint
*/
public URI getEndPoint() {
return endPoint;
}

View File

@ -24,25 +24,21 @@
package org.jclouds.http;
/**
* Indicate whether a request should be retried after a server
* error response (HTTP status code >= 500) based on the request's
* replayable status and the number of attempts already performed.
* Indicate whether a request should be retried after a server error response (HTTP status code >=
* 500) based on the request's replayable status and the number of attempts already performed.
*
* @author James Murty
*/
public interface HttpRetryHandler {
public static final HttpRetryHandler ALWAYS_RETRY = new HttpRetryHandler() {
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
{
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response) {
return true;
}
}
};
/**
* Return true if the command should be retried. This method should only be
* invoked when the response has failed with a HTTP 5xx error indicating a
* server-side error.
* Return true if the command should be retried. This method should only be invoked when the
* response has failed with a HTTP 5xx error indicating a server-side error.
*/
boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
throws InterruptedException;
boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response);
}

View File

@ -0,0 +1,44 @@
/**
*
* Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
*
* ====================================================================
* 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.jclouds.http.annotation;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
/**
* Implies that the object can address {@link org.jclouds.http.HttpResponse}s
* that contain status code 4xx.
*
* @author Adrian Cole
*/
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface ClientError {
}

View File

@ -0,0 +1,44 @@
/**
*
* Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
*
* ====================================================================
* 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.jclouds.http.annotation;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
/**
* Implies that the object can address {@link org.jclouds.http.HttpResponse}s that contain status
* code 3xx.
*
* @author Adrian Cole
*/
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface Redirection {
}

View File

@ -0,0 +1,44 @@
/**
*
* Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
*
* ====================================================================
* 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.jclouds.http.annotation;
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
/**
* Implies that the object can address {@link org.jclouds.http.HttpResponse}s that contain status
* code 5xx.
*
* @author Adrian Cole
*/
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface ServerError {
}

View File

@ -23,17 +23,10 @@
*/
package org.jclouds.http.config;
import java.net.MalformedURLException;
import java.net.URI;
import org.jclouds.http.HttpConstants;
import org.jclouds.http.HttpFutureCommandClient;
import org.jclouds.http.internal.JavaUrlHttpFutureCommandClient;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
/**
* Configures {@link JavaUrlHttpFutureCommandClient}.
@ -53,15 +46,4 @@ public class JavaUrlHttpFutureCommandClientModule extends AbstractModule {
bind(HttpFutureCommandClient.class).to(JavaUrlHttpFutureCommandClient.class);
}
@Singleton
@Provides
protected URI provideAddress(@Named(HttpConstants.PROPERTY_HTTP_ADDRESS) String address,
@Named(HttpConstants.PROPERTY_HTTP_PORT) int port,
@Named(HttpConstants.PROPERTY_HTTP_SECURE) boolean isSecure)
throws MalformedURLException {
return URI.create(String.format("%1$s://%2$s:%3$s", isSecure ? "https" : "http", address,
port));
}
}

View File

@ -35,30 +35,48 @@ import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* Allow replayable request to be retried a limited number of times, and
* impose an exponential back-off delay before returning.
* Allow replayable request to be retried a limited number of times, and impose an exponential
* back-off delay before returning.
* <p>
* The back-off delay grows rapidly according to the formula
* The back-off delay grows rapidly according to the formula
* <code>50 * (<i>{@link HttpFutureCommand#getFailureCount()}</i> ^ 2)</code>. For example:
* <table>
* <tr><th>Number of Attempts</th><th>Delay in milliseconds</th></tr>
* <tr><td>1</td><td>50</td></tr>
* <tr><td>2</td><td>200</td></tr>
* <tr><td>3</td><td>450</td></tr>
* <tr><td>4</td><td>800</td></tr>
* <tr><td>5</td><td>1250</td></tr>
* <tr>
* <th>Number of Attempts</th>
* <th>Delay in milliseconds</th>
* </tr>
* <tr>
* <td>1</td>
* <td>50</td>
* </tr>
* <tr>
* <td>2</td>
* <td>200</td>
* </tr>
* <tr>
* <td>3</td>
* <td>450</td>
* </tr>
* <tr>
* <td>4</td>
* <td>800</td>
* </tr>
* <tr>
* <td>5</td>
* <td>1250</td>
* </tr>
* </table>
* <p>
* This implementation has two side-effects. It increments the command's failure count
* with {@link HttpFutureCommand#incrementFailureCount()}, because this failure count
* value is used to determine how many times the command has already been tried. It
* also closes the response's content input stream to ensure connections are cleaned up.
* This implementation has two side-effects. It increments the command's failure count with
* {@link HttpFutureCommand#incrementFailureCount()}, because this failure count value is used to
* determine how many times the command has already been tried. It also closes the response's
* content input stream to ensure connections are cleaned up.
*
* @author James Murty
*/
public class BackoffLimitedRetryHandler implements HttpRetryHandler {
private final int retryCountLimit;
@Resource
protected Logger logger = Logger.NULL;
@ -67,33 +85,34 @@ public class BackoffLimitedRetryHandler implements HttpRetryHandler {
this.retryCountLimit = retryCountLimit;
}
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
throws InterruptedException
{
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response) {
IOUtils.closeQuietly(response.getContent());
command.incrementFailureCount();
if (!command.getRequest().isReplayable()) {
logger.warn("Cannot retry after server error, command is not replayable: %1$s", command);
return false;
} else if (command.getFailureCount() > retryCountLimit) {
logger.warn("Cannot retry after server error, command has exceeded retry limit %1$d: %2$s",
retryCountLimit, command);
logger.warn(
"Cannot retry after server error, command has exceeded retry limit %1$d: %2$s",
retryCountLimit, command);
return false;
} else {
imposeBackoffExponentialDelay(command.getFailureCount(), command.toString());
return true;
imposeBackoffExponentialDelay(command.getFailureCount(), command.toString());
return true;
}
}
public void imposeBackoffExponentialDelay(int failureCound, String commandDescription)
throws InterruptedException
{
public void imposeBackoffExponentialDelay(int failureCound, String commandDescription) {
long delayMs = (long) (50L * Math.pow(failureCound, 2));
logger.debug("Retry %1$d/%2$d after server error, delaying for %3$d ms: %4$s",
failureCound, retryCountLimit, delayMs, commandDescription);
Thread.sleep(delayMs);
logger.debug("Retry %1$d/%2$d after server error, delaying for %3$d ms: %4$s", failureCound,
retryCountLimit, delayMs, commandDescription);
try {
Thread.sleep(delayMs);
} catch (InterruptedException e) {
logger.error(e, "Interrupted imposing delay");
}
}
}

View File

@ -0,0 +1,41 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.jclouds.http.handlers;
import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpRetryHandler;
/**
* Always returns false.
*
* @author Adrian Cole
*/
public class CannotRetryHandler implements HttpRetryHandler {
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response) {
return false;
}
}

View File

@ -25,19 +25,19 @@ package org.jclouds.http.handlers;
import java.io.IOException;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.util.Utils;
/**
*
* @author Adrian Cole
*/
public class CloseContentAndSetExceptionHandler implements HttpErrorHandler {
public class CloseContentAndSetExceptionErrorHandler implements HttpErrorHandler {
public void handle(HttpFutureCommand<?> command, HttpResponse response) {
public void handleError(HttpFutureCommand<?> command, HttpResponse response) {
String content;
try {
content = response.getContent() != null ? Utils.toStringAndClose(response.getContent())

View File

@ -0,0 +1,64 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.jclouds.http.handlers;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.annotation.ClientError;
import org.jclouds.http.annotation.Redirection;
import org.jclouds.http.annotation.ServerError;
import com.google.inject.Inject;
/**
* Delegates to {@link HttpErrorHandler HttpErrorHandlers} who are annotated according to the
* response codes they relate to.
*
* @author Adrian Cole
*/
public class DelegatingErrorHandler implements HttpErrorHandler {
@Redirection
@Inject(optional = true)
private HttpErrorHandler redirectionHandler = new CloseContentAndSetExceptionErrorHandler();
@ClientError
@Inject(optional = true)
private HttpErrorHandler clientErrorHandler = new CloseContentAndSetExceptionErrorHandler();
@ServerError
@Inject(optional = true)
private HttpErrorHandler serverErrorHandler = new CloseContentAndSetExceptionErrorHandler();
public void handleError(HttpFutureCommand<?> command, org.jclouds.http.HttpResponse response) {
int statusCode = response.getStatusCode();
if (statusCode >= 300 && statusCode < 400) {
redirectionHandler.handleError(command, response);
} else if (statusCode >= 400 && statusCode < 500) {
clientErrorHandler.handleError(command, response);
} else if (statusCode >= 500) {
serverErrorHandler.handleError(command, response);
}
}
}

View File

@ -0,0 +1,68 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.jclouds.http.handlers;
import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientError;
import org.jclouds.http.annotation.Redirection;
import org.jclouds.http.annotation.ServerError;
import com.google.inject.Inject;
/**
* Delegates to {@link HttpRetryHandler HttpRetryHandlers} who are annotated according to the
* response codes they relate to.
*
* @author Adrian Cole
*/
public class DelegatingRetryHandler implements HttpRetryHandler {
@Redirection
@Inject(optional = true)
private HttpRetryHandler redirectionRetryHandler = new RedirectionRetryHandler(5);
@ClientError
@Inject(optional = true)
private HttpRetryHandler clientErrorRetryHandler = new CannotRetryHandler();
@ServerError
@Inject(optional = true)
private HttpRetryHandler serverErrorRetryHandler = new BackoffLimitedRetryHandler(5);
public boolean shouldRetryRequest(HttpFutureCommand<?> command,
org.jclouds.http.HttpResponse response) {
int statusCode = response.getStatusCode();
boolean retryRequest = false;
if (statusCode >= 300 && statusCode < 400) {
retryRequest = redirectionRetryHandler.shouldRetryRequest(command, response);
} else if (statusCode >= 400 && statusCode < 500) {
retryRequest = clientErrorRetryHandler.shouldRetryRequest(command, response);
} else if (statusCode >= 500) {
retryRequest = serverErrorRetryHandler.shouldRetryRequest(command, response);
}
return retryRequest;
}
}

View File

@ -0,0 +1,70 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.jclouds.http.handlers;
import java.net.URI;
import javax.annotation.Resource;
import org.apache.commons.io.IOUtils;
import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpHeaders;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.logging.Logger;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* Handles Retryable responses with error codes in the 3xx range
*
* @author Adrian Cole
*/
public class RedirectionRetryHandler implements HttpRetryHandler {
private final int retryCountLimit;
@Resource
protected Logger logger = Logger.NULL;
@Inject
public RedirectionRetryHandler(@Named("jclouds.http.max-redirects") int retryCountLimit) {
this.retryCountLimit = retryCountLimit;
}
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response) {
IOUtils.closeQuietly(response.getContent());
command.incrementRedirectCount();
String hostHeader = response.getFirstHeaderOrNull(HttpHeaders.LOCATION);
if (hostHeader != null && command.getRedirectCount() < retryCountLimit) {
URI redirectURI = URI.create(hostHeader);
command.getRequest().setEndPoint(redirectURI);
return true;
} else {
return false;
}
}
}

View File

@ -36,8 +36,8 @@ import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.logging.Logger;
import com.google.inject.Inject;
@ -51,10 +51,10 @@ public abstract class BaseHttpFutureCommandClient<Q> implements HttpFutureComman
protected List<HttpRequestFilter> requestFilters = Collections.emptyList();
@Inject(optional = true)
protected HttpErrorHandler httpErrorHandler = new CloseContentAndSetExceptionHandler();
private HttpRetryHandler retryHandler = new DelegatingRetryHandler();
@Inject(optional = true)
protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5);
private HttpErrorHandler errorHandler = new DelegatingErrorHandler();
public void submit(HttpFutureCommand<?> command) {
HttpRequest request = command.getRequest();
@ -66,15 +66,22 @@ public abstract class BaseHttpFutureCommandClient<Q> implements HttpFutureComman
}
HttpResponse response = null;
for (;;) {
logger.trace("%1$s - converting request %2$s", request.getEndPoint(), request);
logger.trace("%s - converting request %s", request.getEndPoint(), request);
nativeRequest = convert(request);
response = invoke(nativeRequest);
int statusCode = response.getStatusCode();
if (statusCode >= 500 && httpRetryHandler.shouldRetryRequest(command, response))
continue;
break;
if (statusCode >= 300) {
if (retryHandler.shouldRetryRequest(command, response)) {
continue;
} else {
errorHandler.handleError(command, response);
break;
}
} else {
processResponse(response, command);
break;
}
}
handleResponse(command, response);
} catch (Exception e) {
command.setException(e);
} finally {
@ -88,14 +95,9 @@ public abstract class BaseHttpFutureCommandClient<Q> implements HttpFutureComman
protected abstract void cleanup(Q nativeResponse);
protected void handleResponse(HttpFutureCommand<?> command, HttpResponse response) {
int code = response.getStatusCode();
if (code >= 300) {
httpErrorHandler.handle(command, response);
} else {
command.getResponseFuture().setResponse(response);
command.getResponseFuture().run();
}
protected void processResponse(HttpResponse response, HttpFutureCommand<?> command) {
command.getResponseFuture().setResponse(response);
command.getResponseFuture().run();
}
}

View File

@ -23,6 +23,9 @@
*/
package org.jclouds.lifecycle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@ -41,18 +44,23 @@ public abstract class BaseLifeCycle implements Runnable, LifeCycle {
@Resource
protected Logger logger = Logger.NULL;
protected final ExecutorService executor;
protected final BaseLifeCycle[] dependencies;
protected final List<LifeCycle> dependencies;
protected final Object statusLock;
protected volatile Status status;
protected AtomicReference<Exception> exception = new AtomicReference<Exception>();
public BaseLifeCycle(ExecutorService executor, BaseLifeCycle... dependencies) {
public BaseLifeCycle(ExecutorService executor, LifeCycle... dependencies) {
this.executor = executor;
this.dependencies = dependencies;
this.dependencies = new ArrayList<LifeCycle>();
this.dependencies.addAll(Arrays.asList(dependencies));
this.statusLock = new Object();
this.status = Status.INACTIVE;
}
public void addDependency(LifeCycle lifeCycle) {
dependencies.add(lifeCycle);
}
public Status getStatus() {
return status;
}
@ -116,14 +124,23 @@ public abstract class BaseLifeCycle implements Runnable, LifeCycle {
}
protected void exceptionIfDepedenciesNotActive() {
for (BaseLifeCycle dependency : dependencies) {
if (dependency.status.compareTo(Status.ACTIVE) != 0) {
throw new IllegalStateException(String.format(
"Illegal state: %1$s for component: %2$s", dependency.status, dependency));
for (LifeCycle dependency : dependencies) {
if (dependency.getStatus().compareTo(Status.ACTIVE) != 0) {
throw new IllegalStateException(String.format("Illegal state: %s for component: %s",
dependency.getStatus(), dependency));
}
}
}
protected Exception getExceptionFromDependenciesOrNull() {
for (LifeCycle dependency : dependencies) {
if (dependency.getException() != null) {
return dependency.getException();
}
}
return null;
}
public Exception getException() {
return this.exception.get();
}

View File

@ -81,10 +81,21 @@ public abstract class BaseHttpFutureCommandClientTest extends BaseJettyTest {
assertEquals(get.get(10, TimeUnit.SECONDS).trim(), XML2);
}
@Test(invocationCount = 50, timeOut = 3000)
public void testGetStringPermanentRedirect() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
// GetString get = factory.createGetString("/permanentredirect");
// assert get != null;
// client.submit(get);
// assertEquals(get.get(10, TimeUnit.SECONDS).trim(), XML2);
// TODO assert misses are only one, as permanent redirects paths should be remembered.
}
@Test(invocationCount = 50, timeOut = 3000)
public void testPutRedirect() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
Put put = factory.createPut("/redirect", "foo");
put.getRequest().getHeaders().put(HttpHeaders.CONTENT_LENGTH, "foo".getBytes().length + "");
assert put != null;
client.submit(put);
assertEquals(put.get(10, TimeUnit.SECONDS), new Boolean(true));

View File

@ -24,6 +24,7 @@
package org.jclouds.http;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
@ -135,6 +136,7 @@ public abstract class BaseJettyTest {
@Override
protected void configure() {
Names.bindProperties(binder(), properties);
bind(URI.class).toInstance(URI.create("http://localhost:" + testPort));
}
}, new JDKLoggingModule(), new HttpCommandsModule(), createClientModule(),
new AbstractModule() {