add better support for matrix

git-svn-id: http://jclouds.googlecode.com/svn/trunk@1983 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-10-14 21:24:11 +00:00
parent e939114702
commit bf0943ac6c
10 changed files with 294 additions and 9 deletions

View File

@ -0,0 +1,56 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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.rest.binders;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import java.util.Map.Entry;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.Binder;
import org.jclouds.rest.internal.GeneratedHttpRequest;
/**
* Binds the map to matrix parameters.
*
* @author Adrian Cole
* @since 4.0
*/
public class BindMapToMatrixParams implements Binder {
@SuppressWarnings("unchecked")
public void bindToRequest(HttpRequest request, Object input) {
checkArgument(checkNotNull(request, "input") instanceof GeneratedHttpRequest,
"this binder is only valid for GeneratedHttpRequests!");
checkArgument(checkNotNull(input, "input") instanceof Map,
"this binder is only valid for Maps!");
Map<String, String> map = (Map<String, String>) input;
for (Entry<String, String> entry : map.entrySet()) {
((GeneratedHttpRequest) request).replaceMatrixParam(entry.getKey(), entry.getValue());
}
}
}

View File

@ -66,6 +66,12 @@ public class GeneratedHttpRequest<T> extends HttpRequest {
return processor;
}
public void replaceMatrixParam(String name, Object... values) {
UriBuilder builder = UriBuilder.fromUri(getEndpoint());
builder.replaceMatrixParam(name, values);
replacePath(builder.build().getPath());
}
public void replaceQueryParam(String name, Object... values) {
UriBuilder builder = UriBuilder.fromUri(getEndpoint());
builder.replaceQueryParam(name, values);

View File

@ -66,7 +66,7 @@ public class BackoffLimitedRetryJavaIntegrationTest extends BaseJettyTest {
}
@Override
protected boolean failOnRequest(HttpServletRequest request, HttpServletResponse response)
protected boolean failEveryTenRequests(HttpServletRequest request, HttpServletResponse response)
throws IOException {
requestCount++;
boolean shouldFail = requestCount >= beginToFailOnRequestNumber

View File

@ -30,11 +30,15 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.jclouds.http.options.GetOptions;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
/**
* Tests for functionality all HttpCommandExecutorServices must express. These tests will operate
* against an in-memory http engine, so as to ensure end-to-end functionality works.
@ -117,6 +121,30 @@ public abstract class BaseHttpCommandExecutorServiceTest extends BaseJettyTest {
assertEquals(put.get(10, TimeUnit.SECONDS).trim(), "fooPOST");
}
@Test(invocationCount = 50, timeOut = 10000)
public void testPostAsInputStream() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
try {
Future<String> put = client.postAsInputStream("", "foo");
assertEquals(put.get(10, TimeUnit.SECONDS).trim(), "fooPOST");
} catch (Exception e) {
postFailures.incrementAndGet();
}
}
protected AtomicInteger postFailures = new AtomicInteger();
@BeforeTest
void resetCounters() {
postFailures.set(0);
}
@Test(dependsOnMethods = "testPostAsInputStream")
public void testPostResults() {
// failures happen when trying to replay inputstreams
assert postFailures.get() > 0;
}
@Test(invocationCount = 50, timeOut = 5000)
public void testPostBinder() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
@ -138,6 +166,13 @@ public abstract class BaseHttpCommandExecutorServiceTest extends BaseJettyTest {
assertEquals(put.get(10, TimeUnit.SECONDS).trim(), "fooPUTREDIRECT");
}
@Test(invocationCount = 50, timeOut = 5000)
public void testKillRobotSlowly() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
Future<String> dead = client.action("robot", "kill", ImmutableMap.of("death", "slow"));
assertEquals(dead.get(10, TimeUnit.SECONDS).trim(), "robot->kill:{death=slow}");
}
@Test(invocationCount = 50, timeOut = 5000)
public void testHead() throws MalformedURLException, ExecutionException, InterruptedException,
TimeoutException {

View File

@ -26,8 +26,11 @@ package org.jclouds.http;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Singleton;
import javax.servlet.ServletException;
@ -53,6 +56,7 @@ import org.testng.annotations.BeforeTest;
import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Module;
@ -111,10 +115,13 @@ public abstract class BaseJettyTest {
private AtomicInteger cycle = new AtomicInteger(0);
private Server server2;
private CloudContext<IntegrationTestClient> context;
private int testPort;
static final Pattern actionPattern = Pattern.compile("/objects/(.*)/action/([a-z]*);?(.*)");
@BeforeTest
@Parameters( { "test-jetty-port" })
public void setUpJetty(@Optional("8123") final int testPort) throws Exception {
this.testPort = testPort;
Handler server1Handler = new AbstractHandler() {
public void handle(String target, HttpServletRequest request,
HttpServletResponse response, int dispatch) throws IOException, ServletException {
@ -131,12 +138,16 @@ public abstract class BaseJettyTest {
response.sendError(500, "no content");
}
} else if (request.getMethod().equals("POST")) {
if (redirectEveryTwentyRequests(request, response))
return;
if (failEveryTenRequests(request, response))
return;
if (request.getContentLength() > 0) {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(
Utils.toStringAndClose(request.getInputStream()) + "POST");
} else {
response.sendError(500, "no content");
handleAction(request, response);
}
} else if (request.getHeader("Range") != null) {
response.sendError(404, "no content");
@ -145,7 +156,7 @@ public abstract class BaseJettyTest {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("test");
} else {
if (failOnRequest(request, response))
if (failEveryTenRequests(request, response))
return;
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
@ -153,6 +164,7 @@ public abstract class BaseJettyTest {
}
((Request) request).setHandled(true);
}
};
server = new Server(testPort);
@ -167,8 +179,14 @@ public abstract class BaseJettyTest {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(
Utils.toStringAndClose(request.getInputStream()) + "PUTREDIRECT");
}
} else if (request.getMethod().equals("POST")) {
if (request.getContentLength() > 0) {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(
Utils.toStringAndClose(request.getInputStream()) + "POST");
} else {
response.sendError(500, "no content");
handleAction(request, response);
}
} else {
response.setContentType("text/xml");
@ -227,7 +245,7 @@ public abstract class BaseJettyTest {
* @return
* @throws IOException
*/
protected boolean failOnRequest(HttpServletRequest request, HttpServletResponse response)
protected boolean failEveryTenRequests(HttpServletRequest request, HttpServletResponse response)
throws IOException {
if (cycle.incrementAndGet() % 10 == 0) {
response.sendError(500);
@ -237,6 +255,16 @@ public abstract class BaseJettyTest {
return false;
}
protected boolean redirectEveryTwentyRequests(HttpServletRequest request,
HttpServletResponse response) throws IOException {
if (cycle.incrementAndGet() % 20 == 0) {
response.sendRedirect("http://localhost:" + (testPort + 1));
((Request) request).setHandled(true);
return true;
}
return false;
}
protected boolean failIfNoContentLength(HttpServletRequest request, HttpServletResponse response)
throws IOException {
if (request.getHeader(HttpHeaders.CONTENT_LENGTH) == null) {
@ -247,4 +275,27 @@ public abstract class BaseJettyTest {
return false;
}
private void handleAction(HttpServletRequest request, HttpServletResponse response)
throws IOException {
final Matcher matcher = actionPattern.matcher(request.getRequestURI());
boolean matchFound = matcher.find();
if (matchFound) {
String objectId = matcher.group(1);
String action = matcher.group(2);
Map<String, String> options = Maps.newHashMap();
if (matcher.groupCount() == 3) {
String optionsGroup = matcher.group(3);
for (String entry : optionsGroup.split(";")) {
if (entry.indexOf('=') >= 0) {
String[] keyValue = entry.split("=");
options.put(keyValue[0], keyValue[1]);
}
}
}
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(objectId + "->" + action + ":" + options);
} else {
response.sendError(500, "no content");
}
}
}

View File

@ -23,6 +23,7 @@
*/
package org.jclouds.http;
import java.util.Map;
import java.util.concurrent.Future;
import javax.ws.rs.GET;
@ -33,6 +34,7 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import org.apache.commons.io.IOUtils;
import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.rest.annotations.BinderParam;
@ -42,6 +44,7 @@ import org.jclouds.rest.annotations.MapBinder;
import org.jclouds.rest.annotations.MapEntityParam;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.rest.binders.BindMapToMatrixParams;
import org.jclouds.rest.binders.BindToJsonEntity;
import org.jclouds.rest.binders.BindToStringEntity;
import org.jclouds.rest.internal.RestAnnotationProcessorTest.Localhost;
@ -96,11 +99,29 @@ public interface IntegrationTestClient {
Future<String> post(@PathParam("id") String id,
@BinderParam(BindToStringEntity.class) String toPut);
@POST
@Path("objects/{id}")
Future<String> postAsInputStream(@PathParam("id") String id,
@BinderParam(BindToInputStreamEntity.class) String toPut);
static class BindToInputStreamEntity extends BindToStringEntity {
@Override
public void bindToRequest(HttpRequest request, Object entity) {
super.bindToRequest(request, entity);
request.setEntity(IOUtils.toInputStream(entity.toString()));
}
}
@POST
@Path("objects/{id}")
@MapBinder(BindToJsonEntity.class)
Future<String> postJson(@PathParam("id") String id, @MapEntityParam("key") String toPut);
@POST
@Path("objects/{id}/action/{action}")
Future<String> action(@PathParam("id") String id, @PathParam("action") String action,
@BinderParam(BindMapToMatrixParams.class) Map<String, String> options);
@GET
@Path("objects/{id}")
@RequestFilters(Filter.class)

View File

@ -0,0 +1,54 @@
package org.jclouds.rest.binders;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import java.io.File;
import java.net.URI;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.ext.RuntimeDelegate;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.jclouds.rest.internal.RuntimeDelegateImpl;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
/**
* Tests behavior of {@code BindMapToMatrixParams}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "rest.BindMapToMatrixParamsTest")
public class BindMapToMatrixParamsTest {
static {
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testMustBeMap() {
BindMapToMatrixParams binder = new BindMapToMatrixParams();
HttpRequest request = new HttpRequest(HttpMethod.POST, URI.create("http://localhost"));
binder.bindToRequest(request, new File("foo"));
}
@Test
public void testCorrect() throws SecurityException, NoSuchMethodException {
BindMapToMatrixParams binder = new BindMapToMatrixParams();
GeneratedHttpRequest<?> request = createMock(GeneratedHttpRequest.class);
request.replaceMatrixParam("imageName", "foo");
request.replaceMatrixParam("serverId", "2");
replay(request);
binder.bindToRequest(request, ImmutableMap.of("imageName", "foo", "serverId", "2"));
}
@Test(expectedExceptions = { NullPointerException.class, IllegalStateException.class })
public void testNullIsBad() {
BindMapToMatrixParams binder = new BindMapToMatrixParams();
GeneratedHttpRequest<?> request = createMock(GeneratedHttpRequest.class);
binder.bindToRequest(request, null);
}
}

View File

@ -87,6 +87,7 @@ import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SkipEncoding;
import org.jclouds.rest.annotations.VirtualHost;
import org.jclouds.rest.binders.BindMapToMatrixParams;
import org.jclouds.rest.binders.BindToJsonEntity;
import org.jclouds.rest.binders.BindToStringEntity;
import org.jclouds.rest.config.RestModule;
@ -100,6 +101,7 @@ import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
@ -739,6 +741,24 @@ public class RestAnnotationProcessorTest {
assertEquals(query, "x-amz-copy-source=/robot");
}
@Endpoint(Localhost.class)
private interface TestMapMatrixParams {
@POST
@Path("objects/{id}/action/{action}")
Future<String> action(@PathParam("id") String id, @PathParam("action") String action,
@BinderParam(BindMapToMatrixParams.class) Map<String, String> options);
}
public void testTestMapMatrixParams() throws SecurityException, NoSuchMethodException,
UnsupportedEncodingException {
Method method = TestMapMatrixParams.class.getMethod("action", String.class, String.class,
Map.class);
GeneratedHttpRequest<TestMapMatrixParams> httpMethod = factory(TestMapMatrixParams.class).createRequest(method,
new Object[] { "robot", "kill", ImmutableMap.of("death", "slow") });
assertEquals(httpMethod.getRequestLine(), "POST http://localhost:8080/objects/robot/action/kill;death=slow HTTP/1.1");
assertEquals(httpMethod.getHeaders().size(), 0);
}
@Endpoint(Localhost.class)
public class TestQueryReplace {

View File

@ -23,6 +23,8 @@
*/
package org.jclouds.gae;
import static org.testng.Assert.assertEquals;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Map;
@ -51,6 +53,29 @@ import com.google.inject.Module;
public class GaeHttpCommandExecutorServiceIntegrationTest extends
BaseHttpCommandExecutorServiceTest {
@Override
@Test(invocationCount = 50, timeOut = 3000)
public void testKillRobotSlowly() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
setupApiProxy();
super.testKillRobotSlowly();
}
@Override
@Test(invocationCount = 50, timeOut = 3000)
public void testPostAsInputStream() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
setupApiProxy();
super.testPostAsInputStream();
}
@Override
@Test(dependsOnMethods = "testPostAsInputStream")
public void testPostResults() {
// GAE converts everything to byte arrays and so failures are not gonna happen
assertEquals(postFailures.get(), 0);
}
@Override
@Test(invocationCount = 50, timeOut = 3000)
public void testPostBinder() throws MalformedURLException, ExecutionException,
@ -62,7 +87,8 @@ public class GaeHttpCommandExecutorServiceIntegrationTest extends
@BeforeTest
void validateExecutor() {
// ExecutorService executorService = injector.getInstance(ExecutorService.class);
// assert executorService.getClass().isAnnotationPresent(SingleThreadCompatible.class) : Arrays
// assert executorService.getClass().isAnnotationPresent(SingleThreadCompatible.class) :
// Arrays
// .asList(executorService.getClass().getAnnotations()).toString()
// + executorService.getClass().getName();

View File

@ -23,7 +23,10 @@
*/
package org.jclouds.http.httpnio.pool;
import java.net.MalformedURLException;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.jclouds.http.BaseHttpCommandExecutorServiceTest;
import org.jclouds.http.httpnio.config.NioTransformingHttpCommandExecutorServiceModule;
@ -40,6 +43,19 @@ import com.google.inject.Module;
public class NioTransformingHttpCommandExecutorServiceTest extends
BaseHttpCommandExecutorServiceTest {
@Override
@Test(enabled = false)
public void testPostAsInputStream() throws MalformedURLException, ExecutionException,
InterruptedException, TimeoutException {
// TODO when these fail, we hang
}
@Override
@Test(enabled = false)
public void testPostResults() {
// see above
}
protected Module createClientModule() {
return new NioTransformingHttpCommandExecutorServiceModule();
}