/**
*
* 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.gae;
import static com.google.appengine.api.urlfetch.FetchOptions.Builder.disallowTruncate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants;
import org.jclouds.concurrent.SingleThreaded;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.Payload;
import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.http.internal.BaseHttpCommandExecutorService;
import org.jclouds.http.internal.HttpWire;
import org.jclouds.http.payloads.ByteArrayPayload;
import org.jclouds.http.payloads.FilePayload;
import org.jclouds.http.payloads.InputStreamPayload;
import org.jclouds.http.payloads.StringPayload;
import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
import com.google.common.util.concurrent.ListenableFuture;
/**
* Google App Engine version of {@link HttpCommandExecutorService}
*
* @author Adrian Cole
*/
@SingleThreaded
@Singleton
public class GaeHttpCommandExecutorService extends BaseHttpCommandExecutorService<HTTPRequest> {
public static final String USER_AGENT = "jclouds/1.0 urlfetch/1.3.0";
private final URLFetchService urlFetchService;
@Inject
public GaeHttpCommandExecutorService(URLFetchService urlFetchService,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor,
DelegatingRetryHandler retryHandler, DelegatingErrorHandler errorHandler, HttpWire wire) {
super(ioExecutor, retryHandler, errorHandler, wire);
this.urlFetchService = urlFetchService;
}
@Override
public ListenableFuture<HttpResponse> submit(HttpCommand command) {
convertHostHeaderToEndPoint(command);
return super.submit(command);
}
/**
* byte [] content is replayable and the only content type supportable by GAE. As such, we
* convert the original request content to a byte array.
*/
@VisibleForTesting
void changeRequestContentToBytes(HttpRequest request) throws IOException {
Payload content = request.getPayload();
if (content == null || content instanceof ByteArrayPayload) {
return;
} else if (content instanceof StringPayload) {
String string = ((StringPayload) content).getRawContent();
request.setPayload(string.getBytes());
} else if (content instanceof InputStreamPayload || content instanceof FilePayload) {
InputStream i = content.getContent();
try {
request.setPayload(ByteStreams.toByteArray(i));
} finally {
Closeables.closeQuietly(i);
}
} else {
throw new UnsupportedOperationException("Content not supported " + content.getClass());
}
}
@VisibleForTesting
protected HttpResponse convert(HTTPResponse gaeResponse) {
HttpResponse response = new HttpResponse();
response.setStatusCode(gaeResponse.getResponseCode());
for (HTTPHeader header : gaeResponse.getHeaders()) {
response.getHeaders().put(header.getName(), header.getValue());
}
if (gaeResponse.getContent() != null) {
response.setContent(new ByteArrayInputStream(gaeResponse.getContent()));
}
return response;
}
@VisibleForTesting
protected HTTPRequest convert(HttpRequest request) throws IOException {
URL url = request.getEndpoint().toURL();
FetchOptions options = disallowTruncate();
options.doNotFollowRedirects();
HTTPRequest gaeRequest = new HTTPRequest(url, HTTPMethod.valueOf(request.getMethod()
.toString()), options);
for (String header : request.getHeaders().keySet()) {
for (String value : request.getHeaders().get(header)) {
gaeRequest.addHeader(new HTTPHeader(header, value));
}
}
gaeRequest.addHeader(new HTTPHeader(HttpHeaders.USER_AGENT, USER_AGENT));
if (request.getPayload() != null) {
changeRequestContentToBytes(request);
gaeRequest.setPayload(((ByteArrayPayload) request.getPayload()).getRawContent());
} else {
gaeRequest.addHeader(new HTTPHeader(HttpHeaders.CONTENT_LENGTH, "0"));
}
return gaeRequest;
}
/**
* As host headers are not supported in GAE/J v1.2.1, we'll change the hostname of the
* destination to the same value as the host header
*
* @param command
*/
@VisibleForTesting
public static void convertHostHeaderToEndPoint(HttpCommand command) {
HttpRequest request = command.getRequest();
String hostHeader = request.getFirstHeaderOrNull(HttpHeaders.HOST);
if (hostHeader != null) {
command.changeSchemeHostAndPortTo(request.getEndpoint().getScheme(), hostHeader, request
.getEndpoint().getPort());
}
}
/**
* nothing to clean up.
*/
@Override
protected void cleanup(HTTPRequest nativeRequest) {
}
@Override
protected HttpResponse invoke(HTTPRequest request) throws IOException {
return convert(urlFetchService.fetch(request));
}
}