| /* |
| * 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 org.apache.sling.tooling.support.install.impl; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UncheckedIOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Collections; |
| import java.util.jar.JarFile; |
| 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; |
| |
| import javax.servlet.Servlet; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| 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.http.whiteboard.HttpWhiteboardConstants; |
| import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardContextSelect; |
| import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * ReST endpoint for installing/updating a bundle from a directory |
| */ |
| @Component(service = Servlet.class) |
| @HttpWhiteboardServletPattern("/system/sling/tooling/install") |
| //choose another servlet context with a higher ranking than the Sling one (https://issues.apache.org/jira/browse/SLING-11677) |
| @HttpWhiteboardContextSelect("(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=org.osgi.service.http)") |
| public class InstallServlet extends HttpServlet { |
| |
| private static final long serialVersionUID = -8820366266126231409L; |
| |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| private static final String DIR = "dir"; |
| |
| private final BundleContext bundleContext; |
| |
| @Activate |
| public InstallServlet(final BundleContext bundleContext) { |
| this.bundleContext = bundleContext; |
| } |
| |
| @Override |
| protected void doPost(HttpServletRequest req, HttpServletResponse resp) |
| throws ServletException, IOException { |
| final String dirPath = req.getParameter(DIR); |
| final boolean refreshPackages = Boolean.parseBoolean(req.getParameter(dirPath)); |
| try { |
| if (dirPath == null) { |
| logger.error("No mandatory dir parameter specified"); |
| resp.setStatus(500); |
| InstallationResult result = new InstallationResult(false, "No mandatory dir parameter specified"); |
| result.render(resp.getWriter()); |
| return; |
| } |
| installBundleFromDirectory(resp, Paths.get(dirPath), refreshPackages); |
| } catch (IOException e) { |
| throw new ServletException(e); |
| } |
| } |
| |
| private void logAndWriteError(Exception e, HttpServletResponse resp) throws IOException { |
| logger.info(e.getMessage(), e); |
| resp.setStatus(500); |
| new InstallationResult(false, e.getMessage()).render(resp.getWriter()); |
| } |
| |
| private void installBundleFromDirectory(HttpServletResponse resp, final Path dir, boolean refreshPackages) throws IOException { |
| try { |
| installBundleFromDirectory(dir, refreshPackages); |
| InstallationResult result = new InstallationResult(true, null); |
| resp.setStatus(200); |
| result.render(resp.getWriter()); |
| } catch (IllegalArgumentException e) { |
| logAndWriteError(e, resp); |
| } |
| } |
| |
| /** |
| * |
| * @param dir |
| * @param refreshPackages |
| * @throws IOException |
| * @throws IllegalArgumentException if the provided directory does not contain a valid exploded OSGi bundle |
| */ |
| Bundle installBundleFromDirectory(final Path dir, boolean refreshPackages) throws IOException { |
| if (Files.isDirectory(dir)) { |
| logger.info("Checking dir {} for bundle install", dir); |
| 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) { |
| // search bundle |
| Bundle found = getBundle(symbolicName); |
| |
| Path tmpJarFile = Files.createTempFile(dir.getFileName().toString(), "bundle"); |
| try { |
| createJar(dir, tmpJarFile, mf); |
| try (InputStream in = Files.newInputStream(tmpJarFile)) { |
| String location = dir.toAbsolutePath().toString(); |
| return installOrUpdateBundle(found, in, location, refreshPackages); |
| } catch (final BundleException be) { |
| throw new IllegalArgumentException("Unable to install/update bundle from dir " + dir, be); |
| } |
| } finally { |
| Files.delete(tmpJarFile); |
| } |
| } else { |
| throw new IllegalArgumentException("Manifest in " + dir + " does not have a symbolic name"); |
| } |
| } |
| } else { |
| throw new IllegalArgumentException("Dir " + dir + " does not have a manifest"); |
| } |
| } else { |
| throw new IllegalArgumentException("Dir " + dir + " does not exist"); |
| } |
| } |
| |
| private Bundle installOrUpdateBundle(Bundle bundle, final InputStream in, String location, boolean refreshPackages) throws BundleException { |
| if (bundle != null) { |
| // update |
| bundle.update(in); |
| } else { |
| // install |
| bundle = bundleContext.installBundle(location, in); |
| bundle.start(); |
| } |
| if (refreshPackages) { |
| refreshBundle(bundle); |
| } |
| return 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 |
| frameworkWiring.refreshBundles(Collections.singleton(bundle)); |
| } |
| |
| 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 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); |
| zos.putNextEntry(anEntry); |
| mf.write(zos); |
| zos.closeEntry(); |
| zipDir(sourceDir, zos, ""); |
| } |
| } |
| |
| 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(); |
| } |
| } |
| |
| 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)) { |
| final ZipEntry anEntry = new ZipEntry(entry); |
| zos.putNextEntry(anEntry); |
| Files.copy(sourceFileOrDir, zos); |
| } |
| } |
| } |
| } |