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 java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.json.Json;
import org.jclouds.cloudservers.domain.BackupSchedule; import org.jclouds.cloudservers.domain.BackupSchedule;
import org.jclouds.rest.binders.BindToJsonPayload; import org.jclouds.rest.binders.BindToJsonPayload;
@ -37,17 +39,19 @@ import com.google.common.collect.ImmutableMap;
*/ */
@Singleton @Singleton
public class BindBackupScheduleToJsonPayload extends BindToJsonPayload { public class BindBackupScheduleToJsonPayload extends BindToJsonPayload {
@Inject
public BindBackupScheduleToJsonPayload(Json jsonBinder) {
super(jsonBinder);
}
@Override @Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) { public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
throw new IllegalStateException( throw new IllegalStateException("Replace Backup Schedule needs an BackupSchedule object, not a Map");
"Replace Backup Schedule needs an BackupSchedule object, not a Map");
} }
@Override @Override
public <R extends HttpRequest> R bindToRequest(R request, Object toBind) { public <R extends HttpRequest> R bindToRequest(R request, Object toBind) {
checkArgument(toBind instanceof BackupSchedule, checkArgument(toBind instanceof BackupSchedule, "this binder is only valid for BackupSchedules!");
"this binder is only valid for BackupSchedules!");
return super.bindToRequest(request, ImmutableMap.of("backupSchedule", toBind)); 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;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.inject.Inject;
import org.jclouds.cloudservers.domain.Addresses;
import org.jclouds.encryption.internal.Base64; import org.jclouds.encryption.internal.Base64;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.cloudservers.domain.Addresses; import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload; import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -40,7 +43,9 @@ import com.google.common.collect.Maps;
* @author Adrian Cole * @author Adrian Cole
* *
*/ */
public class CreateServerOptions extends BindToJsonPayload { public class CreateServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
static class File { static class File {
private final String path; private final String path;
@ -50,11 +55,9 @@ public class CreateServerOptions extends BindToJsonPayload {
this.path = checkNotNull(path, "path"); this.path = checkNotNull(path, "path");
this.contents = Base64.encodeBytes(checkNotNull(contents, "contents")); this.contents = Base64.encodeBytes(checkNotNull(contents, "contents"));
checkArgument(path.getBytes().length < 255, String.format( checkArgument(path.getBytes().length < 255, String.format(
"maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path "maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path.getBytes().length));
.getBytes().length));
checkArgument(contents.length < 10 * 1024, String.format( checkArgument(contents.length < 10 * 1024, String.format(
"maximum size of the file is 10KB. Contents specified is %d bytes", "maximum size of the file is 10KB. Contents specified is %d bytes", contents.length));
contents.length));
} }
public String getContents() { public String getContents() {
@ -92,10 +95,9 @@ public class CreateServerOptions extends BindToJsonPayload {
@Override @Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) { public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present"),
"name parameter not present"), Integer.parseInt(checkNotNull(postParams Integer.parseInt(checkNotNull(postParams.get("imageId"), "imageId parameter not present")), Integer
.get("imageId"), "imageId parameter not present")), Integer.parseInt(checkNotNull( .parseInt(checkNotNull(postParams.get("flavorId"), "flavorId parameter not present")));
postParams.get("flavorId"), "flavorId parameter not present")));
if (metadata.size() > 0) if (metadata.size() > 0)
server.metadata = metadata; server.metadata = metadata;
if (files.size() > 0) if (files.size() > 0)
@ -162,19 +164,15 @@ public class CreateServerOptions extends BindToJsonPayload {
*/ */
public CreateServerOptions withMetadata(Map<String, String> metadata) { public CreateServerOptions withMetadata(Map<String, String> metadata) {
checkNotNull(metadata, "metadata"); checkNotNull(metadata, "metadata");
checkArgument(metadata.size() <= 5, checkArgument(metadata.size() <= 5, "you cannot have more then 5 metadata values. You specified: "
"you cannot have more then 5 metadata values. You specified: " + metadata.size()); + metadata.size());
for (Entry<String, String> entry : metadata.entrySet()) { for (Entry<String, String> entry : metadata.entrySet()) {
checkArgument(entry.getKey().getBytes().length < 255, String.format( checkArgument(entry.getKey().getBytes().length < 255, String.format(
"maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", "maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", entry.getKey(), entry
entry.getKey(), entry.getKey().getBytes().length)); .getKey().getBytes().length));
checkArgument( checkArgument(entry.getKey().getBytes().length < 255, String.format(
entry.getKey().getBytes().length < 255, "maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", entry
String .getKey(), entry.getValue(), entry.getValue().getBytes().length));
.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; this.metadata = metadata;
return this; return this;
@ -196,8 +194,7 @@ public class CreateServerOptions extends BindToJsonPayload {
* sharedIpGroupId is also supplied. * sharedIpGroupId is also supplied.
*/ */
public CreateServerOptions withSharedIp(String publicIp) { public CreateServerOptions withSharedIp(String publicIp) {
checkState(sharedIpGroupId != null, checkState(sharedIpGroupId != null, "sharedIp is invalid unless a shared ip group is specified.");
"sharedIp is invalid unless a shared ip group is specified.");
this.publicIp = checkNotNull(publicIp, "ip"); this.publicIp = checkNotNull(publicIp, "ip");
return this; 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 java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload; import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -36,7 +38,10 @@ import com.google.common.collect.ImmutableMap;
* @author Adrian Cole * @author Adrian Cole
* *
*/ */
public class CreateSharedIpGroupOptions extends BindToJsonPayload { public class CreateSharedIpGroupOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
Integer serverId; Integer serverId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -53,9 +58,8 @@ public class CreateSharedIpGroupOptions extends BindToJsonPayload {
@Override @Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) { public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
SharedIpGroupRequest createRequest = new SharedIpGroupRequest(checkNotNull(postParams SharedIpGroupRequest createRequest = new SharedIpGroupRequest(checkNotNull(postParams.get("name")), serverId);
.get("name")), serverId); return jsonBinder.bindToRequest(request, ImmutableMap.of("sharedIpGroup", createRequest));
return super.bindToRequest(request, ImmutableMap.of("sharedIpGroup", createRequest));
} }
@Override @Override
@ -84,4 +88,5 @@ public class CreateSharedIpGroupOptions extends BindToJsonPayload {
return options.withServer(id); return options.withServer(id);
} }
} }
} }

View File

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

View File

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

View File

@ -26,8 +26,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.inject.Inject;
import org.jclouds.encryption.internal.Base64; import org.jclouds.encryption.internal.Base64;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload; import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -39,7 +42,9 @@ import com.google.common.collect.Maps;
* @author Adrian Cole * @author Adrian Cole
* *
*/ */
public class CreateServerOptions extends BindToJsonPayload { public class CreateServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
static class File { static class File {
private final String path; private final String path;
@ -49,11 +54,9 @@ public class CreateServerOptions extends BindToJsonPayload {
this.path = checkNotNull(path, "path"); this.path = checkNotNull(path, "path");
this.contents = Base64.encodeBytes(checkNotNull(contents, "contents")); this.contents = Base64.encodeBytes(checkNotNull(contents, "contents"));
checkArgument(path.getBytes().length < 255, String.format( checkArgument(path.getBytes().length < 255, String.format(
"maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path "maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path.getBytes().length));
.getBytes().length));
checkArgument(contents.length < 10 * 1024, String.format( checkArgument(contents.length < 10 * 1024, String.format(
"maximum size of the file is 10KB. Contents specified is %d bytes", "maximum size of the file is 10KB. Contents specified is %d bytes", contents.length));
contents.length));
} }
public String getContents() { public String getContents() {
@ -87,10 +90,9 @@ public class CreateServerOptions extends BindToJsonPayload {
@Override @Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) { public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present"),
"name parameter not present"), checkNotNull(postParams checkNotNull(postParams.get("imageRef"), "imageRef parameter not present"), checkNotNull(postParams
.get("imageRef"), "imageRef parameter not present"), checkNotNull( .get("flavorRef"), "flavorRef parameter not present"));
postParams.get("flavorRef"), "flavorRef parameter not present"));
if (metadata.size() > 0) if (metadata.size() > 0)
server.metadata = metadata; server.metadata = metadata;
if (files.size() > 0) if (files.size() > 0)
@ -128,19 +130,15 @@ public class CreateServerOptions extends BindToJsonPayload {
*/ */
public CreateServerOptions withMetadata(Map<String, String> metadata) { public CreateServerOptions withMetadata(Map<String, String> metadata) {
checkNotNull(metadata, "metadata"); checkNotNull(metadata, "metadata");
checkArgument(metadata.size() <= 5, checkArgument(metadata.size() <= 5, "you cannot have more then 5 metadata values. You specified: "
"you cannot have more then 5 metadata values. You specified: " + metadata.size()); + metadata.size());
for (Entry<String, String> entry : metadata.entrySet()) { for (Entry<String, String> entry : metadata.entrySet()) {
checkArgument(entry.getKey().getBytes().length < 255, String.format( checkArgument(entry.getKey().getBytes().length < 255, String.format(
"maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", "maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", entry.getKey(), entry
entry.getKey(), entry.getKey().getBytes().length)); .getKey().getBytes().length));
checkArgument( checkArgument(entry.getKey().getBytes().length < 255, String.format(
entry.getKey().getBytes().length < 255, "maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", entry
String .getKey(), entry.getValue(), entry.getValue().getBytes().length));
.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; this.metadata = metadata;
return this; return this;
@ -164,4 +162,9 @@ public class CreateServerOptions extends BindToJsonPayload {
return options.withMetadata(metadata); 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 java.util.Map;
import javax.inject.Inject;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.rest.MapBinder;
import org.jclouds.rest.binders.BindToJsonPayload; import org.jclouds.rest.binders.BindToJsonPayload;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -35,7 +38,9 @@ import com.google.common.collect.Maps;
* @author Adrian Cole * @author Adrian Cole
* *
*/ */
public class RebuildServerOptions extends BindToJsonPayload { public class RebuildServerOptions implements MapBinder {
@Inject
private BindToJsonPayload jsonBinder;
String imageRef; String imageRef;
@Override @Override
@ -43,7 +48,7 @@ public class RebuildServerOptions extends BindToJsonPayload {
Map<String, String> image = Maps.newHashMap(); Map<String, String> image = Maps.newHashMap();
if (imageRef != null) if (imageRef != null)
image.put("imageRef", imageRef); image.put("imageRef", imageRef);
return super.bindToRequest(request, ImmutableMap.of("rebuild", image)); return jsonBinder.bindToRequest(request, ImmutableMap.of("rebuild", image));
} }
@Override @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) { public RebuildServerOptions withImage(String ref) {
checkNotNull(ref, "image reference should not be null"); 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.net.URI;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.jclouds.location.Region; import org.jclouds.location.Region;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableMap.Builder;
import com.google.inject.ConfigurationException; import com.google.inject.ConfigurationException;
import com.google.inject.Injector; 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>> { public class ProvideRegionToURIViaProperties implements javax.inject.Provider<Map<String, URI>> {
private final Injector injector; private final Injector injector;
private final Multimap<String, String> constants;
@Inject @Inject
ProvideRegionToURIViaProperties(Injector injector) { protected ProvideRegionToURIViaProperties(Injector injector, @Named("CONSTANTS") Multimap<String, String> constants) {
this.injector = injector; this.injector = injector;
this.constants = constants;
} }
@Singleton @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))); String regionString = injector.getInstance(Key.get(String.class, Names.named(PROPERTY_REGIONS)));
Builder<String, URI> regions = ImmutableMap.<String, URI> builder(); Builder<String, URI> regions = ImmutableMap.<String, URI> builder();
for (String region : Splitter.on(',').split(regionString)) { for (String region : Splitter.on(',').split(regionString)) {
regions.put(region, URI.create(injector.getInstance(Key.get(String.class, Names.named(PROPERTY_REGION + "." String regionUri = injector.getInstance(Key.get(String.class, Names.named(PROPERTY_REGION + "." + region
+ region + "." + ENDPOINT))))); + "." + 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(); return regions.build();
} catch (ConfigurationException e) { } catch (ConfigurationException e) {

View File

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

View File

@ -43,8 +43,11 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.Map.Entry;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.concurrent.MoreExecutors; import org.jclouds.concurrent.MoreExecutors;
import org.jclouds.concurrent.SingleThreaded; 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.annotations.VisibleForTesting;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; 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.AbstractModule;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
/** /**
@ -99,6 +106,17 @@ public class RestContextBuilder<S, A> {
this.properties = checkNotNull(properties, "properties"); 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 @Override
protected void configure() { protected void configure() {
Properties toBind = new Properties(); 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; 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; import java.util.Map;
@ -36,8 +36,12 @@ import org.jclouds.rest.MapBinder;
*/ */
public class BindToJsonPayload implements MapBinder { public class BindToJsonPayload implements MapBinder {
protected final Json jsonBinder;
@Inject @Inject
protected Json jsonBinder; public BindToJsonPayload(Json jsonBinder) {
this.jsonBinder = checkNotNull(jsonBinder, "jsonBinder");
}
@Override @Override
public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) { public <R extends HttpRequest> R bindToRequest(R request, Map<String, String> postParams) {
@ -46,8 +50,7 @@ public class BindToJsonPayload implements MapBinder {
@Override @Override
public <R extends HttpRequest> R bindToRequest(R request, Object payload) { 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(checkNotNull(payload, "payload"));
String json = jsonBinder.toJson(payload);
request.setPayload(json); request.setPayload(json);
request.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_JSON); request.getPayload().getContentMetadata().setContentType(MediaType.APPLICATION_JSON);
return request; 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.AsyncClientFactory;
import org.jclouds.rest.HttpAsyncClient; import org.jclouds.rest.HttpAsyncClient;
import org.jclouds.rest.HttpClient; import org.jclouds.rest.HttpClient;
import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
import org.jclouds.rest.internal.AsyncRestClientProxy; import org.jclouds.rest.internal.AsyncRestClientProxy;
import org.jclouds.rest.internal.RestAnnotationProcessor; import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.rest.internal.SeedAnnotationCache;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -49,6 +51,7 @@ import com.google.inject.Key;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.Scopes; import com.google.inject.Scopes;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import com.google.inject.util.Types; import com.google.inject.util.Types;
import com.sun.jersey.api.uri.UriBuilderImpl; import com.sun.jersey.api.uri.UriBuilderImpl;
@ -63,12 +66,19 @@ public class RestModule extends AbstractModule {
protected void configure() { protected void configure() {
install(new SaxParserModule()); install(new SaxParserModule());
install(new GsonModule()); install(new GsonModule());
install(new FactoryModuleBuilder().build(BindToJsonPayloadWrappedWith.Factory.class));
bind(IdentityFunction.class).toInstance(IdentityFunction.INSTANCE); bind(IdentityFunction.class).toInstance(IdentityFunction.INSTANCE);
bind(UriBuilder.class).to(UriBuilderImpl.class); bind(UriBuilder.class).to(UriBuilderImpl.class);
bind(AsyncRestClientProxy.Factory.class).to(Factory.class).in(Scopes.SINGLETON); bind(AsyncRestClientProxy.Factory.class).to(Factory.class).in(Scopes.SINGLETON);
BinderUtils.bindAsyncClient(binder(), HttpAsyncClient.class); BinderUtils.bindAsyncClient(binder(), HttpAsyncClient.class);
BinderUtils.bindClient(binder(), HttpClient.class, HttpAsyncClient.class, BinderUtils.bindClient(binder(), HttpClient.class, HttpAsyncClient.class, ImmutableMap.<Class<?>, Class<?>> of(
ImmutableMap.<Class<?>, Class<?>> of(HttpClient.class, HttpAsyncClient.class)); HttpClient.class, HttpAsyncClient.class));
}
@Provides
@Singleton
protected ConcurrentMap<Class<?>, Boolean> seedAnnotationCache(SeedAnnotationCache seedAnnotationCache) {
return new MapMaker().makeComputingMap(seedAnnotationCache);
} }
@Provides @Provides
@ -88,7 +98,7 @@ public class RestModule extends AbstractModule {
this.factory = factory; this.factory = factory;
} }
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings( { "unchecked", "rawtypes" })
@Override @Override
public Object apply(final ClassMethodArgs from) { public Object apply(final ClassMethodArgs from) {
Class clazz = from.getAsyncClass(); Class clazz = from.getAsyncClass();
@ -111,10 +121,9 @@ public class RestModule extends AbstractModule {
@Inject @Inject
private TransformingHttpCommandExecutorService executorService; private TransformingHttpCommandExecutorService executorService;
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings( { "unchecked", "rawtypes" })
@Override @Override
public TransformingHttpCommand<?> create(HttpRequest request, public TransformingHttpCommand<?> create(HttpRequest request, Function<HttpResponse, ?> transformer) {
Function<HttpResponse, ?> transformer) {
return new TransformingHttpCommandImpl(executorService, request, 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.concat;
import static com.google.common.collect.Iterables.get; import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.transform; 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.Lists.newLinkedList;
import static com.google.common.collect.Maps.filterValues; import static com.google.common.collect.Maps.filterValues;
import static com.google.common.collect.Maps.newHashMap; 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 com.google.common.collect.Sets.newTreeSet;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static javax.ws.rs.core.HttpHeaders.ACCEPT; import static javax.ws.rs.core.HttpHeaders.ACCEPT;
@ -48,12 +44,12 @@ import java.net.URI;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.Resource; import javax.annotation.Resource;
@ -80,7 +76,6 @@ import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.http.functions.ParseJson; import org.jclouds.http.functions.ParseJson;
import org.jclouds.http.functions.ParseSax; import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x; import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
import org.jclouds.http.functions.ReleasePayloadAndReturn; import org.jclouds.http.functions.ReleasePayloadAndReturn;
import org.jclouds.http.functions.ReturnInputStream; 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.UnwrapOnlyJsonValue;
import org.jclouds.http.functions.UnwrapOnlyNestedJsonValue; import org.jclouds.http.functions.UnwrapOnlyNestedJsonValue;
import org.jclouds.http.functions.UnwrapOnlyNestedJsonValueInSet; import org.jclouds.http.functions.UnwrapOnlyNestedJsonValueInSet;
import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.jclouds.http.options.HttpRequestOptions; import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.http.utils.ModifyRequest; import org.jclouds.http.utils.ModifyRequest;
import org.jclouds.internal.ClassMethodArgs; import org.jclouds.internal.ClassMethodArgs;
@ -104,7 +100,6 @@ import org.jclouds.rest.Binder;
import org.jclouds.rest.InputParamValidator; import org.jclouds.rest.InputParamValidator;
import org.jclouds.rest.InvocationContext; import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Delegate;
import org.jclouds.rest.annotations.Endpoint; import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.EndpointParam; import org.jclouds.rest.annotations.EndpointParam;
import org.jclouds.rest.annotations.ExceptionParser; 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.SkipEncoding;
import org.jclouds.rest.annotations.Unwrap; import org.jclouds.rest.annotations.Unwrap;
import org.jclouds.rest.annotations.VirtualHost; import org.jclouds.rest.annotations.VirtualHost;
import org.jclouds.rest.annotations.WrapWith;
import org.jclouds.rest.annotations.XMLResponseParser; import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.rest.binders.BindMapToStringPayload; import org.jclouds.rest.binders.BindMapToStringPayload;
import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions; import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions;
import org.jclouds.util.Maps2;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker; import com.google.common.collect.MapMaker;
import com.google.common.collect.Multimap; 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.common.util.concurrent.ListenableFuture;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral; import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.google.inject.util.Types; import com.google.inject.util.Types;
/** /**
@ -161,7 +159,9 @@ public class RestAnnotationProcessor<T> {
private final Class<T> declaring; 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>>> methodToIndexOfParamToHeaderParamAnnotations = createMethodToIndexOfParamToAnnotation(HeaderParam.class);
static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointAnnotations = createMethodToIndexOfParamToAnnotation(Endpoint.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); static final Map<Method, Map<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointParamAnnotations = createMethodToIndexOfParamToAnnotation(EndpointParam.class);
@ -193,17 +193,12 @@ public class RestAnnotationProcessor<T> {
} }
public Set<Annotation> apply(final Integer index) { public Set<Annotation> apply(final Integer index) {
Set<Annotation> keys = new HashSet<Annotation>(); return ImmutableSet.<Annotation> copyOf(filter(ImmutableList.copyOf(method.getParameterAnnotations()[index]),
List<Annotation> parameterAnnotations = newArrayList(method.getParameterAnnotations()[index]); new Predicate<Annotation>() {
Collection<Annotation> filtered = filter(parameterAnnotations, new Predicate<Annotation>() {
public boolean apply(Annotation input) { public boolean apply(Annotation input) {
return input.annotationType().equals(clazz); return input.annotationType().equals(clazz);
} }
}); }));
for (Annotation annotation : filtered) {
keys.add(annotation);
}
return keys;
} }
} }
@ -220,24 +215,24 @@ public class RestAnnotationProcessor<T> {
}; };
private final Map<Method, Set<Integer>> methodToIndexesOfOptions = new MapMaker() static final Map<Method, Set<Integer>> methodToIndexesOfOptions = new MapMaker()
.makeComputingMap(new Function<Method, Set<Integer>>() { .makeComputingMap(new Function<Method, Set<Integer>>() {
public Set<Integer> apply(final Method method) { public Set<Integer> apply(final Method method) {
Set<Integer> toReturn = newHashSet(); Builder<Integer> toReturn = ImmutableSet.<Integer> builder();
for (int index = 0; index < method.getParameterTypes().length; index++) { for (int index = 0; index < method.getParameterTypes().length; index++) {
Class<?> type = method.getParameterTypes()[index]; Class<?> type = method.getParameterTypes()[index];
if (HttpRequestOptions.class.isAssignableFrom(type) || optionsVarArgsClass.isAssignableFrom(type)) if (HttpRequestOptions.class.isAssignableFrom(type) || optionsVarArgsClass.isAssignableFrom(type))
toReturn.add(index); toReturn.add(index);
} }
return toReturn; return toReturn.build();
} }
}); });
private final ParseSax.Factory parserFactory; private final ParseSax.Factory parserFactory;
private final HttpUtils utils; private final HttpUtils utils;
private final Provider<UriBuilder> uriBuilderProvider; private final Provider<UriBuilder> uriBuilderProvider;
private final ConcurrentMap<Class<?>, Boolean> seedAnnotationCache;
private final String apiVersion; private final String apiVersion;
private char[] skips; private char[] skips;
@Inject @Inject
@ -281,60 +276,28 @@ public class RestAnnotationProcessor<T> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Inject @Inject
public RestAnnotationProcessor(Injector injector, ParseSax.Factory parserFactory, HttpUtils utils, public RestAnnotationProcessor(Injector injector, ConcurrentMap<Class<?>, Boolean> seedAnnotationCache,
@Named(Constants.PROPERTY_API_VERSION) String apiVersion, ParseSax.Factory parserFactory, HttpUtils utils,
TypeLiteral<T> typeLiteral) { TypeLiteral<T> typeLiteral) {
this.declaring = (Class<T>) typeLiteral.getRawType(); this.declaring = (Class<T>) typeLiteral.getRawType();
this.injector = injector; this.injector = injector;
this.parserFactory = parserFactory; this.parserFactory = parserFactory;
this.utils = utils; this.utils = utils;
this.uriBuilderProvider = injector.getProvider(UriBuilder.class); this.uriBuilderProvider = injector.getProvider(UriBuilder.class);
seedCache(declaring); this.seedAnnotationCache = seedAnnotationCache;
seedAnnotationCache.get(declaring);
if (declaring.isAnnotationPresent(SkipEncoding.class)) { if (declaring.isAnnotationPresent(SkipEncoding.class)) {
skips = declaring.getAnnotation(SkipEncoding.class).value(); skips = declaring.getAnnotation(SkipEncoding.class).value();
} else { } else {
skips = new char[] {}; 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) { public Method getDelegateOrNull(Method in) {
return delegationMap.get(new MethodKey(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 { public static class MethodKey {
@Override @Override
@ -389,7 +352,7 @@ public class RestAnnotationProcessor<T> {
private URI callerEndpoint; private URI callerEndpoint;
public void setCaller(ClassMethodArgs caller) { public void setCaller(ClassMethodArgs caller) {
seedCache(caller.getMethod().getDeclaringClass()); seedAnnotationCache.get(caller.getMethod().getDeclaringClass());
this.caller = caller; this.caller = caller;
try { try {
callerEndpoint = getEndpointFor(caller.getMethod(), caller.getArgs(), injector); callerEndpoint = getEndpointFor(caller.getMethod(), caller.getArgs(), injector);
@ -488,7 +451,7 @@ public class RestAnnotationProcessor<T> {
requestBuilder.headers(filterOutContentHeaders(headers)); requestBuilder.headers(filterOutContentHeaders(headers));
try { try {
requestBuilder.endpoint(builder.buildFromEncodedMap(convertUnsafe(tokenValues))); requestBuilder.endpoint(builder.buildFromEncodedMap(Maps2.convertUnsafe(tokenValues)));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} catch (UriBuilderException e) { } catch (UriBuilderException e) {
@ -704,8 +667,8 @@ public class RestAnnotationProcessor<T> {
int index = map.keySet().iterator().next(); int index = map.keySet().iterator().next();
try { try {
URI returnVal = parser.apply(args[index]); URI returnVal = parser.apply(args[index]);
checkArgument(returnVal != null, checkArgument(returnVal != null, String.format("endpoint for [%s] not configured for %s", args[index],
String.format("endpoint for [%s] not configured for %s", args[index], method)); method));
return returnVal; return returnVal;
} catch (NullPointerException e) { } catch (NullPointerException e) {
throw new IllegalArgumentException(String.format("argument at index %d on method %s", index, method), e); throw new IllegalArgumentException(String.format("argument at index %d on method %s", index, method), e);
@ -722,8 +685,8 @@ public class RestAnnotationProcessor<T> {
}); });
try { try {
URI returnVal = parser.apply(argsToParse); URI returnVal = parser.apply(argsToParse);
checkArgument(returnVal != null, checkArgument(returnVal != null, String.format("endpoint for [%s] not configured for %s", argsToParse,
String.format("endpoint for [%s] not configured for %s", argsToParse, method)); method));
return returnVal; return returnVal;
} catch (NullPointerException e) { } catch (NullPointerException e) {
throw new IllegalArgumentException(String.format("argument at indexes %s on method %s", map.keySet(), throw new IllegalArgumentException(String.format("argument at indexes %s on method %s", map.keySet(),
@ -763,7 +726,7 @@ public class RestAnnotationProcessor<T> {
public static final TypeLiteral<ListenableFuture<HttpResponse>> futureHttpResponseLiteral = new TypeLiteral<ListenableFuture<HttpResponse>>() { 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) { public static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) {
ResponseParser annotation = method.getAnnotation(ResponseParser.class); ResponseParser annotation = method.getAnnotation(ResponseParser.class);
if (annotation == null) { if (annotation == null) {
@ -862,33 +825,15 @@ public class RestAnnotationProcessor<T> {
return null; 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) { public static Set<String> getHttpMethods(Method method) {
HashSet<String> methods = new HashSet<String>(); Builder<String> methodsBuilder = ImmutableSet.<String> builder();
for (Annotation annotation : method.getAnnotations()) { for (Annotation annotation : method.getAnnotations()) {
HttpMethod http = annotation.annotationType().getAnnotation(HttpMethod.class); HttpMethod http = annotation.annotationType().getAnnotation(HttpMethod.class);
if (http != null) if (http != null)
methods.add(http.value()); methodsBuilder.add(http.value());
} }
if (methods.size() == 0) Set<String> methods = methodsBuilder.build();
return null; return (methods.size() == 0) ? null : methods;
return methods;
} }
public String getHttpMethodOrConstantOrThrowException(Method method) { public String getHttpMethodOrConstantOrThrowException(Method method) {
@ -908,17 +853,25 @@ public class RestAnnotationProcessor<T> {
return false; return false;
} }
public GeneratedHttpRequest<T> decorateRequest(GeneratedHttpRequest<T> request) { private static final Predicate<Set<?>> notEmpty = new Predicate<Set<?>>() {
OUTER: for (Entry<Integer, Set<Annotation>> entry : filterValues( public boolean apply(Set<?> input) {
methodToIndexOfParamToDecoratorParamAnnotation.get(request.getJavaMethod()),
new Predicate<Set<Annotation>>() {
public boolean apply(Set<Annotation> input) {
return input.size() >= 1; return input.size() >= 1;
} }
}).entrySet()) { };
public GeneratedHttpRequest<T> decorateRequest(GeneratedHttpRequest<T> request) {
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; boolean shouldBreak = false;
BinderParam payloadAnnotation = (BinderParam) entry.getValue().iterator().next(); Annotation annotation = Iterables.get(entry.getValue(), 0);
Binder binder = injector.getInstance(payloadAnnotation.value()); 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) { if (request.getArgs().size() >= entry.getKey() + 1 && request.getArgs().get(entry.getKey()) != null) {
Object input; Object input;
Class<?> parameterType = request.getJavaMethod().getParameterTypes()[entry.getKey()]; Class<?> parameterType = request.getJavaMethod().getParameterTypes()[entry.getKey()];
@ -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<? extends Part> getParts(Method method, Object[] args, Iterable<Entry<String, String>> iterable) {
List<Part> parts = newLinkedList(); List<Part> parts = newLinkedList();
Map<Integer, Set<Annotation>> indexToPartParam = methodToIndexOfParamToPartParamAnnotations.get(method); 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) { private Multimap<String, String> getPathParamKeyValues(Method method, Object... args) {
Multimap<String, String> pathParamValues = LinkedHashMultimap.create(); Multimap<String, String> pathParamValues = LinkedHashMultimap.create();
pathParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations.get(method); Map<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.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) { private Multimap<String, String> getMatrixParamKeyValues(Method method, Object... args) {
Multimap<String, String> matrixParamValues = LinkedHashMultimap.create(); Multimap<String, String> matrixParamValues = LinkedHashMultimap.create();
matrixParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations.get(method); Map<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.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) { private Multimap<String, String> getFormParamKeyValues(Method method, Object... args) {
Multimap<String, String> formParamValues = LinkedHashMultimap.create(); Multimap<String, String> formParamValues = LinkedHashMultimap.create();
formParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations.get(method); Map<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.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) { private Multimap<String, String> getQueryParamKeyValues(Method method, Object... args) {
Multimap<String, String> queryParamValues = LinkedHashMultimap.create(); Multimap<String, String> queryParamValues = LinkedHashMultimap.create();
queryParamValues.putAll(constants);
Map<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations.get(method); Map<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations.get(method);
Map<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.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.base.Predicates.not;
import static com.google.common.collect.Maps.filterKeys; import static com.google.common.collect.Maps.filterKeys;
import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Maps; 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. * General utilities used in jclouds code for {@link Map}s.
@ -39,9 +41,17 @@ import com.google.common.collect.Maps;
*/ */
public class Maps2 { 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 * If the supplied map contains the key {@code k1}, its value will be assigned to the key {@code
* {@code k2}. Note that this doesn't modify the input map. * k2}. Note that this doesn't modify the input map.
* *
* @param <V> * @param <V>
* type of value the map holds * 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 java.net.URI;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpCommand;
@ -35,7 +37,12 @@ import org.jclouds.rest.config.RestModule;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMultimap; 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.Guice;
import com.google.inject.Injector;
import com.google.inject.Provides;
/** /**
* Tests behavior of {@code RedirectionRetryHandler} * Tests behavior of {@code RedirectionRetryHandler}
@ -44,6 +51,19 @@ import com.google.inject.Guice;
*/ */
@Test(groups = "unit") @Test(groups = "unit")
public class RedirectionRetryHandlerTest { 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 @Test
public void test302DoesNotRetry() { public void test302DoesNotRetry() {
@ -55,8 +75,7 @@ public class RedirectionRetryHandlerTest {
replay(command); replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance( RedirectionRetryHandler retry = injector.getInstance(RedirectionRetryHandler.class);
RedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response); assert !retry.shouldRetryRequest(command, response);
@ -75,8 +94,7 @@ public class RedirectionRetryHandlerTest {
replay(command); replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance( RedirectionRetryHandler retry = injector.getInstance(RedirectionRetryHandler.class);
RedirectionRetryHandler.class);
assert !retry.shouldRetryRequest(command, response); assert !retry.shouldRetryRequest(command, response);
@ -88,22 +106,22 @@ public class RedirectionRetryHandlerTest {
public void test302WithPathOnlyHeader() { public void test302WithPathOnlyHeader() {
verifyRedirectRoutes( verifyRedirectRoutes(
new HttpRequest("GET", new HttpRequest("GET", URI
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")), .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, 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")), "/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")),
new HttpRequest( new HttpRequest(
"GET", "GET",
URI.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645"))); URI
.create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/Error.aspx?aspxerrorpath=/api/v0.8b-ext2.5/org.svc/1906645")));
} }
@Test @Test
public void test302ToHttps() { public void test302ToHttps() {
verifyRedirectRoutes( verifyRedirectRoutes(new HttpRequest("GET", URI
new HttpRequest("GET", .create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
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, 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")),// "https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI new HttpRequest("GET", URI
@ -114,9 +132,8 @@ public class RedirectionRetryHandlerTest {
@Test @Test
public void test302ToDifferentPort() { public void test302ToDifferentPort() {
verifyRedirectRoutes( verifyRedirectRoutes(new HttpRequest("GET", URI
new HttpRequest("GET", .create("http://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
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, 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")),// "http://services.enterprisecloud.terremark.com:3030/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI new HttpRequest("GET", URI
@ -127,23 +144,22 @@ public class RedirectionRetryHandlerTest {
@Test @Test
public void test302WithHeader() { public void test302WithHeader() {
verifyRedirectRoutes( verifyRedirectRoutes(new HttpRequest("GET", URI
new HttpRequest("GET", .create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
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, 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( "https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),
"GET", URI.create("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 @Test
public void test302WithHeaderReplacesHostHeader() { public void test302WithHeaderReplacesHostHeader() {
verifyRedirectRoutes( verifyRedirectRoutes(new HttpRequest("GET", URI
new HttpRequest("GET", .create("https://services.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
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,
ImmutableMultimap.of(HttpHeaders.HOST, "services.enterprisecloud.terremark.com")), "HTTP/1.1 302 Found", null, ImmutableMultimap.of(HttpHeaders.LOCATION,
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")),// "https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645")),//
new HttpRequest("GET", URI new HttpRequest("GET", URI
.create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"), .create("https://services1.enterprisecloud.terremark.com/api/v0.8b-ext2.5/org/1906645"),
@ -160,8 +176,7 @@ public class RedirectionRetryHandlerTest {
replay(command); replay(command);
RedirectionRetryHandler retry = Guice.createInjector(new MockModule(), new RestModule()).getInstance( RedirectionRetryHandler retry = injector.getInstance(RedirectionRetryHandler.class);
RedirectionRetryHandler.class);
assert retry.shouldRetryRequest(command, response); assert retry.shouldRetryRequest(command, response);
verify(command); 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.SkipEncoding;
import org.jclouds.rest.annotations.Unwrap; import org.jclouds.rest.annotations.Unwrap;
import org.jclouds.rest.annotations.VirtualHost; import org.jclouds.rest.annotations.VirtualHost;
import org.jclouds.rest.annotations.WrapWith;
import org.jclouds.rest.binders.BindAsHostPrefix; import org.jclouds.rest.binders.BindAsHostPrefix;
import org.jclouds.rest.binders.BindMapToMatrixParams; import org.jclouds.rest.binders.BindMapToMatrixParams;
import org.jclouds.rest.binders.BindToJsonPayload; import org.jclouds.rest.binders.BindToJsonPayload;
@ -200,6 +201,9 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
@Delegate @Delegate
public Callee getCallee(); public Callee getCallee();
@Delegate
public Callee getCallee(@EndpointParam URI endpoint);
} }
@Timeout(duration = 10, timeUnit = TimeUnit.NANOSECONDS) @Timeout(duration = 10, timeUnit = TimeUnit.NANOSECONDS)
@ -212,6 +216,9 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
@Delegate @Delegate
public AsyncCallee getCallee(); public AsyncCallee getCallee();
@Delegate
public AsyncCallee getCallee(@EndpointParam URI endpoint);
} }
@SuppressWarnings("unchecked") @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() { private Injector injectorForClient() {
RestContextSpec<Caller, AsyncCaller> contextSpec = contextSpec("test", "http://localhost:9999", "1", "", RestContextSpec<Caller, AsyncCaller> contextSpec = contextSpec("test", "http://localhost:9999", "1", "",
@ -768,6 +800,10 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
String testUnwrap(); String testUnwrap();
@POST
@Path("/")
String testWrapWith(@WrapWith("foo") String param);
@GET @GET
@Path("/") @Path("/")
@Unwrap @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") @SuppressWarnings("unchecked")
public void testUnwrap2() throws SecurityException, NoSuchMethodException, IOException { public void testUnwrap2() throws SecurityException, NoSuchMethodException, IOException {
Method method = TestPut.class.getMethod("testUnwrap2"); Method method = TestPut.class.getMethod("testUnwrap2");