mirror of https://github.com/apache/jclouds.git
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:
parent
39e96d041f
commit
48c3155450
|
@ -33,7 +33,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @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 Q request;
|
||||||
private final ResponseRunnableFuture<R, T> responseRunnableFuture;
|
private final ResponseRunnableFuture<R, T> responseRunnableFuture;
|
||||||
|
|
|
@ -28,6 +28,6 @@ package org.jclouds.command;
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
public interface FutureCommandClient<O extends FutureCommand<?, ?, ?>> {
|
public interface FutureCommandClient<O extends FutureCommand<?, ?, ?, ?>> {
|
||||||
void submit(O operation);
|
void submit(O operation);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -32,76 +32,78 @@ import javax.annotation.Resource;
|
||||||
import org.jclouds.command.FutureCommand;
|
import org.jclouds.command.FutureCommand;
|
||||||
import org.jclouds.logging.Logger;
|
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
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
public abstract class FutureCommandConnectionHandle<C, O extends FutureCommand<?, ?, ?>> {
|
public abstract class FutureCommandConnectionHandle<E, C, O extends FutureCommand<E, ?, ?, ?>> {
|
||||||
protected final BlockingQueue<C> available;
|
protected final BlockingQueue<C> available;
|
||||||
protected final Semaphore maxConnections;
|
protected final Semaphore maxConnections;
|
||||||
protected final Semaphore completed;
|
protected final Semaphore completed;
|
||||||
protected C conn;
|
protected final E endPoint;
|
||||||
protected O command;
|
protected C conn;
|
||||||
@Resource
|
protected O command;
|
||||||
protected Logger logger = Logger.NULL;
|
@Resource
|
||||||
|
protected Logger logger = Logger.NULL;
|
||||||
|
|
||||||
public FutureCommandConnectionHandle(Semaphore maxConnections,
|
public FutureCommandConnectionHandle(Semaphore maxConnections, BlockingQueue<C> available,
|
||||||
@Assisted O command, @Assisted C conn, BlockingQueue<C> available)
|
E endPoint, O command, C conn) throws InterruptedException {
|
||||||
throws InterruptedException {
|
this.available = available;
|
||||||
this.maxConnections = maxConnections;
|
this.maxConnections = maxConnections;
|
||||||
this.command = command;
|
this.completed = new Semaphore(1);
|
||||||
this.conn = conn;
|
this.endPoint = endPoint;
|
||||||
this.available = available;
|
this.command = command;
|
||||||
this.completed = new Semaphore(1);
|
this.conn = conn;
|
||||||
completed.acquire();
|
completed.acquire();
|
||||||
}
|
}
|
||||||
|
|
||||||
public O getCommand() {
|
public O getCommand() {
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void startConnection();
|
public abstract void startConnection();
|
||||||
|
|
||||||
public boolean isCompleted() {
|
public boolean isCompleted() {
|
||||||
return (completed.availablePermits() == 1);
|
return (completed.availablePermits() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() throws InterruptedException {
|
public void release() throws InterruptedException {
|
||||||
if (isCompleted()) {
|
if (isCompleted() || alreadyReleased()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.trace("%1$s - %2$d - releasing to pool", conn, conn.hashCode());
|
logger.trace("%1$s - %2$d - releasing to pool", conn, conn.hashCode());
|
||||||
available.put(conn);
|
available.put(conn);
|
||||||
conn = null;
|
conn = null;
|
||||||
command = null;
|
command = null;
|
||||||
completed.release();
|
completed.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() throws IOException {
|
private boolean alreadyReleased() {
|
||||||
if (isCompleted()) {
|
return conn == null;
|
||||||
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 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 {
|
public abstract void shutdownConnection() throws IOException;
|
||||||
completed.acquire();
|
|
||||||
completed.release();
|
public void waitFor() throws InterruptedException {
|
||||||
}
|
completed.acquire();
|
||||||
|
completed.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import org.jclouds.command.FutureCommand;
|
import org.jclouds.command.FutureCommand;
|
||||||
import org.jclouds.lifecycle.BaseLifeCycle;
|
import org.jclouds.lifecycle.BaseLifeCycle;
|
||||||
|
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.assistedinject.Assisted;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,130 +41,156 @@ import com.google.inject.name.Named;
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
public abstract class FutureCommandConnectionPool<C, O extends FutureCommand<?, ?, ?>>
|
public abstract class FutureCommandConnectionPool<E, C, O extends FutureCommand<E, ?, ?, ?>>
|
||||||
extends BaseLifeCycle {
|
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 FutureCommandConnectionPool(
|
protected final Semaphore allConnections;
|
||||||
ExecutorService executor,
|
protected final BlockingQueue<C> available;
|
||||||
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 void setResponseException(Exception ex, C conn) {
|
/**
|
||||||
O command = getHandleFromConnection(conn).getCommand();
|
* inputOnly: nothing is taken from this queue.
|
||||||
command.getResponseFuture().setException(ex);
|
*/
|
||||||
}
|
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) {
|
public E getEndPoint() {
|
||||||
O command = getHandleFromConnection(conn).getCommand();
|
return endPoint;
|
||||||
command.cancel(true);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
public static interface Factory<E, C, O extends FutureCommand<E, ?, ?, ?>> {
|
||||||
public C getConnection() throws InterruptedException, TimeoutException {
|
FutureCommandConnectionPool<E, C, O> create(E endPoint);
|
||||||
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");
|
|
||||||
|
|
||||||
logger.trace("%1$s - %2$d - aquired", conn, conn.hashCode());
|
public FutureCommandConnectionPool(ExecutorService executor, Semaphore allConnections,
|
||||||
if (connectionValid(conn)) {
|
BlockingQueue<O> commandQueue, @Named("maxConnectionReuse") int maxConnectionReuse,
|
||||||
logger.debug("%1$s - %2$d - reusing", conn, conn.hashCode());
|
BlockingQueue<C> available, @Assisted E endPoint, BaseLifeCycle... dependencies) {
|
||||||
return conn;
|
super(executor, dependencies);
|
||||||
} else {
|
this.allConnections = allConnections;
|
||||||
logger.debug("%1$s - %2$d - unusable", conn, conn.hashCode());
|
this.resubmitQueue = commandQueue;
|
||||||
shutdownConnection(conn);
|
this.maxConnectionReuse = maxConnectionReuse;
|
||||||
allConnections.release();
|
this.available = available;
|
||||||
return getConnection();
|
this.endPoint = endPoint;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected void fatalException(Exception ex, C conn) {
|
protected void setResponseException(Exception ex, C conn) {
|
||||||
setResponseException(ex, conn);
|
O command = getHandleFromConnection(conn).getCommand();
|
||||||
exception.set(ex);
|
command.getResponseFuture().setException(ex);
|
||||||
shutdown();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
logger.trace("%1$s - %2$d - aquired", conn, conn.hashCode());
|
||||||
throws InterruptedException, TimeoutException {
|
if (connectionValid(conn)) {
|
||||||
exceptionIfNotActive();
|
logger.debug("%1$s - %2$d - reusing", conn, conn.hashCode());
|
||||||
C conn = getConnection();
|
return conn;
|
||||||
FutureCommandConnectionHandle<C, O> handle = futureCommandConnectionHandleFactory
|
} else {
|
||||||
.create(command, conn);
|
logger.debug("%1$s - %2$d - unusable", conn, conn.hashCode());
|
||||||
associateHandleWithConnection(handle, conn);
|
shutdownConnection(conn);
|
||||||
return handle;
|
allConnections.release();
|
||||||
}
|
return getConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void resubmitIfRequestIsReplayable(C connection, Exception e) {
|
protected void fatalException(Exception ex, C conn) {
|
||||||
O command = getCommandFromConnection(connection);
|
setResponseException(ex, conn);
|
||||||
if (command != null) {
|
exception.set(ex);
|
||||||
if (isReplayable(command)) {
|
shutdown();
|
||||||
logger.info("resubmitting command: %1$s", command);
|
}
|
||||||
commandQueue.add(command);
|
|
||||||
} else {
|
|
||||||
command.setException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract boolean isReplayable(O command);
|
protected abstract void shutdownConnection(C conn);
|
||||||
|
|
||||||
O getCommandFromConnection(C connection) {
|
protected abstract boolean connectionValid(C conn);
|
||||||
FutureCommandConnectionHandle<C, O> handle = getHandleFromConnection(connection);
|
|
||||||
if (handle != null && handle.getCommand() != null) {
|
|
||||||
return handle.getCommand();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setExceptionOnCommand(C connection, Exception e) {
|
public FutureCommandConnectionHandle<E, C, O> getHandle(O command) throws InterruptedException,
|
||||||
FutureCommand<?, ?, ?> command = getCommandFromConnection(connection);
|
TimeoutException {
|
||||||
if (command != null) {
|
exceptionIfNotActive();
|
||||||
logger.warn(e, "exception in command: %1$s", command);
|
C conn = getConnection();
|
||||||
command.setException(e);
|
FutureCommandConnectionHandle<E, C, O> handle = createHandle(command, conn);
|
||||||
}
|
associateHandleWithConnection(handle, conn);
|
||||||
}
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract void associateHandleWithConnection(
|
protected abstract FutureCommandConnectionHandle<E, C, O> createHandle(O command, C conn);
|
||||||
FutureCommandConnectionHandle<C, O> handle, C connection);
|
|
||||||
|
|
||||||
protected abstract FutureCommandConnectionHandle<C, O> getHandleFromConnection(
|
protected void resubmitIfRequestIsReplayable(C connection, Exception e) {
|
||||||
C connection);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
package org.jclouds.command.pool;
|
package org.jclouds.command.pool;
|
||||||
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
@ -33,6 +34,8 @@ import org.jclouds.command.FutureCommandClient;
|
||||||
import org.jclouds.lifecycle.BaseLifeCycle;
|
import org.jclouds.lifecycle.BaseLifeCycle;
|
||||||
import org.jclouds.util.Utils;
|
import org.jclouds.util.Utils;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,30 +43,30 @@ import com.google.inject.Inject;
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @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> {
|
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 BlockingQueue<O> commandQueue;
|
||||||
|
private final FutureCommandConnectionPool.Factory<E, C, O> poolFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FutureCommandConnectionPoolClient(ExecutorService executor,
|
public FutureCommandConnectionPoolClient(ExecutorService executor,
|
||||||
FutureCommandConnectionPool<C, O> futureCommandConnectionPool,
|
FutureCommandConnectionPool.Factory<E, C, O> pf, BlockingQueue<O> commandQueue) {
|
||||||
BlockingQueue<O> commandQueue) {
|
super(executor);
|
||||||
super(executor, futureCommandConnectionPool);
|
this.poolFactory = pf;
|
||||||
this.futureCommandConnectionPool = futureCommandConnectionPool;
|
// 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;
|
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}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
@ -72,9 +75,9 @@ public class FutureCommandConnectionPoolClient<C, O extends FutureCommand<?, ?,
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void doShutdown() {
|
protected void doShutdown() {
|
||||||
exception.compareAndSet(null, futureCommandConnectionPool.getException());
|
exception.compareAndSet(null, getExceptionFromDependenciesOrNull());
|
||||||
while (!commandQueue.isEmpty()) {
|
while (!commandQueue.isEmpty()) {
|
||||||
FutureCommand<?, ?, ?> command = (FutureCommand<?, ?, ?>) commandQueue.remove();
|
FutureCommand<E, ?, ?, ?> command = (FutureCommand<E, ?, ?, ?>) commandQueue.remove();
|
||||||
if (command != null) {
|
if (command != null) {
|
||||||
if (exception.get() != null)
|
if (exception.get() != null)
|
||||||
command.setException(exception.get());
|
command.setException(exception.get());
|
||||||
|
@ -119,21 +122,29 @@ public class FutureCommandConnectionPoolClient<C, O extends FutureCommand<?, ?,
|
||||||
*/
|
*/
|
||||||
protected void invoke(O command) {
|
protected void invoke(O command) {
|
||||||
exceptionIfNotActive();
|
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 {
|
try {
|
||||||
connectionHandle = futureCommandConnectionPool.getHandle(command);
|
connectionHandle = pool.getHandle(command);
|
||||||
} catch (InterruptedException e) {
|
} 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);
|
commandQueue.add(command);
|
||||||
return;
|
return;
|
||||||
} catch (TimeoutException e) {
|
} 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);
|
commandQueue.add(command);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionHandle == null) {
|
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);
|
commandQueue.add(command);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -146,7 +157,7 @@ public class FutureCommandConnectionPoolClient<C, O extends FutureCommand<?, ?,
|
||||||
sb.append("FutureCommandConnectionPoolClient");
|
sb.append("FutureCommandConnectionPoolClient");
|
||||||
sb.append("{status=").append(status);
|
sb.append("{status=").append(status);
|
||||||
sb.append(", commandQueue=").append((commandQueue != null) ? commandQueue.size() : 0);
|
sb.append(", commandQueue=").append((commandQueue != null) ? commandQueue.size() : 0);
|
||||||
sb.append(", futureCommandConnectionPool=").append(futureCommandConnectionPool);
|
sb.append(", poolMap=").append(poolMap);
|
||||||
sb.append('}');
|
sb.append('}');
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.jclouds.lifecycle.config.LifeCycleModule;
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,7 +46,7 @@ public abstract class FutureCommandConnectionPoolClientModule<C> extends
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
// @Singleton per uri...
|
||||||
public abstract BlockingQueue<C> provideAvailablePool(
|
public abstract BlockingQueue<C> provideAvailablePool(
|
||||||
@Named(PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS) int max)
|
@Named(PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS) int max)
|
||||||
throws Exception;
|
throws Exception;
|
||||||
|
@ -62,7 +61,7 @@ public abstract class FutureCommandConnectionPoolClientModule<C> extends
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
// @Singleton per uri...
|
||||||
public Semaphore provideTotalConnectionSemaphore(
|
public Semaphore provideTotalConnectionSemaphore(
|
||||||
@Named(PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS) int max)
|
@Named(PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS) int max)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
|
@ -30,9 +30,9 @@ package org.jclouds.http;
|
||||||
*/
|
*/
|
||||||
public interface HttpErrorHandler {
|
public interface HttpErrorHandler {
|
||||||
public static final HttpErrorHandler NOOP = new 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,16 @@ import org.jclouds.logging.Logger;
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @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,
|
public HttpFutureCommand(URI endPoint, HttpMethod method, String uri,
|
||||||
ResponseCallable<T> responseCallable) {
|
ResponseCallable<T> responseCallable) {
|
||||||
|
|
|
@ -25,65 +25,61 @@ package org.jclouds.http;
|
||||||
|
|
||||||
public interface HttpHeaders {
|
public interface HttpHeaders {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be used to specify caching behavior along the request/reply chain. Go
|
* Can be used to specify caching behavior along the request/reply chain. Go to
|
||||||
* to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.9.
|
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.9.
|
||||||
*/
|
*/
|
||||||
public static final String CACHE_CONTROL = "Cache-Control";
|
public static final String CACHE_CONTROL = "Cache-Control";
|
||||||
/**
|
/**
|
||||||
* Specifies presentational information for the object. Go to
|
* Specifies presentational information for the object. Go to
|
||||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html?sec19.5.1.
|
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html?sec19.5.1.
|
||||||
*/
|
*/
|
||||||
public static final String CONTENT_DISPOSITION = "Content-Disposition";
|
public static final String CONTENT_DISPOSITION = "Content-Disposition";
|
||||||
/**
|
/**
|
||||||
* Specifies what content encodings have been applied to the object and thus
|
* Specifies what content encodings have been applied to the object and thus what decoding
|
||||||
* what decoding mechanisms must be applied in order to obtain the
|
* mechanisms must be applied in order to obtain the media-type referenced by the Content-Type
|
||||||
* media-type referenced by the Content-Type header field. Go to
|
* header field. Go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.11.
|
||||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.11.
|
*/
|
||||||
*/
|
public static final String CONTENT_ENCODING = "Content-Encoding";
|
||||||
public static final String CONTENT_ENCODING = "Content-Encoding";
|
/**
|
||||||
/**
|
* The size of the object, in bytes. This is required. Go to
|
||||||
* The size of the object, in bytes. This is required. Go to
|
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.13.
|
||||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.13.
|
*/
|
||||||
*/
|
public static final String CONTENT_LENGTH = "Content-Length";
|
||||||
public static final String CONTENT_LENGTH = "Content-Length";
|
/**
|
||||||
/**
|
* A standard MIME type describing the format of the contents. If none is provided, the default
|
||||||
* A standard MIME type describing the format of the contents. If none is
|
* is binary/octet-stream. Go to http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.17.
|
||||||
* 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";
|
||||||
*/
|
/**
|
||||||
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
|
||||||
* The base64 encoded 128-bit MD5 digest of the message (without the
|
* data that was originally sent.
|
||||||
* 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
|
public static final String CONTENT_MD5 = "Content-MD5";
|
||||||
* originally sent.
|
/**
|
||||||
*/
|
* A user agent that wishes to authenticate itself with a server-- usually, but not necessarily,
|
||||||
public static final String CONTENT_MD5 = "Content-MD5";
|
* 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
|
||||||
* A user agent that wishes to authenticate itself with a server-- usually,
|
* authentication information of the user agent for the realm of the resource being requested.
|
||||||
* but not necessarily, after receiving a 401 response--does so by including
|
*
|
||||||
* an Authorization request-header field with the request. The Authorization
|
* Authorization = "Authorization" ":" credentials
|
||||||
* field value consists of credentials containing the authentication
|
*
|
||||||
* information of the user agent for the realm of the resource being
|
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html" />
|
||||||
* requested.
|
*/
|
||||||
*
|
public static final String AUTHORIZATION = "Authorization";
|
||||||
* Authorization = "Authorization" ":" credentials
|
public static final String HOST = "Host";
|
||||||
*
|
public static final String DATE = "Date";
|
||||||
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html" />
|
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
|
||||||
*/
|
public static final String LAST_MODIFIED = "Last-Modified";
|
||||||
public static final String AUTHORIZATION = "Authorization";
|
public static final String SERVER = "Server";
|
||||||
public static final String HOST = "Host";
|
public static final String ETAG = "ETag";
|
||||||
public static final String DATE = "Date";
|
public static final String RANGE = "Range";
|
||||||
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
|
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
|
||||||
public static final String LAST_MODIFIED = "Last-Modified";
|
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
|
||||||
public static final String SERVER = "Server";
|
public static final String IF_MATCH = "If-Match";
|
||||||
public static final String ETAG = "ETag";
|
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||||
public static final String RANGE = "Range";
|
public static final String CONTENT_RANGE = "Content-Range";
|
||||||
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
|
public static final String LOCATION = "Location";
|
||||||
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";
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -30,6 +30,7 @@ import java.net.URI;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
import org.jclouds.command.Request;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
import org.jclouds.util.Utils;
|
import org.jclouds.util.Utils;
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ import org.jclouds.util.Utils;
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
public class HttpRequest extends HttpMessage {
|
public class HttpRequest extends HttpMessage implements Request<URI> {
|
||||||
|
|
||||||
private URI endPoint;
|
private URI endPoint;
|
||||||
private final HttpMethod method;
|
private final HttpMethod method;
|
||||||
|
@ -100,10 +101,16 @@ public class HttpRequest extends HttpMessage {
|
||||||
this.payload = content;
|
this.payload = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* only the scheme, host, and port of the URI designates the endpoint
|
||||||
|
*/
|
||||||
public void setEndPoint(URI endPoint) {
|
public void setEndPoint(URI endPoint) {
|
||||||
this.endPoint = endPoint;
|
this.endPoint = endPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* only the scheme, host, and port of the URI designates the endpoint
|
||||||
|
*/
|
||||||
public URI getEndPoint() {
|
public URI getEndPoint() {
|
||||||
return endPoint;
|
return endPoint;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,25 +24,21 @@
|
||||||
package org.jclouds.http;
|
package org.jclouds.http;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicate whether a request should be retried after a server
|
* Indicate whether a request should be retried after a server error response (HTTP status code >=
|
||||||
* error response (HTTP status code >= 500) based on the request's
|
* 500) based on the request's replayable status and the number of attempts already performed.
|
||||||
* replayable status and the number of attempts already performed.
|
|
||||||
*
|
*
|
||||||
* @author James Murty
|
* @author James Murty
|
||||||
*/
|
*/
|
||||||
public interface HttpRetryHandler {
|
public interface HttpRetryHandler {
|
||||||
public static final HttpRetryHandler ALWAYS_RETRY = new 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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the command should be retried. This method should only be
|
* Return true if the command should be retried. This method should only be invoked when the
|
||||||
* invoked when the response has failed with a HTTP 5xx error indicating a
|
* response has failed with a HTTP 5xx error indicating a server-side error.
|
||||||
* server-side error.
|
|
||||||
*/
|
*/
|
||||||
boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
|
boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response);
|
||||||
throws InterruptedException;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -23,17 +23,10 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.http.config;
|
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.HttpFutureCommandClient;
|
||||||
import org.jclouds.http.internal.JavaUrlHttpFutureCommandClient;
|
import org.jclouds.http.internal.JavaUrlHttpFutureCommandClient;
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import com.google.inject.name.Named;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures {@link JavaUrlHttpFutureCommandClient}.
|
* Configures {@link JavaUrlHttpFutureCommandClient}.
|
||||||
|
@ -53,15 +46,4 @@ public class JavaUrlHttpFutureCommandClientModule extends AbstractModule {
|
||||||
bind(HttpFutureCommandClient.class).to(JavaUrlHttpFutureCommandClient.class);
|
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -35,24 +35,42 @@ import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow replayable request to be retried a limited number of times, and
|
* Allow replayable request to be retried a limited number of times, and impose an exponential
|
||||||
* impose an exponential back-off delay before returning.
|
* back-off delay before returning.
|
||||||
* <p>
|
* <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:
|
* <code>50 * (<i>{@link HttpFutureCommand#getFailureCount()}</i> ^ 2)</code>. For example:
|
||||||
* <table>
|
* <table>
|
||||||
* <tr><th>Number of Attempts</th><th>Delay in milliseconds</th></tr>
|
* <tr>
|
||||||
* <tr><td>1</td><td>50</td></tr>
|
* <th>Number of Attempts</th>
|
||||||
* <tr><td>2</td><td>200</td></tr>
|
* <th>Delay in milliseconds</th>
|
||||||
* <tr><td>3</td><td>450</td></tr>
|
* </tr>
|
||||||
* <tr><td>4</td><td>800</td></tr>
|
* <tr>
|
||||||
* <tr><td>5</td><td>1250</td></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>
|
* </table>
|
||||||
* <p>
|
* <p>
|
||||||
* This implementation has two side-effects. It increments the command's failure count
|
* This implementation has two side-effects. It increments the command's failure count with
|
||||||
* with {@link HttpFutureCommand#incrementFailureCount()}, because this failure count
|
* {@link HttpFutureCommand#incrementFailureCount()}, because this failure count value is used to
|
||||||
* value is used to determine how many times the command has already been tried. It
|
* determine how many times the command has already been tried. It also closes the response's
|
||||||
* also closes the response's content input stream to ensure connections are cleaned up.
|
* content input stream to ensure connections are cleaned up.
|
||||||
*
|
*
|
||||||
* @author James Murty
|
* @author James Murty
|
||||||
*/
|
*/
|
||||||
|
@ -67,9 +85,7 @@ public class BackoffLimitedRetryHandler implements HttpRetryHandler {
|
||||||
this.retryCountLimit = retryCountLimit;
|
this.retryCountLimit = retryCountLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response)
|
public boolean shouldRetryRequest(HttpFutureCommand<?> command, HttpResponse response) {
|
||||||
throws InterruptedException
|
|
||||||
{
|
|
||||||
IOUtils.closeQuietly(response.getContent());
|
IOUtils.closeQuietly(response.getContent());
|
||||||
|
|
||||||
command.incrementFailureCount();
|
command.incrementFailureCount();
|
||||||
|
@ -78,22 +94,25 @@ public class BackoffLimitedRetryHandler implements HttpRetryHandler {
|
||||||
logger.warn("Cannot retry after server error, command is not replayable: %1$s", command);
|
logger.warn("Cannot retry after server error, command is not replayable: %1$s", command);
|
||||||
return false;
|
return false;
|
||||||
} else if (command.getFailureCount() > retryCountLimit) {
|
} else if (command.getFailureCount() > retryCountLimit) {
|
||||||
logger.warn("Cannot retry after server error, command has exceeded retry limit %1$d: %2$s",
|
logger.warn(
|
||||||
retryCountLimit, command);
|
"Cannot retry after server error, command has exceeded retry limit %1$d: %2$s",
|
||||||
|
retryCountLimit, command);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
imposeBackoffExponentialDelay(command.getFailureCount(), command.toString());
|
imposeBackoffExponentialDelay(command.getFailureCount(), command.toString());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void imposeBackoffExponentialDelay(int failureCound, String commandDescription)
|
public void imposeBackoffExponentialDelay(int failureCound, String commandDescription) {
|
||||||
throws InterruptedException
|
|
||||||
{
|
|
||||||
long delayMs = (long) (50L * Math.pow(failureCound, 2));
|
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",
|
logger.debug("Retry %1$d/%2$d after server error, delaying for %3$d ms: %4$s", failureCound,
|
||||||
failureCound, retryCountLimit, delayMs, commandDescription);
|
retryCountLimit, delayMs, commandDescription);
|
||||||
Thread.sleep(delayMs);
|
try {
|
||||||
|
Thread.sleep(delayMs);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.error(e, "Interrupted imposing delay");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,19 +25,19 @@ package org.jclouds.http.handlers;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jclouds.http.HttpErrorHandler;
|
||||||
import org.jclouds.http.HttpFutureCommand;
|
import org.jclouds.http.HttpFutureCommand;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.http.HttpResponseException;
|
import org.jclouds.http.HttpResponseException;
|
||||||
import org.jclouds.http.HttpErrorHandler;
|
|
||||||
import org.jclouds.util.Utils;
|
import org.jclouds.util.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @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;
|
String content;
|
||||||
try {
|
try {
|
||||||
content = response.getContent() != null ? Utils.toStringAndClose(response.getContent())
|
content = response.getContent() != null ? Utils.toStringAndClose(response.getContent())
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -36,8 +36,8 @@ import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpRequestFilter;
|
import org.jclouds.http.HttpRequestFilter;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.http.HttpRetryHandler;
|
import org.jclouds.http.HttpRetryHandler;
|
||||||
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
import org.jclouds.http.handlers.DelegatingErrorHandler;
|
||||||
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
import org.jclouds.http.handlers.DelegatingRetryHandler;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
@ -51,10 +51,10 @@ public abstract class BaseHttpFutureCommandClient<Q> implements HttpFutureComman
|
||||||
protected List<HttpRequestFilter> requestFilters = Collections.emptyList();
|
protected List<HttpRequestFilter> requestFilters = Collections.emptyList();
|
||||||
|
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
protected HttpErrorHandler httpErrorHandler = new CloseContentAndSetExceptionHandler();
|
private HttpRetryHandler retryHandler = new DelegatingRetryHandler();
|
||||||
|
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5);
|
private HttpErrorHandler errorHandler = new DelegatingErrorHandler();
|
||||||
|
|
||||||
public void submit(HttpFutureCommand<?> command) {
|
public void submit(HttpFutureCommand<?> command) {
|
||||||
HttpRequest request = command.getRequest();
|
HttpRequest request = command.getRequest();
|
||||||
|
@ -66,15 +66,22 @@ public abstract class BaseHttpFutureCommandClient<Q> implements HttpFutureComman
|
||||||
}
|
}
|
||||||
HttpResponse response = null;
|
HttpResponse response = null;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
logger.trace("%1$s - converting request %2$s", request.getEndPoint(), request);
|
logger.trace("%s - converting request %s", request.getEndPoint(), request);
|
||||||
nativeRequest = convert(request);
|
nativeRequest = convert(request);
|
||||||
response = invoke(nativeRequest);
|
response = invoke(nativeRequest);
|
||||||
int statusCode = response.getStatusCode();
|
int statusCode = response.getStatusCode();
|
||||||
if (statusCode >= 500 && httpRetryHandler.shouldRetryRequest(command, response))
|
if (statusCode >= 300) {
|
||||||
continue;
|
if (retryHandler.shouldRetryRequest(command, response)) {
|
||||||
break;
|
continue;
|
||||||
|
} else {
|
||||||
|
errorHandler.handleError(command, response);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
processResponse(response, command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handleResponse(command, response);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
command.setException(e);
|
command.setException(e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -88,14 +95,9 @@ public abstract class BaseHttpFutureCommandClient<Q> implements HttpFutureComman
|
||||||
|
|
||||||
protected abstract void cleanup(Q nativeResponse);
|
protected abstract void cleanup(Q nativeResponse);
|
||||||
|
|
||||||
protected void handleResponse(HttpFutureCommand<?> command, HttpResponse response) {
|
protected void processResponse(HttpResponse response, HttpFutureCommand<?> command) {
|
||||||
int code = response.getStatusCode();
|
command.getResponseFuture().setResponse(response);
|
||||||
if (code >= 300) {
|
command.getResponseFuture().run();
|
||||||
httpErrorHandler.handle(command, response);
|
|
||||||
} else {
|
|
||||||
command.getResponseFuture().setResponse(response);
|
|
||||||
command.getResponseFuture().run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -23,6 +23,9 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.lifecycle;
|
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.ExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@ -41,18 +44,23 @@ public abstract class BaseLifeCycle implements Runnable, LifeCycle {
|
||||||
@Resource
|
@Resource
|
||||||
protected Logger logger = Logger.NULL;
|
protected Logger logger = Logger.NULL;
|
||||||
protected final ExecutorService executor;
|
protected final ExecutorService executor;
|
||||||
protected final BaseLifeCycle[] dependencies;
|
protected final List<LifeCycle> dependencies;
|
||||||
protected final Object statusLock;
|
protected final Object statusLock;
|
||||||
protected volatile Status status;
|
protected volatile Status status;
|
||||||
protected AtomicReference<Exception> exception = new AtomicReference<Exception>();
|
protected AtomicReference<Exception> exception = new AtomicReference<Exception>();
|
||||||
|
|
||||||
public BaseLifeCycle(ExecutorService executor, BaseLifeCycle... dependencies) {
|
public BaseLifeCycle(ExecutorService executor, LifeCycle... dependencies) {
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.dependencies = dependencies;
|
this.dependencies = new ArrayList<LifeCycle>();
|
||||||
|
this.dependencies.addAll(Arrays.asList(dependencies));
|
||||||
this.statusLock = new Object();
|
this.statusLock = new Object();
|
||||||
this.status = Status.INACTIVE;
|
this.status = Status.INACTIVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addDependency(LifeCycle lifeCycle) {
|
||||||
|
dependencies.add(lifeCycle);
|
||||||
|
}
|
||||||
|
|
||||||
public Status getStatus() {
|
public Status getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -116,14 +124,23 @@ public abstract class BaseLifeCycle implements Runnable, LifeCycle {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void exceptionIfDepedenciesNotActive() {
|
protected void exceptionIfDepedenciesNotActive() {
|
||||||
for (BaseLifeCycle dependency : dependencies) {
|
for (LifeCycle dependency : dependencies) {
|
||||||
if (dependency.status.compareTo(Status.ACTIVE) != 0) {
|
if (dependency.getStatus().compareTo(Status.ACTIVE) != 0) {
|
||||||
throw new IllegalStateException(String.format(
|
throw new IllegalStateException(String.format("Illegal state: %s for component: %s",
|
||||||
"Illegal state: %1$s for component: %2$s", dependency.status, dependency));
|
dependency.getStatus(), dependency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Exception getExceptionFromDependenciesOrNull() {
|
||||||
|
for (LifeCycle dependency : dependencies) {
|
||||||
|
if (dependency.getException() != null) {
|
||||||
|
return dependency.getException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Exception getException() {
|
public Exception getException() {
|
||||||
return this.exception.get();
|
return this.exception.get();
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,10 +81,21 @@ public abstract class BaseHttpFutureCommandClientTest extends BaseJettyTest {
|
||||||
assertEquals(get.get(10, TimeUnit.SECONDS).trim(), XML2);
|
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)
|
@Test(invocationCount = 50, timeOut = 3000)
|
||||||
public void testPutRedirect() throws MalformedURLException, ExecutionException,
|
public void testPutRedirect() throws MalformedURLException, ExecutionException,
|
||||||
InterruptedException, TimeoutException {
|
InterruptedException, TimeoutException {
|
||||||
Put put = factory.createPut("/redirect", "foo");
|
Put put = factory.createPut("/redirect", "foo");
|
||||||
|
put.getRequest().getHeaders().put(HttpHeaders.CONTENT_LENGTH, "foo".getBytes().length + "");
|
||||||
assert put != null;
|
assert put != null;
|
||||||
client.submit(put);
|
client.submit(put);
|
||||||
assertEquals(put.get(10, TimeUnit.SECONDS), new Boolean(true));
|
assertEquals(put.get(10, TimeUnit.SECONDS), new Boolean(true));
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
package org.jclouds.http;
|
package org.jclouds.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
@ -135,6 +136,7 @@ public abstract class BaseJettyTest {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
Names.bindProperties(binder(), properties);
|
Names.bindProperties(binder(), properties);
|
||||||
|
bind(URI.class).toInstance(URI.create("http://localhost:" + testPort));
|
||||||
}
|
}
|
||||||
}, new JDKLoggingModule(), new HttpCommandsModule(), createClientModule(),
|
}, new JDKLoggingModule(), new HttpCommandsModule(), createClientModule(),
|
||||||
new AbstractModule() {
|
new AbstractModule() {
|
||||||
|
|
Loading…
Reference in New Issue