/**
*
* 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.httpnio.pool;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.core.HttpHeaders;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.protocol.NHttpRequestExecutionHandler;
import org.apache.http.protocol.HttpContext;
import org.jclouds.Constants;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpCommandRendezvous;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.Payloads;
import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.http.httpnio.util.NioHttpUtils;
import org.jclouds.http.internal.HttpWire;
import org.jclouds.logging.Logger;
import com.google.common.io.Closeables;
/**
* // TODO: Adrian: Document this!
*
* @author Adrian Cole
*/
public class NioHttpCommandExecutionHandler implements NHttpRequestExecutionHandler {
private final ConsumingNHttpEntityFactory EntityFactory;
private final DelegatingRetryHandler retryHandler;
private final DelegatingErrorHandler errorHandler;
private final HttpWire wire;
/**
* inputOnly: nothing is taken from this queue.
*/
private final BlockingQueue<HttpCommandRendezvous<?>> resubmitQueue;
@Resource
protected Logger logger = Logger.NULL;
@Resource
@Named(Constants.LOGGER_HTTP_HEADERS)
protected Logger headerLog = Logger.NULL;
@Inject
public NioHttpCommandExecutionHandler(ConsumingNHttpEntityFactory EntityFactory,
BlockingQueue<HttpCommandRendezvous<?>> resubmitQueue,
DelegatingRetryHandler retryHandler, DelegatingErrorHandler errorHandler, HttpWire wire) {
this.EntityFactory = EntityFactory;
this.resubmitQueue = resubmitQueue;
this.retryHandler = retryHandler;
this.errorHandler = errorHandler;
this.wire = wire;
}
public interface ConsumingNHttpEntityFactory {
public ConsumingNHttpEntity create(HttpEntity httpEntity);
}
public void initalizeContext(HttpContext context, Object attachment) {
}
public HttpEntityEnclosingRequest submitRequest(HttpContext context) {
HttpCommandRendezvous<?> rendezvous = (HttpCommandRendezvous<?>) context
.removeAttribute("command");
if (rendezvous != null) {
HttpRequest request = rendezvous.getCommand().getRequest();
for (HttpRequestFilter filter : request.getFilters()) {
filter.filter(request);
}
logger.debug("Sending request %s: %s", request.hashCode(), request.getRequestLine());
if (request.getPayload() != null && wire.enabled())
request.setPayload(Payloads.newPayload(wire
.output(request.getPayload().getRawContent())));
HttpEntityEnclosingRequest nativeRequest = NioHttpUtils.convertToApacheRequest(request);
HttpUtils.logRequest(headerLog, request, ">>");
return nativeRequest;
}
return null;
}
public ConsumingNHttpEntity responseEntity(HttpResponse response, HttpContext context)
throws IOException {
return EntityFactory.create(response.getEntity());
}
public void handleResponse(HttpResponse apacheResponse, HttpContext context) throws IOException {
NioHttpCommandConnectionHandle handle = (NioHttpCommandConnectionHandle) context
.removeAttribute("command-handle");
if (handle != null) {
try {
HttpCommandRendezvous<?> rendezvous = handle.getCommandRendezvous();
HttpCommand command = rendezvous.getCommand();
org.jclouds.http.HttpResponse response = NioHttpUtils
.convertToJCloudsResponse(apacheResponse);
logger
.debug("Receiving response %s: %s", response.hashCode(), response
.getStatusLine());
HttpUtils.logResponse(headerLog, response, "<<");
if (response.getContent() != null && wire.enabled())
response.setContent(wire.input(response.getContent()));
int statusCode = response.getStatusCode();
if (statusCode >= 300) {
if (retryHandler.shouldRetryRequest(command, response)) {
resubmitQueue.add(rendezvous);
} else {
errorHandler.handleError(command, response);
Closeables.closeQuietly(response.getContent());
assert command.getException() != null : "errorHandler should have set an exception!";
rendezvous.setException(command.getException());
}
} else {
// Close early, if there is no content.
String header = response.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH);
if (response.getStatusCode() == 204 || (header != null && header.equals("0"))) {
Closeables.closeQuietly(response.getContent());
}
// TODO, the connection should be released when the input stream is closed
rendezvous.setResponse(response);
}
} catch (InterruptedException e) {
logger.error(e, "interrupted processing response task");
} finally {
// This here is probably invalid, as the connection is really tied to the server
// response.
releaseConnectionToPool(handle);
}
} else {
throw new IllegalStateException(String.format(
"No command-handle associated with command %1$s", context));
}
}
protected void releaseConnectionToPool(NioHttpCommandConnectionHandle handle) {
try {
handle.release();
} catch (InterruptedException e) {
logger.error(e, "Interrupted releasing handle %1$s", handle);
}
}
public void finalizeContext(HttpContext context) {
NioHttpCommandConnectionHandle handle = (NioHttpCommandConnectionHandle) context
.removeAttribute("command-handle");
if (handle != null) {
try {
handle.cancel();
} catch (Exception e) {
logger.error(e, "Error cancelling handle %1$s", handle);
}
}
}
}