internal enhancements for wrapping json, and specifying an endpoint per delegate

This commit is contained in:
Adrian Cole 2011-05-09 00:19:57 -07:00
parent 4d2520f910
commit 19672407e7
24 changed files with 930 additions and 282 deletions

View File

@ -22,9 +22,11 @@ import static com.google.common.base.Preconditions.checkArgument;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.http.HttpRequest;
import org.jclouds.json.Json;
import org.jclouds.cloudservers.domain.BackupSchedule;
import org.jclouds.rest.binders.BindToJsonPayload;
@ -37,17 +39,19 @@ import com.google.common.collect.ImmutableMap;
*/
@Singleton
public class BindBackupScheduleToJsonPayload extends BindToJsonPayload {
@Inject
public BindBackupScheduleToJsonPayload(Json jsonBinder) {
super(jsonBinder);
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
throw new IllegalStateException(
"Replace Backup Schedule needs an BackupSchedule object, not a Map");
throw new IllegalStateException("Replace Backup Schedule needs an BackupSchedule object, not a Map");
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object toBind) {
checkArgument(toBind instanceof BackupSchedule,
"this binder is only valid for BackupSchedules!");
checkArgument(toBind instanceof BackupSchedule, "this binder is only valid for BackupSchedules!");
return super.bindToRequest(request, ImmutableMap.of("backupSchedule", toBind));
}
}

View File

@ -26,9 +26,12 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import org.jclouds.cloudservers.domain.Addresses;
import org.jclouds.encryption.internal.Base64;
import org.jclouds.http.HttpRequest;
import org.jclouds.cloudservers.domain.Addresses;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap;
@ -40,7 +43,9 @@ import com.google.common.collect.Maps;
* @author Adrian Cole
*
*/
public class CreateServerOptions extends BindToJsonPayload {
public class CreateServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
static class File {
private final String path;
@ -50,11 +55,9 @@ public class CreateServerOptions extends BindToJsonPayload {
this.path = checkNotNull(path, "path");
this.contents = Base64.encodeBytes(checkNotNull(contents, "contents"));
checkArgument(path.getBytes().length < 255, String.format(
"maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path
.getBytes().length));
"maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path.getBytes().length));
checkArgument(contents.length < 10 * 1024, String.format(
"maximum size of the file is 10KB. Contents specified is %d bytes",
contents.length));
"maximum size of the file is 10KB. Contents specified is %d bytes", contents.length));
}
public String getContents() {
@ -92,10 +95,9 @@ public class CreateServerOptions extends BindToJsonPayload {
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"),
"name parameter not present"), Integer.parseInt(checkNotNull(postParams
.get("imageId"), "imageId parameter not present")), Integer.parseInt(checkNotNull(
postParams.get("flavorId"), "flavorId parameter not present")));
ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present"),
Integer.parseInt(checkNotNull(postParams.get("imageId"), "imageId parameter not present")), Integer
.parseInt(checkNotNull(postParams.get("flavorId"), "flavorId parameter not present")));
if (metadata.size() > 0)
server.metadata = metadata;
if (files.size() > 0)
@ -162,19 +164,15 @@ public class CreateServerOptions extends BindToJsonPayload {
*/
public CreateServerOptions withMetadata(Map<String, String> metadata) {
checkNotNull(metadata, "metadata");
checkArgument(metadata.size() <= 5,
"you cannot have more then 5 metadata values. You specified: " + metadata.size());
checkArgument(metadata.size() <= 5, "you cannot have more then 5 metadata values. You specified: "
+ metadata.size());
for (Entry<String, String> entry : metadata.entrySet()) {
checkArgument(entry.getKey().getBytes().length < 255, String.format(
"maximum length of metadata key is 255 bytes. Key specified %s is %d bytes",
entry.getKey(), entry.getKey().getBytes().length));
checkArgument(
entry.getKey().getBytes().length < 255,
String
.format(
"maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes",
entry.getKey(), entry.getValue(),
entry.getValue().getBytes().length));
"maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", entry.getKey(), entry
.getKey().getBytes().length));
checkArgument(entry.getKey().getBytes().length < 255, String.format(
"maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", entry
.getKey(), entry.getValue(), entry.getValue().getBytes().length));
}
this.metadata = metadata;
return this;
@ -196,8 +194,7 @@ public class CreateServerOptions extends BindToJsonPayload {
* sharedIpGroupId is also supplied.
*/
public CreateServerOptions withSharedIp(String publicIp) {
checkState(sharedIpGroupId != null,
"sharedIp is invalid unless a shared ip group is specified.");
checkState(sharedIpGroupId != null, "sharedIp is invalid unless a shared ip group is specified.");
this.publicIp = checkNotNull(publicIp, "ip");
return this;
}
@ -237,4 +234,9 @@ public class CreateServerOptions extends BindToJsonPayload {
}
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
return jsonBinder.bindToRequest(request, input);
}
}

View File

@ -24,8 +24,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap;
@ -36,7 +38,10 @@ import com.google.common.collect.ImmutableMap;
* @author Adrian Cole
*
*/
public class CreateSharedIpGroupOptions extends BindToJsonPayload {
public class CreateSharedIpGroupOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
Integer serverId;
@SuppressWarnings("unused")
@ -53,9 +58,8 @@ public class CreateSharedIpGroupOptions extends BindToJsonPayload {
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
SharedIpGroupRequest createRequest = new SharedIpGroupRequest(checkNotNull(postParams
.get("name")), serverId);
return super.bindToRequest(request, ImmutableMap.of("sharedIpGroup", createRequest));
SharedIpGroupRequest createRequest = new SharedIpGroupRequest(checkNotNull(postParams.get("name")), serverId);
return jsonBinder.bindToRequest(request, ImmutableMap.of("sharedIpGroup", createRequest));
}
@Override
@ -84,4 +88,5 @@ public class CreateSharedIpGroupOptions extends BindToJsonPayload {
return options.withServer(id);
}
}
}

View File

@ -22,7 +22,10 @@ import static com.google.common.base.Preconditions.checkArgument;
import java.util.Map;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap;
@ -34,7 +37,9 @@ import com.google.common.collect.Maps;
* @author Adrian Cole
*
*/
public class RebuildServerOptions extends BindToJsonPayload {
public class RebuildServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
Integer imageId;
@Override
@ -42,7 +47,7 @@ public class RebuildServerOptions extends BindToJsonPayload {
Map<String, Integer> image = Maps.newHashMap();
if (imageId != null)
image.put("imageId", imageId);
return super.bindToRequest(request, ImmutableMap.of("rebuild", image));
return jsonBinder.bindToRequest(request, ImmutableMap.of("rebuild", image));
}
@Override

View File

@ -25,6 +25,9 @@ import static org.easymock.classextension.EasyMock.verify;
import java.net.URI;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
@ -32,7 +35,12 @@ import org.jclouds.rest.BaseRestClientTest.MockModule;
import org.jclouds.rest.config.RestModule;
import org.testng.annotations.Test;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provides;
/**
* Tests behavior of {@code DeltacloudRedirectionRetry}
@ -41,6 +49,19 @@ import com.google.inject.Guice;
*/
@Test(groups = "unit")
public class DeltacloudRedirectionRetryHandlerTest {
Injector injector = Guice.createInjector(new MockModule(), new RestModule(), new AbstractModule() {
@SuppressWarnings("unused")
@Provides
@Singleton
@Named("CONSTANTS")
protected Multimap<String, String> constants() {
return LinkedHashMultimap.create();
}
@Override
protected void configure() {
}
});
@Test
public void test302DoesNotRetryOnDelete() {
@ -53,7 +74,7 @@ public class DeltacloudRedirectionRetryHandlerTest {
replay(command);
DeltacloudRedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
DeltacloudRedirectionRetryHandler retry = injector.getInstance(
DeltacloudRedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response);
@ -74,7 +95,7 @@ public class DeltacloudRedirectionRetryHandlerTest {
replay(command);
DeltacloudRedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
DeltacloudRedirectionRetryHandler retry = injector.getInstance(
DeltacloudRedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response);

View File

@ -26,8 +26,11 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import org.jclouds.encryption.internal.Base64;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap;
@ -39,7 +42,9 @@ import com.google.common.collect.Maps;
* @author Adrian Cole
*
*/
public class CreateServerOptions extends BindToJsonPayload {
public class CreateServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
static class File {
private final String path;
@ -49,11 +54,9 @@ public class CreateServerOptions extends BindToJsonPayload {
this.path = checkNotNull(path, "path");
this.contents = Base64.encodeBytes(checkNotNull(contents, "contents"));
checkArgument(path.getBytes().length < 255, String.format(
"maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path
.getBytes().length));
"maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path.getBytes().length));
checkArgument(contents.length < 10 * 1024, String.format(
"maximum size of the file is 10KB. Contents specified is %d bytes",
contents.length));
"maximum size of the file is 10KB. Contents specified is %d bytes", contents.length));
}
public String getContents() {
@ -87,10 +90,9 @@ public class CreateServerOptions extends BindToJsonPayload {
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"),
"name parameter not present"), checkNotNull(postParams
.get("imageRef"), "imageRef parameter not present"), checkNotNull(
postParams.get("flavorRef"), "flavorRef parameter not present"));
ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present"),
checkNotNull(postParams.get("imageRef"), "imageRef parameter not present"), checkNotNull(postParams
.get("flavorRef"), "flavorRef parameter not present"));
if (metadata.size() > 0)
server.metadata = metadata;
if (files.size() > 0)
@ -128,19 +130,15 @@ public class CreateServerOptions extends BindToJsonPayload {
*/
public CreateServerOptions withMetadata(Map<String, String> metadata) {
checkNotNull(metadata, "metadata");
checkArgument(metadata.size() <= 5,
"you cannot have more then 5 metadata values. You specified: " + metadata.size());
checkArgument(metadata.size() <= 5, "you cannot have more then 5 metadata values. You specified: "
+ metadata.size());
for (Entry<String, String> entry : metadata.entrySet()) {
checkArgument(entry.getKey().getBytes().length < 255, String.format(
"maximum length of metadata key is 255 bytes. Key specified %s is %d bytes",
entry.getKey(), entry.getKey().getBytes().length));
checkArgument(
entry.getKey().getBytes().length < 255,
String
.format(
"maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes",
entry.getKey(), entry.getValue(),
entry.getValue().getBytes().length));
"maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", entry.getKey(), entry
.getKey().getBytes().length));
checkArgument(entry.getKey().getBytes().length < 255, String.format(
"maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", entry
.getKey(), entry.getValue(), entry.getValue().getBytes().length));
}
this.metadata = metadata;
return this;
@ -164,4 +162,9 @@ public class CreateServerOptions extends BindToJsonPayload {
return options.withMetadata(metadata);
}
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
return jsonBinder.bindToRequest(request, input);
}
}

View File

@ -23,7 +23,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap;
@ -35,7 +38,9 @@ import com.google.common.collect.Maps;
* @author Adrian Cole
*
*/
public class RebuildServerOptions extends BindToJsonPayload {
public class RebuildServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
String imageRef;
@Override
@ -43,7 +48,7 @@ public class RebuildServerOptions extends BindToJsonPayload {
Map<String, String> image = Maps.newHashMap();
if (imageRef != null)
image.put("imageRef", imageRef);
return super.bindToRequest(request, ImmutableMap.of("rebuild", image));
return jsonBinder.bindToRequest(request, ImmutableMap.of("rebuild", image));
}
@Override
@ -52,7 +57,8 @@ public class RebuildServerOptions extends BindToJsonPayload {
}
/**
* @param ref - reference of the image to rebuild the server with.
* @param ref
* - reference of the image to rebuild the server with.
*/
public RebuildServerOptions withImage(String ref) {
checkNotNull(ref, "image reference should not be null");

View File

@ -24,14 +24,17 @@ import static org.jclouds.location.reference.LocationConstants.PROPERTY_REGIONS;
import java.net.URI;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.location.Region;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
@ -48,10 +51,12 @@ import com.google.inject.name.Names;
public class ProvideRegionToURIViaProperties implements javax.inject.Provider<Map<String, URI>> {
private final Injector injector;
private final Multimap<String, String> constants;
@Inject
ProvideRegionToURIViaProperties(Injector injector) {
protected ProvideRegionToURIViaProperties(Injector injector, @Named("CONSTANTS") Multimap<String, String> constants) {
this.injector = injector;
this.constants = constants;
}
@Singleton
@ -62,8 +67,13 @@ public class ProvideRegionToURIViaProperties implements javax.inject.Provider<Ma
String regionString = injector.getInstance(Key.get(String.class, Names.named(PROPERTY_REGIONS)));
Builder<String, URI> regions = ImmutableMap.<String, URI> builder();
for (String region : Splitter.on(',').split(regionString)) {
regions.put(region, URI.create(injector.getInstance(Key.get(String.class, Names.named(PROPERTY_REGION + "."
+ region + "." + ENDPOINT)))));
String regionUri = injector.getInstance(Key.get(String.class, Names.named(PROPERTY_REGION + "." + region
+ "." + ENDPOINT)));
for (Entry<String, String> entry : constants.entries()) {
regionUri = regionUri.replace(new StringBuilder().append('{').append(entry.getKey()).append('}').toString(), entry
.getValue());
}
regions.put(region, URI.create(regionUri));
}
return regions.build();
} catch (ConfigurationException e) {

View File

@ -62,7 +62,6 @@ public class RegionToEndpointOrProviderIfNull implements Function<Object, URI> {
+ ", but only the default location " + defaultProvider + " is configured");
checkArgument(from.equals(defaultProvider) || (regionToEndpoint != null && regionToEndpoint.containsKey(from)),
"requested location %s, which is not in the configured locations: %s", from, regionToEndpoint);
return regionToEndpoint.get(from);
}
}

View File

@ -43,8 +43,11 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.concurrent.MoreExecutors;
import org.jclouds.concurrent.SingleThreaded;
@ -69,12 +72,16 @@ import org.jclouds.rest.internal.RestContextImpl;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
/**
@ -99,6 +106,17 @@ public class RestContextBuilder<S, A> {
this.properties = checkNotNull(properties, "properties");
}
@Provides
@Singleton
@Named("CONSTANTS")
protected Multimap<String, String> constants() {
ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.<String, String> builder();
for (Entry<Object, Object> entry : properties.entrySet())
if (entry.getValue() != null)
builder.put(entry.getKey().toString(), entry.getValue().toString());
return LinkedHashMultimap.create(builder.build());
}
@Override
protected void configure() {
Properties toBind = new Properties();

View File

@ -0,0 +1,42 @@
/**
*
* Copyright (C) 2011 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.rest.annotations;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Wraps the payload in json nested one level deep, relating to the value parameter.
*
* ex. "bar" becomes { "foo" :"bar" }
*
* @author Adrian Cole
*/
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface WrapWith {
/**
* what to wrap the value in
*/
String value();
}

View File

@ -18,7 +18,7 @@
*/
package org.jclouds.rest.binders;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
@ -36,8 +36,12 @@ import org.jclouds.rest.MapBinder;
*/
public class BindToJsonPayload implements MapBinder {
protected final Json jsonBinder;
@Inject
protected Json jsonBinder;
public BindToJsonPayload(Json jsonBinder) {
this.jsonBinder = checkNotNull(jsonBinder, "jsonBinder");
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
@ -46,8 +50,7 @@ public class BindToJsonPayload implements MapBinder {
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object payload) {
checkState(jsonBinder != null, "Program error: json should have been injected at this point");
String json = jsonBinder.toJson(payload);
String json = jsonBinder.toJson(checkNotNull(payload, "payload"));
request.setPayload(json);
request.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_JSON);
return request;

View File

@ -0,0 +1,56 @@
/**
*
* Copyright (C) 2011 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.rest.binders;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.Binder;
import com.google.common.collect.ImmutableMap;
import com.google.inject.assistedinject.Assisted;
/**
* Sometimes, cloud apis wrap requests inside an envelope. This addresses this.
*
* @author Adrian Cole
*/
public class BindToJsonPayloadWrappedWith implements Binder {
public static interface Factory {
BindToJsonPayloadWrappedWith create(String envelope);
}
private final BindToJsonPayload jsonBinder;
private final String envelope;
@Inject
BindToJsonPayloadWrappedWith(BindToJsonPayload jsonBinder, @Assisted String envelope) {
this.jsonBinder = checkNotNull(jsonBinder, "jsonBinder");
this.envelope = checkNotNull(envelope, "envelope");
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object payload) {
return jsonBinder.bindToRequest(request, (Object) ImmutableMap.of(envelope, checkNotNull(payload, "payload")));
}
}

View File

@ -36,8 +36,10 @@ import org.jclouds.json.config.GsonModule;
import org.jclouds.rest.AsyncClientFactory;
import org.jclouds.rest.HttpAsyncClient;
import org.jclouds.rest.HttpClient;
import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
import org.jclouds.rest.internal.AsyncRestClientProxy;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.rest.internal.SeedAnnotationCache;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
@ -49,6 +51,7 @@ import com.google.inject.Key;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.name.Names;
import com.google.inject.util.Types;
import com.sun.jersey.api.uri.UriBuilderImpl;
@ -63,12 +66,19 @@ public class RestModule extends AbstractModule {
protected void configure() {
install(new SaxParserModule());
install(new GsonModule());
install(new FactoryModuleBuilder().build(BindToJsonPayloadWrappedWith.Factory.class));
bind(IdentityFunction.class).toInstance(IdentityFunction.INSTANCE);
bind(UriBuilder.class).to(UriBuilderImpl.class);
bind(AsyncRestClientProxy.Factory.class).to(Factory.class).in(Scopes.SINGLETON);
BinderUtils.bindAsyncClient(binder(), HttpAsyncClient.class);
BinderUtils.bindClient(binder(), HttpClient.class, HttpAsyncClient.class,
ImmutableMap.<Class<?>, Class<?>> of(HttpClient.class, HttpAsyncClient.class));
BinderUtils.bindClient(binder(), HttpClient.class, HttpAsyncClient.class, ImmutableMap.<Class<?>, Class<?>> of(
HttpClient.class, HttpAsyncClient.class));
}
@Provides
@Singleton
protected ConcurrentMap<Class<?>, Boolean> seedAnnotationCache(SeedAnnotationCache seedAnnotationCache) {
return new MapMaker().makeComputingMap(seedAnnotationCache);
}
@Provides
@ -88,19 +98,19 @@ public class RestModule extends AbstractModule {
this.factory = factory;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings( { "unchecked", "rawtypes" })
@Override
public Object apply(final ClassMethodArgs from) {
Class clazz = from.getAsyncClass();
TypeLiteral typeLiteral = TypeLiteral.get(clazz);
RestAnnotationProcessor util = (RestAnnotationProcessor) injector.getInstance(Key.get(TypeLiteral.get(Types
.newParameterizedType(RestAnnotationProcessor.class, clazz))));
.newParameterizedType(RestAnnotationProcessor.class, clazz))));
// cannot use child injectors due to the super coarse guice lock on
// Singleton
util.setCaller(from);
ConcurrentMap<ClassMethodArgs, Object> delegateMap = injector.getInstance(Key.get(
new TypeLiteral<ConcurrentMap<ClassMethodArgs, Object>>() {
}, Names.named("async")));
new TypeLiteral<ConcurrentMap<ClassMethodArgs, Object>>() {
}, Names.named("async")));
AsyncRestClientProxy proxy = new AsyncRestClientProxy(injector, factory, util, typeLiteral, delegateMap);
injector.injectMembers(proxy);
return AsyncClientFactory.create(clazz, proxy);
@ -111,10 +121,9 @@ public class RestModule extends AbstractModule {
@Inject
private TransformingHttpCommandExecutorService executorService;
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings( { "unchecked", "rawtypes" })
@Override
public TransformingHttpCommand<?> create(HttpRequest request,
Function<HttpResponse, ?> transformer) {
public TransformingHttpCommand<?> create(HttpRequest request, Function<HttpResponse, ?> transformer) {
return new TransformingHttpCommandImpl(executorService, request, transformer);
}

View File

@ -23,13 +23,9 @@ import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Maps.filterValues;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.newLinkedHashMap;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.newHashSet;
import static com.google.common.collect.Sets.newTreeSet;
import static java.util.Arrays.asList;
import static javax.ws.rs.core.HttpHeaders.ACCEPT;
@ -48,12 +44,12 @@ import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import javax.annotation.Resource;
@ -80,7 +76,6 @@ import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
import org.jclouds.http.functions.ReleasePayloadAndReturn;
import org.jclouds.http.functions.ReturnInputStream;
@ -89,6 +84,7 @@ import org.jclouds.http.functions.ReturnTrueIf2xx;
import org.jclouds.http.functions.UnwrapOnlyJsonValue;
import org.jclouds.http.functions.UnwrapOnlyNestedJsonValue;
import org.jclouds.http.functions.UnwrapOnlyNestedJsonValueInSet;
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.http.utils.ModifyRequest;
import org.jclouds.internal.ClassMethodArgs;
@ -104,7 +100,6 @@ import org.jclouds.rest.Binder;
import org.jclouds.rest.InputParamValidator;
import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.EndpointParam;
import org.jclouds.rest.annotations.ExceptionParser;
@ -123,30 +118,33 @@ import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SkipEncoding;
import org.jclouds.rest.annotations.Unwrap;
import org.jclouds.rest.annotations.VirtualHost;
import org.jclouds.rest.annotations.WrapWith;
import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.rest.binders.BindMapToStringPayload;
import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions;
import org.jclouds.util.Maps2;
import org.jclouds.util.Strings2;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.google.inject.util.Types;
/**
@ -161,7 +159,9 @@ public class RestAnnotationProcessor<T> {
private final Class<T> declaring;
static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToDecoratorParamAnnotation = createMethodToIndexOfParamToAnnotation(BinderParam.class);
// TODO replace with Table object
static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToBinderParamAnnotation = createMethodToIndexOfParamToAnnotation(BinderParam.class);
static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToWrapWithAnnotation = createMethodToIndexOfParamToAnnotation(WrapWith.class);
static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToHeaderParamAnnotations = createMethodToIndexOfParamToAnnotation(HeaderParam.class);
static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointAnnotations = createMethodToIndexOfParamToAnnotation(Endpoint.class);
static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointParamAnnotations = createMethodToIndexOfParamToAnnotation(EndpointParam.class);
@ -175,7 +175,7 @@ public class RestAnnotationProcessor<T> {
static final Map<MethodKey, Method> delegationMap = newHashMap();
static Map<Method, Map<Integer, Set<Annotation>>> createMethodToIndexOfParamToAnnotation(
final Class<? extends Annotation> annotation) {
final Class<? extends Annotation> annotation) {
return new MapMaker().makeComputingMap(new Function<Method, Map<Integer, Set<Annotation>>>() {
public Map<Integer, Set<Annotation>> apply(final Method method) {
return new MapMaker().makeComputingMap(new GetAnnotationsForMethodParameterIndex(method, annotation));
@ -193,23 +193,18 @@ public class RestAnnotationProcessor<T> {
}
public Set<Annotation> apply(final Integer index) {
Set<Annotation> keys = new HashSet<Annotation>();
List<Annotation> parameterAnnotations = newArrayList(method.getParameterAnnotations()[index]);
Collection<Annotation> filtered = filter(parameterAnnotations, new Predicate<Annotation>() {
public boolean apply(Annotation input) {
return input.annotationType().equals(clazz);
}
});
for (Annotation annotation : filtered) {
keys.add(annotation);
}
return keys;
return ImmutableSet.<Annotation> copyOf(filter(ImmutableList.copyOf(method.getParameterAnnotations()[index]),
new Predicate<Annotation>() {
public boolean apply(Annotation input) {
return input.annotationType().equals(clazz);
}
}));
}
}
private static final Class<? extends HttpRequestOptions[]> optionsVarArgsClass = new HttpRequestOptions[] {}
.getClass();
.getClass();
private static final Function<? super Entry<String, String>, ? extends Part> ENTRY_TO_PART = new Function<Entry<String, String>, Part>() {
@ -220,24 +215,24 @@ public class RestAnnotationProcessor<T> {
};
private final Map<Method, Set<Integer>> methodToIndexesOfOptions = new MapMaker()
.makeComputingMap(new Function<Method, Set<Integer>>() {
public Set<Integer> apply(final Method method) {
Set<Integer> toReturn = newHashSet();
for (int index = 0; index < method.getParameterTypes().length; index++) {
Class<?> type = method.getParameterTypes()[index];
if (HttpRequestOptions.class.isAssignableFrom(type) || optionsVarArgsClass.isAssignableFrom(type))
toReturn.add(index);
static final Map<Method, Set<Integer>> methodToIndexesOfOptions = new MapMaker()
.makeComputingMap(new Function<Method, Set<Integer>>() {
public Set<Integer> apply(final Method method) {
Builder<Integer> toReturn = ImmutableSet.<Integer> builder();
for (int index = 0; index < method.getParameterTypes().length; index++) {
Class<?> type = method.getParameterTypes()[index];
if (HttpRequestOptions.class.isAssignableFrom(type) || optionsVarArgsClass.isAssignableFrom(type))
toReturn.add(index);
}
return toReturn.build();
}
return toReturn;
}
});
});
private final ParseSax.Factory parserFactory;
private final HttpUtils utils;
private final Provider<UriBuilder> uriBuilderProvider;
private final ConcurrentMap<Class<?>, Boolean> seedAnnotationCache;
private final String apiVersion;
private char[] skips;
@Inject
@ -250,7 +245,7 @@ public class RestAnnotationProcessor<T> {
@VisibleForTesting
public static Function<HttpResponse, ?> createResponseParser(ParseSax.Factory parserFactory, Injector injector,
Method method, HttpRequest request) {
Method method, HttpRequest request) {
Function<HttpResponse, ?> transformer;
Class<? extends HandlerWithResult<?>> handler = getSaxResponseParserClassOrNull(method);
if (handler != null) {
@ -271,7 +266,7 @@ public class RestAnnotationProcessor<T> {
@VisibleForTesting
public static Function<Exception, ?> createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(
Injector injector, Method method) {
Injector injector, Method method) {
ExceptionParser annotation = method.getAnnotation(ExceptionParser.class);
if (annotation != null) {
return injector.getInstance(annotation.value());
@ -281,60 +276,28 @@ public class RestAnnotationProcessor<T> {
@SuppressWarnings("unchecked")
@Inject
public RestAnnotationProcessor(Injector injector, ParseSax.Factory parserFactory, HttpUtils utils,
TypeLiteral<T> typeLiteral) {
public RestAnnotationProcessor(Injector injector, ConcurrentMap<Class<?>, Boolean> seedAnnotationCache,
@Named(Constants.PROPERTY_API_VERSION) String apiVersion, ParseSax.Factory parserFactory, HttpUtils utils,
TypeLiteral<T> typeLiteral) {
this.declaring = (Class<T>) typeLiteral.getRawType();
this.injector = injector;
this.parserFactory = parserFactory;
this.utils = utils;
this.uriBuilderProvider = injector.getProvider(UriBuilder.class);
seedCache(declaring);
this.seedAnnotationCache = seedAnnotationCache;
seedAnnotationCache.get(declaring);
if (declaring.isAnnotationPresent(SkipEncoding.class)) {
skips = declaring.getAnnotation(SkipEncoding.class).value();
} else {
skips = new char[] {};
}
this.apiVersion = injector.getInstance(Key.get(String.class, Names.named(Constants.PROPERTY_API_VERSION)));
this.apiVersion = apiVersion;
}
public Method getDelegateOrNull(Method in) {
return delegationMap.get(new MethodKey(in));
}
private void seedCache(Class<?> declaring) {
Set<Method> methods = newHashSet(declaring.getMethods());
methods = difference(methods, newHashSet(Object.class.getMethods()));
for (Method method : methods) {
if (isHttpMethod(method)) {
for (int index = 0; index < method.getParameterTypes().length; index++) {
methodToIndexOfParamToDecoratorParamAnnotation.get(method).get(index);
methodToIndexOfParamToHeaderParamAnnotations.get(method).get(index);
methodToIndexOfParamToMatrixParamAnnotations.get(method).get(index);
methodToIndexOfParamToFormParamAnnotations.get(method).get(index);
methodToIndexOfParamToQueryParamAnnotations.get(method).get(index);
methodToIndexOfParamToEndpointAnnotations.get(method).get(index);
methodToIndexOfParamToEndpointParamAnnotations.get(method).get(index);
methodToIndexOfParamToPathParamAnnotations.get(method).get(index);
methodToIndexOfParamToPostParamAnnotations.get(method).get(index);
methodToIndexOfParamToParamParserAnnotations.get(method).get(index);
methodToIndexOfParamToPartParamAnnotations.get(method).get(index);
methodToIndexesOfOptions.get(method);
}
delegationMap.put(new MethodKey(method), method);
} else if (isConstantDeclaration(method)) {
bindConstant(method);
} else if (!method.getDeclaringClass().equals(declaring)) {
logger.trace("skipping potentially overridden method %s", method);
} else if (method.isAnnotationPresent(Delegate.class)) {
logger.trace("skipping delegate method %s", method);
} else if (method.isAnnotationPresent(Provides.class)) {
logger.trace("skipping provider method %s", method);
} else {
logger.trace("Method is not annotated as either http or constant: %s", method);
}
}
}
public static class MethodKey {
@Override
@ -389,7 +352,7 @@ public class RestAnnotationProcessor<T> {
private URI callerEndpoint;
public void setCaller(ClassMethodArgs caller) {
seedCache(caller.getMethod().getDeclaringClass());
seedAnnotationCache.get(caller.getMethod().getDeclaringClass());
this.caller = caller;
try {
callerEndpoint = getEndpointFor(caller.getMethod(), caller.getArgs(), injector);
@ -400,7 +363,7 @@ public class RestAnnotationProcessor<T> {
public GeneratedHttpRequest<T> createRequest(Method method, Object... args) {
inputParamValidator.validateMethodParametersOrThrow(method, args);
ClassMethodArgs cma = logger.isTraceEnabled() ? new ClassMethodArgs(method.getDeclaringClass(), method, args)
: null;
: null;
URI endpoint = callerEndpoint;
try {
@ -488,7 +451,7 @@ public class RestAnnotationProcessor<T> {
requestBuilder.headers(filterOutContentHeaders(headers));
try {
requestBuilder.endpoint(builder.buildFromEncodedMap(convertUnsafe(tokenValues)));
requestBuilder.endpoint(builder.buildFromEncodedMap(Maps2.convertUnsafe(tokenValues)));
} catch (IllegalArgumentException e) {
throw new IllegalStateException(e);
} catch (UriBuilderException e) {
@ -560,14 +523,14 @@ public class RestAnnotationProcessor<T> {
}
public static URI replaceQuery(Provider<UriBuilder> uriBuilderProvider, URI in, String newQuery,
@Nullable Comparator<Entry<String, String>> sorter, char... skips) {
@Nullable Comparator<Entry<String, String>> sorter, char... skips) {
UriBuilder builder = uriBuilderProvider.get().uri(in);
builder.replaceQuery(ModifyRequest.makeQueryLine(ModifyRequest.parseQueryToMap(newQuery), sorter, skips));
return builder.build();
}
private Multimap<String, String> addMatrixParams(Collection<Entry<String, String>> tokenValues, Method method,
Object... args) {
Object... args) {
Multimap<String, String> matrixMap = LinkedListMultimap.create();
if (declaring.isAnnotationPresent(MatrixParams.class)) {
MatrixParams matrix = declaring.getAnnotation(MatrixParams.class);
@ -586,7 +549,7 @@ public class RestAnnotationProcessor<T> {
}
private Multimap<String, String> addFormParams(Collection<Entry<String, String>> tokenValues, Method method,
Object... args) {
Object... args) {
Multimap<String, String> formMap = LinkedListMultimap.create();
if (declaring.isAnnotationPresent(FormParams.class)) {
FormParams form = declaring.getAnnotation(FormParams.class);
@ -605,7 +568,7 @@ public class RestAnnotationProcessor<T> {
}
private Multimap<String, String> addQueryParams(Collection<Entry<String, String>> tokenValues, Method method,
Object... args) {
Object... args) {
Multimap<String, String> queryMap = LinkedListMultimap.create();
if (declaring.isAnnotationPresent(QueryParams.class)) {
QueryParams query = declaring.getAnnotation(QueryParams.class);
@ -624,7 +587,7 @@ public class RestAnnotationProcessor<T> {
}
private void addForm(Multimap<String, String> formParams, FormParams form,
Collection<Entry<String, String>> tokenValues) {
Collection<Entry<String, String>> tokenValues) {
for (int i = 0; i < form.keys().length; i++) {
if (form.values()[i].equals(FormParams.NULL)) {
formParams.removeAll(form.keys()[i]);
@ -636,7 +599,7 @@ public class RestAnnotationProcessor<T> {
}
private void addQuery(Multimap<String, String> queryParams, QueryParams query,
Collection<Entry<String, String>> tokenValues) {
Collection<Entry<String, String>> tokenValues) {
for (int i = 0; i < query.keys().length; i++) {
if (query.values()[i].equals(QueryParams.NULL)) {
queryParams.removeAll(query.keys()[i]);
@ -648,7 +611,7 @@ public class RestAnnotationProcessor<T> {
}
private void addMatrix(Multimap<String, String> matrixParams, MatrixParams matrix,
Collection<Entry<String, String>> tokenValues) {
Collection<Entry<String, String>> tokenValues) {
for (int i = 0; i < matrix.keys().length; i++) {
if (matrix.values()[i].equals(MatrixParams.NULL)) {
matrixParams.removeAll(matrix.keys()[i]);
@ -660,7 +623,7 @@ public class RestAnnotationProcessor<T> {
}
private void addMapPayload(Map<String, String> postParams, PayloadParams mapDefaults,
Collection<Entry<String, String>> tokenValues) {
Collection<Entry<String, String>> tokenValues) {
for (int i = 0; i < mapDefaults.keys().length; i++) {
if (mapDefaults.values()[i].equals(PayloadParams.NULL)) {
postParams.put(mapDefaults.keys()[i], null);
@ -695,7 +658,7 @@ public class RestAnnotationProcessor<T> {
@VisibleForTesting
public static URI getEndpointInParametersOrNull(Method method, final Object[] args, Injector injector) {
Map<Integer, Set<Annotation>> map = indexWithAtLeastOneAnnotation(method,
methodToIndexOfParamToEndpointParamAnnotations);
methodToIndexOfParamToEndpointParamAnnotations);
if (map.size() >= 1 && args.length > 0) {
EndpointParam firstAnnotation = (EndpointParam) get(get(map.values(), 0), 0);
Function<Object, URI> parser = injector.getInstance(firstAnnotation.parser());
@ -704,8 +667,8 @@ public class RestAnnotationProcessor<T> {
int index = map.keySet().iterator().next();
try {
URI returnVal = parser.apply(args[index]);
checkArgument(returnVal != null,
String.format("endpoint for [%s] not configured for %s", args[index], method));
checkArgument(returnVal != null, String.format("endpoint for [%s] not configured for %s", args[index],
method));
return returnVal;
} catch (NullPointerException e) {
throw new IllegalArgumentException(String.format("argument at index %d on method %s", index, method), e);
@ -722,12 +685,12 @@ public class RestAnnotationProcessor<T> {
});
try {
URI returnVal = parser.apply(argsToParse);
checkArgument(returnVal != null,
String.format("endpoint for [%s] not configured for %s", argsToParse, method));
checkArgument(returnVal != null, String.format("endpoint for [%s] not configured for %s", argsToParse,
method));
return returnVal;
} catch (NullPointerException e) {
throw new IllegalArgumentException(String.format("argument at indexes %s on method %s", map.keySet(),
method), e);
method), e);
}
}
}
@ -763,21 +726,21 @@ public class RestAnnotationProcessor<T> {
public static final TypeLiteral<ListenableFuture<HttpResponse>> futureHttpResponseLiteral = new TypeLiteral<ListenableFuture<HttpResponse>>() {
};
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings( { "unchecked", "rawtypes" })
public static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) {
ResponseParser annotation = method.getAnnotation(ResponseParser.class);
if (annotation == null) {
if (method.getReturnType().equals(void.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureVoidLiteral)) {
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureVoidLiteral)) {
return Key.get(ReleasePayloadAndReturn.class);
} else if (method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureBooleanLiteral)) {
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureBooleanLiteral)) {
return Key.get(ReturnTrueIf2xx.class);
} else if (method.getReturnType().equals(InputStream.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureInputStreamLiteral)) {
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureInputStreamLiteral)) {
return Key.get(ReturnInputStream.class);
} else if (method.getReturnType().equals(HttpResponse.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureHttpResponseLiteral)) {
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureHttpResponseLiteral)) {
return Key.get((Class) IdentityFunction.class);
} else if (getAcceptHeadersOrNull(method).contains(MediaType.APPLICATION_JSON)) {
Type returnVal;
@ -803,16 +766,16 @@ public class RestAnnotationProcessor<T> {
parserType = Types.newParameterizedType(UnwrapOnlyNestedJsonValueInSet.class, returnVal);
else
throw new IllegalStateException(String.format(
"depth(%d) edgeCollection(%s) not yet supported for @Unwrap", depth, edgeCollection));
"depth(%d) edgeCollection(%s) not yet supported for @Unwrap", depth, edgeCollection));
} else {
parserType = Types.newParameterizedType(ParseJson.class, returnVal);
}
return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType);
} else if (method.getReturnType().equals(String.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureStringLiteral)) {
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureStringLiteral)) {
return Key.get(ReturnStringIf2xx.class);
} else if (method.getReturnType().equals(URI.class)
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureURILiteral)) {
|| TypeLiteral.get(method.getGenericReturnType()).equals(futureURILiteral)) {
return Key.get(ParseURIFromListOrLocationHeaderIf20x.class);
} else {
throw new IllegalStateException("You must specify a ResponseParser annotation on: " + method.toString());
@ -844,7 +807,7 @@ public class RestAnnotationProcessor<T> {
} else {
if (postBinders[0] instanceof org.jclouds.rest.MapBinder) {
throw new IllegalArgumentException("we currently do not support multiple varargs postBinders in: "
+ method.getName());
+ method.getName());
}
}
} else if (arg instanceof org.jclouds.rest.MapBinder) {
@ -862,41 +825,23 @@ public class RestAnnotationProcessor<T> {
return null;
}
private Multimap<String, String> constants = LinkedHashMultimap.create();
public boolean isHttpMethod(Method method) {
return method.isAnnotationPresent(Path.class) || getHttpMethods(method) != null
|| Sets.newHashSet(method.getParameterTypes()).contains(HttpRequest.class);
}
public boolean isConstantDeclaration(Method method) {
return method.isAnnotationPresent(PathParam.class) && method.isAnnotationPresent(Named.class);
}
public void bindConstant(Method method) {
String key = method.getAnnotation(PathParam.class).value();
String value = injector.getInstance(Key.get(String.class, method.getAnnotation(Named.class)));
constants.put(key, value);
}
public static Set<String> getHttpMethods(Method method) {
HashSet<String> methods = new HashSet<String>();
Builder<String> methodsBuilder = ImmutableSet.<String> builder();
for (Annotation annotation : method.getAnnotations()) {
HttpMethod http = annotation.annotationType().getAnnotation(HttpMethod.class);
if (http != null)
methods.add(http.value());
methodsBuilder.add(http.value());
}
if (methods.size() == 0)
return null;
return methods;
Set<String> methods = methodsBuilder.build();
return (methods.size() == 0) ? null : methods;
}
public String getHttpMethodOrConstantOrThrowException(Method method) {
Set<String> requests = getHttpMethods(method);
if (requests == null || requests.size() != 1) {
throw new IllegalStateException(
"You must use at least one, but no more than one http method or pathparam annotation on: "
+ method.toString());
"You must use at least one, but no more than one http method or pathparam annotation on: "
+ method.toString());
}
return requests.iterator().next();
}
@ -908,17 +853,25 @@ public class RestAnnotationProcessor<T> {
return false;
}
private static final Predicate<Set<?>> notEmpty = new Predicate<Set<?>>() {
public boolean apply(Set<?> input) {
return input.size() >= 1;
}
};
public GeneratedHttpRequest<T> decorateRequest(GeneratedHttpRequest<T> request) {
OUTER: for (Entry<Integer, Set<Annotation>> entry : filterValues(
methodToIndexOfParamToDecoratorParamAnnotation.get(request.getJavaMethod()),
new Predicate<Set<Annotation>>() {
public boolean apply(Set<Annotation> input) {
return input.size() >= 1;
}
}).entrySet()) {
OUTER: for (Entry<Integer, Set<Annotation>> entry : concat(//
filterValues(methodToIndexOfParamToBinderParamAnnotation.get(request.getJavaMethod()), notEmpty)
.entrySet(), //
filterValues(methodToIndexOfParamToWrapWithAnnotation.get(request.getJavaMethod()), notEmpty).entrySet())) {
boolean shouldBreak = false;
BinderParam payloadAnnotation = (BinderParam) entry.getValue().iterator().next();
Binder binder = injector.getInstance(payloadAnnotation.value());
Annotation annotation = Iterables.get(entry.getValue(), 0);
Binder binder;
if (annotation instanceof BinderParam)
binder = injector.getInstance(BinderParam.class.cast(annotation).value());
else
binder = injector.getInstance(BindToJsonPayloadWrappedWith.Factory.class).create(
WrapWith.class.cast(annotation).value());
if (request.getArgs().size() >= entry.getKey() + 1 && request.getArgs().get(entry.getKey()) != null) {
Object input;
Class<?> parameterType = request.getJavaMethod().getParameterTypes()[entry.getKey()];
@ -951,24 +904,24 @@ public class RestAnnotationProcessor<T> {
}
public static Map<Integer, Set<Annotation>> indexWithOnlyOneAnnotation(Method method, String description,
Map<Method, Map<Integer, Set<Annotation>>> toRefine) {
Map<Method, Map<Integer, Set<Annotation>>> toRefine) {
Map<Integer, Set<Annotation>> indexToPayloadAnnotation = indexWithAtLeastOneAnnotation(method, toRefine);
if (indexToPayloadAnnotation.size() > 1) {
throw new IllegalStateException(String.format(
"You must not specify more than one %s annotation on: %s; found %s", description, method.toString(),
indexToPayloadAnnotation));
"You must not specify more than one %s annotation on: %s; found %s", description, method.toString(),
indexToPayloadAnnotation));
}
return indexToPayloadAnnotation;
}
private static Map<Integer, Set<Annotation>> indexWithAtLeastOneAnnotation(Method method,
Map<Method, Map<Integer, Set<Annotation>>> toRefine) {
Map<Method, Map<Integer, Set<Annotation>>> toRefine) {
Map<Integer, Set<Annotation>> indexToPayloadAnnotation = filterValues(toRefine.get(method),
new Predicate<Set<Annotation>>() {
public boolean apply(Set<Annotation> input) {
return input.size() == 1;
}
});
new Predicate<Set<Annotation>>() {
public boolean apply(Set<Annotation> input) {
return input.size() == 1;
}
});
return indexToPayloadAnnotation;
}
@ -987,7 +940,7 @@ public class RestAnnotationProcessor<T> {
} else {
if (options[0] instanceof HttpRequestOptions) {
throw new IllegalArgumentException("we currently do not support multiple varargs options in: "
+ method.getName());
+ method.getName());
}
}
} else {
@ -999,7 +952,7 @@ public class RestAnnotationProcessor<T> {
}
public Multimap<String, String> buildHeaders(Collection<Entry<String, String>> tokenValues, Method method,
final Object... args) {
final Object... args) {
Multimap<String, String> headers = LinkedHashMultimap.create();
addHeaderIfAnnotationPresentOnMethod(headers, method, tokenValues);
Map<Integer, Set<Annotation>> indexToHeaderParam = methodToIndexOfParamToHeaderParamAnnotations.get(method);
@ -1046,7 +999,7 @@ public class RestAnnotationProcessor<T> {
}
public void addHeaderIfAnnotationPresentOnMethod(Multimap<String, String> headers, Method method,
Collection<Entry<String, String>> tokenValues) {
Collection<Entry<String, String>> tokenValues) {
if (declaring.isAnnotationPresent(Headers.class)) {
Headers header = declaring.getAnnotation(Headers.class);
addHeader(headers, header, tokenValues);
@ -1058,7 +1011,7 @@ public class RestAnnotationProcessor<T> {
}
private void addHeader(Multimap<String, String> headers, Headers header,
Collection<Entry<String, String>> tokenValues) {
Collection<Entry<String, String>> tokenValues) {
for (int i = 0; i < header.keys().length; i++) {
String value = header.values()[i];
value = Strings2.replaceTokens(value, tokenValues);
@ -1067,14 +1020,6 @@ public class RestAnnotationProcessor<T> {
}
private Map<String, String> convertUnsafe(Multimap<String, String> in) {
Map<String, String> out = newLinkedHashMap();
for (Entry<String, String> entry : in.entries()) {
out.put(entry.getKey(), entry.getValue());
}
return out;
}
List<? extends Part> getParts(Method method, Object[] args, Iterable<Entry<String, String>> iterable) {
List<Part> parts = newLinkedList();
Map<Integer, Set<Annotation>> indexToPartParam = methodToIndexOfParamToPartParamAnnotations.get(method);
@ -1115,7 +1060,6 @@ public class RestAnnotationProcessor<T> {
private Multimap<String, String> getPathParamKeyValues(Method method, Object... args) {
Multimap<String, String> pathParamValues = LinkedHashMultimap.create();
pathParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
@ -1153,7 +1097,6 @@ public class RestAnnotationProcessor<T> {
private Multimap<String, String> getMatrixParamKeyValues(Method method, Object... args) {
Multimap<String, String> matrixParamValues = LinkedHashMultimap.create();
matrixParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
@ -1183,7 +1126,6 @@ public class RestAnnotationProcessor<T> {
private Multimap<String, String> getFormParamKeyValues(Method method, Object... args) {
Multimap<String, String> formParamValues = LinkedHashMultimap.create();
formParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
@ -1213,7 +1155,6 @@ public class RestAnnotationProcessor<T> {
private Multimap<String, String> getQueryParamKeyValues(Method method, Object... args) {
Multimap<String, String> queryParamValues = LinkedHashMultimap.create();
queryParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);

View File

@ -0,0 +1,126 @@
/**
*
* Copyright (C) 2011 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.rest.internal;
import static com.google.common.collect.Sets.difference;
import static org.jclouds.rest.internal.RestAnnotationProcessor.delegationMap;
import static org.jclouds.rest.internal.RestAnnotationProcessor.getHttpMethods;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToBinderParamAnnotation;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToEndpointAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToEndpointParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToFormParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToHeaderParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToMatrixParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToParamParserAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToPartParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToPathParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToPostParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToQueryParamAnnotations;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexOfParamToWrapWithAnnotation;
import static org.jclouds.rest.internal.RestAnnotationProcessor.methodToIndexesOfOptions;
import java.lang.reflect.Method;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import org.jclouds.http.HttpRequest;
import org.jclouds.logging.Logger;
import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.internal.RestAnnotationProcessor.MethodKey;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provides;
/**
* seeds the annotation cache
*
* @author Adrian Cole
*/
@Singleton
public class SeedAnnotationCache implements Function<Class<?>, Boolean> {
@Resource
protected Logger logger = Logger.NULL;
protected final Multimap<String, String> constants;
protected final Injector injector;
@Inject
public SeedAnnotationCache(Injector injector, @Named("CONSTANTS") Multimap<String, String> constants) {
this.injector = injector;
this.constants = constants;
}
public void bindConstant(Method method) {
String key = method.getAnnotation(PathParam.class).value();
String value = injector.getInstance(Key.get(String.class, method.getAnnotation(Named.class)));
constants.put(key, value);
}
public Boolean apply(Class<?> declaring) {
for (Method method : difference(ImmutableSet.copyOf(declaring.getMethods()), ImmutableSet.copyOf(Object.class
.getMethods()))) {
if (isHttpMethod(method) || method.isAnnotationPresent(Delegate.class)) {
for (int index = 0; index < method.getParameterTypes().length; index++) {
methodToIndexOfParamToBinderParamAnnotation.get(method).get(index);
methodToIndexOfParamToWrapWithAnnotation.get(method).get(index);
methodToIndexOfParamToHeaderParamAnnotations.get(method).get(index);
methodToIndexOfParamToMatrixParamAnnotations.get(method).get(index);
methodToIndexOfParamToFormParamAnnotations.get(method).get(index);
methodToIndexOfParamToQueryParamAnnotations.get(method).get(index);
methodToIndexOfParamToEndpointAnnotations.get(method).get(index);
methodToIndexOfParamToEndpointParamAnnotations.get(method).get(index);
methodToIndexOfParamToPathParamAnnotations.get(method).get(index);
methodToIndexOfParamToPostParamAnnotations.get(method).get(index);
methodToIndexOfParamToParamParserAnnotations.get(method).get(index);
methodToIndexOfParamToPartParamAnnotations.get(method).get(index);
methodToIndexesOfOptions.get(method);
}
delegationMap.put(new MethodKey(method), method);
} else if (isConstantDeclaration(method)) {
bindConstant(method);
} else if (!method.getDeclaringClass().equals(declaring)) {
logger.trace("skipping potentially overridden method %s", method);
} else if (method.isAnnotationPresent(Provides.class)) {
logger.trace("skipping provider method %s", method);
} else {
logger.trace("Method is not annotated as either http or constant: %s", method);
}
}
return true;
}
public static boolean isHttpMethod(Method method) {
return method.isAnnotationPresent(Path.class) || getHttpMethods(method) != null
|| ImmutableSet.copyOf(method.getParameterTypes()).contains(HttpRequest.class);
}
public static boolean isConstantDeclaration(Method method) {
return method.isAnnotationPresent(PathParam.class) && method.isAnnotationPresent(Named.class);
}
}

View File

@ -23,14 +23,16 @@ import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Maps.filterKeys;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.ImmutableMap.Builder;
/**
* General utilities used in jclouds code for {@link Map}s.
@ -39,9 +41,17 @@ import com.google.common.collect.Maps;
*/
public class Maps2 {
public static <K, V> Map<K, V> convertUnsafe(Multimap<K, V> in) {
LinkedHashMap<K, V> out = Maps.newLinkedHashMap();
for (Entry<K, V> entry : in.entries()) {
out.put(entry.getKey(), entry.getValue());
}
return ImmutableMap.copyOf(out);
}
/**
* If the supplied map contains the key {@code k1}, its value will be assigned to the key
* {@code k2}. Note that this doesn't modify the input map.
* If the supplied map contains the key {@code k1}, its value will be assigned to the key {@code
* k2}. Note that this doesn't modify the input map.
*
* @param <V>
* type of value the map holds

View File

@ -0,0 +1,95 @@
/**
*
* Copyright (C) 2011 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.http.handlers;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.reportMatcher;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import java.net.URI;
import org.easymock.IArgumentMatcher;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.io.Payloads;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test;
import com.google.inject.Guice;
/**
*
* @author Adrian Cole
*/
@Test(groups = { "unit" })
public abstract class BaseHttpErrorHandlerTest<T extends HttpErrorHandler> {
abstract protected Class<T> getClassToTest();
protected void assertCodeMakes(String method, URI uri, int statusCode, String message, String content,
Class<? extends Exception> expected, String exceptionMessage) {
assertCodeMakes(method, uri, statusCode, message, "text/xml", content, expected, exceptionMessage);
}
private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType,
String content, Class<? extends Exception> expected, String exceptionMessage) {
T function = Guice.createInjector().getInstance(getClassToTest());
HttpCommand command = createMock(HttpCommand.class);
HttpRequest request = new HttpRequest(method, uri);
HttpResponse response = new HttpResponse(statusCode, message, Payloads.newInputStreamPayload(Strings2
.toInputStream(content)));
response.getPayload().getContentMetadata().setContentType(contentType);
expect(command.getCurrentRequest()).andReturn(request).atLeastOnce();
command.setException(exceptionEq(expected, exceptionMessage));
replay(command);
function.handleError(command, response);
verify(command);
}
public static Exception exceptionEq(final Class<? extends Exception> in, final String exceptionMessage) {
reportMatcher(new IArgumentMatcher() {
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("exceptionEq(");
buffer.append(in);
buffer.append(",");
buffer.append(exceptionMessage);
buffer.append(")");
}
@Override
public boolean matches(Object arg) {
return arg.getClass() == in && exceptionMessage.equals(Exception.class.cast(arg).getMessage());
}
});
return null;
}
}

View File

@ -25,6 +25,8 @@ import static org.easymock.classextension.EasyMock.verify;
import java.net.URI;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.http.HttpCommand;
@ -35,7 +37,12 @@ import org.jclouds.rest.config.RestModule;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provides;
/**
* Tests behavior of {@code RedirectionRetryHandler}
@ -44,6 +51,19 @@ import com.google.inject.Guice;
*/
@Test(groups = "unit")
public class RedirectionRetryHandlerTest {
Injector injector = Guice.createInjector(new MockModule(), new RestModule(), new AbstractModule() {
@SuppressWarnings("unused")
@Provides
@Singleton
@Named("CONSTANTS")
protected Multimap<String, String> constants() {
return LinkedHashMultimap.create();
}
@Override
protected void configure() {
}
});
@Test
public void test302DoesNotRetry() {
@ -55,8 +75,7 @@ public class RedirectionRetryHandlerTest {
replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
RedirectionRetryHandler.class);
RedirectionRetryHandler retry = injector.getInstance(RedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response);
@ -69,14 +88,13 @@ public class RedirectionRetryHandlerTest {
HttpCommand command = createMock(HttpCommand.class);
HttpResponse response = new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(
HttpHeaders.LOCATION, "/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645"));
HttpHeaders.LOCATION, "/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645"));
expect(command.incrementRedirectCount()).andReturn(5);
replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
RedirectionRetryHandler.class);
RedirectionRetryHandler retry = injector.getInstance(RedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response);
@ -88,66 +106,64 @@ public class RedirectionRetryHandlerTest {
public void test302WithPathOnlyHeader() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")),
new HttpRequest(
"GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")));
new HttpRequest("GET", URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")),
new HttpRequest(
"GET",
URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")));
}
@Test
public void test302ToHttps() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")));
verifyRedirectRoutes(new HttpRequest("GET", URI
.create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")));
}
@Test
public void test302ToDifferentPort() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"http://services.enterprisecloud.terremark.com:3030/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("http://services.enterprisecloud.terremark.com:3030/api/v0.8b-ext2.5/org/1906645")));
verifyRedirectRoutes(new HttpRequest("GET", URI
.create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"http://services.enterprisecloud.terremark.com:3030/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("http://services.enterprisecloud.terremark.com:3030/api/v0.8b-ext2.5/org/1906645")));
}
@Test
public void test302WithHeader() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")), new HttpRequest(
"GET", URI.create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")));
verifyRedirectRoutes(new HttpRequest("GET", URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
new HttpRequest("GET", URI
.create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")));
}
@Test
public void test302WithHeaderReplacesHostHeader() {
verifyRedirectRoutes(
new HttpRequest("GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
ImmutableMultimap.of(HttpHeaders.HOST, "services.enterprisecloud.terremark.com")),
new HttpResponse(302, "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
ImmutableMultimap.of(HttpHeaders.HOST, "services1.enterprisecloud.terremark.com")));
verifyRedirectRoutes(new HttpRequest("GET", URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
ImmutableMultimap.of(HttpHeaders.HOST, "services.enterprisecloud.terremark.com")), new HttpResponse(302,
"HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
"https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI
.create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
ImmutableMultimap.of(HttpHeaders.HOST, "services1.enterprisecloud.terremark.com")));
}
@ -160,8 +176,7 @@ public class RedirectionRetryHandlerTest {
replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance(
RedirectionRetryHandler.class);
RedirectionRetryHandler retry = injector.getInstance(RedirectionRetryHandler.class);
assert retry.shouldRetryRequest(command, response);
verify(command);

View File

@ -0,0 +1,28 @@
/**
*
* Copyright (C) 2011 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.json;
/**
*
* @author Adrian Cole
*/
public abstract class BaseItemParserTest<T> extends BaseParserTest<T, T> {
}

View File

@ -0,0 +1,81 @@
/**
*
* Copyright (C) 2011 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.json;
import static org.testng.Assert.assertEquals;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.UnwrapOnlyNestedJsonValue;
import org.jclouds.io.Payloads;
import org.jclouds.json.config.GsonModule;
import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Types;
/**
*
* @author Adrian Cole
*/
public abstract class BaseParserTest<T, G> {
@Test
public void test() {
T expects = expected();
Function<HttpResponse, T> parser = getParser(getInjector());
T response = parser.apply(new HttpResponse(200, "ok", Payloads.newInputStreamPayload(getClass()
.getResourceAsStream(resource()))));
compare(expects, response);
}
public void compare(T expects, T response) {
assertEquals(response.toString(), expects.toString());
}
protected Injector getInjector() {
return Guice.createInjector(new GsonModule() {
@Override
protected void configure() {
bind(DateAdapter.class).to(Iso8601DateAdapter.class);
super.configure();
}
});
}
@SuppressWarnings("unchecked")
protected Function<HttpResponse, T> getParser(Injector i) {
return (Function<HttpResponse, T>) i.getInstance(Key.get(TypeLiteral.get(
Types.newParameterizedType(UnwrapOnlyNestedJsonValue.class, type())).getType()));
}
public abstract Class<G> type();
public abstract String resource();
public abstract T expected();
}

View File

@ -0,0 +1,54 @@
/**
*
* Copyright (C) 2011 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.json;
import static org.testng.Assert.assertEquals;
import java.util.Set;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.UnwrapOnlyNestedJsonValue;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSortedSet;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Types;
/**
*
* @author Adrian Cole
*/
public abstract class BaseSetParserTest<T> extends BaseParserTest<Set<T>, T> {
@SuppressWarnings("unchecked")
// crazy stuff due to type erasure
@Override
protected Function<HttpResponse, Set<T>> getParser(Injector i) {
return (Function<HttpResponse, Set<T>>) i.getInstance(Key.get(TypeLiteral.get(
Types.newParameterizedType(UnwrapOnlyNestedJsonValue.class, Types
.newParameterizedType(Set.class, type()))).getType()));
}
public void compare(Set<T> expects, Set<T> response) {
assertEquals(ImmutableSortedSet.copyOf(response).toString(), ImmutableSortedSet.copyOf(expects).toString());
}
}

View File

@ -0,0 +1,73 @@
/**
*
* Copyright (C) 2011 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.rest.binders;
import static org.testng.Assert.assertEquals;
import java.net.URI;
import org.jclouds.http.HttpRequest;
import org.jclouds.json.config.GsonModule;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.assistedinject.FactoryModuleBuilder;
/**
* Tests behavior of {@code BindToJsonPayloadWrappedWith}
*
* @author Adrian Cole
*/
@Test(groups = "unit")
public class BindToJsonPayloadWrappedWithTest {
Injector injector = Guice.createInjector(new GsonModule(), new FactoryModuleBuilder()
.build(BindToJsonPayloadWrappedWith.Factory.class));
@Test
public void testCorrect() throws SecurityException, NoSuchMethodException {
BindToJsonPayloadWrappedWith binder = new BindToJsonPayloadWrappedWith(injector
.getInstance(BindToJsonPayload.class), "envelope");
HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://momma")).build();
request = binder.bindToRequest(request, ImmutableMap.of("imageName", "foo", "serverId", "2"));
assertEquals(request.getPayload().getRawContent(), "{\"envelope\":{\"imageName\":\"foo\",\"serverId\":\"2\"}}");
}
@Test
public void testFactoryCorrect() throws SecurityException, NoSuchMethodException {
BindToJsonPayloadWrappedWith binder = injector.getInstance(BindToJsonPayloadWrappedWith.Factory.class).create(
"envelope");
HttpRequest request = HttpRequest.builder().method("GET").endpoint(URI.create("http://momma")).build();
request = binder.bindToRequest(request, ImmutableMap.of("imageName", "foo", "serverId", "2"));
assertEquals(request.getPayload().getRawContent(), "{\"envelope\":{\"imageName\":\"foo\",\"serverId\":\"2\"}}");
}
@Test(expectedExceptions = NullPointerException.class)
public void testNullIsBad() {
BindToJsonPayloadWrappedWith binder = new BindToJsonPayloadWrappedWith(injector
.getInstance(BindToJsonPayload.class), "envelope");
binder.bindToRequest(HttpRequest.builder().method("GET").endpoint(URI.create("http://momma")).build(), null);
}
}

View File

@ -132,6 +132,7 @@ import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SkipEncoding;
import org.jclouds.rest.annotations.Unwrap;
import org.jclouds.rest.annotations.VirtualHost;
import org.jclouds.rest.annotations.WrapWith;
import org.jclouds.rest.binders.BindAsHostPrefix;
import org.jclouds.rest.binders.BindMapToMatrixParams;
import org.jclouds.rest.binders.BindToJsonPayload;
@ -200,6 +201,9 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
@Delegate
public Callee getCallee();
@Delegate
public Callee getCallee(@EndpointParam URI endpoint);
}
@Timeout(duration = 10, timeUnit = TimeUnit.NANOSECONDS)
@ -212,6 +216,9 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
@Delegate
public AsyncCallee getCallee();
@Delegate
public AsyncCallee getCallee(@EndpointParam URI endpoint);
}
@SuppressWarnings("unchecked")
@ -284,6 +291,31 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
}
public void testDelegateWithOverridingEndpointOnMethod() throws SecurityException, NoSuchMethodException,
InterruptedException, ExecutionException {
Injector child = injectorForClient();
TransformingHttpCommandExecutorService mock = child.getInstance(TransformingHttpCommandExecutorService.class);
ReleasePayloadAndReturn function = child.getInstance(ReleasePayloadAndReturn.class);
try {
child.getInstance(Callee.class);
assert false : "Callee shouldn't be bound yet";
} catch (ConfigurationException e) {
}
Caller caller = child.getInstance(Caller.class);
expect(mock.submit(requestLineEquals("GET http://howdyboys/client/1/foo HTTP/1.1"), eq(function)))
.andReturn(Futures.<Void> immediateFuture(null)).atLeastOnce();
replay(mock);
caller.getCallee(URI.create("http://howdyboys")).onePath("foo");
verify(mock);
}
private Injector injectorForClient() {
RestContextSpec<Caller, AsyncCaller> contextSpec = contextSpec("test", "http://localhost:9999", "1", "",
@ -768,6 +800,10 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
@Consumes(MediaType.APPLICATION_JSON)
String testUnwrap();
@POST
@Path("/")
String testWrapWith(@WrapWith("foo") String param);
@GET
@Path("/")
@Unwrap
@ -928,6 +964,12 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
}
public void testWrapWith() throws SecurityException, NoSuchMethodException, IOException {
Method method = TestPut.class.getMethod("testWrapWith", String.class);
HttpRequest request = factory(TestPut.class).createRequest(method, "bar");
assertPayloadEquals(request, "{\"foo\":\"bar\"}", "application/json", false);
}
@SuppressWarnings("unchecked")
public void testUnwrap2() throws SecurityException, NoSuchMethodException, IOException {
Method method = TestPut.class.getMethod("testUnwrap2");