/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.pool;
import static com.google.common.base.Preconditions.checkArgument;
import java.net.URI;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Named;
import org.jclouds.Constants;
import org.jclouds.http.HttpCommandRendezvous;
import org.jclouds.lifecycle.BaseLifeCycle;
/**
* // TODO: Adrian: Document this!
*
* @author Adrian Cole
*/
public abstract class HttpCommandConnectionPool<C> extends BaseLifeCycle {
protected final Semaphore allConnections;
protected final BlockingQueue<C> available;
/**
* inputOnly: nothing is taken from this queue.
*/
protected final BlockingQueue<HttpCommandRendezvous<?>> resubmitQueue;
protected final AtomicInteger currentSessionFailures = new AtomicInteger(0);
protected volatile boolean hitBottom = false;
protected final URI endPoint;
protected int maxConnectionReuse;
protected int maxSessionFailures;
public URI getEndPoint() {
return endPoint;
}
public static interface Factory<C> {
HttpCommandConnectionPool<C> create(URI endPoint);
}
public HttpCommandConnectionPool(ExecutorService executor, Semaphore allConnections,
BlockingQueue<HttpCommandRendezvous<?>> rendezvousQueue, BlockingQueue<C> available,
URI endPoint, @Named(Constants.PROPERTY_MAX_CONNECTION_REUSE) int maxConnectionReuse,
@Named(Constants.PROPERTY_MAX_SESSION_FAILURES) int maxSessionFailures) {
super(executor);
checkArgument(maxConnectionReuse >= 1, "maxConnectionReuse must be positive");
checkArgument(maxSessionFailures >= 1, "maxSessionFailures must be positive");
this.maxConnectionReuse = maxConnectionReuse;
this.maxSessionFailures = maxSessionFailures;
this.allConnections = allConnections;
this.resubmitQueue = rendezvousQueue;
this.available = available;
this.endPoint = endPoint;
}
protected void setResponseException(Exception ex, C conn) {
HttpCommandRendezvous<?> rendezvous = getHandleFromConnection(conn).getCommandRendezvous();
setExceptionOnCommand(ex, rendezvous);
}
protected void cancel(C conn) {
HttpCommandRendezvous<?> rendezvous = getHandleFromConnection(conn).getCommandRendezvous();
rendezvous.cancel();
}
protected C getConnection() throws InterruptedException, TimeoutException {
exceptionIfNotActive();
if (!hitBottom) {
hitBottom = available.size() == 0 && allConnections.availablePermits() == 0;
if (hitBottom)
logger.warn("saturated connection pool");
}
logger.trace("Blocking up to %ds for a connection to %s", 5, getEndPoint());
C conn = available.poll(5, TimeUnit.SECONDS);
if (conn == null)
throw new TimeoutException(String.format("Timeout after %ds for a connection to %s", 5,
getEndPoint()));
if (connectionValid(conn)) {
return conn;
} else {
logger.debug("Connection %s unusable for endpoint %s", conn.hashCode(), getEndPoint());
shutdownConnection(conn);
allConnections.release();
return getConnection();
}
}
protected void fatalException(Exception ex, C conn) {
setResponseException(ex, conn);
exception.set(ex);
shutdown();
}
protected abstract void shutdownConnection(C conn);
protected abstract boolean connectionValid(C conn);
public HttpCommandConnectionHandle<C> getHandle(HttpCommandRendezvous<?> rendezvous)
throws InterruptedException, TimeoutException {
exceptionIfNotActive();
C conn = getConnection();
HttpCommandConnectionHandle<C> handle = createHandle(rendezvous, conn);
associateHandleWithConnection(handle, conn);
return handle;
}
protected abstract HttpCommandConnectionHandle<C> createHandle(
HttpCommandRendezvous<?> rendezvous, C conn);
protected void resubmitIfRequestIsReplayable(C connection, Exception e) {
HttpCommandRendezvous<?> rendezvous = getCommandFromConnection(connection);
if (rendezvous != null) {
if (isReplayable(rendezvous)) {
logger.info("resubmitting request: %s", rendezvous.getCommand().getRequest()
.getRequestLine());
resubmitQueue.add(rendezvous);
} else {
setExceptionOnCommand(e, rendezvous);
}
}
}
protected abstract boolean isReplayable(HttpCommandRendezvous<?> rendezvous);
protected HttpCommandRendezvous<?> getCommandFromConnection(C connection) {
HttpCommandConnectionHandle<C> handle = getHandleFromConnection(connection);
if (handle != null && handle.getCommandRendezvous() != null) {
return handle.getCommandRendezvous();
}
return null;
}
protected void setExceptionOnCommand(C connection, Exception e) {
HttpCommandRendezvous<?> rendezvous = getCommandFromConnection(connection);
if (rendezvous != null) {
setExceptionOnCommand(e, rendezvous);
}
}
protected void setExceptionOnCommand(Exception e, HttpCommandRendezvous<?> rendezvous) {
logger.warn(e, "Exception processing command: %s", rendezvous.getCommand());
try {
rendezvous.setException(e);
} catch (InterruptedException e1) {
logger.error(e, "interrupted setting exception on command", rendezvous.getCommand());
}
}
protected abstract void associateHandleWithConnection(HttpCommandConnectionHandle<C> handle,
C connection);
protected abstract HttpCommandConnectionHandle<C> 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;
HttpCommandConnectionPool<?> other = (HttpCommandConnectionPool<?>) obj;
if (endPoint == null) {
if (other.endPoint != null)
return false;
} else if (!endPoint.equals(other.endPoint))
return false;
return true;
}
}