You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by kw...@apache.org on 2016/02/25 14:50:22 UTC

svn commit: r1732304 - in /sling/trunk/tooling/maven/maven-sling-plugin: pom.xml src/main/java/org/apache/sling/maven/bundlesupport/AbstractBundleInstallMojo.java src/main/java/org/apache/sling/maven/bundlesupport/BundleUninstallMojo.java

Author: kwin
Date: Thu Feb 25 13:50:22 2016
New Revision: 1732304

URL: http://svn.apache.org/viewvc?rev=1732304&view=rev
Log:
SLING-5335 add option to deploy through the Sling POST Servlet

This closes #115

Modified:
    sling/trunk/tooling/maven/maven-sling-plugin/pom.xml
    sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/AbstractBundleInstallMojo.java
    sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/BundleUninstallMojo.java

Modified: sling/trunk/tooling/maven/maven-sling-plugin/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/tooling/maven/maven-sling-plugin/pom.xml?rev=1732304&r1=1732303&r2=1732304&view=diff
==============================================================================
--- sling/trunk/tooling/maven/maven-sling-plugin/pom.xml (original)
+++ sling/trunk/tooling/maven/maven-sling-plugin/pom.xml Thu Feb 25 13:50:22 2016
@@ -36,6 +36,10 @@
         Maven Plugin supporting various Sling Development Tasks
     </description>
 
+    <prerequisites>
+       <maven>3.0.0</maven> <!-- for supporting enums as parameters (https://issues.apache.org/jira/browse/MNG-4292) -->
+    </prerequisites>
+
     <scm>
         <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/tooling/maven/maven-sling-plugin</connection>
         <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/tooling/maven/maven-sling-plugin</developerConnection>

Modified: sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/AbstractBundleInstallMojo.java
URL: http://svn.apache.org/viewvc/sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/AbstractBundleInstallMojo.java?rev=1732304&r1=1732303&r2=1732304&view=diff
==============================================================================
--- sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/AbstractBundleInstallMojo.java (original)
+++ sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/AbstractBundleInstallMojo.java Thu Feb 25 13:50:22 2016
@@ -53,6 +53,7 @@ import org.apache.sling.commons.json.JSO
 import org.apache.sling.commons.json.JSONObject;
 import org.apache.sling.commons.osgi.ManifestHeader;
 import org.apache.sling.commons.osgi.ManifestHeader.Entry;
+import org.codehaus.plexus.util.StringUtils;
 
 abstract class AbstractBundleInstallMojo extends AbstractBundlePostMojo {
 
@@ -61,12 +62,12 @@ abstract class AbstractBundleInstallMojo
     /** The fs resource provider factory. */
     private static final String FS_FACTORY = "org.apache.sling.fsprovider.internal.FsResourceProvider";
     /** Mime type for json response. */
-    private static final String JSON_MIME_TYPE = "application/json";
+    protected static final String JSON_MIME_TYPE = "application/json";
     /** Http header for content type. */
     private static final String HEADER_CONTENT_TYPE = "Content-Type";
 
     /**
-     * The URL of the running Sling instance.
+     * The URL of the running Sling instance. The default is only useful for <strong>WebConsole</strong> deployment (see {@link #deploymentMethod}).
      */
     @Parameter(property="sling.url", defaultValue="http://localhost:8080/system/console", required = true)
     protected String slingUrl;
@@ -77,28 +78,63 @@ abstract class AbstractBundleInstallMojo
      * in each POM, while using the same common <code>sling.url</code> in a parent
      * POM (eg. <code>sling.url=http://localhost:8080</code> and
      * <code>sling.urlSuffix=/project/specific/path</code>). This is typically used
-     * in conjunction with a HTTP PUT (<code>sling.usePut=true</code>).
+     * in conjunction with WebDAV or SlingPostServlet deployment methods.
      */
     @Parameter(property="sling.urlSuffix")
     protected String slingUrlSuffix;
 
     /**
-     * If a simple HTTP PUT should be used instead of the standard POST to the
-     * felix console. In the <code>uninstall</code> goal, a HTTP DELETE will be
+     * If a PUT via WebDAV should be used instead of the standard POST to the
+     * Felix Web Console. In the <code>uninstall</code> goal, a HTTP DELETE will be
      * used.
+     * 
+     * @deprecated Use {@link #deployMethod} instead.
      */
-    @Parameter(property="sling.usePut", defaultValue = "false", required = true)
+    @Parameter(property="sling.usePut", defaultValue = "false")
     protected boolean usePut;
-    
+
+    /**
+     * Possible methodologies for deploying (installing and uninstalling)
+     * bundles from the remote server.
+     * Use camel-case values because those are used when you configure the plugin (and uppercase with separators "_" just looks ugly in that context)
+     */
+    enum BundleDeploymentMethod {
+        /** Via POST to Felix Web Console */
+        WebConsole,
+
+        /** Via WebDAV */
+        WebDAV,
+
+        /** Via POST to Sling directly */
+        SlingPostServlet;
+    }
+
+    /**
+     * Bundle deployment method. One of the following three values are allowed
+     * <ol>
+     *  <li><strong>WebConsole</strong>, uses the <a href="http://felix.apache.org/documentation/subprojects/apache-felix-web-console/web-console-restful-api.html#post-requests">
+     *  Felix Web Console REST API</a> for deployment (HTTP POST). This is the default. 
+     *  Make sure that {@link #slingUrl} points to the Felix Web Console in that case.</li>
+     *  <li><strong>WebDAV</strong>, uses <a href="https://sling.apache.org/documentation/development/repository-based-development.html">
+     *  WebDAV</a> for deployment (HTTP PUT). Make sure that {@link #slingUrl} points to the entry path of 
+     *  the Sling WebDAV bundle (usually below regular Sling root URL). Issues a HTTP Delete for the uninstall goal.
+     *  <li><strong>SlingPostServlet</strong>, uses the <a href="">Sling Post Servlet</a> for deployment (HTTP POST).
+     *  Make sure that {@link #slingUrl} points a path which is handled by the Sling POST Servlet (usually below regular Sling root URL).</li>
+     * </ol>
+     * 
+     * This has precedence over the deprecated parameter {@link #usePut}.
+     */
+    @Parameter(property="sling.deploy.method", required = false)
+    protected BundleDeploymentMethod deploymentMethod;
+
     /**
-     * The jcr:primaryType to be used when creating intermediate paths for HTTP PUT deployment
+     * The jcr:primaryType to be used when creating intermediate paths. Only applies to WebDAV deployment
      */
     @Parameter(property = "sling.deploy.intermediatePathPrimaryType" , defaultValue = "sling:Folder")
     protected String intermediatePathPrimaryType;
-    
+
     /**
-     * The content type / mime type used for the HTTP PUT (if
-     * <code>sling.usePut=true</code>).
+     * The content type / mime type used for WebDAV or Sling POST deployment.
      */
     @Parameter(property="sling.mimeType", defaultValue = "application/java-archive", required = true)
     protected String mimeType;
@@ -116,19 +152,22 @@ abstract class AbstractBundleInstallMojo
     private String password;
 
     /**
-     * The startlevel for the uploaded bundle
+     * The startlevel for the uploaded bundle. Only applies when POSTing to
+     * Felix Web Console.
      */
     @Parameter(property="sling.bundle.startlevel", defaultValue = "20", required = true)
     private String bundleStartLevel;
 
     /**
-     * Whether to start the uploaded bundle or not
+     * Whether to start the uploaded bundle or not. Only applies when POSTing
+     * to Felix Web Console
      */
     @Parameter(property="sling.bundle.start", defaultValue = "true", required = true)
     private boolean bundleStart;
 
     /**
-     * Whether to refresh the packages after installing the uploaded bundle
+     * Whether to refresh the packages after installing the uploaded bundle.
+     * Only applies when POSTing to Felix Web Console
      */
     @Parameter(property="sling.refreshPackages", defaultValue = "true", required = true)
     private boolean refreshPackages;
@@ -144,7 +183,7 @@ abstract class AbstractBundleInstallMojo
      */
     @Parameter(defaultValue = "${project}", required = true, readonly = true)
     protected MavenProject project;
-    
+
     public AbstractBundleInstallMojo() {
         super();
     }
@@ -164,13 +203,15 @@ abstract class AbstractBundleInstallMojo
     }
 
     /**
-     * Returns the URL for PUT or DELETE by appending the filename to the
-     * targetURL.
+     * Returns the URL with the filename appended to it.
+     * @param targetURL the original requested targetURL to append fileName to
+     * @param fileName the name of the file to append to the targetURL.
      */
-    protected String getPutURL(String targetURL, String fileName) {
+    protected String getURLWithFilename(String targetURL, String fileName) {
         return targetURL + (targetURL.endsWith("/") ? "" : "/") + fileName;
     }
 
+    @Override
     public void execute() throws MojoExecutionException {
 
         // get the file to upload
@@ -192,14 +233,25 @@ abstract class AbstractBundleInstallMojo
 
         String targetURL = getTargetURL();
 
+        BundleDeploymentMethod deploymentMethod = getDeploymentMethod();
         getLog().info(
             "Installing Bundle " + bundleName + "(" + bundleFile + ") to "
-                + targetURL + " via " + (usePut ? "PUT" : "POST"));
+                + targetURL + " via " + deploymentMethod);
 
-        if (usePut) {
-            put(targetURL, bundleFile);
-        } else {
-            post(targetURL, bundleFile);
+
+        switch (deploymentMethod) {
+        case SlingPostServlet:
+            postToSling(targetURL, bundleFile);
+            break;
+        case WebConsole:
+            postToFelix(targetURL, bundleFile);
+            break;
+        case WebDAV:
+            putViaWebDav(targetURL, bundleFile);
+            break;
+        // sanity check to make sure it gets handled in some fashion
+        default:
+            throw new MojoExecutionException("Unrecognized BundleDeployMethod " + deploymentMethod);
         }
 
         if ( mountByFS ) {
@@ -208,6 +260,23 @@ abstract class AbstractBundleInstallMojo
     }
 
     /**
+     * Retrieve the bundle deployment method matching the configuration.
+     * @return bundle deployment method matching the plugin configuration.
+     */
+    protected BundleDeploymentMethod getDeploymentMethod() throws MojoExecutionException {
+        if (this.deploymentMethod == null) {
+            if (usePut) {
+                getLog().warn("Using deprecated configuration parameter 'usePut=true', please instead use the new parameter 'deploymentMethod=WebDAV'!");
+                return BundleDeploymentMethod.WebDAV;
+            } else {
+                return BundleDeploymentMethod.WebConsole;
+            }
+        } else {
+            return deploymentMethod;
+        }
+    }
+
+    /**
      * Helper method to throw a meaningful exception for an outdated felix
      * web console.
      * @throws MojoExecutionException
@@ -236,7 +305,15 @@ abstract class AbstractBundleInstallMojo
         return client;
     }
 
-    protected void post(String targetURL, File file)
+    /**
+     * Install the bundle via POST to the Felix Web Console
+     * @param targetURL the URL to the Felix Web Console Bundles listing
+     * @param file the file to POST
+     * @throws MojoExecutionException
+     * @see <a href="http://felix.apache.org/documentation/subprojects/apache-felix-web-console/web-console-restful-api.html#post-requests">Webconsole RESTful API</a>
+     * @see <a href="https://github.com/apache/felix/blob/trunk/webconsole/src/main/java/org/apache/felix/webconsole/internal/core/BundlesServlet.java">BundlesServlet@Github</a>
+     */
+    protected void postToFelix(String targetURL, File file)
             throws MojoExecutionException {
 
         // append pseudo path after root URL to not get redirected for nothing
@@ -286,28 +363,87 @@ abstract class AbstractBundleInstallMojo
         }
     }
 
-    protected void put(String targetURL, File file) throws MojoExecutionException {
+    /**
+     * Perform the operation via POST to SlingPostServlet
+     * @param targetURL the URL of the Sling instance to post the file to.
+     * @param file the file being interacted with the POST to Sling.
+     * @throws MojoExecutionException
+     */
+    protected void postToSling(String targetURL, File file) throws MojoExecutionException {
+
+        /* truncate off trailing slash as this has special behaviorisms in
+         * the SlingPostServlet around created node name conventions */
+        if (targetURL.endsWith("/")) {
+            targetURL = targetURL.substring(0, targetURL.length()-1);
+        }
+        // append pseudo path after root URL to not get redirected for nothing
+        final PostMethod filePost = new PostMethod(targetURL);
+
+        try {
+
+            Part[] parts = new Part[2];
+            // Add content type to force the configured mimeType value
+            parts[0] = new FilePart("*", new FilePartSource(
+                file.getName(), file), mimeType, null);
+            // Add TypeHint to have jar be uploaded as file (not as resource)
+            parts[1] = new StringPart("*@TypeHint", "nt:file");
+
+            /* Request JSON response from Sling instead of standard HTML, to
+             * reduce the payload size (if the PostServlet supports it). */
+            filePost.setRequestHeader("Accept", JSON_MIME_TYPE);
+            filePost.setRequestEntity(new MultipartRequestEntity(parts,
+                filePost.getParams()));
+
+            int status = getHttpClient().executeMethod(filePost);
+            // SlingPostServlet may return 200 or 201 on creation, accept both
+            if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) {
+                getLog().info("Bundle installed");
+            } else {
+                String msg = "Installation failed, cause: "
+                    + HttpStatus.getStatusText(status);
+                if (failOnError) {
+                    throw new MojoExecutionException(msg);
+                } else {
+                    getLog().error(msg);
+                }
+            }
+        } catch (Exception ex) {
+            throw new MojoExecutionException("Installation on " + targetURL
+                + " failed, cause: " + ex.getMessage(), ex);
+        } finally {
+            filePost.releaseConnection();
+        }
+    }
+
+    /**
+     * Puts the file via PUT (leveraging WebDAV). Creates the intermediate folders as well.
+     * @param targetURL
+     * @param file
+     * @throws MojoExecutionException
+     * @see <a href="https://tools.ietf.org/html/rfc4918#section-9.7.1">RFC 4918</a>
+     */
+    protected void putViaWebDav(String targetURL, File file) throws MojoExecutionException {
 
         boolean success = false;
         int status;
-        
+
         try {
             status = performPut(targetURL, file);
             if (status >= 200 && status < 300) {
                 success = true;
-            } else if ( status == HttpStatus.SC_CONFLICT) {
-                
+            } else if (status == HttpStatus.SC_CONFLICT) {
+
                 getLog().debug("Bundle not installed due missing parent folders. Attempting to create parent structure.");
                 createIntermediaryPaths(targetURL);
-                
+
                 getLog().debug("Re-attempting bundle install after creating parent folders.");
                 status = performPut(targetURL, file);
                 if (status >= 200 && status < 300) {
                     success = true;
                 }
             }
-            
-            if ( !success ) {
+
+            if (!success) {
                 String msg = "Installation failed, cause: "
                     + HttpStatus.getStatusText(status);
                 if (failOnError) {
@@ -321,10 +457,10 @@ abstract class AbstractBundleInstallMojo
                 + " failed, cause: " + ex.getMessage(), ex);
         }
     }
-    
+
     private int performPut(String targetURL, File file) throws HttpException, IOException {
-        
-        PutMethod filePut = new PutMethod(getPutURL(targetURL, file.getName()));
+
+        PutMethod filePut = new PutMethod(getURLWithFilename(targetURL, file.getName()));
         try {
             filePut.setRequestEntity(new FileRequestEntity(file, mimeType));
             return getHttpClient().executeMethod(filePut);
@@ -334,10 +470,10 @@ abstract class AbstractBundleInstallMojo
     }
 
     private void createIntermediaryPaths(String targetURL) throws HttpException, IOException, MojoExecutionException {
-        
+
         for ( String intermediatePath : IntermediatePathsExtractor.extractIntermediatePaths(targetURL)) {
             getLog().debug("Creating intermediate path at " + intermediatePath);
-            
+
             // verify if the path exists by calling the JSON servlet
             // this should always return a 200 OK status if it exists or a 4040 otherwise
             GetMethod get = new GetMethod(intermediatePath + ".json");
@@ -350,11 +486,11 @@ abstract class AbstractBundleInstallMojo
             } finally {
                 get.releaseConnection();
             }
-            
+
             // create the path if it does not exist
             PostMethod post = new PostMethod(intermediatePath);
             try {
-                post.addParameter(new NameValuePair("jcr:primaryType", intermediatePathPrimaryType)); 
+                post.addParameter(new NameValuePair("jcr:primaryType", intermediatePathPrimaryType));
                 int result = getHttpClient().executeMethod(post);
                 if ( result != HttpStatus.SC_CREATED && result != HttpStatus.SC_OK) {
                     throw new MojoExecutionException("Failed creating intermediate path at " + intermediatePath + "."

Modified: sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/BundleUninstallMojo.java
URL: http://svn.apache.org/viewvc/sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/BundleUninstallMojo.java?rev=1732304&r1=1732303&r2=1732304&view=diff
==============================================================================
--- sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/BundleUninstallMojo.java (original)
+++ sling/trunk/tooling/maven/maven-sling-plugin/src/main/java/org/apache/sling/maven/bundlesupport/BundleUninstallMojo.java Thu Feb 25 13:50:22 2016
@@ -26,6 +26,9 @@ import org.apache.commons.httpclient.Htt
 import org.apache.commons.httpclient.HttpStatus;
 import org.apache.commons.httpclient.methods.DeleteMethod;
 import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.Part;
+import org.apache.commons.httpclient.methods.multipart.StringPart;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
@@ -50,6 +53,7 @@ public class BundleUninstallMojo extends
     /**
      * @see org.apache.maven.plugin.AbstractMojo#execute()
      */
+    @Override
     public void execute() throws MojoExecutionException {
         // only upload if packaging as an osgi-bundle
         final File bundleFile = new File(bundleFileName);
@@ -60,24 +64,34 @@ public class BundleUninstallMojo extends
         }
 
         String targetURL = getTargetURL();
-        
+
+        BundleDeploymentMethod deployMethod = getDeploymentMethod();
         getLog().info(
-            "Unistalling Bundle " + bundleName + ") from "
-                + targetURL + " via " + (usePut ? "DELETE" : "POST"));
-        
+            "Unistalling Bundle " + bundleName + " from "
+                + targetURL + " via " + deployMethod);
+
         configure(targetURL, bundleFile);
-        
-        if (usePut) {
-            delete(targetURL, bundleFile);
-        } else {
-            post(targetURL, bundleName);
+
+        switch (deployMethod) {
+        case SlingPostServlet:
+            postToSling(targetURL, bundleFile);
+            break;
+        case WebConsole:
+            postToFelix(targetURL, bundleName);
+            break;
+        case WebDAV:
+            deleteViaWebDav(targetURL, bundleFile);
+            break;
+        // sanity check to make sure it gets handled in some fashion
+        default:
+            throw new MojoExecutionException("Unrecognized BundleDeployMethod " + deployMethod);
         }
     }
 
-    protected void delete(String targetURL, File file)
+    protected void deleteViaWebDav(String targetURL, File file)
         throws MojoExecutionException {
-        
-        final DeleteMethod delete = new DeleteMethod(getPutURL(targetURL, file.getName()));
+
+        final DeleteMethod delete = new DeleteMethod(getURLWithFilename(targetURL, file.getName()));
 
         try {
 
@@ -97,7 +111,38 @@ public class BundleUninstallMojo extends
         }
     }
 
-    protected void post(String targetURL, String symbolicName)
+    @Override
+    protected void postToSling(String targetURL, File file)
+        throws MojoExecutionException {
+        final PostMethod post = new PostMethod(getURLWithFilename(targetURL, file.getName()));
+
+        try {
+            // Add SlingPostServlet operation flag for deleting the content
+            Part[] parts = new Part[1];
+            parts[0] = new StringPart(":operation", "delete");
+            post.setRequestEntity(new MultipartRequestEntity(parts,
+                    post.getParams()));
+
+            // Request JSON response from Sling instead of standard HTML
+            post.setRequestHeader("Accept", JSON_MIME_TYPE);
+
+            int status = getHttpClient().executeMethod(post);
+            if (status == HttpStatus.SC_OK) {
+                getLog().info("Bundle uninstalled");
+            } else {
+                getLog().error(
+                    "Uninstall failed, cause: "
+                        + HttpStatus.getStatusText(status));
+            }
+        } catch (Exception ex) {
+            throw new MojoExecutionException("Uninstall from " + targetURL
+                + " failed, cause: " + ex.getMessage(), ex);
+        } finally {
+            post.releaseConnection();
+        }
+    }
+
+    protected void postToFelix(String targetURL, String symbolicName)
     throws MojoExecutionException {
         final PostMethod post = new PostMethod(targetURL + "/bundles/" + symbolicName);
         post.addParameter("action", "uninstall");
@@ -126,6 +171,7 @@ public class BundleUninstallMojo extends
      * @param file The artifact (bundle)
      * @throws MojoExecutionException
      */
+    @Override
     protected void configure(String targetURL, File file)
     throws MojoExecutionException {
         getLog().info("Removing file system provider configurations...");