| /******************************************************************************* |
| * Copyright (c) 2007 Cisco Systems, Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * E. Dillon (Cisco Systems, Inc.) - reformat for Code Open-Sourcing |
| *******************************************************************************/ |
| package org.eclipse.tigerstripe.workbench.internal.core.plugin; |
| |
| import java.io.File; |
| import java.io.FileWriter; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.stream.Collectors; |
| import java.util.zip.ZipFile; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.core.runtime.FileLocator; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IContributor; |
| import org.eclipse.core.runtime.IExtension; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.tigerstripe.workbench.TigerstripeException; |
| import org.eclipse.tigerstripe.workbench.internal.api.IPluginManager; |
| import org.eclipse.tigerstripe.workbench.internal.api.ITigerstripeRuntime; |
| import org.eclipse.tigerstripe.workbench.internal.api.impl.ITigerstripeConstants; |
| import org.eclipse.tigerstripe.workbench.internal.api.utils.ClassLoaderUtils; |
| import org.eclipse.tigerstripe.workbench.internal.core.NewTigerstripeRuntime; |
| import org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable.ContributedPlugin; |
| import org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable.ExternalContributedPlugin; |
| import org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable.IPluggablePluginHousing; |
| import org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable.IPluginHousing; |
| import org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable.MatchedConfigHousing; |
| import org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable.PluggableHousing; |
| import org.eclipse.tigerstripe.workbench.internal.core.plugin.pluggable.PluggablePlugin; |
| import org.eclipse.tigerstripe.workbench.internal.core.util.OSGIRef; |
| import org.eclipse.tigerstripe.workbench.internal.core.util.ZipFileUnzipper; |
| import org.eclipse.tigerstripe.workbench.project.IPluginConfig; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.Version; |
| |
| /** |
| * @author Eric Dillon |
| * |
| * The plugin manager (singleton) manages all plugin housings |
| * |
| */ |
| public class PluginManager implements IPluginManager { |
| |
| private final List<IPluginHousing> housings; |
| private ITigerstripeRuntime runtime; |
| |
| public PluginManager(ITigerstripeRuntime runtime) { |
| this.runtime = runtime; |
| this.housings = new ArrayList<>(); |
| reload(); |
| } |
| |
| public void dispose() { |
| housings.forEach(IPluginHousing::dispose); |
| housings.clear(); |
| this.runtime = null; |
| } |
| |
| public void reload() { |
| housings.clear(); |
| loadPluggableHousings(); |
| } |
| |
| /** |
| * @deprecated - Access the active generation runtime instance using |
| * {@link NewTigerstripeRuntime#getPluginManager()}. |
| * @return |
| */ |
| @Deprecated |
| public static PluginManager getManager() { |
| return (PluginManager) NewTigerstripeRuntime.getThreadActiveRuntime().getPluginManager(); |
| } |
| |
| public boolean isOsgiVersioningActive() { |
| return true; |
| } |
| |
| public static boolean isOsgiVersioning() { |
| return true; |
| } |
| |
| /** |
| * Resolves the plugin reference and returns the corresponding plugin |
| * housing. |
| * |
| * @param ref |
| * - PluginConfig the plugin reference to resolve |
| * @return PluginHousing - the plugin housing corresponding to the given |
| * PluginConfig. |
| * @throws UnknownPluginException |
| * , if the PluginConfig cannot be resolved |
| */ |
| public IPluginHousing resolvePlugin(IPluginConfig ref) throws UnknownPluginException { |
| |
| for (IPluginHousing housing : housings) { |
| if (housing.matchRef(ref)) |
| return housing; |
| } |
| throw new UnknownPluginException(ref); |
| } |
| |
| /** |
| * Resolves the plugin reference and returns the corresponding plugin |
| * housing. |
| * |
| * @param ref |
| * - PluginConfig the plugin reference to resolve |
| * @return PluginHousing - the plugin housing corresponding to the given |
| * PluginConfig. |
| * @throws UnknownPluginException |
| * , if the PluginConfig cannot be resolved |
| * @deprecated - Use {@link #resolvePlugin(IPluginConfig)} which accepts and returns interface class instead. |
| */ |
| @Deprecated |
| public PluginHousing resolveReference(PluginConfig ref) throws UnknownPluginException { |
| IPluginHousing plugin = resolvePlugin(ref); |
| if (plugin instanceof PluginHousing) { |
| return (PluginHousing) plugin; |
| } |
| throw new UnknownPluginException(ref); |
| } |
| |
| public List<IPluggablePluginHousing> getPluginHousings() { |
| return housings.stream().filter(IPluggablePluginHousing.class::isInstance).map(IPluggablePluginHousing.class::cast) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * |
| * @return |
| * @deprecated - Use {@link #getPluginHousings()} which returns interface class instead |
| */ |
| @Deprecated |
| public List<PluggableHousing> getRegisteredPluggableHousings() { |
| return getPluginHousings().stream().filter(PluggableHousing.class::isInstance).map(PluggableHousing.class::cast) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Returns the available housings |
| * |
| */ |
| public List<IPluginHousing> getPlugins() { |
| return this.housings; |
| } |
| |
| /** |
| * Returns the available housings |
| * @deprecated - Use {@link #getPlugins()} which returns interface class instead |
| */ |
| @Deprecated |
| public List<PluginHousing> getRegisteredHousings() { |
| return getPlugins().stream().filter(PluginHousing.class::isInstance).map(PluginHousing.class::cast) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Loads pluggable housings by scanning a known directory |
| * |
| */ |
| private void loadPluggableHousings() { |
| FilenameFilter filter = new FilenameFilter() { |
| public boolean accept(File directory, String filename) { |
| // TODO - Accept all files that can be opened as a zip file (i.e |
| // .jar too)? Or just .zip files? |
| try (ZipFile zip = new ZipFile(new File(directory, filename))){ |
| return true; |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| }; |
| |
| |
| String pluginsRoot = runtime.getGeneratorDeployLocation(); |
| File pluginDir = new File(pluginsRoot); |
| |
| if (pluginDir.exists() && pluginDir.canRead()) { |
| String[] files = pluginDir.list(filter); |
| for (String file : files) { |
| // First un-zip the file |
| |
| String unZippedFile = pluginsRoot + File.separator + "." + file; |
| String zippedFile = pluginsRoot + File.separator + file; |
| |
| if (unZippedFile.endsWith(".zip")) { |
| unZippedFile = unZippedFile.substring(0, unZippedFile.length() - 4); |
| } |
| try { |
| |
| // Since 2.2.0 we now check on the existence of a tstamp.txt |
| // file |
| // in the unzip dir to avoid un-zipping blindly everytime |
| // all plugins |
| long unzippedTStamp = readTStamp(unZippedFile); |
| File zip = new File(pluginsRoot + File.separator + file); |
| if (zip.exists()) { |
| long zipTStamp = zip.lastModified(); |
| if (zipTStamp != unzippedTStamp) { |
| ZipFileUnzipper.unzip(pluginsRoot + File.separator + file, unZippedFile); |
| writeTStamp(zipTStamp, unZippedFile); |
| } |
| } |
| |
| PluggablePlugin pluginBody = new PluggablePlugin(runtime, unZippedFile); |
| if (pluginBody.isValid()) { |
| PluggableHousing housing = new PluggableHousing(pluginBody); |
| registerHousing(housing); |
| runtime.logErrorMessage( "Loaded Tigerstripe plugin: " |
| + pluginBody.getPluginName() + " " + pluginBody.getVersion()); |
| } else { |
| runtime.logErrorMessage( |
| "Contributed Tigerstripe plugin is not valid: " + pluginBody.getPluginName()); |
| } |
| } catch (TigerstripeException e) { |
| runtime.logErrorMessage("TigerstripeException detected", e); |
| } |
| } |
| } |
| |
| // If standalone - we can;t see contributed stuff, so look for things |
| // on the classPath. |
| if (runtime.getRuntype() == NewTigerstripeRuntime.STANDALONE_RUN) { |
| List<String> paths = ClassLoaderUtils.getCurrentClasspath().collect(Collectors.toList()); |
| for (String path : paths) { |
| |
| path = StringUtils.substringAfter(path, "file:"); |
| try (JarFile jarFile = new JarFile(path)) { |
| JarEntry pluginDescriptor = jarFile.getJarEntry(ITigerstripeConstants.PLUGIN_DESCRIPTOR); |
| if (pluginDescriptor != null){ |
| try { |
| Manifest mf = jarFile.getManifest(); |
| Attributes attrs = mf.getMainAttributes(); |
| registerExternalJarPlugin(path, attrs.getValue("Bundle-Name"), jarFile.getInputStream(pluginDescriptor)); |
| } catch (Exception t) { |
| runtime.logErrorMessage("Failed to instantiate generator from External source "+ jarFile.getName(), t); |
| } |
| } |
| } catch (IOException e1) { |
| // Not a jar, so ignore ... |
| } |
| } |
| } else { |
| try { |
| IConfigurationElement[] elements = Platform.getExtensionRegistry() |
| .getConfigurationElementsFor("org.eclipse.tigerstripe.workbench.base.contributedGenerator"); |
| // TODO check for only one contrib |
| try { |
| for (IConfigurationElement element : elements) { |
| if (element.getName().equals("generator")) { |
| // Need to get the file from the contributing plugin |
| IContributor contributor = ((IExtension) element.getParent()).getContributor(); |
| Bundle bundle = Platform.getBundle(contributor.getName()); |
| File f = FileLocator.getBundleFile(bundle); |
| |
| PluggablePlugin pluginBody = new ContributedPlugin(runtime,f.getAbsolutePath(), bundle); |
| pluginBody.setCanDelete(false); |
| if (pluginBody.isValid()) { |
| PluggableHousing housing = new PluggableHousing(pluginBody); |
| registerHousing(housing); |
| runtime.logInfoMessage( "Loaded Tigerstripe generator: " |
| + pluginBody.getPluginName() + " " + pluginBody.getVersion()); |
| } else { |
| runtime.logErrorMessage( |
| "Contributed Tigerstripe generator is not valid: " + pluginBody.getPluginName()); |
| } |
| } |
| } |
| } catch (Exception e) { |
| runtime.logErrorMessage("Failed to instantiate generator from Extension Point", e); |
| } |
| } catch (Exception e) { |
| runtime.logWarnMessage("Failed to find eclipse Platfrom ... searching"); |
| |
| } |
| } |
| } |
| |
| private void registerExternalJarPlugin(String path, String bundleName, InputStream descriptorStream) throws TigerstripeException { |
| PluggablePlugin pluginBody = new ExternalContributedPlugin(runtime, path, bundleName, descriptorStream); |
| pluginBody.setCanDelete(false); |
| if (pluginBody.isValid()) { |
| PluggableHousing housing = new PluggableHousing(pluginBody); |
| registerHousing(housing); |
| runtime.logInfoMessage( "Loaded Tigerstripe generator: " |
| + pluginBody.getPluginName() + " " + pluginBody.getVersion()); |
| } else { |
| runtime.logErrorMessage( |
| "Contributed Tigerstripe generator is not valid: " + pluginBody.getPluginName()); |
| } |
| } |
| |
| private static String readFileAsString(String filePath) throws java.io.IOException { |
| return FileUtils.readFileToString(new File(filePath)); |
| } |
| |
| private long readTStamp(String zipDir) { |
| String tStampFile = zipDir + File.separator + "tstamp.txt"; |
| try { |
| String tsStr = readFileAsString(tStampFile); |
| if (tsStr != null && tsStr.length() != 0) { |
| return Long.parseLong(tsStr); |
| } |
| return -1; |
| } catch (IOException e) { |
| return -1; |
| } |
| } |
| |
| private void writeTStamp(long tStamp, String zipDir) { |
| String tStampFile = zipDir + File.separator + "tstamp.txt"; |
| FileWriter writer = null; |
| try { |
| writer = new FileWriter(tStampFile); |
| writer.append(String.valueOf(tStamp)); |
| } catch (IOException e) { |
| runtime.logErrorMessage("IOException detected", e); |
| } finally { |
| if (writer != null) { |
| try { |
| writer.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| private void registerHousing(PluginHousing housing) throws TigerstripeException { |
| if (!this.housings.contains(housing)) { |
| this.housings.add(housing); |
| } |
| } |
| |
| public void unRegisterHousing(IPluginHousing housing) throws TigerstripeException { |
| this.housings.remove(housing); |
| } |
| |
| /** |
| * |
| * @param housings |
| * @param plugins |
| * @return |
| * @deprecated - Use {@link #findMatchingPlugin(Collection, IPluginConfig[])} instead which accepts and returns interface classes |
| */ |
| @Deprecated |
| public org.eclipse.tigerstripe.workbench.internal.core.util.MatchedConfigHousing resolve(Collection<PluggableHousing> housings, IPluginConfig[] plugins) { |
| MatchedConfigHousing configHousing = findMatchingPlugin(housings.stream().filter(IPluggablePluginHousing.class::isInstance) |
| .map(IPluggablePluginHousing.class::cast).collect(Collectors.toList()), plugins); |
| if (configHousing != null && configHousing.getHousing() instanceof PluggableHousing) { |
| return new org.eclipse.tigerstripe.workbench.internal.core.util.MatchedConfigHousing(configHousing.getConfig(), (PluggableHousing) configHousing.getHousing()); |
| } |
| return null; |
| } |
| |
| /* |
| * This should only ever be called if the OSGI versioning is being used. The |
| * housings should all share the name that you are searching for. |
| */ |
| public MatchedConfigHousing findMatchingPlugin(Collection<IPluggablePluginHousing> housings, IPluginConfig[] plugins) { |
| |
| IPluggablePluginHousing potentialHousing = null; |
| IPluginConfig usedPluginConfig = null; |
| Version currentVersion = null; |
| String name = null; |
| |
| for (IPluggablePluginHousing candidateHousing : housings) { |
| OSGIRef pluginRef = null; |
| if (candidateHousing.getPluginName() == null) { |
| runtime.logErrorMessage("Candidate Housing name should not be null: " + candidateHousing.toString()); |
| continue; |
| } |
| String housingId = candidateHousing.getPluginName() + ":" + candidateHousing.getVersion(); |
| name = candidateHousing.getPluginName(); |
| Version v = null; |
| try { |
| v = new Version(candidateHousing.getVersion()); |
| } catch (IllegalArgumentException e) { |
| // This housing has a bad version string.. we need to ignore it. |
| runtime.logErrorMessage("Illegal Version format for Housing '" + housingId, e); |
| continue; |
| } |
| if (potentialHousing == null) { |
| potentialHousing = candidateHousing; |
| } |
| |
| for (IPluginConfig plugin : plugins) { |
| if (name.equals(plugin.getPluginName())) { |
| String id = plugin.getPluginName() + ":" + plugin.getVersion(); |
| try { |
| pluginRef = OSGIRef.parseRef(plugin.getVersion()); |
| } catch (IllegalArgumentException e) { |
| // This ref has a bad version string.. we need to ignore |
| // it. |
| runtime.logErrorMessage("Illegal Version format for Plugin '" + id, e); |
| continue; |
| } |
| |
| if (pluginRef == null) { |
| runtime.logErrorMessage("Failed to resolve OSGI Plugin Reference for Plugin " + id); |
| continue; |
| } |
| |
| if (pluginRef.isInScope(v)) { |
| if (currentVersion == null || v.compareTo(currentVersion) > 0) { |
| currentVersion = v; |
| usedPluginConfig = plugin; |
| potentialHousing = candidateHousing; |
| } |
| } else if (currentVersion == null) { |
| usedPluginConfig = plugin; |
| } |
| } |
| } |
| } |
| return new MatchedConfigHousing(usedPluginConfig, potentialHousing); |
| } |
| |
| public MatchedConfigHousing resolve(IPluginConfig plugin) { |
| |
| if (StringUtils.isEmpty(plugin.getPluginName())) { |
| runtime.logErrorMessage("Plugin name must not be null: " + plugin.toString()); |
| return new MatchedConfigHousing(plugin, null); |
| } |
| |
| String id = plugin.getPluginName() + ":" + plugin.getVersion(); |
| |
| OSGIRef pluginRef = null; |
| try { |
| pluginRef = OSGIRef.parseRef(plugin.getVersion()); |
| } catch (IllegalArgumentException e) { |
| // This ref has a bad version string.. we need to ignore it. |
| runtime.logErrorMessage("Illegal Version format for Plugin '" + id, e); |
| return new MatchedConfigHousing(plugin, null); |
| } |
| if (pluginRef == null) { |
| runtime.logErrorMessage("Failed to resolve OSGI Plugin Reference for Plugin " + id); |
| return new MatchedConfigHousing(plugin, null); |
| } |
| |
| IPluggablePluginHousing potentialHousing = null; |
| Version currentVersion = null; |
| |
| for (IPluggablePluginHousing housing : getRegisteredPluggableHousings()) { |
| if (plugin.getPluginName().equals(housing.getPluginName())) { |
| String housingId = housing.getPluginName() + ":" + housing.getVersion(); |
| Version v = null; |
| try { |
| v = new Version(housing.getVersion()); |
| } catch (IllegalArgumentException e) { |
| // This housing has a bad version string.. we need to ignore |
| // it. |
| runtime.logErrorMessage("Illegal Version format for Candidate Housing '" + housingId, e); |
| continue; |
| } |
| |
| if (pluginRef.isInScope(v)) { |
| if (currentVersion == null || v.compareTo(currentVersion) > 0) { |
| currentVersion = v; |
| potentialHousing = housing; |
| } |
| } |
| } |
| } |
| return new MatchedConfigHousing(plugin, potentialHousing); |
| } |
| |
| } |