/*
* Copyright (C) 2004 Paul Browne, http://www.firstpartners.net,
* built with the help of Fast-Soft (fastsoftdev@yahoo.com)
*
* released under terms of the GPL license
* http://www.opensource.org/licenses/gpl-license.php
*
* This product includes software developed by the
* Apache Software Foundation (http://www.apache.org)."
*
* This product includes software developed by the
* Spring Framework Project (http://www.springframework.org)."
*
*/
package net.fp.rp.search.mid.global;
import java.io.File;
import java.io.FileFilter;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import net.fp.rp.common.exception.RpException;
import net.fp.rp.search.back.extractor.convertor.IDocumStructConvertor;
import net.fp.rp.search.back.struct.DocumStruct;
import net.fp.rp.search.common.util.Util;
import net.fp.rp.search.mid.loader.RPClassLoader;
import net.fp.rp.search.plugins.ICategoryManager;
import net.fp.rp.search.plugins.ICategoryStore;
import net.fp.rp.search.plugins.IDataExtractor;
import net.fp.rp.search.plugins.IFeedbackDataStore;
import net.fp.rp.search.plugins.IIndexManager;
import net.fp.rp.search.plugins.INewInformation;
import net.fp.rp.search.plugins.IPlugin;
import net.fp.rp.search.plugins.events.IInterestedInAdd;
import net.fp.rp.search.plugins.events.IInterestedInFeedback;
import net.fp.rp.search.plugins.events.IInterestedInResultsFilter;
import net.fp.rp.search.plugins.events.IInterestedInSearch;
import org.apache.log4j.Logger;
import org.apache.commons.codec.binary.Base64;
/**
* Central point to get handle to the plugins available to the system<BR/> This
* one propogates Events to the loaded plugins.
*
* @author Firstpartners.net
* @version 1.1
* Copyright @link www.firstpartners.net/red
*/
public class PluginManager {
/** Logger for this class and subclasses */
protected final static Logger logger = Logger.getLogger(PluginManager.class);
/** Root plugins */
private String root;
/** Category manager */
private static ICategoryManager categoryManager;
/** Index manager */
private static IIndexManager indexManager;
/** Category store */
private static ICategoryStore categoryStores;
/** Feedback store */
private static IFeedbackDataStore feedbackDataStores;
/** List of plugins interested in add */
private static LinkedList m_listPluginsInAdd;
/** List of plugins interested in feedback */
private static LinkedList m_listPluginsInFeedback;
/** List of plugins interested in filter */
private static LinkedList m_listPluginsInFilter;
/** List of plugins interested in search */
private static LinkedList m_listPluginsInSearch;
/** List of plugins interested in extract data */
private static LinkedList m_listPluginsInExtract;
/** User credentials */
private static String m_encodedCredentials = null;
/** Proxy configuration */
private Proxy proxy = null;
/** List of plugins which belongs to the system */
private Collection plugins;
/** List of the running plugins ( controlled in the search process ) */
private static LinkedList m_listRunningPlugins;
/** Actual class loader which extends the tomcat.class loader */
private static RPClassLoader m_loader =null;
/** Flag to indicate that search processs is active */
private int countSearch = 0;
/**
* Gives a chance to modify the search data before it is indexed
*/
private static IDocumStructConvertor documStructConvertor=null;
/**
* Creates a new PluginManager object.
*/
protected PluginManager() {
//instantiate here the required variables
m_listPluginsInAdd = new LinkedList();
m_listPluginsInFeedback = new LinkedList();
m_listPluginsInFilter = new LinkedList();
m_listPluginsInSearch = new LinkedList();
m_listPluginsInExtract = new LinkedList();
m_listRunningPlugins = new LinkedList();
m_encodedCredentials = null;
}
/**
* 'Walks' Directory Tree from point X (specified in config file) and loads
* all classes that implement the IPlugin interface. Holds these as
* internal Cache for the other methods on this class to query for
* specific plugin types (e.g. getDataSources / getMetaDataStores).
*
* @throws RpException If an error must to be signaled
*/
void loadPlugins() throws RpException {
logger.info("Start to load the plugins ");
//set the actual rp class loader
m_loader = new RPClassLoader( this.getClass().getClassLoader() );
//Atention here must to set also the Proxy data (general configuration)
if (proxy != null) {
if (!(Util.isEmpty(proxy.getHost())) &&
!(Util.isEmpty(proxy.getPort()))) {
//set the proxy data
logger.info("Proxy configuration was required ");
System.getProperties().put("proxySet", "true");
System.getProperties().put("proxyHost", proxy.getHost());
System.getProperties().put("proxyPort", proxy.getPort());
if (!(Util.isEmpty(proxy.getUser())) &&
!(Util.isEmpty(proxy.getPassword()))) {
logger.info(
" Proxy configuration with credentials was required ");
String credential = proxy.getUser() + ":" +
proxy.getPassword();
//was: this is deprecated in Java 6 m_encodedCredentials = new sun.misc.BASE64Encoder().encode(credential.getBytes());
byte[] encodedBytes = Base64.encodeBase64(credential.getBytes());
m_encodedCredentials = new String(encodedBytes);
// String decodedString = new String(Base64.decodeBase64(encodedBytes));
}
}
}
logger.info("Detect the system plugin types ... ");
//iterate on the plugin list and call in background the onLoad process
Iterator it = plugins.iterator();
while (it.hasNext()) {
//get the list of specialized plugins (InAdd,InSearch....
//Attention one plugin can implement 2 or more specialized interfaces.
Object plugin = it.next();
if ( isPlugin (plugin)) {
logger.info("Detected Plugin of Type:"+plugin.getClass());
PluginThread background = new PluginThread((IPlugin) plugin);
background.setPriority( Thread.MAX_PRIORITY );
background.start();
} else {
logger.warn("The specified class:" +
plugin.getClass().getName() +
" is not a plugin will be ignored");
}
}
//add the category and index manager to the plugin list
isPlugin(indexManager);
isPlugin(categoryManager);
//load the index& category manager
indexManager.onLoad();
if(categoryManager!=null){
categoryManager.onLoad();
}
logger.info("Load the external plugins ... ");
//load the other plugins
//Plugin defined in the other package/or extend class which implement IPLugin
LinkedList otherPlugins = new LinkedList();
logger.info("Root is " + root);
//detect the plugins from extensible plugins folder and added to the default plugin list
if ( ! Util.isEmpty( root )) {
//plugins folder
File pluginFolder = new File(root);
logger.info("Process the plugins file" + pluginFolder);
if (pluginFolder.isDirectory() && pluginFolder.exists()) {
File[] list = pluginFolder.listFiles( new FileFilter() {
public boolean accept(File arg0) {
// TODO Auto-generated method stub
logger.info("PRoces" + arg0.getName());
if ( arg0.getName().endsWith(".class"))//later extension for jar and zip
return true;
else
return false;
}
});
//parse the list
for (int i=0;i< list.length; i++)
{
logger.info("Process the file path:" + list[i].getPath() +" Name:"+list[i].getName() );
logger.info("Class name is "+ list[i].getName().substring(0, list[i].getName().indexOf(".") ));
try {
m_loader.addClassFile ( list[i] );
//add the object to the plugin list
Object plugin = m_loader.loadAppClass( list[i].getName().substring(0, list[i].getName().indexOf(".")) ).newInstance();
//add the object to the plugin list
otherPlugins.add( plugin );
}
catch (ClassNotFoundException e){
logger.warn("Error in getting the class "+e.getMessage());
}
catch (InstantiationException e){
logger.debug("Error in instantiation process"+e.getMessage());
}
catch (IllegalAccessException e){
logger.debug("Error in instantiation process"+e.getMessage());
}
}
}
}
it = otherPlugins.iterator();
while (it.hasNext()) {
//get the list of specialized plugins (InAdd,InSearch....
//Attention one plugin can implement 2 or more specialized interfaces.
Object plugin = it.next();
if ( isPlugin (plugin)) {
PluginThread background = new PluginThread((IPlugin) plugin);
background.setPriority( Thread.MAX_PRIORITY );
background.start();
} else {
logger.warn("The specified class:" +
plugin.getClass().getName() +
" is not a plugin will be ignored");
}
}
}
/**
* Validate if the specified object is a plugin (implement one of the
* default IPlugin interfaces). If yes, also the detect the plugin type
*
* @param plugin Plugin object to validate
* @return True if the object is a plugin object, false otherwise
*/
private boolean isPlugin( Object plugin) {
//Null check
if(plugin==null){
logger.debug("Plugin is null");
return false;
}
Class pluginClass =plugin.getClass();
logger.debug("Detect the type of the plugin class" +
pluginClass.getName());
boolean isPlugin = false;
if ( plugin instanceof IInterestedInAdd ){
addPluginTypeInAdd( (IInterestedInAdd) plugin );
logger.debug("Plugin: " + pluginClass.getName() +
" implement InAdd interface");
isPlugin = true;
}
if ( plugin instanceof IInterestedInFeedback ){
addPluginTypeInFeedback((IInterestedInFeedback) plugin);
logger.debug("Plugin: " + plugin.getClass().getName() +
" implement InFeedback interface");
isPlugin = true;
}
if ( plugin instanceof IInterestedInResultsFilter){
addPluginTypeInFilter((IInterestedInResultsFilter) plugin);
logger.debug("Plugin: " + plugin.getClass().getName() +
" implement InResultsFilter interface");
isPlugin = true;
}
if ( plugin instanceof IInterestedInSearch){
addPluginTypeInSearch((IInterestedInSearch) plugin);
logger.debug("Plugin: " + plugin.getClass().getName() +
" implement InSearch interface");
isPlugin = true;
}
if ( plugin instanceof IDataExtractor){
addPluginTypeInExtract((IDataExtractor) plugin);
logger.debug("Plugin: " + plugin.getClass().getName() +
" implement IDataExtractor interface");
isPlugin = true;
}
if ( plugin instanceof IPlugin){
logger.debug("Plugin: " + plugin.getClass().getName() +
" implement IPlugin interface");
isPlugin = true;
}
return isPlugin;
}
public static IDataExtractor getBestExtractor(final INewInformation addInfo) throws RpException{
IDataExtractor extractor = null;
int max = 0;
IDataExtractor[] extractors = PluginManager.getDataExtractors();
for (int j = 0; j < extractors.length; j++) {
int val = extractors[j].canHandle(addInfo);
if (val > max) {
max = val;
extractor = extractors[j];
}
}
if (extractor != null) {
Logger.getRootLogger().debug(
"Best extractor for handling the information is :" +
extractor.getClass().getName());
} else {
Logger.getRootLogger().warn(
"No extractor is available for extract the data " + addInfo.getUri());
}
return extractor;
}
/**
* Add the document structure to the system
*
* @param doc DocumentStruct to be added to the system
*
* @return
* @throws RpException
*/
public static void storeAndAddDocument(DocumStruct doc) {
//Check if the configuration files says that the data should be modified
if(documStructConvertor!=null){
doc=documStructConvertor.modifyData(doc);
}
try {
//store the document
PluginManager.getCategoryStores().store( doc );
//re-index the document
PluginManager.getIndexManager().addToIndex( doc.getIndexDocument() );
}
catch (RpException e){
logger.warn("Error in saving the document (as xml/index) ",e);
}
}
/**
* Get a handle to all the available DataStores (classes)
*
* @return single IBasicCategoryStore - as specified in the config file
*
* @throws RpException if there are no such plugins available at runtime
*/
public static ICategoryStore getCategoryStores() throws RpException {
if (categoryStores != null) {
return categoryStores;
} else {
throw new RpException("pluginmanager.plugins.nocategorystore");
}
}
/**
* Get a handle to the FeedbackDataStore
*
* @return IFeedbackDataStore (one per system , as per config file)
*
* @throws RpException if there are no such plugins available at runtime
*/
public static IFeedbackDataStore getFeedbackDataStores()
{
if (feedbackDataStores != null) {
return feedbackDataStores;
} else {
logger.debug("No Plugin Registered as being interested in Search Feedback");
return null;
}
}
/**
* Get a handle to all the plugins interested in receiving feedback events
*
* @return array of IInterestedInFeedback - one for each plugin available.
* Guaranteed not to be null , zero length or contain any null
* elements in array.
*
* @throws RpException if there are no such plugins available at runtime
*/
public static IInterestedInFeedback[] getInterestedInFeedback()
throws RpException {
if ((indexManager != null) ) {
if ((m_listPluginsInFeedback != null) &&
(m_listPluginsInFeedback.size() > 0)) {
IInterestedInFeedback[] list = new IInterestedInFeedback[m_listPluginsInFeedback.size()];
m_listPluginsInFeedback.toArray(list);
return list;
} else {
logger.debug("There are no configured plugins interested in Feedback");
return new IInterestedInFeedback[0];
}
} else {
throw new RpException("pluginmanager.noinit");
}
}
/**
* Get a handle to all the plugins interested in receiving add events
*
* @return array of IInterestedInFeedback - one for each plugin available.
* Guaranteed not to be null , but can be zero length or contain any null
* elements in array.
*
* @throws RpException if there are no such plugins available at runtime
*/
public static IInterestedInAdd[] getInterestedInAdd()
throws RpException {
if ((indexManager != null) ) {
if ((m_listPluginsInAdd != null) &&
(m_listPluginsInAdd.size() > 0)) {
IInterestedInAdd[] list = new IInterestedInAdd[m_listPluginsInAdd.size()];
m_listPluginsInAdd.toArray(list);
return list;
} else {
logger.warn("No plugins are interested in Add events (apart from the Indexer)");
// return new IInterestedInAdd[0];
throw new RpException("pluginmanager.plugins.noadd");
}
} else {
throw new RpException("pluginmanager.noinit");
}
}
/**
* Get a handle to all the plugins interested in receiving Search Events
*
* @return array of IInterestedInFeedback - one for each plugin available.
* Guaranteed not to be null , zero length or contain any null
* elements in array.
*
* @throws RpException if there are no such plugins available at runtime
*/
public static IInterestedInSearch[] getInterestedInSearch()
throws RpException {
if ((indexManager != null) ) {
if ((m_listPluginsInSearch != null) &&
(m_listPluginsInSearch.size() > 0)) {
IInterestedInSearch[] list = new IInterestedInSearch[m_listPluginsInSearch.size()];
m_listPluginsInSearch.toArray(list);
return list;
} else {
throw new RpException("pluginmanager.plugins.nosearch");
}
} else {
throw new RpException("pluginmanager.noinit");
}
}
/**
* Get a handle to all the plugins interested in receiving feedback
*
* @return array of IInterestedInFeedback - one for each plugin available.
* Guaranteed not to be null , zero length or contain any null
* elements in array.
*
* @throws RpException if there are no such plugins available at runtime
*/
public static IInterestedInResultsFilter[] getInterestedInFilterResults()
throws RpException {
if ((indexManager != null) && (categoryManager != null)) {
if ((m_listPluginsInFilter != null) &&
(m_listPluginsInFilter.size() > 0)) {
IInterestedInResultsFilter[] list = new IInterestedInResultsFilter[m_listPluginsInFilter.size()];
m_listPluginsInFilter.toArray(list);
return list;
} else {
throw new RpException("pluginmanager.plugins.nofilter");
}
} else {
throw new RpException("pluginmanager.noinit");
}
}
/**
* Get a handle to all the plugins that can extract data
*
* @return array of IInterestedInFeedback - one for each plugin available.
* Guaranteed not to be null , zero length or contain any null
* elements in array.
*
* @throws RpException if there are no such plugins available at runtime
*/
public static IDataExtractor[] getDataExtractors()
throws RpException {
if ((indexManager != null) && (categoryManager != null)) {
if ((m_listPluginsInExtract != null) &&
(m_listPluginsInExtract.size() > 0)) {
IDataExtractor[] list = new IDataExtractor[m_listPluginsInExtract.size()];
m_listPluginsInExtract.toArray(list);
return list;
} else {
throw new RpException("pluginmanager.plugins.noextractor");
}
} else {
throw new RpException("pluginmanager.noinit");
}
}
/**
* Add the controller thread formed by plugin and information to the
* running list in order to be controlled
*
* @param plugin
* @param info
*/
public static void addPluginThread(IInterestedInAdd plugin, INewInformation info,boolean useThreading) throws RpException {
if(useThreading){
logger.debug("Starting an Add Thread");
//call the add in background
ControllerThread control = new ControllerThread(plugin, info);
control.setPriority(Thread.NORM_PRIORITY);
control.start();
//add the control thread to the list
m_listRunningPlugins.add(control);
} else {
logger.debug("Calling Add, no thread");
plugin.addInformation(info);
}
}
/**
* Validate if the system is in adding process
*
* @return True if exists one plugin which is running process
*/
public static boolean isInAddProcess() {
boolean result = false;
int count = m_listRunningPlugins.size();
//logger.info("Plugin has " + count +" plugins which are running ");
for (int i = count - 1; i >= 0; i--) {
Thread t = (Thread) m_listRunningPlugins.get(i);
if (t.isAlive()) {
result = true;
break;
}
}
return result;
}
/**
* Set the low priority for the threads (a search is in running )
*/
private void setPriority(final int priority ) {
logger.info("Change the priority of the active plugins threads" );
synchronized( m_listRunningPlugins ) {
new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
int count = m_listRunningPlugins.size();
for (int i = count - 1; i >= 0; i--) {
Thread t = (Thread) m_listRunningPlugins.get(i);
if (t.isAlive()) {
t.setPriority( priority );
} else {
logger.debug("Plugin finish the adding job" +
t.getName());
m_listRunningPlugins.remove( i );
}
}
}
}).start();
}
}
/**
* A new search operation was invoked into the system
*/
public void startSearchOp(){
synchronized ( this ){
logger.debug("CounterSearch is " + countSearch);
if (countSearch == 0) {
//set all the thread priority to low
setPriority( Thread.MIN_PRIORITY);
}
countSearch ++ ;
}
}
/**
* A search operation was finished
*/
public void endSearchOp(){
synchronized ( this ){
logger.debug("CounterSearch is " + countSearch);
countSearch--;
if ( countSearch==0 ){
//TODO Maybe is ok to set the High priority
//set the priority to norm priority
setPriority(Thread.NORM_PRIORITY);
}
}
}
/**
* Add the plugin to InAdd list
*
* @param plugin Plugin object to be add
*/
private void addPluginTypeInAdd(IInterestedInAdd plugin) {
logger.debug("");
m_listPluginsInAdd.add(plugin);
}
/**
* Add the plugin to InFeedback list
*
* @param plugin Plugin object to be add
*/
private void addPluginTypeInFeedback(IInterestedInFeedback plugin) {
m_listPluginsInFeedback.add(plugin);
}
/**
* Add the plugin to InFilter list
*
* @param plugin Plugin object to be add
*/
private void addPluginTypeInFilter(IInterestedInResultsFilter plugin) {
m_listPluginsInFilter.add(plugin);
}
/**
* Add the plugin to the InSearch list
*
* @param plugin Plugin object to be add
*/
private void addPluginTypeInSearch(IInterestedInSearch plugin) {
m_listPluginsInSearch.add(plugin);
}
/**
* Add the plugin to the InExtract list
*
* @param plugin Plugin object to be add
*/
private void addPluginTypeInExtract(IDataExtractor plugin) {
m_listPluginsInExtract.add(plugin);
}
/**
* @return Returns the root.
*/
public String getRoot() {
return root;
}
/**
* @param root The root to set.
*/
public void setRoot(String root) {
this.root = root;
}
/**
* Get the proxy credentials in Base64 (sun) encoding
*
* @return
*/
public static String getProxyCredentials() {
return m_encodedCredentials;
}
/**
* @return Returns the categoryManager.
*/
public static ICategoryManager getCategoryManager() {
return categoryManager;
}
/**
* @param categoryManager The categoryManager to set.
*/
public void setCategoryManager(ICategoryManager categoryManager) {
PluginManager.categoryManager = categoryManager;
}
/**
* @return Returns the indexManager.
*/
public static IIndexManager getIndexManager() {
return indexManager;
}
/**
* @param indexManager The indexManager to set.
*/
public void setIndexManager(IIndexManager indexManager) {
PluginManager.indexManager = indexManager;
}
/**
* @return Returns the plugins.
*/
public Collection getPlugins() {
return plugins;
}
/**
* @param plugins The plugins to set.
*/
public void setPlugins(Collection plugins) {
logger.debug("Set Plugins Called: isNull? "+(plugins==null));
this.plugins = plugins;
}
/**
* @return Returns the proxy.
*/
public Proxy getProxy() {
return proxy;
}
/**
* @param proxy The proxy to set.
*/
public void setProxy(Proxy proxy) {
this.proxy = proxy;
}
/**
* @param proxy The CategoryStore to set.
*/
public void setCategoryStores(ICategoryStore categoryStore) {
PluginManager.categoryStores = categoryStore;
}
/**
* @param proxy The FeedbackStore to set.
*/
public void setFeedbackDataStores(IFeedbackDataStore feedbackDataStore) {
PluginManager.feedbackDataStores = feedbackDataStore;
}
/**
* @param documStructConvertor The documStructConvertor to set.
*/
public void setDocumStructConvertor(IDocumStructConvertor documStructConvertor) {
PluginManager.documStructConvertor = documStructConvertor;
}
/**
* @return Returns the documStructConvertor.
*/
public IDocumStructConvertor getDocumStructConvertor() {
return documStructConvertor;
}
/**
* This class is responsable for initialization of the plugin Process of
* initialzation (onLoad process ) will be made in background
*
* @author Firstpartners.net
* @version 1.1
*/
class PluginThread extends Thread {
/** Plugin to run */
IPlugin m_plugin;
/**
* Creates a new PluginThread object.
*
* @param plugin
*/
public PluginThread(IPlugin plugin) {
m_plugin = plugin;
logger.debug("Process the plugin : " + plugin.getClass().getName());
}
/**
* Make the initialization of the plugin
*/
public void run() {
try {
m_plugin.onLoad();
} catch (RpException e) {
logger.warn("Exception occured in loading the plugin" +
e.getMessage(), e);
}
}
}
}