diff --git a/extensions/gae/src/test/java/org/jclouds/gae/URLFetchServiceClient.java b/extensions/gae/src/test/java/org/jclouds/gae/URLFetchServiceClient.java new file mode 100644 index 0000000000..5895b84ed6 --- /dev/null +++ b/extensions/gae/src/test/java/org/jclouds/gae/URLFetchServiceClient.java @@ -0,0 +1,169 @@ +/** + * + * Copyright (C) 2009 Adrian Cole + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.gae; + +import static com.google.appengine.api.urlfetch.FetchOptions.Builder.disallowTruncate; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.jclouds.http.BaseHttpFutureCommandClient; +import org.jclouds.http.HttpFutureCommand; +import org.jclouds.http.HttpFutureCommandClient; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.HttpResponse; + +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.inject.Inject; + +/** + * Google App Engine version of {@link HttpFutureCommandClient} + * + * @author Adrian Cole + */ +public class URLFetchServiceClient extends BaseHttpFutureCommandClient { + private final URLFetchService urlFetchService; + + @Inject + public URLFetchServiceClient(URL target, URLFetchService urlFetchService) + throws MalformedURLException { + super(target); + this.urlFetchService = urlFetchService; + } + + public void submit(HttpFutureCommand command) { + HttpRequest request = command.getRequest(); + HTTPResponse gaeResponse = null; + try { + for (HttpRequestFilter filter : requestFilters) { + filter.filter(request); + } + HttpResponse response = null; + for (;;) { + logger.trace("%1$s - converting request %2$s", target, request); + HTTPRequest gaeRequest = convert(request); + if (logger.isTraceEnabled()) + logger.trace("%1$s - submitting request %2$s, headers: %3$s", + target, gaeRequest.getURL(), + headersAsString(gaeRequest.getHeaders())); + gaeResponse = this.urlFetchService.fetch(gaeRequest); + if (logger.isTraceEnabled()) + logger.trace( + "%1$s - received response code %2$s, headers: %3$s", + target, gaeResponse.getResponseCode(), + headersAsString(gaeResponse.getHeaders())); + response = convert(gaeResponse); + if (isRetryable(command, response)) + continue; + break; + } + handleResponse(command, response); + } catch (Exception e) { + if (gaeResponse != null && gaeResponse.getContent() != null) { + logger.error(e, + "error encountered during the execution: %1$s%n%2$s", + gaeResponse, new String(gaeResponse.getContent())); + } + command.setException(e); + } + } + + String headersAsString(List headers) { + StringBuilder builder = new StringBuilder(""); + for (HTTPHeader header : headers) + builder.append("[").append(header.getName()).append("=").append( + header.getValue()).append("],"); + return builder.toString(); + } + + /** + * 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 { + Object content = request.getPayload(); + if (content == null || content instanceof byte[]) { + return; + } else if (content instanceof String) { + String string = (String) content; + request.setPayload(string.getBytes()); + } else if (content instanceof InputStream || content instanceof File) { + InputStream i = content instanceof InputStream ? (InputStream) content + : new FileInputStream((File) content); + try { + request.setPayload(IOUtils.toByteArray(i)); + } finally { + IOUtils.closeQuietly(i); + } + } else { + throw new UnsupportedOperationException("Content not supported " + + content.getClass()); + } + + } + + @VisibleForTesting + 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 + HTTPRequest convert(HttpRequest request) throws IOException { + URL url = new URL(target, request.getUri()); + HTTPRequest gaeRequest = new HTTPRequest(url, HTTPMethod + .valueOf(request.getMethod()), disallowTruncate()); + for (String header : request.getHeaders().keySet()) { + for (String value : request.getHeaders().get(header)) + gaeRequest.addHeader(new HTTPHeader(header, value)); + } + if (request.getPayload() != null) { + changeRequestContentToBytes(request); + gaeRequest.setPayload((byte[]) request.getPayload()); + } + return gaeRequest; + } +} diff --git a/extensions/gae/src/test/java/org/jclouds/gae/URLFetchServiceClientTest.java b/extensions/gae/src/test/java/org/jclouds/gae/URLFetchServiceClientTest.java new file mode 100644 index 0000000000..8b3229667b --- /dev/null +++ b/extensions/gae/src/test/java/org/jclouds/gae/URLFetchServiceClientTest.java @@ -0,0 +1,185 @@ +/** + * + * Copyright (C) 2009 Adrian Cole + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.gae; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createMock; +import static org.easymock.classextension.EasyMock.createNiceMock; +import static org.easymock.classextension.EasyMock.replay; +import static org.testng.Assert.assertEquals; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.jclouds.http.HttpHeaders; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import com.google.appengine.api.urlfetch.HTTPHeader; +import com.google.appengine.api.urlfetch.HTTPRequest; +import com.google.appengine.api.urlfetch.HTTPResponse; +import com.google.appengine.api.urlfetch.URLFetchService; + +/** + * + * @author Adrian Cole + */ +@Test +public class URLFetchServiceClientTest { + URLFetchServiceClient client; + URL url; + + @BeforeTest + void setupClient() throws MalformedURLException { + url = new URL("http://localhost:80"); + client = new URLFetchServiceClient(url, + createNiceMock(URLFetchService.class)); + } + + @Test + void testConvertWithHeaders() { + HTTPResponse gaeResponse = createMock(HTTPResponse.class); + expect(gaeResponse.getResponseCode()).andReturn(200); + List headers = new ArrayList(); + headers.add(new HTTPHeader(HttpHeaders.CONTENT_TYPE, "text/xml")); + expect(gaeResponse.getHeaders()).andReturn(headers); + expect(gaeResponse.getContent()).andReturn(null).atLeastOnce(); + replay(gaeResponse); + HttpResponse response = client.convert(gaeResponse); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getContent(), null); + assertEquals(response.getHeaders().size(), 1); + assertEquals(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE), + "text/xml"); + } + + @Test + void testConvertWithContent() throws IOException { + HTTPResponse gaeResponse = createMock(HTTPResponse.class); + expect(gaeResponse.getResponseCode()).andReturn(200); + List headers = new ArrayList(); + headers.add(new HTTPHeader(HttpHeaders.CONTENT_TYPE, "text/xml")); + expect(gaeResponse.getHeaders()).andReturn(headers); + expect(gaeResponse.getContent()).andReturn("hello".getBytes()) + .atLeastOnce(); + replay(gaeResponse); + HttpResponse response = client.convert(gaeResponse); + assertEquals(response.getStatusCode(), 200); + assertEquals(IOUtils.toString(response.getContent()), "hello"); + assertEquals(response.getHeaders().size(), 1); + assertEquals(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE), + "text/xml"); + } + + @Test + void testConvertRequestGetsTargetAndUri() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + HTTPRequest gaeRequest = client.convert(request); + assertEquals(gaeRequest.getURL().getPath(), "/foo"); + } + + @Test + void testConvertRequestSetsFetchOptions() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + HTTPRequest gaeRequest = client.convert(request); + assert gaeRequest.getFetchOptions() != null; + } + + @Test + void testConvertRequestSetsHeaders() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + request.getHeaders().put("foo", "bar"); + HTTPRequest gaeRequest = client.convert(request); + assertEquals(gaeRequest.getHeaders().get(0).getName(), "foo"); + assertEquals(gaeRequest.getHeaders().get(0).getValue(), "bar"); + } + + @Test + void testConvertRequestNoContent() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + HTTPRequest gaeRequest = client.convert(request); + assert gaeRequest.getPayload() == null; + assertEquals(gaeRequest.getHeaders().size(), 0); + } + + @Test + void testConvertRequestStringContent() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + request.setPayload("hoot!"); + testHoot(request); + } + + @Test + void testConvertRequestInputStreamContent() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + request.setPayload(IOUtils.toInputStream("hoot!")); + testHoot(request); + } + + @Test + void testConvertRequestBytesContent() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + request.setPayload("hoot!".getBytes()); + testHoot(request); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + void testConvertRequestBadContent() throws IOException { + HttpRequest request = new HttpRequest("GET", "foo"); + request.setPayload(new Date()); + client.convert(request); + + } + + @Test + @Parameters("basedir") + void testConvertRequestFileContent(String basedir) throws IOException { + File file = new File(basedir, "target/testfiles/hoot"); + file.getParentFile().mkdirs(); + IOUtils.write("hoot!", new FileOutputStream(file)); + HttpRequest request = new HttpRequest("GET", "foo"); + request.setPayload(file); + testHoot(request); + } + + private void testHoot(HttpRequest request) throws IOException { + request.getHeaders().put(HttpHeaders.CONTENT_TYPE,"text/plain"); + HTTPRequest gaeRequest = client.convert(request); + assertEquals(gaeRequest.getHeaders().get(0).getName(), + HttpHeaders.CONTENT_TYPE); + assertEquals(gaeRequest.getHeaders().get(0).getValue(), "text/plain"); + assertEquals(new String(gaeRequest.getPayload()), "hoot!"); + } + +} diff --git a/pom.xml b/pom.xml index f2fcc56953..694b0efbdd 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ core extensions/httpnio extensions/log4j - gae + extensions/gae s3