| /* |
| * 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.jackrabbit.vault.packaging.impl; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.Enumeration; |
| import java.util.Properties; |
| import java.util.TreeMap; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.jackrabbit.vault.fs.api.VaultInputSource; |
| import org.apache.jackrabbit.vault.fs.io.Archive; |
| import org.apache.jackrabbit.vault.packaging.InstallContext; |
| import org.apache.jackrabbit.vault.packaging.InstallContext.Phase; |
| import org.apache.jackrabbit.vault.packaging.InstallHook; |
| import org.apache.jackrabbit.vault.packaging.InstallHookProcessor; |
| import org.apache.jackrabbit.vault.packaging.PackageException; |
| import org.apache.jackrabbit.vault.packaging.VaultPackage; |
| import org.apache.jackrabbit.vault.util.Constants; |
| import org.apache.jackrabbit.vault.util.Text; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * processor for install hooks |
| */ |
| public class InstallHookProcessorImpl implements InstallHookProcessor { |
| |
| /** |
| * default logger |
| */ |
| private static final Logger log = LoggerFactory.getLogger(InstallHookProcessorImpl.class); |
| |
| private final TreeMap<String, Hook> hooks = new TreeMap<String, Hook>(); |
| |
| public void registerHooks(Archive archive, ClassLoader classLoader) throws PackageException { |
| try { |
| Archive.Entry root = archive.getRoot(); |
| root = root.getChild(Constants.META_INF); |
| if (root == null) { |
| log.warn("Archive {} does not have a {} directory.", archive, Constants.META_INF); |
| return; |
| } |
| root = root.getChild(Constants.VAULT_DIR); |
| if (root == null) { |
| log.warn("Archive {} does not have a {} directory.", archive, Constants.VAULT_DIR); |
| return; |
| } |
| root = root.getChild(Constants.HOOKS_DIR); |
| if (root == null) { |
| log.debug("Archive {} does not have a {} directory.", archive, Constants.HOOKS_DIR); |
| } else { |
| for (Archive.Entry entry : root.getChildren()) { |
| // only respect .jar files |
| if (entry.getName().endsWith(".jar")) { |
| registerHook(archive.getInputSource(entry), classLoader); |
| } |
| } |
| } |
| |
| // also look for external hooks in properties |
| // currently only the format: "installhook.{name}.class" is supported |
| Properties props = archive.getMetaInf().getProperties(); |
| if (props != null) { |
| Enumeration names = props.propertyNames(); |
| while (names.hasMoreElements()) { |
| String name = names.nextElement().toString(); |
| if (name.startsWith(VaultPackage.PREFIX_INSTALL_HOOK)) { |
| String[] segs = Text.explode(name.substring(VaultPackage.PREFIX_INSTALL_HOOK.length()), '.'); |
| if (segs.length == 0 || segs.length > 2 || !"class".equals(segs[1])) { |
| throw new PackageException("Invalid installhook property: " + name); |
| } |
| Hook hook = new Hook(segs[0], props.getProperty(name), classLoader); |
| initHook(hook); |
| } |
| } |
| } |
| } catch (IOException e) { |
| throw new PackageException("I/O Error while registering hooks", e); |
| } |
| } |
| |
| public void registerHook(VaultInputSource input, ClassLoader classLoader) throws IOException, PackageException { |
| // first we need to spool the jar file to disk. |
| File jarFile = File.createTempFile("vaulthook", ".jar"); |
| Hook hook = new Hook(input.getSystemId(), jarFile, classLoader); |
| |
| try (OutputStream out = FileUtils.openOutputStream(jarFile); |
| InputStream in = input.getByteStream()) { |
| IOUtils.copy(in, out); |
| } catch (IOException e) { |
| hook.destroy(); |
| throw e; |
| } |
| initHook(hook); |
| } |
| |
| private void initHook(Hook hook) throws IOException, PackageException { |
| try { |
| hook.init(); |
| } catch (IOException e) { |
| log.error("Error while initializing hook: {}", e.toString()); |
| hook.destroy(); |
| throw e; |
| } catch (PackageException e) { |
| log.error("Error while initializing hook: {}", e.toString()); |
| hook.destroy(); |
| throw e; |
| } |
| hooks.put(hook.name, hook); |
| log.info("Hook {} registered.", hook.name); |
| } |
| |
| public boolean hasHooks() { |
| return !hooks.isEmpty(); |
| } |
| |
| public boolean execute(InstallContext context) { |
| for (Hook hook : hooks.values()) { |
| try { |
| hook.getHook().execute(context); |
| } catch (PackageException e) { |
| // abort processing only for prepare and installed phase |
| if (context.getPhase() == InstallContext.Phase.PREPARE || context.getPhase() == InstallContext.Phase.INSTALLED) { |
| log.warn("Hook " + hook.name +" threw package exception. {} aborted.", context.getPhase(), e); |
| return false; |
| } |
| log.warn("Hook " + hook.name +" threw package exception. Ignored", e); |
| } catch (Throwable e) { |
| log.warn("Hook " + hook.name +" threw runtime exception.", e); |
| } |
| // if in end phase, shutdown hooks |
| if (context.getPhase() == InstallContext.Phase.END) { |
| hook.destroy(); |
| } |
| } |
| return true; |
| } |
| |
| private class Hook { |
| |
| private final String name; |
| |
| private final File jarFile; |
| |
| private ClassLoader parentClassLoader; |
| |
| private InstallHook hook; |
| |
| private String mainClassName; |
| |
| private Hook(String name, String mainClassName, ClassLoader parentClassLoader) { |
| this.name = name; |
| this.mainClassName = mainClassName; |
| this.parentClassLoader = parentClassLoader; |
| this.jarFile = null; |
| } |
| |
| private Hook(String name, File jarFile, ClassLoader parentClassLoader) { |
| this.name = name; |
| this.jarFile = jarFile; |
| this.parentClassLoader = parentClassLoader; |
| } |
| |
| private void destroy() { |
| parentClassLoader = null; |
| hook = null; |
| if (jarFile != null) { |
| FileUtils.deleteQuietly(jarFile); |
| } |
| } |
| |
| private void init() throws IOException, PackageException { |
| try { |
| if (jarFile != null) { |
| // open jar file and get manifest |
| JarFile jar = new JarFile(jarFile); |
| Manifest mf = jar.getManifest(); |
| if (mf == null) { |
| throw new PackageException("hook jar file does not have a manifest: " + name); |
| } |
| mainClassName = mf.getMainAttributes().getValue("Main-Class"); |
| if (mainClassName == null) { |
| throw new PackageException("hook manifest file does not have a Main-Class entry: " + name); |
| } |
| // create classloader |
| if (parentClassLoader == null) { |
| try { |
| // 1st fallback is the current classes classloader (the bundle classloader in the OSGi context) |
| loadMainClass(URLClassLoader.newInstance( |
| new URL[]{jarFile.toURI().toURL()}, |
| this.getClass().getClassLoader())); |
| } catch (ClassNotFoundException cnfe) { |
| // 2nd fallback is the thread context classloader |
| loadMainClass(URLClassLoader.newInstance( |
| new URL[]{jarFile.toURI().toURL()}, |
| Thread.currentThread().getContextClassLoader())); |
| } |
| } else { |
| loadMainClass(URLClassLoader.newInstance( |
| new URL[]{jarFile.toURI().toURL()}, |
| parentClassLoader)); |
| } |
| } else { |
| // create classloader |
| if (parentClassLoader == null) { |
| try { |
| // 1st fallback is the current classes classloader (the bundle classloader in the OSGi context) |
| loadMainClass(this.getClass().getClassLoader()); |
| } catch (ClassNotFoundException cnfe) { |
| // 2nd fallback is the thread context classloader |
| loadMainClass(Thread.currentThread().getContextClassLoader()); |
| } |
| } else { |
| loadMainClass(parentClassLoader); |
| } |
| } |
| } catch (ClassNotFoundException cnfe) { |
| throw new PackageException("hook's main class " + mainClassName + " not found: " + name, cnfe); |
| } |
| |
| } |
| |
| private void loadMainClass(ClassLoader classLoader) throws PackageException, ClassNotFoundException { |
| log.info("Loading Hook {}: Main-Class = {}", name, mainClassName); |
| |
| // find main class |
| Class clazz = classLoader.loadClass(mainClassName); |
| if (!InstallHook.class.isAssignableFrom(clazz)) { |
| throw new PackageException("hook's main class " + mainClassName + " does not implement the InstallHook interface: " + name); |
| } |
| // create instance |
| try { |
| hook = (InstallHook) clazz.newInstance(); |
| } catch (Exception e) { |
| throw new PackageException("hook's main class " + mainClassName + " could not be instantiated.", e); |
| } |
| } |
| |
| public InstallHook getHook() { |
| return hook; |
| } |
| } |
| } |