package com.abiquo.abicloud.vsm.plugin;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.abiquo.abicloud.vsm.exception.MonitorException;
import com.abiquo.abicloud.vsm.exception.PluginException;
import com.abiquo.abicloud.vsm.model.IMonitor;
/**
* Maintain Monitors's plugins. It loads from package "com.abiquo.abicloud.vsm.plugin.impl" (by
* default, can add more paths where look up) all the classes implementing IMonitor interface, only
* one implementation class allowed for each MonitorType. Requires on the implementation class a
* default empty constructor (reflection instantiation). TODO: allow URI paths / java sources
* (resolving dependences �bcel/maven?)
*/
public class MonitorManager
{
/** The logger object */
private final static Logger logger = LoggerFactory.getLogger(MonitorManager.class);
/** Enumerate all paths where look up for plugin classes */
private List<String> pluginClassPaths;
/**
* All IMonitor plugin implementation classes indexed by its Monitor type
*/
private Map<String, Class< ? extends IMonitor>> htMonitorPlugins;
/**
* All singleton IMonitors objects indexed by its Monitor type / address
*/
private Map<String, Map<URL, IMonitor>> htMonitors;
private final static String MonitorInterface = "com.abiquo.abicloud.vsm.model.IMonitor";
private final static String MonitorPackage = "com.abiquo.abicloud.vsm.plugin.impl";
private final static String MonitorClassPahtProperty = "java.library.path"; // "java.ext.dirs"
private final static boolean isRefreshOnCreation = false;
/**
* Creates a new Plugin Manager and load all IMonitor implementations located at package
* "com.abiquo.abicloud.vsm.plugin.impl"
*/
public MonitorManager()
{
htMonitorPlugins = new Hashtable<String, Class< ? extends IMonitor>>();
htMonitors = new Hashtable<String, Map<URL, IMonitor>>();
pluginClassPaths = new ArrayList<String>();
URL baseClasspath =
MonitorManager.class.getClassLoader().getResource(
MonitorPackage.replace('.', File.separatorChar));// classes
pluginClassPaths.add(baseClasspath.getPath());
if (isRefreshOnCreation)
{
logger.info("refresh plugins");
refreshMonitorPlugins();
logger.info("refresh plugins done");
}
else
{
logger.info("ONLY: adds Virtual Box Monitor plugin");
// TODO Load the plugins monitor
/*
* try { //addMonitorClass(VirtualBoxMonitor.class.asSubclass(IMonitor.class), false);
* // do not update } catch (PluginException e) {
* logger.error("VirtualBox plugin can not be loaded :" + e.getLocalizedMessage()); }
*/
}
}
/**
* Enumerates all the available Monitors types. No multiple implementations of the same Monitor
* type allowed.
*
* @return a list containing all IMonitor.getMonitorTyper() for each IMonitor implementation.
* getMonitorType is the IMonitor identifier, so MUST be different for each class.
* @see IMonitor.getMonitorType()
*/
public String[] getMonitorTypes()
{
return htMonitorPlugins.keySet().toArray(new String[htMonitorPlugins.keySet().size()]);
}
/**
* Returns an Monitor singleton instance by its type.
*
* @param type the desired Monitor type
* @return a singleton instance for the IMonitor implementation for given Monitor type.
* @throws PluginException if there is not an implementation class for this Monitor type
*/
public List<IMonitor> getAllMonitors(String type) throws MonitorException
{
List<IMonitor> monitor;
if (htMonitors.containsKey(type))
{
monitor = new ArrayList<IMonitor>(htMonitors.get(type).values());
}
else
{
throw new MonitorException("There is any Monitor for type " + type);
}
return monitor;
}
/**
* Gets the singleton instance . Or creates a new instance.
*/
public IMonitor getMonitor(String type, URL address) throws PluginException
{
IMonitor monitor;
Map<URL, IMonitor> monitorsByAdd;
if (htMonitors.containsKey(type))
{
monitorsByAdd = htMonitors.get(type);
if (monitorsByAdd.containsKey(address))
{
return htMonitors.get(type).get(address);
}
}
else
{
monitorsByAdd = new Hashtable<URL, IMonitor>();
}
monitor = instantiateMonitor(type);
// TODO to think if the monitor needs some kind of initialization
// monitor.init(address);
// monitor.connect(address);
monitorsByAdd.put(address, monitor);
htMonitors.put(type, monitorsByAdd);
return monitor;
}
/**
* Creates a new Monitor plugin.
*
* @param type the desired Monitor type
* @return new plugin instance for the given Monitor type.
* @throws PluginException if there is not any class implementing the desired Monitor type or
* exist but can not no be instantiated (not default empty constructor ?)
* @see IMonitor.getMonitorType()
*/
protected IMonitor instantiateMonitor(String type) throws PluginException
{
Class< ? extends IMonitor> classmonitor;
IMonitor monitor;
if (htMonitorPlugins.containsKey(type))
{
classmonitor = htMonitorPlugins.get(type);
try
{
// TODO: required default constructor
monitor = classmonitor.newInstance();
}
catch (Exception e1) // InstantiationException or IllegalAccessException
{
// TODO: try to find the right constructor
throw new PluginException("Failed to instantiate Monitor plugin " + "for " + type
+ " using class " + classmonitor.getCanonicalName(), e1);
}
}
else
{
throw new PluginException("Plugin for Monitor type " + type + " not loaded ");
// TODO try to reload ??
}
return monitor;
}
/**
* Cleans up the existing IMonitors plugin implementation classes and start search on all given
* paths (pluginClassPaths) look up for new IMonitor implementations.
*/
public void refreshMonitorPlugins()
{
htMonitorPlugins.clear();
logger.info("Loading Monitors plugins from ");
for (String path : pluginClassPaths)
{
logger.info(" path :" + path);
File fPath = new File(path);
if (fPath.isDirectory())
{
// TODO: if there are subdirectories
for (File f : fPath.listFiles())
{
try
{
loadMonitors(f.getAbsolutePath());
}
catch (PluginException e)
{
logger.error("Failed to load plugin at " + f.getAbsolutePath()
+ "\n Caused by:" + e.getLocalizedMessage() + "\n"
+ e.getCause().getLocalizedMessage());
}
}
}
else
{
try
{
loadMonitors(path);
}
catch (PluginException e)
{
logger.error("Failed to load plugin at " + path + "\n Caused by:"
+ e.getLocalizedMessage() + "\n" + e.getCause().getLocalizedMessage());
}
}
}// for paths
}
/**
* Checks if javaFilePath contain class o source of an IMonitor implementation, if so, adds to
* existing plugin repository indexed by its getMonitorType.
*
* @param javaFilePath candidate java file to implement IMonitor
* @throws PluginException if the given class can not no be loaded or is not a java class
* (ClassFormatException)
*/
private void loadMonitors(String javaFilePath) throws PluginException
{
JavaClass java_class;
if (javaFilePath.endsWith(".class"))
{
try
{
java_class = new ClassParser(javaFilePath).parse();
}
catch (IOException e1)
{
final String ex_msg =
"IOException while loading class file " + javaFilePath + "\n Caused by "
+ e1.getCause().getLocalizedMessage();
// log.severe(ex_msg);
throw new PluginException(ex_msg, e1);
}
catch (ClassFormatException e2)
{
final String ex_msg =
"ClassFormatException while loading class file " + javaFilePath
+ "\n Caused by " + e2.getCause().getLocalizedMessage();
// log.severe(ex_msg);
throw new PluginException(ex_msg, e2);
}
} // dot class
else
// java or jar
{
try
{
java_class = Repository.lookupClass(javaFilePath);
// TODO Repository.getRepository().findClass(javaFilePath);
}
catch (ClassNotFoundException e)
{
final String ex_msg =
"ClassNotFoundException while loading java file " + javaFilePath
+ "\n Caused by " + e.getCause().getLocalizedMessage();
throw new PluginException(ex_msg, e);
}
} // dot java
// log.fine("Try to load " + java_class.getClassName());
try
{
if (Repository.implementationOf(java_class, MonitorInterface))
{
Class< ? extends IMonitor> classmonitor =
Class.forName(java_class.getClassName()).asSubclass(IMonitor.class);
if (!addMonitorClass(classmonitor, false)) // do not update
{
throw new PluginException("Monitor Type already defined, descarted plugin implementation at class"
+ classmonitor.getCanonicalName(),
new Throwable());
}
}
// else do nothing
}
catch (ClassNotFoundException e) // must fail before
{
final String ex_msg =
"ClassNotFoundException while loading java file " + javaFilePath + "\n Caused by "
+ e.getCause().getLocalizedMessage();
throw new PluginException(ex_msg, e);
}
}
/**
* @return a list containing all the class paths where plugins are look up.
*/
public List<String> getPluginClassPaths()
{
return pluginClassPaths;
}
/***
* Adds the given path at the end of pluginClassPaths. Remember only one instance allowed for
* each Monitor type, so if some class already implements a plugin before (find on previous
* class paths) it will throw a PluginException when try to load from the new path .
*
* @param path the new path where to look up for IMonitor implementations.
* @param isHighPriority if true, will find first on this class path, otherwise will look up on
* it before all others.
* @return true if the given path are not already on the list, exist and it could be accessible,
* false otherwise.
*/
public boolean addPluginClassPath(String path, boolean isHighPriority)
{
if (pluginClassPaths.contains(path))
{
return false;
}
else
{
File fPath = new File(path);
if (!fPath.exists())
{
logger.error("Plugin path " + path + " do not exist");
return false;
}
else if (!fPath.canRead())
{
logger.error("Plugin path " + path + " can not be read");
return false;
}
else
{
logger.info("Added plugin path " + path);
if (isHighPriority)
{
pluginClassPaths.add(0, path);
// TODO Repository.getRepository().getClassPath().getClassPath()
System.setProperty("java.ext.dirs", path + File.pathSeparatorChar
+ System.getProperty(MonitorClassPahtProperty));
}
else
{
pluginClassPaths.add(path);
// TODO Repository.getRepository().getClassPath().getClassPath()
System.setProperty("java.ext.dirs", System
.getProperty(MonitorClassPahtProperty)
+ File.pathSeparatorChar + path);
}
return true;
}
}// not contain
}
/**
* Deletes the given IMonitor class path
*
* @param path the IMonitor class path to want to remove.
* @return true if success remove an existing IMonitor class path,false if already do not exist.
*/
public boolean removePluginClassPath(String path)
{
return pluginClassPaths.remove(path);
}
/**
* Gets the current plugin index.
*
* @return all the Monitors plugin class implementations indexed by its Monitor type.
*/
public Map<String, Class< ? extends IMonitor>> getMonitorMap()
{
return htMonitorPlugins;
}
/**
* Adds a new IMonitor class implementation indexed by its getMonitorType.
*
* @param MonitorClass the IMonitor implementation class want to add.
* @param update require to overwrite already defined implementation class.
* @return true if success add the new IMonitor class, false if update is not required and some
* IMonitor class already defined for the same Monitor type.
* @throws PluginException if the given class can not no be instantiated (not default empty
* constructor ?)
*/
public boolean addMonitorClass(Class< ? extends IMonitor> monitorClass, boolean update)
throws PluginException
{
try
{
String monitorType;
monitorType = monitorClass.newInstance().getMonitorType();
if (htMonitorPlugins.containsKey(monitorType) && !update)
{
return false;
}
else
{
htMonitorPlugins.put(monitorType, monitorClass);
logger.info("Added an IMonitor implementation at "
+ monitorClass.getCanonicalName() + "for Monitor type " + monitorType);
return true;
}
}
catch (InstantiationException e)
{
final String ex_msg =
"ClassNotFoundException while loading java class file "
+ monitorClass.getCanonicalName();
throw new PluginException(ex_msg, e);
}
catch (IllegalAccessException e)
{
final String ex_msg =
"ClassNotFoundException while loading java clas file "
+ monitorClass.getCanonicalName();
throw new PluginException(ex_msg, e);
}
}
/**
* Deletes the IMonitor class implementation for the given type.
*
* @param MonitorType the IMonitor identifier want to remove.
* @return true if success remove an existing IMonitor class, false if do not exist any IMonitor
* class for the given type.
*/
public boolean removeMonitorClassFor(String MonitorType)
{
if (htMonitorPlugins.containsKey(MonitorType))
{
htMonitorPlugins.remove(MonitorType);
return true;
}
else
{
return false;
}
}
/**
* Tests load Monitor plugin, reload each 10 seconds.
*/
public static void main(String args[]) throws Exception
{
final MonitorManager pm = new MonitorManager();
Thread thReload = new Thread()
{
public void run()
{
pm.refreshMonitorPlugins();
for (String sh : pm.getMonitorTypes())
{
System.out.println("Monitor " + sh);
}
try
{
Thread.sleep(10000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
run();
}
};
thReload.start();
}
// ///// TODO DEPENDENCES RESOLVER :: triged if PluginException at class Instantiation
/*
* public static String[] getClassDependencies(JavaClass.getConstantPool() pool) { String[]
* tempArray = new String[pool.getLength()]; int size = 0; StringBuffer buf = new
* StringBuffer(); for(int idx = 0; idx < pool.getLength(); idx++) { Constant c =
* pool.getConstant(idx); if(c != null && c.getTag() == Constants.CONSTANT_Class) { ConstantUtf8
* c1 = (ConstantUtf8) pool.getConstant(((ConstantClass)c).getNameIndex()); buf.setLength(0);
* buf.append(c1.getBytes()); for(int n = 0; n < buf.length(); n++) { if(buf.charAt(n) == '/') {
* buf.setCharAt(n, '.'); } } tempArray[size++] = buf.toString(); } } String[] dependencies =
* new String[size]; System.arraycopy(tempArray, 0, dependencies, 0, size); return dependencies;
* }
*/
}