/* * File : PluginInterfaceImpl.java * Created : 12 nov. 2003 * By : Olivier * * Azureus - a Java Bittorrent client * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details ( see the LICENSE file ). * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.gudy.azureus2.pluginsimpl.local; import java.util.*; import java.io.File; import java.net.URL; import org.gudy.azureus2.platform.PlatformManagerFactory; import org.gudy.azureus2.plugins.*; import org.gudy.azureus2.plugins.dht.mainline.*; import org.gudy.azureus2.plugins.logging.Logger; import org.gudy.azureus2.plugins.messaging.MessageManager; import org.gudy.azureus2.plugins.network.ConnectionManager; import org.gudy.azureus2.pluginsimpl.local.deprecate.PluginDeprecation; import org.gudy.azureus2.pluginsimpl.local.dht.mainline.*; import org.gudy.azureus2.pluginsimpl.local.clientid.ClientIDManagerImpl; import org.gudy.azureus2.pluginsimpl.local.ddb.DDBaseImpl; import org.gudy.azureus2.pluginsimpl.local.download.DownloadManagerImpl; import org.gudy.azureus2.pluginsimpl.local.ipc.IPCInterfaceImpl; import org.gudy.azureus2.pluginsimpl.local.ipfilter.IPFilterImpl; import org.gudy.azureus2.pluginsimpl.local.logging.LoggerImpl; import org.gudy.azureus2.pluginsimpl.local.messaging.MessageManagerImpl; import org.gudy.azureus2.pluginsimpl.local.network.ConnectionManagerImpl; import org.gudy.azureus2.pluginsimpl.local.sharing.ShareManagerImpl; import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentManagerImpl; import org.gudy.azureus2.pluginsimpl.local.tracker.*; import org.gudy.azureus2.pluginsimpl.local.ui.*; import org.gudy.azureus2.pluginsimpl.local.ui.config.*; import org.gudy.azureus2.pluginsimpl.local.utils.*; import org.gudy.azureus2.pluginsimpl.local.update.*; import org.gudy.azureus2.plugins.ipc.IPCInterface; import org.gudy.azureus2.plugins.ipfilter.IPFilter; import org.gudy.azureus2.plugins.tracker.Tracker; import org.gudy.azureus2.plugins.ui.config.Parameter; import org.gudy.azureus2.plugins.ui.config.PluginConfigUIFactory; import org.gudy.azureus2.plugins.platform.PlatformManager; import org.gudy.azureus2.plugins.sharing.*; import org.gudy.azureus2.plugins.clientid.ClientIDManager; import org.gudy.azureus2.plugins.ddb.DistributedDatabase; import org.gudy.azureus2.plugins.download.*; import org.gudy.azureus2.plugins.torrent.*; import org.gudy.azureus2.plugins.ui.*; import org.gudy.azureus2.plugins.ui.config.ConfigSection; import org.gudy.azureus2.plugins.utils.*; import org.gudy.azureus2.plugins.update.*; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.core3.logging.*; import com.aelitis.azureus.core.AzureusCoreComponent; import com.aelitis.azureus.core.util.CopyOnWriteList; /** * @author Olivier * */ public final class PluginInterfaceImpl implements PluginInterface, AzureusCoreComponent { private static final LogIDs LOGID = org.gudy.azureus2.core3.logging.LogIDs.PLUGIN; private Plugin plugin; private PluginInitializer initialiser; private Object initialiser_key; protected ClassLoader class_loader; private CopyOnWriteList<PluginListener> listeners = new CopyOnWriteList<PluginListener>(); private Set<PluginListener> init_complete_fired_set = new HashSet<PluginListener>(); private CopyOnWriteList<PluginEventListener> event_listeners = new CopyOnWriteList<PluginEventListener>(); private String key; private String pluginConfigKey; private Properties props; private String pluginDir; private PluginConfigImpl config; private String plugin_version; private Logger logger; private IPCInterfaceImpl ipc_interface; protected List children = new ArrayList(); private PluginStateImpl state; /** * This is the plugin ID value we were given when we were created. * * We might use it, but it depends what value is the plugins properties * (which will override this value). */ private String given_plugin_id; /** * We store this value as soon as someone calls getPluginID(), meaning * we will return a consistent value for the plugin's lifetime. */ private String plugin_id_to_use; protected PluginInterfaceImpl( Plugin _plugin, PluginInitializer _initialiser, Object _initialiser_key, ClassLoader _class_loader, List<File> _verified_files, String _key, Properties _props, String _pluginDir, String _plugin_id, String _plugin_version ) throws PluginException { // check we're being created by the core StackTraceElement[] stack = Thread.currentThread().getStackTrace(); int pos = 0; while( !stack[pos].getClassName().equals( PluginInterfaceImpl.class.getName())){ pos++; } String caller_class = stack[pos+1].getClassName(); if ( !( caller_class.equals( "org.gudy.azureus2.pluginsimpl.local.PluginInitializer" ) || caller_class.equals( "org.gudy.azureus2.pluginsimpl.local.PluginInterfaceImpl" ))){ throw( new PluginException( "Invalid caller" )); } // check we haven't been subclassed String class_name = getClass().getCanonicalName(); if ( class_name == null || !class_name.equals( "org.gudy.azureus2.pluginsimpl.local.PluginInterfaceImpl" )){ throw( new PluginException( "Subclassing not permitted" )); } plugin = _plugin; initialiser = _initialiser; initialiser_key = _initialiser_key; class_loader = _class_loader; key = _key; pluginConfigKey = "Plugin." + _key; props = new propertyWrapper(_props ); pluginDir = _pluginDir; config = new PluginConfigImpl(this,pluginConfigKey); given_plugin_id = _plugin_id; plugin_version = _plugin_version; ipc_interface = new IPCInterfaceImpl( initialiser, plugin ); state = new PluginStateImpl(this, initialiser); boolean verified = false; boolean bad = false; if ( _plugin_id.endsWith( "_v" )){ if ( plugin.getClass() == FailedPlugin.class ){ verified = true; }else{ if ( _verified_files != null ){ File jar = FileUtil.getJarFileFromClass( plugin.getClass()); if ( jar != null ){ for ( File file: _verified_files ){ if ( file.equals( jar )){ verified = true; } } } } } if ( !verified ){ bad = true; } } PluginInitializer.setVerified( this, plugin, verified, bad ); } public Plugin getPlugin() { return( plugin ); } public boolean isOperational() { PluginDeprecation.call("isOperational", this.given_plugin_id); return getPluginState().isOperational(); } public Object getInitializerKey() { return( initialiser_key ); } public PluginManager getPluginManager() { return( initialiser.getPluginManager()); } public String getApplicationName() { return Constants.APP_NAME; } public String getAzureusName() { return( Constants.AZUREUS_NAME ); } public String getAzureusVersion() { return( Constants.AZUREUS_VERSION ); } public void addConfigSection(ConfigSection section) { // Method is used by autocat. ConfigSectionRepository.getInstance().addConfigSection(section, this); } public void removeConfigSection(ConfigSection section) { ConfigSectionRepository.getInstance().removeConfigSection(section); } public ConfigSection[] getConfigSections() { ArrayList<ConfigSection> list = ConfigSectionRepository.getInstance().getList(); for (Iterator<ConfigSection> iter = list.iterator(); iter.hasNext();) { ConfigSection configSection = iter.next(); if (configSection instanceof ConfigSectionHolder) { if (((ConfigSectionHolder)configSection).getPluginInterface() != this) { iter.remove(); } } } return list.toArray(new ConfigSection[0]); } /** * @deprecated */ public void openTorrentFile(String fileName) { PluginDeprecation.call("openTorrentFile", this.getPluginID()); try{ getDownloadManager().addDownload( new File(fileName)); }catch( DownloadException e ){ throw( new RuntimeException(e)); } } /** * @deprecated */ public void openTorrentURL(String url) { PluginDeprecation.call("openTorrentURL", this.getPluginID()); try{ getDownloadManager().addDownload( new URL( url )); }catch( Throwable e ){ throw( new RuntimeException(e)); } } public void setPluginName( String name ) { props.put( "plugin.name", name ); } public String getPluginName() { String name = null; if ( props != null ){ name = (String)props.get( "plugin.name"); } if ( name == null ){ try{ name = new File(pluginDir).getName(); }catch( Throwable e ){ } } if ( name == null || name.length() == 0 ){ name = plugin.getClass().getName(); } return( name ); } public void setPluginVersion( String version ) { props.put( "plugin.version", version ); } public String getPluginVersion() { String version = (String)props.get("plugin.version"); if ( version == null ){ version = plugin_version; } return( version ); } public String getPluginID() { String id = (String)props.get("plugin.id"); // hack alert - azupdater needs to change its plugin id due to general hackage if ( id != null && id.equals( "azupdater" )){ plugin_id_to_use = id; } if (plugin_id_to_use != null) {return plugin_id_to_use;} // Calculate what plugin ID value to use - look at the properties file // first, and if that isn't correct, base it on the given plugin ID // value we were given. if (id == null) {id = given_plugin_id;} if (id == null) {id = "<none>";} plugin_id_to_use = id; return plugin_id_to_use; } public boolean isMandatory() { PluginDeprecation.call("isMandatory", this.given_plugin_id); return getPluginState().isMandatory(); } public boolean isBuiltIn() { PluginDeprecation.call("isBuiltIn", this.given_plugin_id); return getPluginState().isBuiltIn(); } public Properties getPluginProperties() { return(props); } public String getPluginDirectoryName() { return pluginDir; } public String getPerUserPluginDirectoryName(){ String name; if ( pluginDir == null ){ name = getPluginID(); }else{ name = new File( pluginDir).getName(); } String str = new File(new File(SystemProperties.getUserPath(),"plugins"),name).getAbsolutePath(); if ( pluginDir == null ){ return( str ); } try{ if ( new File( pluginDir ).getCanonicalPath().equals( new File( str ).getCanonicalPath())){ return( pluginDir ); } }catch( Throwable e ){ } return( str ); } public void setPluginDirectoryName( String name ) { initialiser_key = new File(name); pluginDir = name; } public void addConfigUIParameters(Parameter[] parameters, String displayName) { ParameterRepository.getInstance().addPlugin(parameters, displayName); } public PluginConfig getPluginconfig() { return config; } public PluginConfigUIFactory getPluginConfigUIFactory() { return new PluginConfigUIFactoryImpl(config,pluginConfigKey); } public String getPluginConfigKey() { return( pluginConfigKey ); } public Tracker getTracker() { return( TrackerImpl.getSingleton()); } public ShareManager getShareManager() throws ShareException { return( ShareManagerImpl.getSingleton()); } public DownloadManager getDownloadManager() { return( DownloadManagerImpl.getSingleton(initialiser.getAzureusCore())); } public MainlineDHTManager getMainlineDHTManager() { return new MainlineDHTManagerImpl(initialiser.getAzureusCore()); } public TorrentManager getTorrentManager() { return( TorrentManagerImpl.getSingleton().specialise( this )); } public Logger getLogger() { if ( logger == null ){ logger = new LoggerImpl( this ); } return( logger ); } public IPFilter getIPFilter() { return( new IPFilterImpl()); } public Utilities getUtilities() { return( new UtilitiesImpl( initialiser.getAzureusCore(), this )); } public ShortCuts getShortCuts() { return( new ShortCutsImpl(this)); } public UIManager getUIManager() { return( new UIManagerImpl( this )); } public UpdateManager getUpdateManager() { return( UpdateManagerImpl.getSingleton( initialiser.getAzureusCore())); } protected void unloadSupport() { ipc_interface.unload(); UIManagerImpl.unload( this ); } public boolean isUnloadable() { PluginDeprecation.call("unloadable", this.given_plugin_id); return getPluginState().isUnloadable(); } public void reload() throws PluginException { PluginDeprecation.call("reload", this.given_plugin_id); getPluginState().reload(); } public void unload() throws PluginException { PluginDeprecation.call("unload", this.given_plugin_id); getPluginState().unload(); } public void uninstall() throws PluginException { PluginDeprecation.call("uninstall", this.given_plugin_id); getPluginState().uninstall(); } public boolean isInitialisationThread() { return( initialiser.isInitialisationThread()); } public ClientIDManager getClientIDManager() { return( ClientIDManagerImpl.getSingleton()); } public ConnectionManager getConnectionManager() { return ConnectionManagerImpl.getSingleton( initialiser.getAzureusCore()); } public MessageManager getMessageManager() { return MessageManagerImpl.getSingleton( initialiser.getAzureusCore() ); } public DistributedDatabase getDistributedDatabase() { return( DDBaseImpl.getSingleton(initialiser.getAzureusCore())); } public PlatformManager getPlatformManager() { return( PlatformManagerFactory.getPlatformManager()); } protected void initialisationComplete() { Iterator<PluginListener> it = listeners.iterator(); while( it.hasNext()){ try{ fireInitComplete( it.next()); }catch( Throwable e ){ Debug.printStackTrace( e ); } } for (int i=0;i<children.size();i++){ ((PluginInterfaceImpl)children.get(i)).initialisationComplete(); } } protected void fireInitComplete( PluginListener listener ) { synchronized( init_complete_fired_set ){ if ( init_complete_fired_set.contains( listener )){ return; } init_complete_fired_set.add( listener ); } try { listener.initializationComplete(); } catch (Exception e) { Debug.out(e); } } protected void closedownInitiated() { Iterator it = listeners.iterator(); while( it.hasNext()){ try{ ((PluginListener)it.next()).closedownInitiated(); }catch( Throwable e ){ Debug.printStackTrace( e ); } } for (int i=0;i<children.size();i++){ ((PluginInterfaceImpl)children.get(i)).closedownInitiated(); } } protected void closedownComplete() { Iterator it = listeners.iterator(); while( it.hasNext()){ try{ ((PluginListener)it.next()).closedownComplete(); }catch( Throwable e ){ Debug.printStackTrace( e ); } } for (int i=0;i<children.size();i++){ ((PluginInterfaceImpl)children.get(i)).closedownComplete(); } } public ClassLoader getPluginClassLoader() { return( class_loader ); } public PluginInterface getLocalPluginInterface( Class plugin_class, String id ) throws PluginException { try{ Plugin p = (Plugin)plugin_class.newInstance(); // Discard plugin.id from the properties, we want the // plugin ID we create to take priority - not a value // from the original plugin ID properties file. Properties local_props = new Properties(props); local_props.remove("plugin.id"); if( id.endsWith( "_v" )){ throw( new Exception( "Verified plugins must be loaded from a jar" )); } PluginInterfaceImpl pi = new PluginInterfaceImpl( p, initialiser, initialiser_key, class_loader, null, key + "." + id, local_props, pluginDir, getPluginID() + "." + id, plugin_version ); initialiser.fireCreated( pi ); p.initialize( pi ); children.add( pi ); return( pi ); }catch( Throwable e ){ if ( e instanceof PluginException ){ throw((PluginException)e); } throw( new PluginException( "Local initialisation fails", e )); } } public IPCInterfaceImpl getIPC() { return( ipc_interface ); } public boolean isShared() { PluginDeprecation.call("isShared", this.given_plugin_id); return getPluginState().isShared(); } // Not exposed in the interface. void setAsFailed() { getPluginState().setDisabled(true); state.failed = true; } protected void destroy() { class_loader = null; // unhook the reference to the plugin but leave with a valid reference in case // something tries to use it plugin = new FailedPlugin( "Plugin '" + getPluginID() + "' has been unloaded!", null ); } public void addListener( PluginListener l ) { listeners.add(l); if ( initialiser.isInitialisationComplete()){ fireInitComplete( l ); } } public void removeListener( final PluginListener l ) { listeners.remove(l); // we want to remove this ref, but there is a *small* chance that there's a parallel thread firing the complete so // decrease chance of hanging onto unwanted ref new DelayedEvent( "PIL:clear", 10000, new AERunnable() { public void runSupport() { synchronized( init_complete_fired_set ){ init_complete_fired_set.remove(l); } } }); } public void addEventListener( final PluginEventListener l ) { initialiser.runPEVTask( new AERunnable() { public void runSupport() { List<PluginEvent> events = initialiser.getPEVHistory(); for ( PluginEvent event: events ){ try{ l.handleEvent( event ); }catch( Throwable e ){ Debug.out( e ); } } event_listeners.add(l); } }); } public void removeEventListener( final PluginEventListener l ) { initialiser.runPEVTask( new AERunnable() { public void runSupport() { event_listeners.remove(l); } }); } public void firePluginEvent( final PluginEvent event ) { initialiser.runPEVTask( new AERunnable() { public void runSupport() { firePluginEventSupport( event ); } }); } protected void firePluginEventSupport( PluginEvent event ) { Iterator<PluginEventListener> it = event_listeners.iterator(); while( it.hasNext()){ try{ PluginEventListener listener = it.next(); listener.handleEvent( event ); }catch( Throwable e ){ Debug.printStackTrace( e ); } } for (int i=0;i<children.size();i++){ ((PluginInterfaceImpl)children.get(i)).firePluginEvent(event); } } protected void generateEvidence( IndentWriter writer ) { writer.println( getPluginName()); try{ writer.indent(); writer.println( "id:" + getPluginID() + ",version:" + getPluginVersion()); String user_dir = FileUtil.getUserFile( "plugins" ).toString(); String shared_dir = FileUtil.getApplicationFile( "plugins" ).toString(); String plugin_dir = getPluginDirectoryName(); String type; boolean built_in = false; if ( plugin_dir.startsWith( shared_dir )){ type = "shared"; }else if ( plugin_dir.startsWith( user_dir )){ type = "per-user"; }else{ built_in = true; type = "built-in"; } PluginState ps = getPluginState(); String info = getPluginconfig().getPluginStringParameter( "plugin.info" ); writer.println( "type:" + type + ",enabled=" + !ps.isDisabled() + ",load_at_start=" + ps.isLoadedAtStartup() + ",operational=" + ps.isOperational() + (info==null||info.length()==0?"":( ",info=" + info ))); if ( ps.isOperational()){ Plugin plugin = getPlugin(); if ( plugin instanceof AEDiagnosticsEvidenceGenerator ){ try{ writer.indent(); ((AEDiagnosticsEvidenceGenerator)plugin).generate( writer ); }catch( Throwable e ){ writer.println( "Failed to generate plugin-specific info: " + Debug.getNestedExceptionMessage( e )); }finally{ writer.exdent(); } } }else{ if ( !built_in ){ File dir = new File( plugin_dir ); if ( dir.exists()){ String[] files = dir.list(); if ( files != null ){ String files_str = ""; for ( String f: files ){ files_str += (files_str.length()==0?"":", ") + f; } writer.println( " files: " + files_str ); } } } } }finally{ writer.exdent(); } } public boolean isDisabled() { PluginDeprecation.call("isDisabled", this.given_plugin_id); return getPluginState().isDisabled(); } public void setDisabled(boolean disabled) { PluginDeprecation.call("setDisabled", this.given_plugin_id); getPluginState().setDisabled(disabled); } public PluginState getPluginState() { return this.state; } PluginStateImpl getPluginStateImpl() { return this.state; } // unfortunately we need to protect ourselves against the plugin itself trying to set // plugin.version and plugin.id as this screws things up if they get it "wrong". // They should be setting these things in the plugin.properties file // currently the RSSImport plugin does this (version 1.1 sets version as 1.0) protected class propertyWrapper extends Properties { protected boolean initialising = true; protected propertyWrapper( Properties _props ) { Iterator it = _props.keySet().iterator(); while( it.hasNext()){ Object key = it.next(); put( key, _props.get(key)); } initialising = false; } public Object setProperty( String str, String val ) { // if its us then we probably know what we're doing :P if ( ! ( plugin.getClass().getName().startsWith( "org.gudy") || plugin.getClass().getName().startsWith( "com.aelitis."))){ if ( str.equalsIgnoreCase( "plugin.id" ) || str.equalsIgnoreCase("plugin.version" )){ if (org.gudy.azureus2.core3.logging.Logger.isEnabled()) org.gudy.azureus2.core3.logging.Logger .log(new LogEvent(LOGID, LogEvent.LT_WARNING, "Plugin '" + getPluginName() + "' tried to set property '" + str + "' - action ignored")); return( null ); } } return( super.setProperty( str, val )); } public Object put( Object key, Object value ) { // if its us then we probably know what we're doing :P if ( ! ( plugin.getClass().getName().startsWith( "org.gudy") || plugin.getClass().getName().startsWith( "com.aelitis."))){ if ((!initialising ) && key instanceof String ){ String k_str = (String)key; if ( k_str.equalsIgnoreCase( "plugin.id" ) || k_str.equalsIgnoreCase("plugin.version" )){ if (org.gudy.azureus2.core3.logging.Logger.isEnabled()) org.gudy.azureus2.core3.logging.Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "Plugin '" + getPluginName() + "' tried to set property '" + k_str + "' - action ignored")); return( null ); } } } return( super.put( key, value )); } public Object get( Object key ) { return( super.get(key)); } } }