/* * Copyright (C) 2011 Rhegium Team * * 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. */ package org.rhegium.internal.modules; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.log4j.Logger; import org.rhegium.internal.utils.StringUtils; import org.sourceprojects.lycia.LyciaParser; import org.sourceprojects.lycia.fluent.FluentBuilder; public final class PluginContextHelper { private static final Logger LOG = Logger.getLogger(PluginContextHelper.class); private static final FluentBuilder<PluginLyciaContextObject> BUILDER = FluentBuilder.<PluginLyciaContextObject> prepare() .parser(FluentBuilder.pojoParser(new PluginXmlLyciaParser())).configure(FluentBuilder.validateSchema(false)); private PluginContextHelper() { } public static final PluginClassLoader buildPluginClassLoader(File pluginPath, File workPath, ClassLoader parent) { try { URL[] urls = findAllPluginJars(pluginPath, workPath); if (urls == null) { return null; } return new PluginClassLoader(urls, parent); } catch (final IOException e) { throw new IllegalStateException(e); } } public static final PluginDescriptor buildPluginDescriptor(File pluginPath, File workPath, ClassLoader parent) { final PluginClassLoader pluginClassLoader = buildPluginClassLoader(pluginPath, workPath, parent); if (pluginClassLoader == null) { return null; } final PluginLyciaContextObject contextObject = new PluginLyciaContextObject(pluginClassLoader); final LyciaParser<PluginLyciaContextObject> parser = BUILDER.configure(FluentBuilder.contextObject(contextObject)) .build(); try (final InputStream is = pluginClassLoader.getResourceAsStream("META-INF/rhegium-plugin.xml")) { parser.parse(is); final PluginDescriptor pluginDescriptor = contextObject.getPluginDescriptor(); final PluginClassLoader pcl = contextObject.getClassLoader(); pcl.setExports(pluginDescriptor.getExports()); pcl.setPluginName(pluginDescriptor.getName()); return pluginDescriptor; } catch (final Exception e) { throw new IllegalStateException(e); } } public final static Collection<ResolvablePluginDependency> precheckAndReorderPluginDescriptors( final Collection<ResolvablePluginDependency> pluginDescriptors) { checkForDuplicateIds(pluginDescriptors); final List<ResolvablePluginDependency> reordered = new ArrayList<ResolvablePluginDependency>(); for (final ResolvablePluginDependency pluginDescriptor : preorderByPrioritized(pluginDescriptors)) { if (!pluginDescriptor.isResolved()) { try { reordered.addAll(pluginDescriptor.resolve(pluginDescriptors)); } catch (final Exception e) { throw new RuntimeException(StringUtils.join(" ", "Plugin ", pluginDescriptor.getId(), " has unresolved dependencies"), e); } } } return reordered; } public static final URL[] findAllPluginJars(final File pluginPath, final File workPath) throws IOException { if (pluginPath == null) { throw new IllegalArgumentException("PluginPath cannot be null"); } if (pluginPath.isFile()) { final String filename = pluginPath.getName().toLowerCase(); if (filename.endsWith(".jar")) { LOG.info(StringUtils.join(" ", "Deploy plugin from JAR file ", pluginPath.getAbsolutePath())); return new URL[] { pluginPath.toURI().toURL() }; } else if (filename.endsWith(".zip")) { LOG.info("Deploy plugin from ZIP file " + pluginPath.getAbsolutePath()); final File deploymentPath = new File(workPath, filename); if (deploymentPath.exists()) { throw new IllegalStateException(StringUtils.join(" ", "Deployment directory '", deploymentPath.getAbsolutePath(), "' cannot exists - Is there ", "some other instance running?")); } deploymentPath.mkdirs(); extractZipToFolder(pluginPath, deploymentPath); final List<URL> jars = findAllPluginJars0(deploymentPath); return jars.toArray(new URL[jars.size()]); } return null; } LOG.info("Deploy plugin from directory " + pluginPath.getAbsolutePath()); final List<URL> jars = findAllPluginJars0(pluginPath); return jars.toArray(new URL[jars.size()]); } private static void extractZipToFolder(final File pluginPath, final File deploymentPath) throws IOException { final ZipFile zipFile = new ZipFile(pluginPath); final Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { final ZipEntry entry = entries.nextElement(); final File target = new File(deploymentPath, entry.getName()); if (entry.isDirectory()) { target.mkdirs(); } else { try (final InputStream input = zipFile.getInputStream(entry); final FileOutputStream output = new FileOutputStream(target)) { final byte[] buffer = new byte[1024]; int nrBytesRead; while ((nrBytesRead = input.read(buffer)) > 0) { output.write(buffer, 0, nrBytesRead); } } } } } private static List<URL> findAllPluginJars0(final File pluginPath) throws IOException { final List<URL> jars = new ArrayList<URL>(); for (final File entry : pluginPath.listFiles()) { if (entry.isDirectory()) { jars.addAll(findAllPluginJars0(entry)); } else if (entry.isFile() && entry.getName().toLowerCase().endsWith(".jar")) { jars.add(entry.toURI().toURL()); } } return jars; } private static Collection<ResolvablePluginDependency> preorderByPrioritized( Collection<ResolvablePluginDependency> pluginDescriptors) { final List<ResolvablePluginDependency> descriptors = new ArrayList<ResolvablePluginDependency>(); final List<ResolvablePluginDependency> temp = new ArrayList<ResolvablePluginDependency>(); for (final ResolvablePluginDependency descriptor : pluginDescriptors) { if (descriptor.isPrioritized()) { descriptors.add(descriptor); } else { temp.add(descriptor); } } descriptors.addAll(temp); return descriptors; } private static void checkForDuplicateIds(final Collection<ResolvablePluginDependency> pluginDescriptors) { final Set<ResolvablePluginDependency> ids = new HashSet<ResolvablePluginDependency>(); final Iterator<ResolvablePluginDependency> iterator = pluginDescriptors.iterator(); while (iterator.hasNext()) { final ResolvablePluginDependency pluginDescriptor = iterator.next(); boolean addPluginDescriptor = true; for (final ResolvablePluginDependency descriptor : ids) { if (descriptor.getId().equals(pluginDescriptor.getId())) { if (descriptor.getPluginClass().equals(pluginDescriptor.getPluginClass())) { LOG.warn(StringUtils.join(" ", "Found same plugin descriptor '", pluginDescriptor.getId(), "' in multiple classloaders...")); addPluginDescriptor = false; iterator.remove(); break; } throw new RuntimeException(StringUtils.join(" ", "Dupplicate Id '", pluginDescriptor.getId(), "' in plugins ['", descriptor.getName(), "', '", pluginDescriptor.getName(), "']")); } } if (addPluginDescriptor) { ids.add(pluginDescriptor); } } } }