/*!
* PENTAHO CORPORATION PROPRIETARY AND CONFIDENTIAL
*
* Copyright 2002 - 2014 Pentaho Corporation (Pentaho). All rights reserved.
*
* NOTICE: All information including source code contained herein is, and
* remains the sole property of Pentaho and its licensors. The intellectual
* and technical concepts contained herein are proprietary and confidential
* to, and are trade secrets of Pentaho and may be covered by U.S. and foreign
* patents, or patents in process, and are protected by trade secret and
* copyright laws. The receipt or possession of this source code and/or related
* information does not convey or imply any rights to reproduce, disclose or
* distribute its contents, or to manufacture, use, or sell anything that it
* may describe, in whole or in part. Any reproduction, modification, distribution,
* or public display of this information without the express written authorization
* from Pentaho is strictly prohibited and in violation of applicable laws and
* international treaties. Access to the source code contained herein is strictly
* prohibited to anyone except those individuals and entities who have executed
* confidentiality and non-disclosure agreements or other agreements with Pentaho,
* explicitly covering such access.
*/
package org.pentaho.platform.plugin.services.pluginmgr;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.pentaho.platform.api.engine.IConfiguration;
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.IObjectCreator;
import org.pentaho.platform.api.engine.IPentahoObjectFactory;
import org.pentaho.platform.api.engine.IPentahoObjectReference;
import org.pentaho.platform.api.engine.IPentahoObjectRegistration;
import org.pentaho.platform.api.engine.IPentahoRegistrableObjectFactory;
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.ISystemConfig;
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.pojo.IPluginPerspective;
import org.pentaho.platform.config.PropertiesFileConfiguration;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.core.system.objfac.spring.PentahoBeanScopeValidatorPostProcessor;
import org.pentaho.platform.engine.core.system.objfac.StandaloneSpringPentahoObjectFactory;
import org.pentaho.platform.engine.core.system.objfac.references.PrototypePentahoObjectReference;
import org.pentaho.platform.engine.core.system.objfac.references.SingletonPentahoObjectReference;
import org.pentaho.platform.plugin.services.messages.Messages;
import org.pentaho.platform.plugin.services.pluginmgr.servicemgr.ServiceConfig;
import org.pentaho.platform.util.xml.dom4j.XmlDom4JHelper;
import org.pentaho.ui.xul.XulOverlay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
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.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.FileSystemResource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* An IPluginManager implementation based on registering objects to the PentahoSystem and querying for them there. This
* supports finding types registered through other systems, allowing for the transition away from IPluginManager to
* plain PentahoSystem.getXXX calls.
* <p/>
* Created by nbaker on 4/18/14.
*/
public class PentahoSystemPluginManager implements IPluginManager {
private static final String DEFAULT_PERSPECTIVE = "generatedContent";
private static final String METAPROVIDER_KEY_PREFIX = "METAPROVIDER-";
public static final String CONTENT_TYPE = "content-type";
public static final String PLUGIN_ID = "plugin-id";
public static final String SETTINGS_PREFIX = "settings/";
private final Multimap<String, IPentahoObjectRegistration> handleRegistry =
Multimaps.synchronizedMultimap( ArrayListMultimap
.<String, IPentahoObjectRegistration>create() );
private ISystemConfig systemConfig = PentahoSystem.get( ISystemConfig.class );
private Logger logger = LoggerFactory.getLogger( getClass() );
private Set<IPluginManagerListener> listeners = new HashSet<IPluginManagerListener>();
private static void createAndRegisterLifecycleListeners( 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
.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 );
}
}
@Override
public Set<String> getContentTypes() {
final HashSet<String> types = new HashSet<String>();
final List<IContentInfo> contentInfos = PentahoSystem.getAll( IContentInfo.class, null );
for ( IContentInfo contentInfo : contentInfos ) {
types.add( contentInfo.getExtension() );
}
return types;
}
@Override
public IContentInfo getContentTypeInfo( String type ) {
if ( type.contains( "." ) ) {
type = type.substring( type.lastIndexOf( "." ) + 1 );
}
return PentahoSystem.get( IContentInfo.class, PentahoSessionHolder.getSession(), Collections
.singletonMap( "extension", type ) );
}
@Override
public IContentGenerator getContentGeneratorForType( String type, IPentahoSession session )
throws ObjectFactoryException {
return PentahoSystem.get( IContentGenerator.class, session, Collections.singletonMap( CONTENT_TYPE, type ) );
}
@Override
public boolean reload() {
return reload( PentahoSessionHolder.getSession() );
}
private void unloadPlugins() {
// we do not need to synchronize here since unloadPlugins
// is called within the synchronized block in reload
for ( IPlatformPlugin plugin : PentahoSystem.getAll( IPlatformPlugin.class ) ) {
try {
plugin.unLoaded();
// if a spring app context was registered for this plugin, remove and close it
final GenericApplicationContext appContext = PentahoSystem
.get( GenericApplicationContext.class, null, Collections.singletonMap( PLUGIN_ID, plugin.getId() ) );
if ( appContext != null ) {
final StandaloneSpringPentahoObjectFactory pentahoObjectFactory =
StandaloneSpringPentahoObjectFactory.getInstance( appContext );
if ( pentahoObjectFactory != null ) {
PentahoSystem.deregisterObjectFactory( pentahoObjectFactory );
}
appContext.close();
}
} 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 );
}
final ClassLoader classLoader =
PentahoSystem.get( ClassLoader.class, null, Collections.singletonMap( PLUGIN_ID, plugin.getId() ) );
if ( classLoader != null ) {
try {
( (PluginClassLoader) classLoader ).close();
} catch ( IOException e ) {
logger.error( "errror closing plugin clasloader", e );
}
}
}
for ( Map.Entry<String, IPentahoObjectRegistration> entry : handleRegistry.entries() ) {
entry.getValue().remove();
}
handleRegistry.clear();
}
@Override
public boolean reload( IPentahoSession session ) {
boolean anyErrors = false;
IPluginProvider pluginProvider = PentahoSystem.get( IPluginProvider.class, "IPluginProvider", session );
List<IPlatformPlugin> providedPlugins = Collections.emptyList();
try {
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" );
org.pentaho.platform.util.logging.Logger.error( getClass().toString(), msg, e1 );
PluginMessageLogger.add( msg );
anyErrors = true;
}
for ( IPlatformPlugin plugin : providedPlugins ) {
try {
IPlatformPlugin existingPlugin =
PentahoSystem.get( IPlatformPlugin.class, null, Collections.singletonMap( PLUGIN_ID, plugin.getId() ) );
if ( existingPlugin != null ) {
throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString(
"PluginManager.ERROR_0024_PLUGIN_ALREADY_LOADED_BY_SAME_NAME", plugin.getId() ) );
}
final ClassLoader classloader = createClassloader( plugin );
// Register the classloader, Spring App Context and Object Factory with PentahoSystem
IPentahoObjectRegistration handle = PentahoSystem.registerReference(
new SingletonPentahoObjectReference.Builder<IPlatformPlugin>( IPlatformPlugin.class )
.object( plugin )
.attributes( Collections.<String, Object>singletonMap( PLUGIN_ID, plugin.getId() ) ).build(),
IPlatformPlugin.class
);
registerReference( plugin.getId(), handle );
handle =
PentahoSystem.registerReference(
new SingletonPentahoObjectReference.Builder<ClassLoader>( ClassLoader.class ).object( classloader )
.attributes( Collections.<String, Object>singletonMap( PLUGIN_ID, plugin.getId() ) ).build(),
ClassLoader.class
);
registerReference( plugin.getId(), handle );
final GenericApplicationContext beanFactory = createBeanFactory( plugin, classloader );
final StandaloneSpringPentahoObjectFactory pentahoFactory =
new StandaloneSpringPentahoObjectFactory( "Plugin Factory ( " + plugin.getId() + " )" );
pentahoFactory.init( null, beanFactory );
beanFactory.refresh();
handle =
PentahoSystem.registerReference(
new SingletonPentahoObjectReference.Builder<GenericApplicationContext>(
GenericApplicationContext.class )
.object( beanFactory )
.attributes( Collections.<String, Object>singletonMap( PLUGIN_ID, plugin.getId() ) ).build(),
IPentahoRegistrableObjectFactory.Types.ALL
);
registerReference( plugin.getId(), handle );
handle =
PentahoSystem.registerReference(
new SingletonPentahoObjectReference.Builder<IPentahoObjectFactory>( IPentahoObjectFactory.class )
.object( pentahoFactory )
.attributes( Collections.<String, Object>singletonMap( PLUGIN_ID, plugin.getId() ) ).build(),
IPentahoObjectFactory.class
);
registerReference( plugin.getId(), handle );
} catch ( Throwable t ) {
// this has been logged already
anyErrors = true;
String msg =
Messages.getInstance().getErrorString(
"PluginManager.ERROR_0011_FAILED_TO_REGISTER_PLUGIN", plugin.getId() );
org.pentaho.platform.util.logging.Logger.error( getClass().toString(), msg, t );
PluginMessageLogger.add( msg );
}
}
for ( IPlatformPlugin plugin : providedPlugins ) {
try {
registerPlugin( 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() );
org.pentaho.platform.util.logging.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" );
org.pentaho.platform.util.logging.Logger.error( getClass().toString(), msg, e );
PluginMessageLogger.add( msg );
}
}
for ( IPluginManagerListener listener : listeners ) {
listener.onReload();
}
return !anyErrors;
}
@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() ) );
}
ClassLoader loader = PentahoSystem.get( ClassLoader.class, null,
Collections.singletonMap( PLUGIN_ID, plugin.getId() ) );
GenericApplicationContext beanFactory = PentahoSystem
.get( GenericApplicationContext.class, null, Collections.singletonMap( PLUGIN_ID, plugin.getId() ) );
createAndRegisterLifecycleListeners( plugin, loader );
plugin.init();
registerContentTypes( plugin, loader, beanFactory );
registerContentGenerators( plugin, loader, beanFactory );
registerPerspectives( plugin, loader );
registerOverlays( plugin );
registerSettings( plugin, loader );
// service registry must take place after bean registry since
// a service class may be configured as a plugin bean
registerServices( plugin, loader, beanFactory );
PluginMessageLogger
.add( Messages.getInstance().getString( "PluginManager.PLUGIN_REGISTERED", plugin.getId() ) );
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() );
org.pentaho.platform.util.logging.Logger.error( getClass().toString(), msg, t );
PluginMessageLogger.add( msg );
}
}
private void registerOverlays( IPlatformPlugin plugin ) {
int priority = plugin.getOverlays().size();
for ( XulOverlay overlay : plugin.getOverlays() ) {
// preserve ordering as it may be significant
final IPentahoObjectRegistration referenceHandle = PentahoSystem.registerReference(
new SingletonPentahoObjectReference.Builder<XulOverlay>( XulOverlay.class )
.object( overlay ).attributes(
Collections.<String, Object>singletonMap( PLUGIN_ID, plugin.getId() ) ).priority( priority ).build(),
XulOverlay.class
);
priority--;
registerReference( plugin.getId(), referenceHandle );
}
}
private void registerServices( IPlatformPlugin plugin, ClassLoader loader, GenericApplicationContext beanFactory )
throws PlatformPluginRegistrationException {
IServiceManager svcManager = PentahoSystem.get( IServiceManager.class, null );
for ( PluginServiceDefinition pws : plugin.getServices() ) {
for ( ServiceConfig ws : createServiceConfigs( pws, plugin, loader, beanFactory ) ) {
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, GenericApplicationContext beanFactory )
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() ) );
}
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;
assertUnique( beanFactory, 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();
beanFactory.registerBeanDefinition( serviceClassKey, beanDef );
if ( !this.isBeanRegistered( serviceClassKey ) ) {
throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString(
"PluginManager.ERROR_0020_NO_SERVICE_CLASS_REGISTERED", serviceClassKey ) );
}
// 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 );
}
services.add( ws );
}
return services;
}
private void registerSettings( IPlatformPlugin plugin, ClassLoader loader ) {
IPluginResourceLoader resLoader = PentahoSystem.get( IPluginResourceLoader.class, null );
InputStream stream = resLoader.getResourceAsStream( loader, "settings.xml" );
if( stream == null ){
// No settings.xml is fine
return;
}
Properties properties = new Properties();
try {
Document docFromStream = XmlDom4JHelper.getDocFromStream( stream );
for ( Object element : docFromStream.getRootElement().elements() ) {
Element ele = (Element) element;
String name = ele.getName();
String value = ele.getText();
properties.put( "settings/" + name, value );
}
} catch ( DocumentException | IOException e ) {
logger.error( "Error parsing settings.xml for plugin: "+ plugin.getId(), e );
}
try {
systemConfig.registerConfiguration( new PropertiesFileConfiguration( plugin.getId(), properties ) );
} catch ( IOException e ) {
logger.error( "Error registering settings.xml for plugin: "+ plugin.getId(), e );
}
}
private void registerPerspectives( IPlatformPlugin plugin, ClassLoader loader ) {
for ( IPluginPerspective pluginPerspective : plugin.getPluginPerspectives() ) {
// PentahoSystem.get( IPluginPerspectiveManager.class ).addPluginPerspective( pluginPerspective );
final IPentahoObjectRegistration referenceHandle = PentahoSystem.registerReference(
new SingletonPentahoObjectReference.Builder<IPluginPerspective>( IPluginPerspective.class )
.object( pluginPerspective )
.attributes( Collections.<String, Object>singletonMap( PLUGIN_ID, plugin.getId() ) ).build(),
IPluginPerspective.class
);
registerReference( plugin.getId(), referenceHandle );
}
}
private void registerContentGenerators( IPlatformPlugin plugin, ClassLoader loader,
final GenericApplicationContext beanFactory )
throws PlatformPluginRegistrationException {
// register the content generators
for ( final IContentGeneratorInfo cgInfo : plugin.getContentGenerators() ) {
// define the bean in the factory
BeanDefinition beanDef =
BeanDefinitionBuilder.rootBeanDefinition( cgInfo.getClassname() ).setScope( BeanDefinition.SCOPE_PROTOTYPE )
.getBeanDefinition();
// register bean with alias of content generator id (old way)
beanFactory.registerBeanDefinition( cgInfo.getId(), beanDef );
// register bean with alias of type (with default perspective) as well (new way)
beanFactory.registerAlias( cgInfo.getId(), cgInfo.getType() );
PluginMessageLogger.add( Messages.getInstance().getString(
"PluginManager.USER_CONTENT_GENERATOR_REGISTERED", cgInfo.getId(), plugin.getId() ) );
final HashMap<String, Object> attributes = new HashMap<String, Object>();
attributes.put( PLUGIN_ID, plugin.getId() );
attributes.put( CONTENT_TYPE, cgInfo.getType() );
final IPentahoObjectRegistration referenceHandle = PentahoSystem.registerReference(
new PrototypePentahoObjectReference.Builder<IContentGenerator>( IContentGenerator.class )
.creator( new IObjectCreator<IContentGenerator>() {
@Override
public IContentGenerator create( IPentahoSession session ) {
return (IContentGenerator) beanFactory.getBean( cgInfo.getId() );
}
} ).attributes( attributes ).build(), IContentGenerator.class
);
registerReference( plugin.getId(), referenceHandle );
}
// The remaining operations require a beanFactory
if ( beanFactory == null ) {
return;
}
String[] names =
BeanFactoryUtils.beanNamesForTypeIncludingAncestors( beanFactory.getBeanFactory(), IContentGenerator.class );
ArrayList<String> ids = new ArrayList<String>();
for ( String beanName : names ) {
ids.add( beanName );
Collections.addAll( ids, beanFactory.getAliases( beanName ) );
}
for ( final String beanName : ids ) {
final HashMap<String, Object> attributes = new HashMap<String, Object>();
attributes.put( PLUGIN_ID, plugin.getId() );
attributes.put( CONTENT_TYPE, beanName );
final IPentahoObjectRegistration referenceHandle = PentahoSystem.registerReference(
new PrototypePentahoObjectReference.Builder<IContentGenerator>( IContentGenerator.class )
.creator( new IObjectCreator<IContentGenerator>() {
@Override
public IContentGenerator create( IPentahoSession session ) {
return (IContentGenerator) beanFactory.getBean( beanName );
}
} ).attributes( attributes ).build(),
IContentGenerator.class
);
registerReference( plugin.getId(), referenceHandle );
}
}
protected void registerContentTypes( IPlatformPlugin plugin, ClassLoader loader,
GenericApplicationContext beanFactory )
throws PlatformPluginRegistrationException {
// index content types and define any file meta providersIContentGeneratorInfo
for ( IContentInfo info : plugin.getContentInfos() ) {
final HashMap<String, Object> attributes = new HashMap<String, Object>();
attributes.put( PLUGIN_ID, plugin.getId() );
attributes.put( "extension", info.getExtension() );
IPentahoObjectRegistration handle = PentahoSystem.registerReference(
new SingletonPentahoObjectReference.Builder<IContentInfo>( IContentInfo.class ).object( info )
.attributes( attributes ).build(),
IContentInfo.class
);
registerReference( plugin.getId(), handle );
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( beanFactory, plugin.getId(), METAPROVIDER_KEY_PREFIX + info.getExtension() );
BeanDefinition beanDef =
BeanDefinitionBuilder.rootBeanDefinition( metaProviderClass ).setScope( BeanDefinition.SCOPE_PROTOTYPE )
.getBeanDefinition();
beanFactory.registerBeanDefinition( METAPROVIDER_KEY_PREFIX + info.getExtension(),
beanDef );
}
}
}
private GenericApplicationContext createBeanFactory( IPlatformPlugin plugin, ClassLoader classloader )
throws PlatformPluginRegistrationException {
if ( !( classloader instanceof PluginClassLoader ) ) {
throw new PlatformPluginRegistrationException(
"Can't determine plugin dir to load spring file because classloader is not of type PluginClassLoader. "
+ "This is since we are probably in a unit test"
);
}
//
// Get the native factory (the factory that comes preconfigured via either Spring bean files or via JUnit test
//
BeanFactory nativeBeanFactory = getNativeBeanFactory( plugin, classloader );
//
// 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( classloader );
beanFactory.getBeanFactory().setBeanClassLoader( classloader );
if ( nativeBeanFactory != null ) {
beanFactory.getBeanFactory().setParentBeanFactory( nativeBeanFactory );
}
}
beanFactory.addBeanFactoryPostProcessor( new PentahoBeanScopeValidatorPostProcessor() );
//
// 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() );
try {
assertUnique( beanFactory, plugin.getId(), def.getBeanId() );
} catch ( PlatformPluginRegistrationException e ) {
logger.error( MessageFormat
.format( "Unable to register plugin bean, a bean by the id {0} is already defined in plugin: {1}",
def.getBeanId(), plugin.getId() ) );
continue;
}
// 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 );
}
return beanFactory;
}
/**
* A utility method that throws an exception if a bean with the id is already defined for this plugin
*/
protected void assertUnique( GenericApplicationContext applicationContext, String pluginId, String beanId )
throws PlatformPluginRegistrationException {
if ( applicationContext.containsBean( beanId ) ) {
throw new PlatformPluginRegistrationException( Messages.getInstance().getErrorString(
"PluginManager.ERROR_0018_BEAN_ALREADY_REGISTERED", beanId, pluginId ) );
}
}
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" ) );
}
nativeFactory = testFactory;
} else {
File f = new File( ( (PluginClassLoader) loader ).getPluginDir(), "plugin.spring.xml" );
if ( f.exists() ) {
logger.debug( "Found plugin spring file @ " + f.getAbsolutePath() );
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;
}
private void registerReference( String id, IPentahoObjectRegistration handle ) {
this.handleRegistry.get( id ).add( handle );
}
private ClassLoader createClassloader( IPlatformPlugin plugin ) throws PlatformPluginRegistrationException {
String pluginDirPath =
PentahoSystem.getApplicationContext().getSolutionPath( "system/" + plugin.getSourceDescription() );
// 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( "//", "/" );
org.pentaho.platform.util.logging.Logger
.debug( this,
"plugin dir for " + plugin.getId() + " is [" + pluginDirPath + "]" );
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() ) );
}
PluginClassLoader loader = new PluginClassLoader( pluginDir, this.getClass().getClassLoader() );
if ( plugin.getLoaderType() == IPlatformPlugin.ClassLoaderType.OVERRIDING ) {
loader.setOverrideLoad( true );
}
return loader;
}
@Override
public List<XulOverlay> getOverlays() {
return PentahoSystem.getAll( XulOverlay.class );
}
@Override
public Object getBean( String beanId ) throws PluginBeanException {
if ( beanId == null ) {
throw new IllegalArgumentException( "beanId cannot be null" );
}
for ( GenericApplicationContext beanFactory : PentahoSystem.getAll( GenericApplicationContext.class ) ) {
if ( beanFactory.containsBean( beanId ) ) {
try {
return beanFactory.getBean( beanId );
} catch ( Throwable ex ) { // Catching throwable on purpose
throw new PluginBeanException( ex );
}
}
}
throw new PluginBeanException( Messages.getInstance().getString(
"PluginManager.WARN_CLASS_NOT_REGISTERED", beanId ) );
}
@Override
public IContentGenerator getContentGenerator( String type, String perspectiveName ) {
IContentGenerator cg = null;
String beanId;
if ( perspectiveName == null || perspectiveName.equals( DEFAULT_PERSPECTIVE ) ) {
beanId = type;
} else {
beanId = type + "." + perspectiveName;
}
IContentGenerator contentGenerator = PentahoSystem
.get( IContentGenerator.class, PentahoSessionHolder.getSession(),
Collections.singletonMap( CONTENT_TYPE, beanId ) );
if ( contentGenerator == null ) {
contentGenerator = PentahoSystem
.get( IContentGenerator.class, PentahoSessionHolder.getSession(),
Collections.singletonMap( CONTENT_TYPE, perspectiveName ) );
}
return contentGenerator;
}
@Override
public Class<?> loadClass( String beanId ) throws PluginBeanException {
if ( beanId == null ) {
throw new IllegalArgumentException( "beanId cannot be null" );
}
Class<?> type = null;
for ( IPentahoObjectReference<GenericApplicationContext> reference : PentahoSystem.getObjectReferences(
GenericApplicationContext.class, null ) ) {
if ( !reference.getAttributes().containsKey( PLUGIN_ID ) ) {
// This GenericApplicationContext was not registered from the plugin manager as it lacks plugin-id
continue;
}
final GenericApplicationContext beanFactory = reference.getObject();
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 ) );
}
return type;
}
@Override
public boolean isBeanRegistered( String beanId ) {
if ( beanId == null ) {
throw new IllegalArgumentException( "beanId cannot be null" );
}
for ( GenericApplicationContext beanFactory : PentahoSystem.getAll( GenericApplicationContext.class ) ) {
if ( beanFactory.containsBean( beanId ) ) {
return true;
}
}
return false;
}
@Override
public void unloadAllPlugins() {
unloadPlugins();
}
@Override
public String getPluginIdForType( String contentType ) {
final IPentahoObjectReference<IContentGenerator> objectReference = PentahoSystem
.getObjectReference( IContentGenerator.class, PentahoSessionHolder.getSession(),
Collections.singletonMap( CONTENT_TYPE, contentType ) );
if ( objectReference != null && objectReference.getAttributes().containsKey( PLUGIN_ID ) ) {
return objectReference.getAttributes().get( PLUGIN_ID ).toString();
} else {
// fallback for the case where everything is registered in the new form [contentType].[method]
final List<IPentahoObjectReference<IContentGenerator>> objectReferences =
PentahoSystem.getObjectReferences( IContentGenerator.class, PentahoSessionHolder.getSession() );
for ( IPentahoObjectReference<IContentGenerator> reference : objectReferences ) {
if ( reference.getAttributes().containsKey( CONTENT_TYPE ) ) {
final String o = (String) reference.getAttributes().get( CONTENT_TYPE );
if ( o.contains( "." ) && o.substring( 0, o.lastIndexOf( "." ) ).equals( contentType ) ) {
return (String) reference.getAttributes().get( PLUGIN_ID );
}
}
}
return null;
}
}
@Override
public List<String> getPluginRESTPerspectivesForType( String contentType ) {
List<String> retList = new ArrayList<String>();
final List<IPentahoObjectReference<IContentGenerator>> objectReferences = PentahoSystem
.getObjectReferences( IContentGenerator.class, PentahoSessionHolder.getSession(),
Collections.singletonMap( CONTENT_TYPE, contentType ) );
for ( IPentahoObjectReference<IContentGenerator> objectReference : objectReferences ) {
if ( objectReference.getAttributes().containsKey( "id" ) ) {
final String id = (String) objectReference.getAttributes().get( "id" );
if ( id != null && id.contains( "." ) ) {
retList.add( id.substring( id.lastIndexOf( "." ) + 1 ) );
}
}
}
return retList;
}
@Override
public List<String> getPluginRESTPerspectivesForId( String id ) {
List<String> retList = new ArrayList<String>();
final List<IPentahoObjectReference<IContentGenerator>> objectReferences = PentahoSystem
.getObjectReferences( IContentGenerator.class, PentahoSessionHolder.getSession(),
Collections.singletonMap( PLUGIN_ID, id ) );
for ( IPentahoObjectReference<IContentGenerator> objectReference : objectReferences ) {
if ( objectReference.getAttributes().containsKey( "id" ) ) {
final String beanId = (String) objectReference.getAttributes().get( "id" );
if ( beanId != null && beanId.contains( "." ) ) {
retList.add( beanId.substring( beanId.lastIndexOf( "." ) + 1 ) );
}
}
}
return retList;
}
@Override
public String getPluginIdForClassLoader( ClassLoader classLoader ) {
if ( classLoader == null ) {
return null;
}
final List<IPentahoObjectReference<ClassLoader>> objectReferences =
PentahoSystem.getObjectReferences( ClassLoader.class, null );
for ( IPentahoObjectReference<ClassLoader> objectReference : objectReferences ) {
if ( objectReference.getObject().equals( classLoader ) ) {
return objectReference.getAttributes().get( PLUGIN_ID ).toString();
}
}
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( "/" );
String[] servicePathElements = trimLeadingSlash( servicePath ).split( "/" );
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;
}
@Override
public Object getPluginSetting( String pluginId, String key, String defaultValue ) {
final IConfiguration pluginConfig = systemConfig.getConfiguration( pluginId );
if ( pluginConfig != null ) {
try {
// key can be the plain setting name or "settings/" + key. The old system was flexible in this regard so we need
// to be as well
if( pluginConfig.getProperties().containsKey( key ) ){
return pluginConfig.getProperties().getProperty( key );
}
if( key.startsWith( SETTINGS_PREFIX ) ){
return defaultValue;
}
// try it with settings on the front
String compositeKey = SETTINGS_PREFIX + key;
if( pluginConfig.getProperties().containsKey( compositeKey ) ){
return pluginConfig.getProperties().getProperty( compositeKey );
}
// fall-down to the default
return defaultValue;
} catch ( IOException e ) {
logger.error( "unable to access plugin settings", e );
}
}
return defaultValue;
}
@Deprecated
public String getServicePlugin( String path ) {
for ( IPlatformPlugin plugin : PentahoSystem.getAll( IPlatformPlugin.class ) ) {
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;
}
@Override
public ClassLoader getClassLoader( String pluginId ) {
return PentahoSystem.get( ClassLoader.class, null, Collections.singletonMap( PLUGIN_ID, pluginId ) );
}
@Override
public ListableBeanFactory getBeanFactory( String pluginId ) {
return PentahoSystem.get( ApplicationContext.class, null, Collections.singletonMap( PLUGIN_ID, pluginId ) );
}
@Override
public boolean isStaticResource( String path ) {
for ( IPlatformPlugin plugin : PentahoSystem.getAll( IPlatformPlugin.class ) ) {
String pluginId = getStaticResourcePluginId( plugin, path );
if ( pluginId != null ) {
return true;
}
}
return false;
}
@Override
public boolean isPublic( String pluginId, String path ) {
IPlatformPlugin plugin = PentahoSystem.get( IPlatformPlugin.class, PentahoSessionHolder.getSession(),
Collections.singletonMap( PLUGIN_ID, 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;
}
@Override
public InputStream getStaticResource( String path ) {
for ( IPlatformPlugin plugin : PentahoSystem.getAll( IPlatformPlugin.class ) ) {
Map<String, String> resourceMap = plugin.getStaticResourceMap();
for ( String url : resourceMap.keySet() ) {
if ( isRequested( url, path ) ) {
IPluginResourceLoader resLoader = PentahoSystem.get( IPluginResourceLoader.class, null );
ClassLoader classLoader =
PentahoSystem.get( ClassLoader.class, null, Collections.singletonMap( PLUGIN_ID, plugin.getId() ) );
String resourcePath = path.replace( url, resourceMap.get( url ) );
return resLoader.getResourceAsStream( classLoader, resourcePath );
}
}
}
return null;
}
@Override
public List<String> getRegisteredPlugins() {
List<String> retList = new ArrayList<String>();
final List<IPlatformPlugin> plugins = PentahoSystem.getAll( IPlatformPlugin.class );
for ( IPlatformPlugin plugin : plugins ) {
retList.add( plugin.getId() );
}
return retList;
}
@Override
public List<String> getExternalResourcesForContext( String context ) {
List<String> resources = new ArrayList<String>();
for ( IPlatformPlugin plugin : PentahoSystem.getAll( IPlatformPlugin.class ) ) {
List<String> pluginRes = plugin.getExternalResourcesForContext( context );
if ( pluginRes != null ) {
resources.addAll( pluginRes );
}
}
return resources;
}
@Override
public void addPluginManagerListener( IPluginManagerListener listener ) {
this.listeners.add( listener );
}
}