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 2022/11/11 19:17:23 UTC

[sling-org-apache-sling-tooling-support-install] 01/01: SLING-11672 Migrate from Felix HTTP Whiteboard to OSGi Whiteboard

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/osgi-http-whiteboard
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-tooling-support-install.git

commit a54ece9bfe6c3406e7a58551c223551fbc12f360
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Fri Nov 11 20:17:17 2022 +0100

    SLING-11672 Migrate from Felix HTTP Whiteboard to OSGi Whiteboard
    
    Update dependencies
    Make refresh packages optional
---
 pom.xml                                            |  28 ++-
 .../support/install/impl/InstallServlet.java       | 233 +++++++++------------
 .../support/install/impl/InstallationResult.java   |   5 +-
 3 files changed, 115 insertions(+), 151 deletions(-)

diff --git a/pom.xml b/pom.xml
index 87737b9..d169fb4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling-bundle-parent</artifactId>
-        <version>48</version>
+        <version>49</version>
     </parent>
 
     <artifactId>org.apache.sling.tooling.support.install</artifactId>
@@ -37,11 +37,12 @@
         <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-tooling-support-install.git</url>
         <tag>HEAD</tag>
     </scm>
-    
+
     <dependencies>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
@@ -50,7 +51,12 @@
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>osgi.core</artifactId>
+            <artifactId>org.osgi.framework</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.http.whiteboard</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -64,22 +70,12 @@
             <version>2.4</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-            <version>3.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>commons-fileupload</groupId>
-            <artifactId>commons-fileupload</artifactId>
-            <version>1.2.2</version>
-            <scope>provided</scope>
-        </dependency>
+        
+        <!-- embedded dependency -->
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.utils</artifactId>
-            <version>1.9.0</version>
+            <version>1.11.8</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>
diff --git a/src/main/java/org/apache/sling/tooling/support/install/impl/InstallServlet.java b/src/main/java/org/apache/sling/tooling/support/install/impl/InstallServlet.java
index 9501f9a..9c25c2e 100644
--- a/src/main/java/org/apache/sling/tooling/support/install/impl/InstallServlet.java
+++ b/src/main/java/org/apache/sling/tooling/support/install/impl/InstallServlet.java
@@ -16,17 +16,20 @@
  */
 package org.apache.sling.tooling.support.install.impl;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.List;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.jar.JarFile;
 import java.util.jar.JarInputStream;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
+import java.util.stream.Stream;
 import java.util.zip.Deflater;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -36,31 +39,28 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
 
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.BoundedInputStream;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
+import org.osgi.framework.wiring.FrameworkWiring;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletMultipart;
+import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Prototype for installing/updating a bundle from a directory
+ * ReST endpoint for installing/updating a bundle from a directory or a JAR
  */
-@Component(service = Servlet.class,
-    property = {
-            Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
-            "alias=/system/sling/tooling/install"
-    })
+@Component(service = Servlet.class)
+@HttpWhiteboardServletPattern("/system/sling/tooling/install")
+@HttpWhiteboardServletMultipart(enabled = true, fileSizeThreshold = InstallServlet.UPLOAD_IN_MEMORY_SIZE_THRESHOLD)
 public class InstallServlet extends HttpServlet {
 
     private static final long serialVersionUID = -8820366266126231409L;
@@ -69,15 +69,13 @@ public class InstallServlet extends HttpServlet {
 
     private static final String DIR = "dir";
 
-    private static final int UPLOAD_IN_MEMORY_SIZE_THRESHOLD = 512 * 1024 * 1024;
+    public static final int UPLOAD_IN_MEMORY_SIZE_THRESHOLD = 512 * 1024 * 1024;
+    public static final int MANIFEST_SIZE_IN_INPUTSTREAM = 2 * 1024 * 1024;
 
-    private BundleContext bundleContext;
-
-    @Reference
-    private PackageAdmin packageAdmin;
+    private final BundleContext bundleContext;
 
     @Activate
-    protected void activate(final BundleContext bundleContext) {
+    public InstallServlet(final BundleContext bundleContext) {
         this.bundleContext = bundleContext;
     }
 
@@ -85,9 +83,8 @@ public class InstallServlet extends HttpServlet {
     protected void doPost(HttpServletRequest req, HttpServletResponse resp)
             throws ServletException, IOException {
         final String dirPath = req.getParameter(DIR);
-
-        boolean isMultipart = ServletFileUpload.isMultipartContent(req);
-
+        final boolean refreshPackages = Boolean.parseBoolean(req.getParameter(dirPath));
+        boolean isMultipart = req.getContentType() != null && req.getContentType().toLowerCase().indexOf("multipart/form-data") > -1;
         if (dirPath == null && !isMultipart) {
             logger.error("No dir parameter specified : {} and no multipart content found", req.getParameterMap());
             resp.setStatus(500);
@@ -96,77 +93,57 @@ public class InstallServlet extends HttpServlet {
             result.render(resp.getWriter());
             return;
         }
-
         if (isMultipart) {
-            installBasedOnUploadedJar(req, resp);
+            installBasedOnUploadedJar(req, resp, refreshPackages);
         } else {
-            installBasedOnDirectory(resp, new File(dirPath));
+            installBasedOnDirectory(resp, Paths.get(dirPath), refreshPackages);
         }
     }
 
-    private void installBasedOnUploadedJar(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    private void installBasedOnUploadedJar(HttpServletRequest req, HttpServletResponse resp, boolean refreshPackages) throws IOException, ServletException {
 
         InstallationResult result = null;
 
-        try {
-            DiskFileItemFactory factory = new DiskFileItemFactory();
-            // try to hold even largish bundles in memory to potentially improve performance
-            factory.setSizeThreshold(UPLOAD_IN_MEMORY_SIZE_THRESHOLD);
-
-            ServletFileUpload upload = new ServletFileUpload();
-            upload.setFileItemFactory(factory);
-
-            @SuppressWarnings("unchecked")
-            List<FileItem> items = upload.parseRequest(req);
-            if (items.size() != 1) {
-                logAndWriteError("Found " + items.size() + " items to process, but only updating 1 bundle is supported", resp);
-                return;
-            }
+        Collection<Part> parts = req.getParts();
+        if (parts.size() != 1) {
+            logAndWriteError("Found " + parts.size() + " items to process, but only updating 1 bundle is supported", resp);
+            return;
+        }
 
-            FileItem item = items.get(0);
+        Part part = parts.iterator().next();
 
-            JarInputStream jar = null;
-            InputStream rawInput = null;
-            try {
-                jar = new JarInputStream(item.getInputStream());
+        try (InputStream input = new BufferedInputStream(part.getInputStream(), MANIFEST_SIZE_IN_INPUTSTREAM)) {
+            input.mark(MANIFEST_SIZE_IN_INPUTSTREAM);
+            final String version;
+            final String symbolicName;
+            try (JarInputStream jar = new JarInputStream(new BoundedInputStream(input, MANIFEST_SIZE_IN_INPUTSTREAM))) {
+                
                 Manifest manifest = jar.getManifest();
                 if (manifest == null) {
                     logAndWriteError("Uploaded jar file does not contain a manifest", resp);
                     return;
                 }
-
-                final String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-
+                symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
                 if (symbolicName == null) {
                     logAndWriteError("Manifest does not have a " + Constants.BUNDLE_SYMBOLICNAME, resp);
                     return;
                 }
+                version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+            }
+            // go back to beginning of input stream
+            // the JarInputStream is used only for validation, we need a fresh input stream for updating
+            input.reset();
 
-                final String version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-
-                // the JarInputStream is used only for validation, we need a fresh input stream for updating
-                rawInput = item.getInputStream();
-
-                Bundle found = getBundle(symbolicName);
-                try {
-                    installOrUpdateBundle(found, rawInput, "inputstream:" + symbolicName + "-" + version + ".jar");
+            Bundle found = getBundle(symbolicName);
+            try {
+                installOrUpdateBundle(found, input, "inputstream:" + symbolicName + "-" + version + ".jar", refreshPackages);
 
-                    result = new InstallationResult(true, null);
-                    resp.setStatus(200);
-                    result.render(resp.getWriter());
-                    return;
-                } catch (BundleException e) {
-                    logAndWriteError("Unable to install/update bundle " + symbolicName, e, resp);
-                    return;
-                }
-            } finally {
-                IOUtils.closeQuietly(jar);
-                IOUtils.closeQuietly(rawInput);
+                result = new InstallationResult(true, null);
+                resp.setStatus(200);
+                result.render(resp.getWriter());
+            } catch (BundleException e) {
+                logAndWriteError("Unable to install/update bundle " + symbolicName, e, resp);
             }
-
-        } catch (FileUploadException e) {
-            logAndWriteError("Failed parsing uploaded bundle", e, resp);
-            return;
         }
     }
 
@@ -182,49 +159,39 @@ public class InstallServlet extends HttpServlet {
         new InstallationResult(false, message + " : " + e.getMessage()).render(resp.getWriter());
     }
 
-    private void installBasedOnDirectory(HttpServletResponse resp, final File dir) throws FileNotFoundException,
-            IOException {
-
+    private void installBasedOnDirectory(HttpServletResponse resp, final Path dir, boolean refreshPackages) throws IOException {
         InstallationResult result = null;
 
-        if ( dir.exists() && dir.isDirectory() ) {
+        if (Files.isDirectory(dir)) {
             logger.info("Checking dir {} for bundle install", dir);
-            final File manifestFile = new File(dir, JarFile.MANIFEST_NAME);
-            if ( manifestFile.exists() ) {
-                FileInputStream fis = null;
-                try {
-                    fis = new FileInputStream(manifestFile);
+            final Path manifestFile = dir.resolve(JarFile.MANIFEST_NAME);
+            if (Files.exists(dir)) {
+                try (InputStream fis = Files.newInputStream(manifestFile)) {
                     final Manifest mf = new Manifest(fis);
 
                     final String symbolicName = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-                    if ( symbolicName != null ) {
+                    if (symbolicName != null) {
                         // search bundle
                         Bundle found = getBundle(symbolicName);
 
-                        final File tempFile = File.createTempFile(dir.getName(), "bundle");
+                        Path tmpJarFile = Files.createTempFile(dir.getFileName().toString(), "bundle");
                         try {
-                            createJar(dir, tempFile, mf);
-
-                            final InputStream in = new FileInputStream(tempFile);
-                            try {
-                                String location = dir.getAbsolutePath();
-
-                                installOrUpdateBundle(found, in, location);
+                            createJar(dir, tmpJarFile, mf);
+                            try(InputStream in = Files.newInputStream(tmpJarFile)) {
+                                String location = dir.toAbsolutePath().toString();
+                                installOrUpdateBundle(found, in, location, refreshPackages);
                                 result = new InstallationResult(true, null);
                                 resp.setStatus(200);
                                 result.render(resp.getWriter());
-                                return;
-                            } catch ( final BundleException be ) {
+                            } catch (final BundleException be) {
                                 logAndWriteError("Unable to install/update bundle from dir " + dir, be, resp);
                             }
                         } finally {
-                            tempFile.delete();
+                            Files.delete(tmpJarFile);
                         }
                     } else {
                         logAndWriteError("Manifest in " + dir + " does not have a symbolic name", resp);
                     }
-                } finally {
-                    IOUtils.closeQuietly(fis);
                 }
             } else {
                 result = new InstallationResult(false, "Dir " + dir + " does not have a manifest");
@@ -236,7 +203,7 @@ public class InstallServlet extends HttpServlet {
         }
     }
 
-    private void installOrUpdateBundle(Bundle bundle, final InputStream in, String location) throws BundleException {
+    private void installOrUpdateBundle(Bundle bundle, final InputStream in, String location, boolean refreshPackages) throws BundleException {
         if (bundle != null) {
             // update
             bundle.update(in);
@@ -245,10 +212,16 @@ public class InstallServlet extends HttpServlet {
             final Bundle b = bundleContext.installBundle(location, in);
             b.start();
         }
+        if (refreshPackages) {
+            refreshBundle( bundle );
+        }
+    }
 
+    private void refreshBundle(Bundle bundle) {
+        FrameworkWiring frameworkWiring = bundleContext.getBundle(Constants.SYSTEM_BUNDLE_ID).adapt(FrameworkWiring.class);
         // take into account added/removed packages for updated bundles and newly satisfied optional package imports
         // for new installed bundles
-        packageAdmin.refreshPackages(new Bundle[] { bundle });
+        frameworkWiring.refreshBundles(Collections.singleton(bundle));
     }
 
     private Bundle getBundle(final String symbolicName) {
@@ -262,10 +235,8 @@ public class InstallServlet extends HttpServlet {
         return found;
     }
 
-    private static void createJar(final File sourceDir, final File jarFile, final Manifest mf)
-    throws IOException {
-        final JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarFile));
-        try {
+    private static void createJar(final Path sourceDir, final Path jarFile, final Manifest mf) throws IOException {
+        try (JarOutputStream zos = new JarOutputStream(Files.newOutputStream(jarFile))) {
             zos.setLevel(Deflater.NO_COMPRESSION);
             // manifest first
             final ZipEntry anEntry = new ZipEntry(JarFile.MANIFEST_NAME);
@@ -273,38 +244,36 @@ public class InstallServlet extends HttpServlet {
             mf.write(zos);
             zos.closeEntry();
             zipDir(sourceDir, zos, "");
-        } finally {
-            try {
-                zos.close();
-            } catch ( final IOException ignore ) {
-                // ignore
-            }
         }
     }
 
-    public static void zipDir(final File sourceDir, final ZipOutputStream zos, final String path)
-    throws IOException {
-        final byte[] readBuffer = new byte[8192];
-        int bytesIn = 0;
+    public static void zipDir(final Path sourceDir, final ZipOutputStream zos, final String prefix) throws IOException {
+        try (Stream<Path> stream = Files.list(sourceDir)) {
+            stream.forEach(p -> 
+            {
+                try {
+                    zipFileOrDir(p, zos, prefix);
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            });
+        } catch (UncheckedIOException ioe) {
+            throw ioe.getCause();
+        }
+    }
 
-        for(final File f : sourceDir.listFiles()) {
-            if (f.isDirectory()) {
-                final String prefix = path + f.getName() + "/";
-                zos.putNextEntry(new ZipEntry(prefix));
-                zipDir(f, zos, prefix);
-            } else {
-                final String entry = path + f.getName();
-                if ( !JarFile.MANIFEST_NAME.equals(entry) ) {
-                    final FileInputStream fis = new FileInputStream(f);
-                    try {
-                        final ZipEntry anEntry = new ZipEntry(entry);
-                        zos.putNextEntry(anEntry);
-                        while ( (bytesIn = fis.read(readBuffer)) != -1) {
-                            zos.write(readBuffer, 0, bytesIn);
-                        }
-                    } finally {
-                        fis.close();
-                    }
+    private static void zipFileOrDir(final Path sourceFileOrDir, final ZipOutputStream zos, final String prefix) throws IOException {
+        if (Files.isDirectory(sourceFileOrDir)) {
+            final String newPrefix = prefix + sourceFileOrDir.getFileName() + "/";
+            zos.putNextEntry(new ZipEntry(newPrefix));
+            zipDir(sourceFileOrDir, zos, newPrefix);
+        } else {
+            final String entry = prefix + sourceFileOrDir.getFileName();
+            if (!JarFile.MANIFEST_NAME.equals(entry)) {
+                try (InputStream fis = Files.newInputStream(sourceFileOrDir)) {
+                    final ZipEntry anEntry = new ZipEntry(entry);
+                    zos.putNextEntry(anEntry);
+                    IOUtils.copy(fis, zos);
                 }
             }
         }
diff --git a/src/main/java/org/apache/sling/tooling/support/install/impl/InstallationResult.java b/src/main/java/org/apache/sling/tooling/support/install/impl/InstallationResult.java
index 25f9924..d97894f 100644
--- a/src/main/java/org/apache/sling/tooling/support/install/impl/InstallationResult.java
+++ b/src/main/java/org/apache/sling/tooling/support/install/impl/InstallationResult.java
@@ -19,13 +19,12 @@ package org.apache.sling.tooling.support.install.impl;
 import java.io.IOException;
 import java.io.Writer;
 
-import org.apache.commons.lang3.StringUtils;
 import org.apache.felix.utils.json.JSONWriter;
 
 public class InstallationResult {
 
     private final boolean status;
-    private final String message;
+    private final String message; // may be null
 
     public InstallationResult(boolean status, String message) {
         this.status = status;
@@ -38,7 +37,7 @@ public class InstallationResult {
             JSONWriter writer = new JSONWriter(out);
             writer.object();
             writer.key("status").value(status ? "OK" : "FAILURE");
-            if (!StringUtils.isEmpty(message)) {
+            if (message != null) {
                 writer.key("message").value(message);
             }
             writer.endObject();