/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.platform.plugin.services.pluginmgr; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.platform.api.action.IAction; import org.pentaho.platform.api.engine.IContentGenerator; import org.pentaho.platform.api.engine.IContentGeneratorInfo; import org.pentaho.platform.api.engine.IContentInfo; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.IPlatformPlugin; import org.pentaho.platform.api.engine.IPluginLifecycleListener; import org.pentaho.platform.api.engine.IPluginManager; import org.pentaho.platform.api.engine.IPluginManagerListener; import org.pentaho.platform.api.engine.IPluginProvider; import org.pentaho.platform.api.engine.IPluginResourceLoader; import org.pentaho.platform.api.engine.IServiceManager; import org.pentaho.platform.api.engine.ISolutionFileMetaProvider; import org.pentaho.platform.api.engine.ObjectFactoryException; import org.pentaho.platform.api.engine.PlatformPluginRegistrationException; import org.pentaho.platform.api.engine.PluginBeanDefinition; import org.pentaho.platform.api.engine.PluginBeanException; import org.pentaho.platform.api.engine.PluginLifecycleException; import org.pentaho.platform.api.engine.PluginServiceDefinition; import org.pentaho.platform.api.engine.ServiceException; import org.pentaho.platform.api.engine.ServiceInitializationException; import org.pentaho.platform.api.engine.perspective.IPluginPerspectiveManager; import org.pentaho.platform.api.engine.perspective.pojo.IPluginPerspective; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.core.system.objfac.StandaloneSpringPentahoObjectFactory; import org.pentaho.platform.engine.core.system.objfac.spring.PentahoBeanScopeValidatorPostProcessor; import org.pentaho.platform.plugin.services.messages.Messages; import org.pentaho.platform.plugin.services.pluginmgr.servicemgr.ServiceConfig; import org.pentaho.platform.util.logging.Logger; import org.pentaho.ui.xul.XulOverlay; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.FileSystemResource; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; public class DefaultPluginManager implements IPluginManager { private static final Log logger = LogFactory.getLog( DefaultPluginManager.class ); private static final String DEFAULT_PERSPECTIVE = "generatedContent"; // A namespacing prefix is added when registering meta provider objects in the object factory private static final String METAPROVIDER_KEY_PREFIX = "METAPROVIDER-"; //$NON-NLS-1$ protected Map<String, ClassLoader> classLoaderMap = Collections.synchronizedMap( new HashMap<String, ClassLoader>() ); protected Map<String, GenericApplicationContext> beanFactoryMap = Collections .synchronizedMap( new HashMap<String, GenericApplicationContext>() ); protected Map<String, IPlatformPlugin> registeredPlugins = new Hashtable<String, IPlatformPlugin>(); protected Map<String, IContentInfo> contentTypeByExtension = Collections .synchronizedMap( new HashMap<String, IContentInfo>() ); protected List<XulOverlay> overlaysCache = Collections.synchronizedList( new ArrayList<XulOverlay>() ); @Override public Set<String> getContentTypes() { // map.keySet returns a set backed by the map, so we cannot allow modification of the set return Collections.unmodifiableSet( contentTypeByExtension.keySet() ); } @Override public List<XulOverlay> getOverlays() { return Collections.unmodifiableList( overlaysCache ); } @Override public IContentInfo getContentTypeInfo( String type ) { return contentTypeByExtension.get( type ); } /** * Clears all the lists and maps in preparation for reloading the state from the plugin provider. Fires the plugin * unloaded event for each known plugin. */ private void unloadPlugins() { overlaysCache.clear(); classLoaderMap.clear(); // TODO: can we reset/reload the spring bean factory here? contentTypeByExtension.clear(); // we do not need to synchronize here since unloadPlugins // is called within the synchronized block in reload for ( IPlatformPlugin plugin : registeredPlugins.values() ) { try { plugin.unLoaded(); } catch ( Throwable t ) { // we do not want any type of exception to leak out and cause a problem here // A plugin unload should not adversely affect anything downstream, it should // log an error and otherwise fail silently String msg = Messages.getInstance().getErrorString( "PluginManager.ERROR_0014_PLUGIN_FAILED_TO_PROPERLY_UNLOAD", plugin.getId() ); //$NON-NLS-1$ Logger.error( getClass().toString(), msg, t ); PluginMessageLogger.add( msg ); } } registeredPlugins.clear(); } @Override public List<String> getRegisteredPlugins() { List<String> pluginIds = new ArrayList<String>( registeredPlugins.size() ); for ( IPlatformPlugin plugin : registeredPlugins.values() ) { pluginIds.add( plugin.getId() ); } return pluginIds; } @Deprecated public final boolean reload( IPentahoSession session ) { return reload(); } @Override public final boolean reload() { IPentahoSession session = PentahoSessionHolder.getSession(); boolean anyErrors = false; IPluginProvider pluginProvider = PentahoSystem.get( IPluginProvider.class, "IPluginProvider", session ); List<IPlatformPlugin> providedPlugins = null; try { synchronized ( registeredPlugins ) { this.unloadPlugins(); } // the plugin may fail to load during getPlugins without an exception thrown if the provider // is capable of discovering the plugin fine but there are structural problems with the plugin // itself. In this case a warning should be logged by the provider, but, again, no exception // is expected. providedPlugins = pluginProvider.getPlugins( session ); } catch ( PlatformPluginRegistrationException e1 ) { String msg = Messages.getInstance().getErrorString( "PluginManager.ERROR_0012_PLUGIN_DISCOVERY_FAILED" ); //$NON-NLS-1$ Logger.error( getClass().toString(), msg, e1 ); PluginMessageLogger.add( msg ); anyErrors = true; } // TODO: refresh appc context here? synchronized ( providedPlugins ) { for ( IPlatformPlugin plugin : providedPlugins ) { try { registeredPlugins.put( plugin.getId(), plugin ); ClassLoader loader = setPluginClassLoader( plugin ); initializeBeanFactory( plugin, loader ); } catch ( Throwable t ) { // this has been logged already anyErrors = true; String msg = Messages.getInstance().getErrorString( "PluginManager.ERROR_0011_FAILED_TO_REGISTER_PLUGIN", plugin.getId() ); //$NON-NLS-1$ Logger.error( getClass().toString(), msg, t ); PluginMessageLogger.add( msg ); } } registeredPlugins.clear(); for ( IPlatformPlugin plugin : providedPlugins ) { try { GenericApplicationContext beanFactory = beanFactoryMap.get( plugin.getId() ); if ( beanFactory != null ) { beanFactory.refresh(); } registerPlugin( plugin ); registeredPlugins.put( plugin.getId(), plugin ); } catch ( Throwable t ) { // this has been logged already anyErrors = true; String msg = Messages.getInstance().getErrorString( "PluginManager.ERROR_0011_FAILED_TO_REGISTER_PLUGIN", plugin.getId() ); //$NON-NLS-1$ Logger.error( getClass().toString(), msg, t ); PluginMessageLogger.add( msg ); } } } IServiceManager svcManager = PentahoSystem.get( IServiceManager.class, null ); if ( svcManager != null ) { try { svcManager.initServices(); } catch ( ServiceInitializationException e ) { String msg = Messages.getInstance() .getErrorString( "PluginManager.ERROR_0022_SERVICE_INITIALIZATION_FAILED" ); //$NON-NLS-1$ Logger.error( getClass().toString(), msg, e ); PluginMessageLogger.add( msg ); } } return !anyErrors; } /** * Gets the plugin ready to handle lifecycle events. */ private static void bootStrapPlugin( IPlatformPlugin plugin, ClassLoader loader ) throws PlatformPluginRegistrationException { Object listener = null; try { if ( !StringUtils.isEmpty( plugin.getLifecycleListenerClassname() ) ) { listener = loader.loadClass( plugin.getLifecycleListenerClassname() ).newInstance(); } } catch ( Throwable t ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0017_COULD_NOT_LOAD_PLUGIN_LIFECYCLE_LISTENER", plugin.getId(), plugin //$NON-NLS-1$ .getLifecycleListenerClassname() ), t ); } if ( listener != null ) { if ( !IPluginLifecycleListener.class.isAssignableFrom( listener.getClass() ) ) { throw new PlatformPluginRegistrationException( Messages .getInstance() .getErrorString( "PluginManager.ERROR_0016_PLUGIN_LIFECYCLE_LISTENER_WRONG_TYPE", plugin.getId(), plugin.getLifecycleListenerClassname() ) ); //$NON-NLS-1$ } plugin.addLifecycleListener( (IPluginLifecycleListener) listener ); } } @SuppressWarnings( "unchecked" ) private void registerPlugin( final IPlatformPlugin plugin ) throws PlatformPluginRegistrationException, PluginLifecycleException { // TODO: we should treat the registration of a plugin as an atomic operation // with rollback if something is broken if ( StringUtils.isEmpty( plugin.getId() ) ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0026_PLUGIN_INVALID", plugin.getSourceDescription() ) ); //$NON-NLS-1$ } if ( registeredPlugins.containsKey( plugin.getId() ) ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0024_PLUGIN_ALREADY_LOADED_BY_SAME_NAME", plugin.getId() ) ); //$NON-NLS-1$ } ClassLoader loader = setPluginClassLoader( plugin ); bootStrapPlugin( plugin, loader ); plugin.init(); registerContentTypes( plugin, loader ); registerContentGenerators( plugin, loader ); registerPerspectives( plugin, loader ); // cache overlays overlaysCache.addAll( plugin.getOverlays() ); // service registry must take place after bean registry since // a service class may be configured as a plugin bean registerServices( plugin, loader ); PluginMessageLogger .add( Messages.getInstance().getString( "PluginManager.PLUGIN_REGISTERED", plugin.getId() ) ); //$NON-NLS-1$ try { plugin.loaded(); } catch ( Throwable t ) { // The plugin has already been loaded, so there is really no logical response to any type // of failure here except to log an error and otherwise fail silently String msg = Messages.getInstance().getErrorString( "PluginManager.ERROR_0015_PLUGIN_LOADED_HANDLING_FAILED", plugin.getId() ); //$NON-NLS-1$ Logger.error( getClass().toString(), msg, t ); PluginMessageLogger.add( msg ); } } private void registerPerspectives( IPlatformPlugin plugin, ClassLoader loader ) { for ( IPluginPerspective pluginPerspective : plugin.getPluginPerspectives() ) { PentahoSystem.get( IPluginPerspectiveManager.class ).addPluginPerspective( pluginPerspective ); } } protected void registerContentTypes( IPlatformPlugin plugin, ClassLoader loader ) throws PlatformPluginRegistrationException { // index content types and define any file meta providers for ( IContentInfo info : plugin.getContentInfos() ) { contentTypeByExtension.put( info.getExtension(), info ); String metaProviderClass = plugin.getMetaProviderMap().get( info.getExtension() ); // if a meta-provider is defined for this content type, then register it... if ( !StringUtils.isEmpty( metaProviderClass ) ) { Class<?> clazz = null; String defaultErrMsg = Messages .getInstance() .getErrorString( "PluginManager.ERROR_0013_FAILED_TO_SET_CONTENT_TYPE_META_PROVIDER", metaProviderClass, info.getExtension() ); //$NON-NLS-1$ try { // do a test load to fail early if class not found clazz = loader.loadClass( metaProviderClass ); } catch ( Exception e ) { throw new PlatformPluginRegistrationException( defaultErrMsg, e ); } // check that the class is an accepted type if ( !( ISolutionFileMetaProvider.class.isAssignableFrom( clazz ) ) ) { throw new PlatformPluginRegistrationException( Messages .getInstance() .getErrorString( "PluginManager.ERROR_0019_WRONG_TYPE_FOR_CONTENT_TYPE_META_PROVIDER", metaProviderClass, info.getExtension() ) ); //$NON-NLS-1$ } // the class is ok, so register it with the factory assertUnique( plugin.getId(), METAPROVIDER_KEY_PREFIX + info.getExtension() ); BeanDefinition beanDef = BeanDefinitionBuilder.rootBeanDefinition( metaProviderClass ).setScope( BeanDefinition.SCOPE_PROTOTYPE ) .getBeanDefinition(); beanFactoryMap.get( plugin.getId() ).registerBeanDefinition( METAPROVIDER_KEY_PREFIX + info.getExtension(), beanDef ); } } } /** * The native bean factory is the bean factory that has had all of its bean definitions loaded natively. In other * words, the plugin manager will not add any further bean definitions (i.e. from a plugin.xml file) into this * factory. This factory represents the one responsible for holding bean definitions for plugin.spring.xml or, if in a * unit test environment, the unit test pre-loaded bean factory. * * @return a bean factory will preconfigured bean definitions or <code>null</code> if no bean definition source is * available */ protected BeanFactory getNativeBeanFactory( final IPlatformPlugin plugin, final ClassLoader loader ) { BeanFactory nativeFactory = null; if ( plugin.getBeanFactory() != null ) { // then we are probably in a unit test so just use the preconfigured one BeanFactory testFactory = plugin.getBeanFactory(); if ( testFactory instanceof ConfigurableBeanFactory ) { ( (ConfigurableBeanFactory) testFactory ).setBeanClassLoader( loader ); } else { logger.warn( Messages.getInstance().getString( "PluginManager.WARN_WRONG_BEAN_FACTORY_TYPE" ) ); //$NON-NLS-1$ } nativeFactory = testFactory; } else { File f = new File( ( (PluginClassLoader) loader ).getPluginDir(), "plugin.spring.xml" ); //$NON-NLS-1$ if ( f.exists() ) { logger.debug( "Found plugin spring file @ " + f.getAbsolutePath() ); //$NON-NLS-1$ FileSystemResource fsr = new FileSystemResource( f ); GenericApplicationContext appCtx = new GenericApplicationContext() { @Override protected void prepareBeanFactory( ConfigurableListableBeanFactory clBeanFactory ) { super.prepareBeanFactory( clBeanFactory ); clBeanFactory.setBeanClassLoader( loader ); } @Override public ClassLoader getClassLoader() { return loader; } }; XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader( appCtx ); xmlReader.setBeanClassLoader( loader ); xmlReader.loadBeanDefinitions( fsr ); nativeFactory = appCtx; } } return nativeFactory; } /** * Initializes a bean factory for serving up instance of plugin classes. * * @return an instance of the factory that allows callers to continue to define more beans on it programmatically */ protected void initializeBeanFactory( final IPlatformPlugin plugin, final ClassLoader loader ) throws PlatformPluginRegistrationException { if ( !( loader instanceof PluginClassLoader ) ) { logger .warn( "Can't determine plugin dir to load spring file because classloader is not of type PluginClassLoader. " //$NON-NLS-1$ + "This is since we are probably in a unit test" ); //$NON-NLS-1$ return; } // // Get the native factory (the factory that comes preconfigured via either Spring bean files or via JUnit test // BeanFactory nativeBeanFactory = getNativeBeanFactory( plugin, loader ); // // Now create the definable factory for accepting old style bean definitions from IPluginProvider // GenericApplicationContext beanFactory = null; if ( nativeBeanFactory != null && nativeBeanFactory instanceof GenericApplicationContext ) { beanFactory = (GenericApplicationContext) nativeBeanFactory; } else { beanFactory = new GenericApplicationContext(); beanFactory.setClassLoader( loader ); beanFactory.getBeanFactory().setBeanClassLoader( loader ); if ( nativeBeanFactory != null ) { beanFactory.getBeanFactory().setParentBeanFactory( nativeBeanFactory ); } } beanFactory.addBeanFactoryPostProcessor( new PentahoBeanScopeValidatorPostProcessor() ); beanFactoryMap.put( plugin.getId(), beanFactory ); // // Register any beans defined via the pluginProvider // // we do not have to synchronize on the bean set here because the // map that backs the set is never modified after the plugin has // been made available to the plugin manager for ( PluginBeanDefinition def : plugin.getBeans() ) { // register by classname if id is null def.setBeanId( ( def.getBeanId() == null ) ? def.getClassname() : def.getBeanId() ); assertUnique( plugin.getId(), def.getBeanId() ); // defining plugin beans the old way through the plugin provider ifc supports only prototype scope BeanDefinition beanDef = BeanDefinitionBuilder.rootBeanDefinition( def.getClassname() ).setScope( BeanDefinition.SCOPE_PROTOTYPE ) .getBeanDefinition(); beanFactory.registerBeanDefinition( def.getBeanId(), beanDef ); } StandaloneSpringPentahoObjectFactory pentahoFactory = new StandaloneSpringPentahoObjectFactory( "Plugin Factory ( " + plugin.getId() + " )" ); pentahoFactory.init( null, beanFactory ); } /** * A utility method that throws an exception if a bean with the id is already defined for this plugin */ protected void assertUnique( String pluginId, String beanId ) throws PlatformPluginRegistrationException { if ( beanFactoryMap.get( pluginId ).containsBean( beanId ) ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0018_BEAN_ALREADY_REGISTERED", beanId, pluginId ) ); //$NON-NLS-1$ } } private void registerServices( IPlatformPlugin plugin, ClassLoader loader ) throws PlatformPluginRegistrationException { IServiceManager svcManager = PentahoSystem.get( IServiceManager.class, null ); for ( PluginServiceDefinition pws : plugin.getServices() ) { for ( ServiceConfig ws : createServiceConfigs( pws, plugin, loader ) ) { try { svcManager.registerService( ws ); } catch ( ServiceException e ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0025_SERVICE_REGISTRATION_FAILED", ws.getId(), plugin.getId() ), e ); //$NON-NLS-1$ } } } } /* * A utility method to convert plugin version of webservice definition to the official engine version consumable by an * IServiceManager */ private Collection<ServiceConfig> createServiceConfigs( PluginServiceDefinition pws, IPlatformPlugin plugin, ClassLoader loader ) throws PlatformPluginRegistrationException { Collection<ServiceConfig> services = new ArrayList<ServiceConfig>(); // Set the service type (one service config instance created per service type) // if ( pws.getTypes() == null || pws.getTypes().length < 1 ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0023_SERVICE_TYPE_UNSPECIFIED", pws.getId() ) ); //$NON-NLS-1$ } for ( String type : pws.getTypes() ) { ServiceConfig ws = new ServiceConfig(); ws.setServiceType( type ); ws.setTitle( pws.getTitle() ); ws.setDescription( pws.getDescription() ); String serviceClassName = ( StringUtils.isEmpty( pws.getServiceClass() ) ) ? pws.getServiceBeanId() : pws.getServiceClass(); String serviceId; if ( !StringUtils.isEmpty( pws.getId() ) ) { serviceId = pws.getId(); } else { serviceId = serviceClassName; if ( serviceClassName.indexOf( '.' ) > 0 ) { serviceId = serviceClassName.substring( serviceClassName.lastIndexOf( '.' ) + 1 ); } } ws.setId( serviceId ); // Register the service class // final String serviceClassKey = ws.getServiceType() + "-" + ws.getId() + "/" + serviceClassName; //$NON-NLS-1$ //$NON-NLS-2$ assertUnique( plugin.getId(), serviceClassKey ); // defining plugin beans the old way through the plugin provider ifc supports only prototype scope BeanDefinition beanDef = BeanDefinitionBuilder.rootBeanDefinition( serviceClassName ).setScope( BeanDefinition.SCOPE_PROTOTYPE ) .getBeanDefinition(); beanFactoryMap.get( plugin.getId() ).registerBeanDefinition( serviceClassKey, beanDef ); if ( !this.isBeanRegistered( serviceClassKey ) ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0020_NO_SERVICE_CLASS_REGISTERED", serviceClassKey ) ); //$NON-NLS-1$ } // Load/set the service class and supporting types // try { ws.setServiceClass( loadClass( serviceClassKey ) ); ArrayList<Class<?>> classes = new ArrayList<Class<?>>(); if ( pws.getExtraClasses() != null ) { for ( String extraClass : pws.getExtraClasses() ) { classes.add( loadClass( extraClass ) ); } } ws.setExtraClasses( classes ); } catch ( PluginBeanException e ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0021_SERVICE_CLASS_LOAD_FAILED", serviceClassKey ), e ); //$NON-NLS-1$ } services.add( ws ); } return services; } private ClassLoader setPluginClassLoader( IPlatformPlugin plugin ) throws PlatformPluginRegistrationException { ClassLoader loader = classLoaderMap.get( plugin.getId() ); if ( loader == null ) { String pluginDirPath = PentahoSystem.getApplicationContext() .getSolutionPath( "system/" + plugin.getSourceDescription() ); //$NON-NLS-1$ // need to scrub out duplicate file delimeters otherwise we will // not be able to locate resources in jars. This classloader ultimately // needs to be made less fragile pluginDirPath = pluginDirPath.replace( "//", "/" ); //$NON-NLS-1$ //$NON-NLS-2$ Logger.debug( this, "plugin dir for " + plugin.getId() + " is [" + pluginDirPath + "]" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ File pluginDir = new File( pluginDirPath ); if ( !pluginDir.exists() || !pluginDir.isDirectory() || !pluginDir.canRead() ) { throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString( "PluginManager.ERROR_0027_PLUGIN_DIR_UNAVAILABLE", pluginDir.getAbsolutePath() ) ); //$NON-NLS-1$ } loader = new PluginClassLoader( pluginDir, this.getClass().getClassLoader() ); if ( plugin.getLoaderType() == IPlatformPlugin.ClassLoaderType.OVERRIDING ) { ( (PluginClassLoader) loader ).setOverrideLoad( true ); } classLoaderMap.put( plugin.getId(), loader ); } return loader; } public ClassLoader getClassLoader( IPlatformPlugin plugin ) { return getClassLoader( plugin.getId() ); } @Override public ClassLoader getClassLoader( String pluginId ) { return classLoaderMap.get( pluginId ); } // public ListableBeanFactory asBeanFactory() { // return beanFactoryMap.get(pluginId); // } @Override public ListableBeanFactory getBeanFactory( String pluginId ) { return beanFactoryMap.get( pluginId ).getBeanFactory(); } private void registerContentGenerators( IPlatformPlugin plugin, ClassLoader loader ) throws PlatformPluginRegistrationException { // register the content generators for ( IContentGeneratorInfo cgInfo : plugin.getContentGenerators() ) { // define the bean in the factory BeanDefinition beanDef = BeanDefinitionBuilder.rootBeanDefinition( cgInfo.getClassname() ).setScope( BeanDefinition.SCOPE_PROTOTYPE ) .getBeanDefinition(); GenericApplicationContext factory = beanFactoryMap.get( plugin.getId() ); // register bean with alias of content generator id (old way) factory.registerBeanDefinition( cgInfo.getId(), beanDef ); // register bean with alias of type (with default perspective) as well (new way) factory.registerAlias( cgInfo.getId(), cgInfo.getType() ); PluginMessageLogger.add( Messages.getInstance().getString( "PluginManager.USER_CONTENT_GENERATOR_REGISTERED", cgInfo.getId(), plugin.getId() ) ); //$NON-NLS-1$ } } public Object getBean( String beanId, Class<?> requiredType ) { if ( beanId == null ) { throw new IllegalArgumentException( "beanId cannot be null" ); //$NON-NLS-1$ } Object bean = null; for ( GenericApplicationContext beanFactory : beanFactoryMap.values() ) { if ( beanFactory.containsBean( beanId ) ) { if ( requiredType == null ) { bean = beanFactory.getBean( beanId ); } else { bean = beanFactory.getBean( beanId, requiredType ); } } } if ( bean == null ) { throw new NoSuchBeanDefinitionException( "Could not find bean with id " + beanId ); } return bean; } @Override public Object getBean( String beanId ) throws PluginBeanException { if ( beanId == null ) { throw new IllegalArgumentException( "beanId cannot be null" ); //$NON-NLS-1$ } Object bean = null; for ( GenericApplicationContext beanFactory : beanFactoryMap.values() ) { if ( beanFactory.containsBean( beanId ) ) { try { bean = beanFactory.getBean( beanId ); } catch ( Throwable ex ) { // Catching throwable on purpose throw new PluginBeanException( ex ); } } } if ( bean == null ) { throw new PluginBeanException( Messages.getInstance().getString( "PluginManager.WARN_CLASS_NOT_REGISTERED", beanId ) ); //$NON-NLS-1$ } return bean; } @Override public IContentGenerator getContentGenerator( String type, String perspectiveName ) { IContentGenerator cg = null; if ( perspectiveName == null || perspectiveName.equals( DEFAULT_PERSPECTIVE ) ) { cg = (IContentGenerator) getBean( type, IContentGenerator.class ); } else { String beanId = ( perspectiveName == null ) ? type : type + "." + perspectiveName; //$NON-NLS-1$ try { cg = (IContentGenerator) getBean( beanId, IContentGenerator.class ); } catch ( NoSuchBeanDefinitionException e ) { // fallback condition, look for a type agnostic content generator try { cg = (IContentGenerator) getBean( perspectiveName, IContentGenerator.class ); } catch ( NoSuchBeanDefinitionException e2 ) { throw new NoSuchBeanDefinitionException( "Failed to find bean: " + e.getMessage() + " : " + e2.getMessage() ); } } } return cg; } public IAction getAction( String type, String perspectiveName ) { IAction action = null; String beanId = ( perspectiveName == null ) ? type : type + "." + perspectiveName; //$NON-NLS-1$ try { action = (IAction) getBean( beanId, IAction.class ); } catch ( NoSuchBeanDefinitionException e ) { // fallback condition, look for a type agnostic content generator try { action = (IAction) getBean( perspectiveName, IAction.class ); } catch ( NoSuchBeanDefinitionException e2 ) { throw new NoSuchBeanDefinitionException( "Failed to find bean: " + e.getMessage() + " : " + e2.getMessage() ); } } return action; } @Override public Class<?> loadClass( String beanId ) throws PluginBeanException { if ( beanId == null ) { throw new IllegalArgumentException( "beanId cannot be null" ); //$NON-NLS-1$ } Class<?> type = null; for ( GenericApplicationContext beanFactory : beanFactoryMap.values() ) { if ( beanFactory.containsBean( beanId ) ) { try { type = beanFactory.getType( beanId ); break; } catch ( Throwable ex ) { // Catching throwable on purpose throw new PluginBeanException( ex ); } } } if ( type == null ) { throw new PluginBeanException( Messages.getInstance().getString( "PluginManager.WARN_CLASS_NOT_REGISTERED", beanId ) ); //$NON-NLS-1$ } return type; } @Override public boolean isBeanRegistered( String beanId ) { if ( beanId == null ) { throw new IllegalArgumentException( "beanId cannot be null" ); //$NON-NLS-1$ } boolean registered = false; for ( GenericApplicationContext beanFactory : beanFactoryMap.values() ) { if ( beanFactory.containsBean( beanId ) ) { registered = true; } } return registered; } @Override public void unloadAllPlugins() { synchronized ( registeredPlugins ) { this.unloadPlugins(); } } public Object getPluginSetting( IPlatformPlugin plugin, String key, String defaultValue ) { return getPluginSetting( plugin.getId(), key, defaultValue ); } @Override public Object getPluginSetting( String pluginId, String key, String defaultValue ) { IPluginResourceLoader resLoader = PentahoSystem.get( IPluginResourceLoader.class, null ); ClassLoader classLoader = classLoaderMap.get( pluginId ); return resLoader.getPluginSetting( classLoader, key, defaultValue ); } private Collection<String> getBeanIdsForType( String pluginId, Class<?> clazz ) { ArrayList<String> ids = new ArrayList<String>(); ListableBeanFactory fac = beanFactoryMap.get( pluginId ).getBeanFactory(); String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( fac, clazz ); for ( String beanName : names ) { ids.add( beanName ); for ( String beanAlias : fac.getAliases( beanName ) ) { ids.add( beanAlias ); } } return ids; } @Override public String getPluginIdForType( String contentType ) { for ( String pluginId : getRegisteredPlugins() ) { for ( String beanId : getBeanIdsForType( pluginId, IContentGenerator.class ) ) { String serviceContentType = beanId; if ( beanId.contains( "." ) ) { //$NON-NLS-1$ serviceContentType = beanId.substring( 0, beanId.indexOf( '.' ) ); } if ( contentType.equals( serviceContentType ) ) { return pluginId; } } } // if no content generator was found in any of the plugins that can service contentType, return null return null; } @Override @Deprecated public IContentGenerator getContentGeneratorForType( String type, IPentahoSession session ) throws ObjectFactoryException { try { return getContentGenerator( type, (String) null ); } catch ( NoSuchBeanDefinitionException e ) { throw new ObjectFactoryException( e ); } } @Override public String getPluginIdForClassLoader( ClassLoader classLoader ) { if ( classLoader == null ) { return null; } for ( String pluginId : classLoaderMap.keySet() ) { ClassLoader maybeClassLoader = classLoaderMap.get( pluginId ); if ( maybeClassLoader.equals( classLoader ) ) { return pluginId; } } return null; } private String trimLeadingSlash( String path ) { return ( path.startsWith( "/" ) ) ? path.substring( 1 ) : path; //$NON-NLS-1$ } /** * Return <code>true</code> if the servicePath is being addressed by the requestPath. The request path is said to * request the service if it contains at least ALL of the elements of the servicePath, in order. It may include more * than these elements but it must contain at least the servicePath. * * @param servicePath * @param requestPath * @return <code>true</code> if the servicePath is being addressed by the requestPath */ protected boolean isRequested( String servicePath, String requestPath ) { String[] requestPathElements = trimLeadingSlash( requestPath ).split( "/" ); //$NON-NLS-1$ String[] servicePathElements = trimLeadingSlash( servicePath ).split( "/" ); //$NON-NLS-1$ if ( requestPathElements.length < servicePathElements.length ) { return false; } for ( int i = 0; i < servicePathElements.length; i++ ) { if ( !requestPathElements[ i ].equals( servicePathElements[ i ] ) ) { return false; } } return true; } @Deprecated public String getServicePlugin( String path ) { for ( IPlatformPlugin plugin : registeredPlugins.values() ) { String pluginId = getStaticResourcePluginId( plugin, path ); if ( pluginId != null ) { return pluginId; } for ( IContentGeneratorInfo contentGenerator : plugin.getContentGenerators() ) { String cgId = contentGenerator.getId(); if ( isRequested( cgId, path ) ) { return plugin.getId(); } } } return null; } private String getStaticResourcePluginId( IPlatformPlugin plugin, String path ) { Map<String, String> resourceMap = plugin.getStaticResourceMap(); for ( String url : resourceMap.keySet() ) { if ( isRequested( url, path ) ) { return plugin.getId(); } } return null; } @Deprecated public boolean isStaticResource( String path ) { for ( IPlatformPlugin plugin : registeredPlugins.values() ) { String pluginId = getStaticResourcePluginId( plugin, path ); if ( pluginId != null ) { return true; } } return false; } public boolean isPublic( String pluginId, String path ) { IPlatformPlugin plugin = registeredPlugins.get( pluginId ); if ( plugin == null ) { return false; } Map<String, String> resourceMap = plugin.getStaticResourceMap(); if ( path.startsWith( "/" ) ) { //$NON-NLS-1$ path = path.substring( 1 ); } for ( String pluginRelativeDir : resourceMap.values() ) { if ( path.startsWith( pluginRelativeDir ) ) { return true; } } return false; } @Deprecated public InputStream getStaticResource( String path ) { for ( IPlatformPlugin plugin : registeredPlugins.values() ) { Map<String, String> resourceMap = plugin.getStaticResourceMap(); for ( String url : resourceMap.keySet() ) { if ( isRequested( url, path ) ) { IPluginResourceLoader resLoader = PentahoSystem.get( IPluginResourceLoader.class, null ); ClassLoader classLoader = classLoaderMap.get( plugin.getId() ); String resourcePath = path.replace( url, resourceMap.get( url ) ); return resLoader.getResourceAsStream( classLoader, resourcePath ); } } } return null; } public List<String> getExternalResourcesForContext( String context ) { List<String> resources = new ArrayList<String>(); for ( IPlatformPlugin plugin : registeredPlugins.values() ) { List<String> pluginRes = plugin.getExternalResourcesForContext( context ); if ( pluginRes != null ) { resources.addAll( pluginRes ); } } return resources; } @Override public List<String> getPluginRESTPerspectivesForType( String contentType ) { List<String> pluginPerspectives = new ArrayList<String>(); for ( String pluginId : getRegisteredPlugins() ) { for ( String beanId : getBeanIdsForType( pluginId, IContentGenerator.class ) ) { String serviceContentType = beanId; if ( beanId.contains( "." ) ) { //$NON-NLS-1$ serviceContentType = beanId.substring( 0, beanId.indexOf( '.' ) ); } if ( serviceContentType != null && serviceContentType.equals( contentType ) ) { if ( beanId.contains( "." ) ) { //$NON-NLS-1$ pluginPerspectives.add( beanId.substring( beanId.lastIndexOf( '.' ), beanId.length() ) ); } } } } return pluginPerspectives; } @Override public List<String> getPluginRESTPerspectivesForId( String id ) { List<String> pluginPerspectives = new ArrayList<String>(); for ( String pluginId : getRegisteredPlugins() ) { if ( id.equals( pluginId ) ) { for ( String beanId : getBeanIdsForType( pluginId, IContentGenerator.class ) ) { if ( beanId.contains( "." ) ) { //$NON-NLS-1$ pluginPerspectives.add( beanId.substring( beanId.lastIndexOf( '.' ) + 1, beanId.length() ) ); } } } } return pluginPerspectives; } @Override public void addPluginManagerListener( IPluginManagerListener listener ) { } }