Adds a Map<String,Object> message body reader and uses it in EffectorApi
* Adds FormMapProvider, which reads an InputStream into a Map<String, Object>, where the Object values are either a String, a List<String> or null. * Uses this in EffectorApi to support form encoded data. * Changes signature of EffectorApi.invoke parameters argument from Map<String, String> to Map<String, Object>. Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/a94678da Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/a94678da Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/a94678da Branch: refs/heads/master Commit: a94678da8290ea3f31832caf5735cf9f81cc6a9f Parents: 1035445 Author: Sam Corbett <sam.corb...@cloudsoftcorp.com> Authored: Wed Jul 16 18:34:42 2014 +0100 Committer: Sam Corbett <sam.corb...@cloudsoftcorp.com> Committed: Wed Jul 16 19:22:12 2014 +0100 ---------------------------------------------------------------------- .../java/brooklyn/rest/api/EffectorApi.java | 5 +- .../ApplicationResourceIntegrationTest.java | 2 +- .../rest-client/src/test/webapp/WEB-INF/web.xml | 1 + .../java/brooklyn/rest/BrooklynRestApi.java | 2 + .../rest/resources/EffectorResource.java | 2 +- .../brooklyn/rest/util/FormMapProvider.java | 82 ++++++++++++++++++++ .../brooklyn/rest/util/WebResourceUtils.java | 12 +++ .../rest-server/src/main/webapp/WEB-INF/web.xml | 1 + .../rest/resources/ApplicationResourceTest.java | 18 +++++ .../rest/testing/BrooklynRestApiTest.java | 2 + 10 files changed, 123 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java ---------------------------------------------------------------------- diff --git a/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java b/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java index 8204927..c2ec68e 100644 --- a/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java +++ b/usage/rest-api/src/main/java/brooklyn/rest/api/EffectorApi.java @@ -59,6 +59,7 @@ public interface EffectorApi { @ApiErrors(value = { @ApiError(code = 404, reason = "Could not find application, entity or effector") }) + @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED}) public Response invoke( @ApiParam(name = "application", value = "Application ID or name", required = true) @PathParam("application") String application, @@ -80,7 +81,7 @@ public interface EffectorApi { @ApiParam(name = "parameters", value = "Effector parameters (as key value pairs)", required = false) @Valid - Map<String, String> parameters + Map<String, Object> parameters ) ; - + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-client/src/test/java/brooklyn/rest/client/ApplicationResourceIntegrationTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-client/src/test/java/brooklyn/rest/client/ApplicationResourceIntegrationTest.java b/usage/rest-client/src/test/java/brooklyn/rest/client/ApplicationResourceIntegrationTest.java index b1ce82b..b20e94d 100644 --- a/usage/rest-client/src/test/java/brooklyn/rest/client/ApplicationResourceIntegrationTest.java +++ b/usage/rest-client/src/test/java/brooklyn/rest/client/ApplicationResourceIntegrationTest.java @@ -153,7 +153,7 @@ public class ApplicationResourceIntegrationTest { @Test(groups = "Integration", dependsOnMethods = {"testListSensorsRedis", "testListEntities"}) public void testTriggerRedisStopEffector() throws Exception { String entityId = getManagementContext().getApplications().iterator().next().getChildren().iterator().next().getId(); - Response response = api.getEffectorApi().invoke("redis-app", entityId, "stop", "5000", ImmutableMap.<String, String>of()); + Response response = api.getEffectorApi().invoke("redis-app", entityId, "stop", "5000", ImmutableMap.<String, Object>of()); assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode()); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-client/src/test/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/usage/rest-client/src/test/webapp/WEB-INF/web.xml b/usage/rest-client/src/test/webapp/WEB-INF/web.xml index 0b211f4..7fe2c9c 100644 --- a/usage/rest-client/src/test/webapp/WEB-INF/web.xml +++ b/usage/rest-client/src/test/webapp/WEB-INF/web.xml @@ -65,6 +65,7 @@ <param-name>com.sun.jersey.config.property.classnames</param-name> <param-value> brooklyn.rest.apidoc.ApidocHelpMessageBodyWriter; + brooklyn.rest.util.FormMapProvider; org.codehaus.jackson.jaxrs.JacksonJsonProvider; brooklyn.rest.resources.ActivityResource; brooklyn.rest.resources.ApidocResource; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-server/src/main/java/brooklyn/rest/BrooklynRestApi.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynRestApi.java b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynRestApi.java index 69e3429..afcbe5c 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynRestApi.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynRestApi.java @@ -42,6 +42,7 @@ import brooklyn.rest.resources.ServerResource; import brooklyn.rest.resources.UsageResource; import brooklyn.rest.resources.VersionResource; import brooklyn.rest.util.DefaultExceptionMapper; +import brooklyn.rest.util.FormMapProvider; import com.google.common.collect.Iterables; @@ -78,6 +79,7 @@ public class BrooklynRestApi { List<Object> resources = new ArrayList<Object>(); resources.add(new DefaultExceptionMapper()); resources.add(new JacksonJsonProvider()); + resources.add(new FormMapProvider()); return resources; } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-server/src/main/java/brooklyn/rest/resources/EffectorResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/resources/EffectorResource.java b/usage/rest-server/src/main/java/brooklyn/rest/resources/EffectorResource.java index 6543770..a7434d1 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/resources/EffectorResource.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/resources/EffectorResource.java @@ -75,7 +75,7 @@ public class EffectorResource extends AbstractBrooklynRestResource implements Ef @Override public Response invoke(String application, String entityToken, String effectorName, - String timeout, Map<String, String> parameters) { + String timeout, Map<String, Object> parameters) { final EntityLocal entity = brooklyn().getEntity(application, entityToken); // TODO check effectors? http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-server/src/main/java/brooklyn/rest/util/FormMapProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/FormMapProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/util/FormMapProvider.java new file mode 100644 index 0000000..eb6a60a --- /dev/null +++ b/usage/rest-server/src/main/java/brooklyn/rest/util/FormMapProvider.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package brooklyn.rest.util; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import javax.ws.rs.Consumes; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.sun.jersey.core.impl.provider.entity.FormMultivaluedMapProvider; +import com.sun.jersey.core.util.MultivaluedMapImpl; + +/** + * A MessageBodyReader producing a <code>Map<String, Object></code>, where Object + * is either a <code>String</code>, a <code>List<String></code> or null. + */ +@Provider +@Consumes(MediaType.APPLICATION_FORM_URLENCODED) +public class FormMapProvider implements MessageBodyReader<Map<String, Object>> { + + @Override + public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { + if (!Map.class.equals(type) || !(genericType instanceof ParameterizedType)) { + return false; + } + ParameterizedType parameterized = (ParameterizedType) genericType; + return parameterized.getActualTypeArguments().length == 2 && + parameterized.getActualTypeArguments()[0] == String.class && + parameterized.getActualTypeArguments()[1] == Object.class; + } + + @Override + public Map<String, Object> readFrom(Class<Map<String, Object>> type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) + throws IOException, WebApplicationException { + FormMultivaluedMapProvider delegate = new FormMultivaluedMapProvider(); + MultivaluedMap<String, String> multi = new MultivaluedMapImpl(); + multi = delegate.readFrom(multi, mediaType, entityStream); + + Map<String, Object> map = Maps.newHashMapWithExpectedSize(multi.keySet().size()); + for (String key : multi.keySet()) { + List<String> value = multi.get(key); + if (value.size() > 1) { + map.put(key, Lists.newArrayList(value)); + } else if (value.size() == 1) { + map.put(key, Iterables.getOnlyElement(value)); + } else { + map.put(key, null); + } + } + return map; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-server/src/main/java/brooklyn/rest/util/WebResourceUtils.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/brooklyn/rest/util/WebResourceUtils.java b/usage/rest-server/src/main/java/brooklyn/rest/util/WebResourceUtils.java index 0e19b29..61e4217 100644 --- a/usage/rest-server/src/main/java/brooklyn/rest/util/WebResourceUtils.java +++ b/usage/rest-server/src/main/java/brooklyn/rest/util/WebResourceUtils.java @@ -38,6 +38,16 @@ public class WebResourceUtils { private static final Logger log = LoggerFactory.getLogger(WebResourceUtils.class); + /** @throws WebApplicationException With code 400 bad request */ + public static WebApplicationException badRequest(String format, Object... args) { + String msg = String.format(format, args); + if (log.isDebugEnabled()) log.debug("returning 400 bad request ("+msg+")"); + throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) + .type(MediaType.APPLICATION_JSON_TYPE) + .entity(ApiError.builder().message(msg).build()).build()); + } + + /** @throws WebApplicationException With code 401 unauthorized */ public static WebApplicationException unauthorized(String format, Object... args) { String msg = String.format(format, args); if (log.isDebugEnabled()) log.debug("returning 401 unauthorized("+msg+")"); @@ -46,6 +56,7 @@ public class WebResourceUtils { .entity(ApiError.builder().message(msg).build()).build()); } + /** @throws WebApplicationException With code 404 not found */ public static WebApplicationException notFound(String format, Object... args) { String msg = String.format(format, args); if (log.isDebugEnabled()) log.debug("returning 404 notFound("+msg+") - may be a stale browser session"); @@ -54,6 +65,7 @@ public class WebResourceUtils { .entity(ApiError.builder().message(msg).build()).build()); } + /** @throws WebApplicationException With code 412 precondition failed */ public static WebApplicationException preconditionFailed(String format, Object... args) { String msg = String.format(format, args); if (log.isDebugEnabled()) log.debug("returning 412 preconditionFailed("+msg+")"); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-server/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/webapp/WEB-INF/web.xml b/usage/rest-server/src/main/webapp/WEB-INF/web.xml index 61d4781..932691d 100644 --- a/usage/rest-server/src/main/webapp/WEB-INF/web.xml +++ b/usage/rest-server/src/main/webapp/WEB-INF/web.xml @@ -63,6 +63,7 @@ <param-name>com.sun.jersey.config.property.classnames</param-name> <param-value> brooklyn.rest.apidoc.ApidocHelpMessageBodyWriter; + brooklyn.rest.util.FormMapProvider; org.codehaus.jackson.jaxrs.JacksonJsonProvider; brooklyn.rest.resources.ActivityResource; brooklyn.rest.resources.ApidocResource; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java index 1a030a9..22454f5 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceTest.java @@ -30,6 +30,7 @@ import java.util.Set; import java.util.concurrent.TimeoutException; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import org.codehaus.jackson.JsonGenerationException; @@ -80,6 +81,7 @@ import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.UniformInterfaceException; +import com.sun.jersey.core.util.MultivaluedMapImpl; @Test(singleThreaded = true) public class ApplicationResourceTest extends BrooklynRestResourceTest { @@ -431,6 +433,22 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest { assertEquals(result, "foo4"); } + @Test(dependsOnMethods = "testListSensors") + public void testTriggerSampleEffectorWithFormData() throws InterruptedException, IOException { + MultivaluedMap<String, String> data = new MultivaluedMapImpl(); + data.add("param1", "foo"); + data.add("param2", "4"); + ClientResponse response = client().resource("/v1/applications/simple-app/entities/simple-ent/effectors/"+ + RestMockSimpleEntity.SAMPLE_EFFECTOR.getName()) + .type(MediaType.APPLICATION_FORM_URLENCODED) + .post(ClientResponse.class, data); + + assertEquals(response.getStatus(), Response.Status.ACCEPTED.getStatusCode()); + + String result = response.getEntity(String.class); + assertEquals(result, "foo4"); + } + @Test(dependsOnMethods = "testTriggerSampleEffector") public void testBatchSensorValues() { Map<String,String> sensors = client().resource("/v1/applications/simple-app/entities/simple-ent/sensors/current-state") http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a94678da/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java index 6391672..4370053 100644 --- a/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java +++ b/usage/rest-server/src/test/java/brooklyn/rest/testing/BrooklynRestApiTest.java @@ -18,6 +18,7 @@ */ package brooklyn.rest.testing; +import brooklyn.rest.util.FormMapProvider; import io.brooklyn.camp.brooklyn.BrooklynCampPlatformLauncherNoServer; import org.testng.annotations.AfterClass; @@ -95,6 +96,7 @@ public abstract class BrooklynRestApiTest extends ResourceTest { protected void addResources() { addProvider(DefaultExceptionMapper.class); + addProvider(FormMapProvider.class); for (Object r: BrooklynRestApi.getBrooklynRestResources()) addResource(r); }