blob: ad4a72df74315fc6b3e33dae31f06d9729ec9c2b [file] [log] [blame]
/*******************************************************************************
* 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);
}
}