Merge pull request #1138 from tbouron/fix/application-rest-api

Fix /v1/applications REST endpoints
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
index cfcf5f3..435c6f9 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
@@ -135,7 +135,7 @@
                     required = true)
             @PathParam("application") String application);
 
-    /** @deprecated since 1.1 use {@link #createWithFormat(byte[], String)} instead */
+    /** @deprecated since 1.1 use {@link #createWithFormat(String, String)} instead */
     @Deprecated
     @POST
     @Consumes("application/deprecated-yaml-app-spec")
@@ -147,7 +147,8 @@
                     required = true)
             String yaml);
 
-    /** @deprecated since 1.1 use {@link #createFromYamlWithAppId(String, String, String)}  instead */
+    /** @deprecated since 1.1 use {@link #createFromYamlAndAppId(String, String)}
+     * or {@link #createFromYamlAndFormatAndAppId(String, String, String)} instead */
     @Deprecated
     @POST
     @Consumes("application/deprecated-yaml-app-spec")
@@ -156,10 +157,11 @@
             @ApiParam(name = "applicationSpec", value = "App spec in CAMP YAML format", required = true) String yaml,
             @ApiParam(name = "application", value = "Application id", required = true) @PathParam("application") String appId);
 
-    @Beta
     @PUT
     @Path("/{application}")
-    @Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_FORM_URLENCODED})
+    @Consumes({"application/x-yaml",
+            // see http://stackoverflow.com/questions/332129/yaml-mime-type
+            "text/yaml", "text/x-yaml", "application/yaml"})
     @ApiOperation(
             value = "[BETA] Create and start a new application from YAML, with the given id",
             response = org.apache.brooklyn.rest.domain.TaskSummary.class
@@ -168,7 +170,23 @@
             @ApiResponse(code = 404, message = "Undefined entity or location"),
             @ApiResponse(code = 409, message = "Application already registered")
     })
-    public Response createFromYamlWithAppId(
+    public Response createFromYamlAndAppId(
+            @ApiParam(name = "plan", value = "Plan", required = true) String yaml,
+            @ApiParam(name = "application", value = "Application id", required = true) @PathParam("application") String appId);
+
+    @Beta
+    @PUT
+    @Path("/{application}")
+    @Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_FORM_URLENCODED})
+    @ApiOperation(
+            value = "[BETA] Create and start a new application from YAML and format with the given id",
+            response = org.apache.brooklyn.rest.domain.TaskSummary.class
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = 404, message = "Undefined entity or location"),
+            @ApiResponse(code = 409, message = "Application already registered")
+    })
+    public Response createFromYamlAndFormatAndAppId(
             @ApiParam(name = "plan", value = "Plan", required = true)
             @FormParam("plan") String yaml,
             @ApiParam(name = "format", value = "Format eg broolyn-camp", required = false)
@@ -204,6 +222,18 @@
                     required = true)
             @Valid String contents);
 
+    @POST
+    @Consumes
+    @ApiOperation(
+            value = "Create and start a new application from YAML",
+            response = org.apache.brooklyn.rest.domain.TaskSummary.class
+    )
+    @ApiResponses(value = {
+            @ApiResponse(code = 404, message = "Undefined entity or location")
+    })
+    public Response createFromBytes(
+            @ApiParam(name = "plan", value = "Application plan to deploy", required = true) byte[] plan);
+
     @Beta
     @POST
     @Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_FORM_URLENCODED})
diff --git a/rest/rest-resources/pom.xml b/rest/rest-resources/pom.xml
index 42753f7..824f961 100644
--- a/rest/rest-resources/pom.xml
+++ b/rest/rest-resources/pom.xml
@@ -207,6 +207,12 @@
             <classifier>tests</classifier>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpmime</artifactId>
+            <version>4.3.1</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
index 925e5f6..e9803182 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
@@ -26,7 +26,7 @@
 
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
@@ -397,7 +397,12 @@
     }
 
     @Override
-    public Response createFromYamlWithAppId(String yaml, String format, String appId) {
+    public Response createFromYamlAndAppId(String yaml, String appId) {
+        return createFromYamlAndFormatAndAppId(yaml, null, appId);
+    }
+
+    @Override
+    public Response createFromYamlAndFormatAndAppId(String yaml, String format, String appId) {
         return createFromYaml(yaml, format, Optional.of(appId));
     }
 
@@ -505,7 +510,7 @@
 
     @Override
     public Response createPoly(byte[] inputToAutodetectType) {
-        return createWithFormat(Arrays.toString(inputToAutodetectType), null);
+        return createWithFormat(new String(inputToAutodetectType, StandardCharsets.UTF_8), null);
     }
 
     @Override
@@ -515,6 +520,11 @@
     }
 
     @Override
+    public Response createFromBytes(byte[] plan) {
+        return createPoly(plan);
+    }
+
+    @Override
     public Response createWithFormat(String inputToAutodetectType, String format) {
         log.debug("Creating app from autodetecting input");
 
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index 71dbcc1..bd85306 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -31,6 +31,7 @@
 import java.io.InputStream;
 import java.net.URI;
 import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -87,6 +88,7 @@
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.StringPredicates;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.apache.http.HttpHeaders;
 import org.apache.http.entity.ContentType;
 import org.slf4j.Logger;
@@ -242,6 +244,25 @@
         assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "simple-app-yaml");
     }
 
+    @Test(dependsOnMethods = { "testDeployApplication", "testLocatedLocation" })
+    public void testDeployApplicationYamlWithFormat() throws Exception {
+        String yaml = "{ name: deploy-format-app-yaml, location: localhost, services: [ { serviceType: "+BasicApplication.class.getCanonicalName()+" } ] }";
+
+        List<Attachment> body = new LinkedList<Attachment>();
+        body.add(new Attachment("plan", "text/plain", yaml));
+        body.add(new Attachment("format", "text/plain", "brooklyn-camp"));
+
+        Response response = client().path("/applications")
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.MULTIPART_FORM_DATA)
+                .post(body);
+        assertTrue(response.getStatus()/100 == 2, "response is "+response);
+
+        // Expect app to be running
+        URI appUri = response.getLocation();
+        waitForApplicationToBeRunning(response.getLocation());
+        assertEquals(client().path(appUri).get(ApplicationSummary.class).getSpec().getName(), "deploy-format-app-yaml");
+    }
+
     @Test
     public void testReferenceCatalogEntity() throws Exception {
         getManagementContext().getCatalog().addItems(Strings.lines(