blob: 111f106c408f97047d8b0e3eb0e99bc80fabe351 [file] [log] [blame]
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.ning;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.http.internal.BaseHttpCommandExecutorService;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.io.payloads.FilePayload;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.Closeables;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Request;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.Response;
import com.ning.http.client.generators.InputStreamBodyGenerator;
/**
* Todo Write me
*
* @author Sam Tunnicliffe
* @author Adrian Cole
*/
public class NingHttpCommandExecutorService implements HttpCommandExecutorService {
public static final String USER_AGENT = "jclouds/1.0 ning http/1.0.0";
private final AsyncHttpClient client;
private final ConvertToNingRequest convertToNingRequest;
private final ConvertToJCloudsResponse convertToJCloudsResponse;
private final DelegatingRetryHandler retryHandler;
private final DelegatingErrorHandler errorHandler;
@Inject
public NingHttpCommandExecutorService(AsyncHttpClient client, ConvertToNingRequest convertToNingRequest,
ConvertToJCloudsResponse convertToJCloudsResponse, DelegatingRetryHandler retryHandler,
DelegatingErrorHandler errorHandler) {
this.client = client;
this.convertToNingRequest = convertToNingRequest;
this.convertToJCloudsResponse = convertToJCloudsResponse;
this.retryHandler = retryHandler;
this.errorHandler = errorHandler;
}
@Override
public ListenableFuture<HttpResponse> submit(HttpCommand command) {
try {
for (;;) {
Future<Response> responseF = client.executeRequest(convertToNingRequest.apply(command.getCurrentRequest()));
final HttpResponse httpResponse = convertToJCloudsResponse.apply(responseF.get());
int statusCode = httpResponse.getStatusCode();
if (statusCode >= 300) {
if (retryHandler.shouldRetryRequest(command, httpResponse)) {
continue;
} else {
errorHandler.handleError(command, httpResponse);
return wrapAsFuture(httpResponse);
}
} else {
return wrapAsFuture(httpResponse);
}
}
} catch (IOException e) {
throw Throwables.propagate(e);
} catch (InterruptedException e) {
throw Throwables.propagate(e);
} catch (ExecutionException e) {
throw Throwables.propagate(e);
}
}
private ListenableFuture<HttpResponse> wrapAsFuture(final HttpResponse httpResponse) {
return Futures.makeListenable(new AbstractFuture<HttpResponse>() {
@Override
public HttpResponse get() throws InterruptedException, ExecutionException {
return httpResponse;
}
});
}
@Singleton
public static class ConvertToNingRequest implements Function<HttpRequest, Request> {
public Request apply(HttpRequest request) {
for (HttpRequestFilter filter : request.getFilters()) {
request = filter.filter(request);
}
RequestBuilder builder = new RequestBuilder(request.getMethod());
builder.setUrl(request.getEndpoint().toASCIIString());
Payload payload = request.getPayload();
if (payload != null) {
boolean chunked = "chunked".equals(request.getFirstHeaderOrNull("Transfer-Encoding"));
if (request.getPayload().getContentMetadata().getContentMD5() != null)
builder.addHeader("Content-MD5", CryptoStreams.base64(request.getPayload().getContentMetadata()
.getContentMD5()));
if (request.getPayload().getContentMetadata().getContentType() != null)
builder.addHeader(HttpHeaders.CONTENT_TYPE, request.getPayload().getContentMetadata().getContentType());
if (request.getPayload().getContentMetadata().getContentLanguage() != null)
builder.addHeader(HttpHeaders.CONTENT_LANGUAGE, request.getPayload().getContentMetadata()
.getContentLanguage());
if (request.getPayload().getContentMetadata().getContentEncoding() != null)
builder.addHeader(HttpHeaders.CONTENT_ENCODING, request.getPayload().getContentMetadata()
.getContentEncoding());
if (request.getPayload().getContentMetadata().getContentDisposition() != null)
builder.addHeader("Content-Disposition", request.getPayload().getContentMetadata()
.getContentDisposition());
if (!chunked) {
Long length = checkNotNull(request.getPayload().getContentMetadata().getContentLength(),
"payload.getContentLength");
builder.addHeader(HttpHeaders.CONTENT_LENGTH, length.toString());
}
setPayload(builder, payload);
} else {
builder.addHeader(HttpHeaders.CONTENT_LENGTH, "0");
}
builder.addHeader(HttpHeaders.USER_AGENT, USER_AGENT);
for (String header : request.getHeaders().keySet()) {
for (String value : request.getHeaders().get(header)) {
builder.addHeader(header, value);
}
}
return builder.build();
}
void setPayload(RequestBuilder requestBuilder, Payload payload) {
if (payload instanceof FilePayload) {
requestBuilder.setBody(((FilePayload) payload).getRawContent());
} else {
requestBuilder.setBody(new InputStreamBodyGenerator(payload.getInput()));
}
}
}
@Singleton
public static class ConvertToJCloudsResponse implements Function<Response, HttpResponse> {
public HttpResponse apply(Response nativeResponse) {
InputStream in = null;
try {
in = BaseHttpCommandExecutorService.consumeOnClose(nativeResponse.getResponseBodyAsStream());
} catch (IOException e) {
Closeables.closeQuietly(in);
propagate(e);
assert false : "should have propagated exception";
}
Payload payload = in != null ? Payloads.newInputStreamPayload(in) : null;
Multimap<String, String> headers = LinkedHashMultimap.create();
for (Entry<String, List<String>> header : nativeResponse.getHeaders()) {
headers.putAll(header.getKey(), header.getValue());
}
if (payload != null)
payload.getContentMetadata().setPropertiesFromHttpHeaders(headers);
return new HttpResponse(nativeResponse.getStatusCode(), nativeResponse.getStatusText(), payload,
RestAnnotationProcessor.filterOutContentHeaders(headers));
}
}
}