/*!
* 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.reporting.libraries.base.boot;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.config.ExtendedConfiguration;
import org.pentaho.reporting.libraries.base.config.ExtendedConfigurationWrapper;
import org.pentaho.reporting.libraries.base.config.HierarchicalConfiguration;
import org.pentaho.reporting.libraries.base.config.PropertyFileConfiguration;
import org.pentaho.reporting.libraries.base.config.SystemPropertyConfiguration;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.versioning.DependencyInformation;
import org.pentaho.reporting.libraries.base.versioning.ProjectInformation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
/**
* The common base for all Boot classes.
* <p/>
* This initializes the subsystem and all dependent subsystems. Implementors of this class have to provide a public
* static getInstance() method which returns a singleton instance of the booter implementation.
* <p/>
* Further creation of Boot object should be prevented using protected or private constructors in that class, or proper
* singleton behaviour cannot be guaranteed.
*
* @author Thomas Morgner
*/
public abstract class AbstractBoot implements SubSystem {
/**
* The logger for this class.
*/
private static final Log LOGGER = LogFactory.getLog( AbstractBoot.class );
/**
* The configuration wrapper around the plain configuration.
*/
private ExtendedConfigurationWrapper extWrapper;
/**
* A packageManager instance of the package manager.
*/
private PackageManager packageManager;
/**
* Global configuration.
*/
private Configuration globalConfig;
/**
* A flag indicating whether the booting is currenly in progress.
*/
private boolean bootInProgress;
/**
* A flag indicating whether the booting is complete.
*/
private boolean bootDone;
/**
* The reason why booting failed for easier debugging or logging.
*/
private Exception bootFailed;
private ObjectFactory objectFactory;
/**
* Default constructor.
*/
protected AbstractBoot() {
}
/**
* Returns the packageManager instance of the package manager.
*
* @return The package manager.
*/
public synchronized PackageManager getPackageManager() {
if ( this.packageManager == null ) {
this.packageManager = new PackageManager( this );
}
return this.packageManager;
}
/**
* Returns the global configuration.
*
* @return The global configuration.
*/
public synchronized Configuration getGlobalConfig() {
if ( this.globalConfig == null ) {
this.globalConfig = loadConfiguration();
}
return this.globalConfig;
}
/**
* Checks, whether the booting is in progress.
*
* @return true, if the booting is in progress, false otherwise.
*/
public final synchronized boolean isBootInProgress() {
return this.bootInProgress;
}
/**
* Checks, whether the booting is complete.
*
* @return true, if the booting is complete, false otherwise.
*/
public synchronized boolean isBootDone() {
return this.bootDone;
}
public String getConfigurationDomain() {
return getProjectInfo().getProductId();
}
/**
* Loads the configuration. This will be called exactly once.
*
* @return The configuration.
*/
protected abstract Configuration loadConfiguration();
/**
* Starts the boot process. The boot process is synchronized and will block if parallel booting is not finished yet.
* Any failure in booting will set the <code>bootFailed</code> property to true. If booting is finished, the
* <code>bootDone</code> property is set to true.
*/
public final void start() {
synchronized( this ) {
if ( isBootDone() ) {
return;
}
if ( isBootFailed() ) {
LOGGER.error( getClass() + " failed to boot: " + bootFailed.getMessage() );
}
while ( isBootInProgress() ) {
try {
wait();
} catch ( InterruptedException e ) {
// ignore ..
}
}
if ( isBootDone() ) {
notifyAll();
return;
}
this.bootInProgress = true;
}
try {
// boot dependent libraries ...
final ProjectInformation info = getProjectInfo();
if ( info != null ) {
performBootDependencies( info.getLibraries() );
performBootDependencies( info.getOptionalLibraries() );
}
performBoot();
if ( LOGGER.isInfoEnabled() ) {
if ( info != null ) {
LOGGER.info( info.getName() + ' ' + info.getVersion() + " started." );
} else {
LOGGER.info( getClass() + " started." );
}
}
} catch ( Exception e ) {
LOGGER.error( getClass() + " failed to boot: ", e );
this.bootFailed = e;
} finally {
synchronized( this ) {
this.bootInProgress = false;
this.bootDone = true;
notifyAll();
}
}
}
/**
* Boots all dependent libraries. The dependencies must be initialized properly before the booting of this library or
* application can start. If any of the dependencies fails to initialize properly, the whole boot-process will be
* aborted.
*
* @param childs the array of dependencies, never null.
*/
private void performBootDependencies( final DependencyInformation[] childs ) {
if ( childs == null ) {
return;
}
for ( int i = 0; i < childs.length; i++ ) {
final DependencyInformation child = childs[ i ];
if ( child instanceof ProjectInformation == false ) {
continue;
}
final ProjectInformation projectInformation = (ProjectInformation) child;
final AbstractBoot boot = loadBooter( projectInformation.getBootClass() );
if ( boot != null ) {
// but we're waiting until the booting is complete ...
synchronized( boot ) {
boot.start();
while ( boot.isBootDone() == false &&
boot.isBootFailed() == false ) {
try {
boot.wait();
} catch ( InterruptedException e ) {
// ignore it ..
}
}
if ( boot.isBootFailed() ) {
this.bootFailed = boot.getBootFailureReason();
LOGGER.error( "Dependent project failed to boot up: " +
projectInformation.getBootClass() + " failed to boot: ", this.bootFailed );
return;
}
}
}
}
}
/**
* Checks whether the booting failed. If booting failed, the reason for the failure (the Exception that caused the
* error) is stored as property <code>bootFailureReason</code>.
*
* @return true, if booting failed, false otherwise.
*/
public boolean isBootFailed() {
return this.bootFailed != null;
}
/**
* Returns the failure reason for the boot process. This method returns null, if booting was successful.
*
* @return the failure reason.
*/
public Exception getBootFailureReason() {
return bootFailed;
}
/**
* Performs the boot.
*/
protected abstract void performBoot();
/**
* Returns the project info.
*
* @return The project info.
*/
protected abstract ProjectInformation getProjectInfo();
/**
* Loads the specified booter implementation.
*
* @param classname the class name.
* @return The boot class.
*/
protected AbstractBoot loadBooter( final String classname ) {
return loadBooter( classname, getClass() );
}
/**
* Loads the specified booter-class.
*
* @param classname the classname of the booter class.
* @param source the source-class from where to get the classloader.
* @return the instantiated booter or null, if no booter could be loaded.
*/
public static AbstractBoot loadBooter( final String classname, final Class source ) {
if ( classname == null ) {
return null;
}
if ( source == null ) {
throw new NullPointerException();
}
try {
final ClassLoader loader = ObjectUtilities.getClassLoader( source );
final Class c = Class.forName( classname, false, loader );
final Method m = c.getMethod( "getInstance", (Class[]) null );
return (AbstractBoot) m.invoke( null, (Object[]) null );
} catch ( Exception e ) {
LOGGER.info( "Unable to boot dependent class: " + classname );
return null;
}
}
/**
* Creates a default configuration setup, which loads its settings from the static configuration (defaults provided by
* the developers of the library) and the user configuration (settings provided by the deployer). The deployer's
* settings override the developer's settings.
* <p/>
* If the parameter <code>addSysProps</code> is set to true, the system properties will be added as third
* configuration layer. The system properties configuration allows to override all other settings.
*
* @param staticConfig the resource name of the developers configuration
* @param userConfig the resource name of the deployers configuration
* @param addSysProps a flag defining whether to include the system properties into the configuration.
* @param source the classloader source to load resources from.
* @return the configured Configuration instance.
*/
protected HierarchicalConfiguration createDefaultHierarchicalConfiguration
( final String staticConfig,
final String userConfig,
final boolean addSysProps,
final Class source ) {
if ( source == null ) {
throw new NullPointerException( "SourceClass must not be null." );
}
final HierarchicalConfiguration globalConfig = new HierarchicalConfiguration( getClass() );
if ( staticConfig != null ) {
final PropertyFileConfiguration rootProperty = new PropertyFileConfiguration();
rootProperty.load( staticConfig, source );
globalConfig.insertConfiguration( rootProperty );
globalConfig.insertConfiguration( getPackageManager().getPackageConfiguration() );
}
if ( userConfig != null ) {
final String userConfigStripped;
if ( userConfig.charAt( 0 ) == '/' ) {
userConfigStripped = userConfig.substring( 1 );
} else {
userConfigStripped = userConfig;
}
try {
final Enumeration userConfigs = ObjectUtilities.getClassLoader( source ).getResources( userConfigStripped );
final ArrayList<PropertyFileConfiguration> configs = new ArrayList<PropertyFileConfiguration>();
while ( userConfigs.hasMoreElements() ) {
final URL url = (URL) userConfigs.nextElement();
try {
final PropertyFileConfiguration baseProperty = new PropertyFileConfiguration();
final InputStream in = url.openStream();
try {
baseProperty.load( in );
} finally {
in.close();
}
configs.add( baseProperty );
} catch ( IOException ioe ) {
LOGGER.warn( "Failed to load the user configuration at " + url, ioe );
}
}
final PropertyFileConfiguration compressedUserConfig = new PropertyFileConfiguration();
for ( int i = configs.size() - 1; i >= 0; i-- ) {
final PropertyFileConfiguration baseProperty = configs.get( i );
compressedUserConfig.addAll( baseProperty );
}
globalConfig.insertConfiguration( compressedUserConfig );
} catch ( IOException e ) {
LOGGER.warn( "Failed to lookup the user configurations.", e );
}
}
if ( addSysProps ) {
final SystemPropertyConfiguration systemConfig = new SystemPropertyConfiguration();
globalConfig.insertConfiguration( systemConfig );
}
return globalConfig;
}
/**
* Returns the global configuration as extended configuration.
*
* @return the extended configuration.
*/
public synchronized ExtendedConfiguration getExtendedConfig() {
if ( extWrapper == null ) {
extWrapper = new ExtendedConfigurationWrapper( getGlobalConfig() );
}
return extWrapper;
}
public synchronized ObjectFactory getObjectFactory() {
try {
if ( objectFactory == null ) {
final String configProperty = getGlobalConfig().getConfigProperty( ObjectFactoryBuilder.class.getName(),
DefaultObjectFactoryBuilder.class.getName() );
final ObjectFactoryBuilder objectFactoryBuilder =
ObjectUtilities.loadAndInstantiate( configProperty, getClass(), ObjectFactoryBuilder.class );
objectFactory = objectFactoryBuilder.createObjectFactory( this );
}
return objectFactory;
} catch ( Throwable t ) {
throw new IllegalStateException( "ObjectFactory is not configured properly", t );
}
}
}