From 38b2bef0239847385041e443486cf5b460ece24b Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sat, 20 Mar 2010 16:22:17 -0700 Subject: [PATCH] Issue 211: added gae, but waiting for service to go live --- aws/demos/googleappengine/pom.xml | 2 +- demos/gae-tweetstore-spring/pom.xml | 4 +- demos/gae-tweetstore/pom.xml | 4 +- extensions/gae/pom.xml | 17 +- .../AsyncGaeHttpCommandExecutorService.java | 172 ++++++++++++++++++ .../gae/GaeHttpCommandExecutorService.java | 26 +-- .../GoogleAppEngineConfigurationModule.java | 2 - ...CommandExecutorServiceIntegrationTest.java | 78 ++------ .../GaeHttpCommandExecutorServiceTest.java | 2 +- 9 files changed, 199 insertions(+), 108 deletions(-) create mode 100644 extensions/gae/src/main/java/org/jclouds/gae/AsyncGaeHttpCommandExecutorService.java diff --git a/aws/demos/googleappengine/pom.xml b/aws/demos/googleappengine/pom.xml index 620404c015..4f191ded92 100644 --- a/aws/demos/googleappengine/pom.xml +++ b/aws/demos/googleappengine/pom.xml @@ -101,7 +101,7 @@ com.google.appengine appengine-tools-api - 1.3.0 + 1.3.2 system ${appengine.home}/lib/appengine-tools-api.jar diff --git a/demos/gae-tweetstore-spring/pom.xml b/demos/gae-tweetstore-spring/pom.xml index 2226397c0e..0e8b66ded5 100644 --- a/demos/gae-tweetstore-spring/pom.xml +++ b/demos/gae-tweetstore-spring/pom.xml @@ -156,12 +156,12 @@ com.google.appengine appengine-api-labs - 1.3.0 + 1.3.2 com.google.appengine appengine-tools-api - 1.3.0 + 1.3.2 system ${appengine.home}/lib/appengine-tools-api.jar diff --git a/demos/gae-tweetstore/pom.xml b/demos/gae-tweetstore/pom.xml index d84f067236..9ebf86a837 100644 --- a/demos/gae-tweetstore/pom.xml +++ b/demos/gae-tweetstore/pom.xml @@ -146,12 +146,12 @@ com.google.appengine appengine-api-labs - 1.3.0 + 1.3.2 com.google.appengine appengine-tools-api - 1.3.0 + 1.3.2 system ${appengine.home}/lib/appengine-tools-api.jar diff --git a/extensions/gae/pom.xml b/extensions/gae/pom.xml index 3c72cbc8b2..5108126cd6 100644 --- a/extensions/gae/pom.xml +++ b/extensions/gae/pom.xml @@ -44,26 +44,27 @@ jclouds-joda ${project.version} - - ${project.groupId} - jclouds-bouncycastle - ${project.version} - com.google.appengine appengine-api - 1.3.0 + 1.3.2 com.google.appengine appengine-api-stubs - 1.3.0 + 1.3.2 + test + + + com.google.appengine + appengine-testing + 1.3.2 test com.google.appengine appengine-local-runtime - 1.3.0 + 1.3.2 test diff --git a/extensions/gae/src/main/java/org/jclouds/gae/AsyncGaeHttpCommandExecutorService.java b/extensions/gae/src/main/java/org/jclouds/gae/AsyncGaeHttpCommandExecutorService.java new file mode 100644 index 0000000000..58841b6652 --- /dev/null +++ b/extensions/gae/src/main/java/org/jclouds/gae/AsyncGaeHttpCommandExecutorService.java @@ -0,0 +1,172 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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.MalformedURLException; +import java.net.URL; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.core.HttpHeaders; + +import org.jclouds.concurrent.ConcurrentUtils; +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.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.appengine.repackaged.com.google.common.base.Throwables; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.io.ByteStreams; +import com.google.common.io.Closeables; +import com.google.common.util.concurrent.Executors; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Google App Engine version of {@link HttpCommandExecutorService} using their fetchAsync call + * + * @author Adrian Cole + */ +@SingleThreaded +@Singleton +public class AsyncGaeHttpCommandExecutorService implements HttpCommandExecutorService { + public static final String USER_AGENT = "jclouds/1.0 urlfetch/1.3.2"; + + private final URLFetchService urlFetchService; + private final ConvertToGaeRequest convertToGaeRequest; + private final ConvertToJcloudsResponse convertToJcloudsResponse; + + @Inject + public AsyncGaeHttpCommandExecutorService(URLFetchService urlFetchService, + ConvertToGaeRequest convertToGaeRequest, + ConvertToJcloudsResponse convertToJcloudsResponse) { + this.urlFetchService = urlFetchService; + this.convertToGaeRequest = convertToGaeRequest; + this.convertToJcloudsResponse = convertToJcloudsResponse; + } + + @Override + public ListenableFuture submit(HttpCommand command) { + // TODO: this needs to handle retrying and filtering + return Futures.compose(ConcurrentUtils.makeListenable(urlFetchService + .fetchAsync(convertToGaeRequest.apply(command.getRequest())), Executors + .sameThreadExecutor()), convertToJcloudsResponse); + } + + @Singleton + public static class ConvertToJcloudsResponse implements Function { + + @Override + public HttpResponse apply(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; + } + } + + @Singleton + public static class ConvertToGaeRequest implements Function { + /** + * 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) { + 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 { + try { + request.setPayload(ByteStreams.toByteArray(i)); + } catch (IOException e) { + Throwables.propagate(e); + } + } finally { + Closeables.closeQuietly(i); + } + } else { + throw new UnsupportedOperationException("Content not supported " + content.getClass()); + } + + } + + @Override + public HTTPRequest apply(HttpRequest request) { + URL url = null; + try { + url = request.getEndpoint().toURL(); + } catch (MalformedURLException e) { + Throwables.propagate(e); + } + + FetchOptions options = disallowTruncate(); + options.followRedirects(); + + 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; + } + + } + +} diff --git a/extensions/gae/src/main/java/org/jclouds/gae/GaeHttpCommandExecutorService.java b/extensions/gae/src/main/java/org/jclouds/gae/GaeHttpCommandExecutorService.java index 2681a702d6..c63cd38c2c 100644 --- a/extensions/gae/src/main/java/org/jclouds/gae/GaeHttpCommandExecutorService.java +++ b/extensions/gae/src/main/java/org/jclouds/gae/GaeHttpCommandExecutorService.java @@ -33,7 +33,6 @@ 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; @@ -56,7 +55,6 @@ 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} @@ -66,7 +64,7 @@ import com.google.common.util.concurrent.ListenableFuture; @SingleThreaded @Singleton public class GaeHttpCommandExecutorService extends BaseHttpCommandExecutorService { - public static final String USER_AGENT = "jclouds/1.0 urlfetch/1.3.0"; + public static final String USER_AGENT = "jclouds/1.0 urlfetch/1.3.2"; private final URLFetchService urlFetchService; @@ -78,12 +76,6 @@ public class GaeHttpCommandExecutorService extends BaseHttpCommandExecutorServic this.urlFetchService = urlFetchService; } - @Override - public ListenableFuture 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. @@ -149,22 +141,6 @@ public class GaeHttpCommandExecutorService extends BaseHttpCommandExecutorServic 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. */ diff --git a/extensions/gae/src/main/java/org/jclouds/gae/config/GoogleAppEngineConfigurationModule.java b/extensions/gae/src/main/java/org/jclouds/gae/config/GoogleAppEngineConfigurationModule.java index db05b66f8e..96541bd21c 100644 --- a/extensions/gae/src/main/java/org/jclouds/gae/config/GoogleAppEngineConfigurationModule.java +++ b/extensions/gae/src/main/java/org/jclouds/gae/config/GoogleAppEngineConfigurationModule.java @@ -22,7 +22,6 @@ import org.jclouds.concurrent.SingleThreaded; import org.jclouds.concurrent.config.ConfiguresExecutorService; import org.jclouds.concurrent.config.ExecutorServiceModule; import org.jclouds.date.joda.config.JodaDateServiceModule; -import org.jclouds.encryption.bouncycastle.config.BouncyCastleEncryptionServiceModule; import org.jclouds.gae.GaeHttpCommandExecutorService; import org.jclouds.http.HttpCommandExecutorService; import org.jclouds.http.TransformingHttpCommandExecutorService; @@ -51,7 +50,6 @@ public class GoogleAppEngineConfigurationModule extends ExecutorServiceModule { @Override protected void configure() { super.configure(); - install(new BouncyCastleEncryptionServiceModule()); install(new JodaDateServiceModule()); bind(HttpCommandExecutorService.class).to(GaeHttpCommandExecutorService.class); bind(TransformingHttpCommandExecutorService.class).to( diff --git a/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceIntegrationTest.java b/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceIntegrationTest.java index 31e0fa8638..7de962323b 100644 --- a/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceIntegrationTest.java +++ b/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceIntegrationTest.java @@ -20,9 +20,7 @@ package org.jclouds.gae; import static org.testng.Assert.assertEquals; -import java.io.File; import java.net.MalformedURLException; -import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -30,12 +28,10 @@ import java.util.concurrent.TimeoutException; import org.jclouds.gae.config.GoogleAppEngineConfigurationModule; import org.jclouds.http.BaseHttpCommandExecutorServiceTest; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; -import org.testng.v6.Maps; -import com.google.appengine.tools.development.ApiProxyLocalImpl; -import com.google.apphosting.api.ApiProxy; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalURLFetchServiceTestConfig; import com.google.inject.Module; /** @@ -79,21 +75,9 @@ public class GaeHttpCommandExecutorServiceIntegrationTest extends super.testPostBinder(); } - @BeforeTest - void validateExecutor() { - // ExecutorService executorService = injector.getInstance(ExecutorService.class); - // assert executorService.getClass().isAnnotationPresent(SingleThreadCompatible.class) : - // Arrays - // .asList(executorService.getClass().getAnnotations()).toString() - // + executorService.getClass().getName(); - - } - @BeforeMethod void setupApiProxy() { - ApiProxy.setEnvironmentForCurrentThread(new TestEnvironment()); - ApiProxy.setDelegate(new ApiProxyLocalImpl(new File(".")) { - }); + new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig()).setUp(); } @Override @@ -177,11 +161,11 @@ public class GaeHttpCommandExecutorServiceIntegrationTest extends } @Override - @Test(enabled = false) + @Test(invocationCount = 50, timeOut = 3000) public void testGetStringWithHeader() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException { - // GAE does not support sending headers in their test stub as of version - // 1.2.0 + setupApiProxy(); + super.testGetStringWithHeader(); } @Override @@ -192,54 +176,14 @@ public class GaeHttpCommandExecutorServiceIntegrationTest extends super.testHead(); } - @Test(enabled = false) + @Override + @Test(invocationCount = 50, timeOut = 3000) public void testRequestFilter() throws MalformedURLException, ExecutionException, InterruptedException, TimeoutException { - // GAE does not support sending headers in their test stub as of version - // 1.2.0 + setupApiProxy(); + super.testRequestFilter(); } - - class TestEnvironment implements ApiProxy.Environment { - public String getAppId() { - return "Unit Tests"; - } - - public String getVersionId() { - return "1.0"; - } - - public void setDefaultNamespace(String s) { - } - - public String getRequestNamespace() { - return null; - } - - public String getDefaultNamespace() { - return null; - } - - public String getAuthDomain() { - return null; - } - - public boolean isLoggedIn() { - return false; - } - - public String getEmail() { - return null; - } - - public boolean isAdmin() { - return false; - } - - public Map getAttributes() { - return Maps.newHashMap(); - } - } - + protected Module createConnectionModule() { return new GoogleAppEngineConfigurationModule(); } diff --git a/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceTest.java b/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceTest.java index d857fe9581..7c7936ac35 100644 --- a/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceTest.java +++ b/extensions/gae/src/test/java/org/jclouds/gae/GaeHttpCommandExecutorServiceTest.java @@ -138,7 +138,7 @@ public class GaeHttpCommandExecutorServiceTest { assert gaeRequest.getPayload() == null; assertEquals(gaeRequest.getHeaders().size(), 2);// content length, user agent assertEquals(gaeRequest.getHeaders().get(0).getName(), HttpHeaders.USER_AGENT); - assertEquals(gaeRequest.getHeaders().get(0).getValue(), "jclouds/1.0 urlfetch/1.3.0"); + assertEquals(gaeRequest.getHeaders().get(0).getValue(), "jclouds/1.0 urlfetch/1.3.2"); } @Test