org.wisdom.resources.WebJarDeployer.java Source code

Java tutorial

Introduction

Here is the source code for org.wisdom.resources.WebJarDeployer.java

Source

/*
 * #%L
 * Wisdom-Framework
 * %%
 * Copyright (C) 2013 - 2014 Wisdom Framework
 * %%
 * Licensed 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.
 * #L%
 */
package org.wisdom.resources;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.ow2.chameleon.core.services.Deployer;
import org.ow2.chameleon.core.services.ExtensionBasedDeployer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.zip.ZipEntry;

/**
 * Tracks the webjars from the 'hot' directories (managed by Chameleon) and manage them.
 */
public class WebJarDeployer extends ExtensionBasedDeployer {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebJarDeployer.class);

    private final BundleContext context;

    private final File cache;
    private final WebJarController controller;

    private Set<FileWebJarLib> libs = new LinkedHashSet<>();
    private ServiceRegistration<Deployer> reg;

    /**
     * Creates an install of the {@link org.wisdom.resources.WebJarDeployer}.
     *
     * @param context          the bundle context
     * @param webJarController the instance of controller in which libraries are added and removed
     */
    public WebJarDeployer(BundleContext context, WebJarController webJarController) {
        super("jar");
        this.context = context;
        this.controller = webJarController;
        cache = context.getBundle().getDataFile("webjars");
        if (!cache.isDirectory()) {
            boolean made = cache.mkdirs();
            LOGGER.debug("Creating webjars directory : {}", made);
        }
    }

    /**
     * A new file was created. It checks whether or not this file contained web jar libraries,
     * and if so proceed to their installation (i.e. un-packaking and indexation).
     *
     * @param file the file
     */
    @Override
    public synchronized void onFileCreate(File file) {
        final Set<DetectedWebJar> listOfDetectedWebJarLib = isWebJar(file);
        if (listOfDetectedWebJarLib != null) {
            JarFile jar = null;
            try {
                jar = new JarFile(file);
                List<FileWebJarLib> installed = new ArrayList<>();
                for (DetectedWebJar detected : listOfDetectedWebJarLib) {
                    FileWebJarLib lib = expand(detected, jar);
                    if (lib != null) {
                        libs.add(lib);
                        installed.add(lib);
                        LOGGER.info("{} unpacked to {}", lib.name, lib.root.getAbsolutePath());
                    }
                }
                controller.addWebJarLibs(installed);
            } catch (IOException e) {
                LOGGER.error("Cannot open the jar file {}", file.getAbsolutePath(), e);
            } finally {
                IOUtils.closeQuietly(jar);
            }

        }
    }

    private FileWebJarLib expand(DetectedWebJar lib, JarFile jar) {
        File out = new File(cache, lib.id);
        Enumeration<? extends ZipEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            if (entry.getName().startsWith(WebJarController.WEBJAR_LOCATION) && !entry.isDirectory()) {
                // Compute destination.
                File output = new File(out, entry.getName().substring(WebJarController.WEBJAR_LOCATION.length()));
                InputStream stream = null;
                try {
                    stream = jar.getInputStream(entry);
                    boolean made = output.getParentFile().mkdirs();
                    LOGGER.debug("{} directory created : {} ", output.getParentFile().getAbsolutePath(), made);
                    FileUtils.copyInputStreamToFile(stream, output);
                } catch (IOException e) {
                    LOGGER.error("Cannot unpack " + entry.getName() + " from " + lib.file.getName(), e);
                    return null;
                } finally {
                    IOUtils.closeQuietly(stream);
                }
            }
        }
        File root = new File(out, lib.name + "/" + lib.version);
        return new FileWebJarLib(lib.name, lib.version, root, lib.file.getName());
    }

    /**
     * An accepted file was updated.
     * This methods acts as a file removal and creation.
     *
     * @param file the file
     */
    @Override
    public synchronized void onFileChange(File file) {
        onFileDelete(file);
        onFileCreate(file);
    }

    /**
     * An accepted file was deleted. We remove the contained libraries from the list and delete the directory in
     * which the library was contained.
     * <p>
     * We can't open it to find the contained web jars,
     * to we need to use the 'source' we set when the {@link org.wisdom.resources.FileWebJarLib} instances were created.
     *
     * @param file the file
     */
    @Override
    public synchronized void onFileDelete(File file) {
        // The file is already deleted, so we can't open it.
        // So we use the source.
        Set<FileWebJarLib> copy = new LinkedHashSet<>(libs);
        List<FileWebJarLib> toRemove = new ArrayList<>();
        for (FileWebJarLib lib : copy) {
            if (lib.source != null && lib.source.equals(file.getName())) {
                // Found, remove it.
                libs.remove(lib);
                toRemove.add(lib);
                // Delete the directory
                FileUtils.deleteQuietly(lib.root);
            }
        }

        controller.removeWebJarLibs(toRemove);
    }

    /**
     * Checks whether the given file is a WebJar or not (http://www.webjars.org/documentation).
     * The check is based on the presence of {@literal META-INF/resources/webjars/} directory in the jar file.
     *
     * @param file the file.
     * @return the set of libraries found in the file, {@code null} if none.
     */
    public static Set<DetectedWebJar> isWebJar(File file) {
        Set<DetectedWebJar> found = new LinkedHashSet<>();
        if (file.isFile() && file.getName().endsWith(".jar")) {
            JarFile jar = null;
            try {
                jar = new JarFile(file);

                // Fast return if the base structure is not there
                if (jar.getEntry(WebJarController.WEBJAR_LOCATION) == null) {
                    return null;
                }

                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    Matcher matcher = WebJarController.WEBJAR_REGEX.matcher(entry.getName());
                    if (matcher.matches()) {
                        found.add(new DetectedWebJar(matcher.group(1), matcher.group(2), entry.getName(), file));
                    }
                }
            } catch (IOException e) {
                LOGGER.error("Cannot check if the file {} is a webjar, " + "cannot open it", file.getName(), e);
                return null;
            } finally {
                final JarFile finalJar = jar;
                IOUtils.closeQuietly(new Closeable() {
                    @Override
                    public void close() throws IOException {
                        if (finalJar != null) {
                            finalJar.close();
                        }
                    }
                });
            }

            for (DetectedWebJar lib : found) {
                LOGGER.info("Web Library found in {} : {}", file.getName(), lib.id);
            }

            return found;
        }

        return null;
    }

    /**
     * Registers the deployer service.
     */
    public synchronized void start() {
        reg = context.registerService(Deployer.class, this, null);
    }

    /**
     * Un-registers the deployer service.
     */
    public synchronized void stop() {
        if (reg != null) {
            reg.unregister();
        }
    }

    /**
     * A structure holding a webjar found in a jar file.
     * Don't forget that a single jar file can contain any number of libraries.
     */
    private static final class DetectedWebJar {

        /**
         * The id computed as follows: {@code name-version}.
         */
        public final String id;
        /**
         * the name of the library.
         */
        public final String name;
        /**
         * the version of the library.
         */
        public final String version;
        /**
         * the path within the jar file.
         */
        public final String path;
        /**
         * the file containing the library.
         */
        public final File file;

        /**
         * Creates an instance of {@link org.wisdom.resources.WebJarDeployer.DetectedWebJar}.
         *
         * @param name    the name
         * @param version the version
         * @param path    the path
         * @param file    the file
         */
        private DetectedWebJar(String name, String version, String path, File file) {
            this.name = name;
            this.version = version;
            this.path = path;
            this.id = name + "-" + version;
            this.file = file;
        }

        /**
         * Two {@link org.wisdom.resources.WebJarDeployer.DetectedWebJar} instances are equal if they have the same id.
         *
         * @param obj the object to compare with.
         * @return {@code true} if the two compared objects have the same id, {@code false} otherwise.
         */
        @Override
        public boolean equals(Object obj) {
            // We don't use instanceOf because we don't need inheritance (the class if final).
            return obj != null // Check not null
                    && obj.getClass().equals(DetectedWebJar.class) // Check it's the right class
                    && id.equals(((DetectedWebJar) obj).id); // Check id
        }

        /**
         * Computes the hash code of the current instance.
         *
         * @return the hash code
         */
        @Override
        public int hashCode() {
            int result = id.hashCode();
            result = 31 * result + name.hashCode();
            result = 31 * result + version.hashCode();
            return result;
        }
    }
}