SLING-3019 - Provide a mechanism to install a bundle based on a
directory
Allow updating a bundle from an uploaded jar file.
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1534645 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 0ec7b01..99d5b37 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,5 +99,11 @@
<version>2.4</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>commons-fileupload</groupId>
+ <artifactId>commons-fileupload</artifactId>
+ <version>1.2.2</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
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 765eda7..47b3bc0 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
@@ -22,7 +22,9 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.Deflater;
@@ -38,13 +40,20 @@
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
+import org.osgi.service.packageadmin.PackageAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+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;
/**
* Prototype for installing/updating a bundle from a directory
@@ -60,8 +69,13 @@
private static final String DIR = "dir";
+ private static final int UPLOAD_IN_MEMORY_SIZE_THRESHOLD = 512 * 1024 * 1024;
+
private BundleContext bundleContext;
+ @Reference
+ private PackageAdmin packageAdmin;
+
@Activate
protected void activate(final BundleContext bundleContext) {
this.bundleContext = bundleContext;
@@ -71,16 +85,98 @@
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
final String dirPath = req.getParameter(DIR);
- if ( dirPath == null ) {
- logger.error("No dir parameter specified : {}", req.getParameterMap());
+
+ boolean isMultipart = ServletFileUpload.isMultipartContent(req);
+
+ if (dirPath == null && !isMultipart) {
+ logger.error("No dir parameter specified : {} and no multipart content found", req.getParameterMap());
resp.setStatus(500);
InstallationResult result = new InstallationResult(false, "No dir parameter specified: "
- + req.getParameterMap());
+ + req.getParameterMap() + " and no multipart content found");
result.render(resp.getWriter());
return;
}
- final File dir = new File(dirPath);
- installBasedOnDirectory(resp, dir);
+
+ if (isMultipart) {
+ installBasedOnUploadedJar(req, resp);
+ } else {
+ installBasedOnDirectory(resp, new File(dirPath));
+ }
+ }
+
+ private void installBasedOnUploadedJar(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+
+ 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;
+ }
+
+ FileItem item = items.get(0);
+
+ JarInputStream jar = null;
+ InputStream rawInput = null;
+ try {
+ jar = new JarInputStream(item.getInputStream());
+ 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);
+ if (symbolicName == null) {
+ logAndWriteError("Manifest does not have a " + Constants.BUNDLE_SYMBOLICNAME, resp);
+ return;
+ }
+
+ // 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, null);
+
+ 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);
+ }
+
+ } catch (FileUploadException e) {
+ logAndWriteError("Failed parsing uploaded bundle", e, resp);
+ return;
+ }
+ }
+
+ private void logAndWriteError(String message, HttpServletResponse resp) throws IOException {
+ logger.info(message);
+ resp.setStatus(500);
+ new InstallationResult(false, message).render(resp.getWriter());
+ }
+
+ private void logAndWriteError(String message, Exception e, HttpServletResponse resp) throws IOException {
+ logger.info(message, e);
+ resp.setStatus(500);
+ new InstallationResult(false, message + " : " + e.getMessage()).render(resp.getWriter());
}
private void installBasedOnDirectory(HttpServletResponse resp, final File dir) throws FileNotFoundException,
@@ -100,13 +196,7 @@
final String symbolicName = mf.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
if ( symbolicName != null ) {
// search bundle
- Bundle found = null;
- for(final Bundle b : this.bundleContext.getBundles() ) {
- if ( symbolicName.equals(b.getSymbolicName()) ) {
- found = b;
- break;
- }
- }
+ Bundle found = getBundle(symbolicName);
final File tempFile = File.createTempFile(dir.getName(), "bundle");
try {
@@ -114,14 +204,9 @@
final InputStream in = new FileInputStream(tempFile);
try {
- if ( found != null ) {
- // update
- found.update(in);
- } else {
- // install
- final Bundle b = bundleContext.installBundle(dir.getAbsolutePath(), in);
- b.start();
- }
+ String location = dir.getAbsolutePath();
+
+ installOrUpdateBundle(found, in, location);
result = new InstallationResult(true, null);
resp.setStatus(200);
result.render(resp.getWriter());
@@ -157,6 +242,30 @@
}
}
+ private void installOrUpdateBundle(Bundle bundle, final InputStream in, String location) throws BundleException {
+ if (bundle != null) {
+ // update
+ bundle.update(in);
+
+ packageAdmin.refreshPackages(new Bundle[] { bundle });
+ } else {
+ // install
+ final Bundle b = bundleContext.installBundle(location, in);
+ b.start();
+ }
+ }
+
+ private Bundle getBundle(final String symbolicName) {
+ Bundle found = null;
+ for (final Bundle b : this.bundleContext.getBundles()) {
+ if (symbolicName.equals(b.getSymbolicName())) {
+ found = b;
+ break;
+ }
+ }
+ 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));