/*
* #%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;
}
}
}