/*!
* 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.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 );
}
}
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 ) {
}
}