/*
* 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) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.style.StyleKey;
import org.pentaho.reporting.libraries.base.boot.AbstractBoot;
import org.pentaho.reporting.libraries.base.boot.DefaultModuleInfo;
import org.pentaho.reporting.libraries.base.boot.PackageManager;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.config.HierarchicalConfiguration;
import org.pentaho.reporting.libraries.base.config.ModifiableConfiguration;
import org.pentaho.reporting.libraries.base.config.PropertyFileConfiguration;
import org.pentaho.reporting.libraries.base.config.SystemPropertyConfiguration;
import org.pentaho.reporting.libraries.base.util.CSVTokenizer;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.versioning.ProjectInformation;
/**
* An utility class to safely boot and initialize the Pentaho-Reporting library. This class should be called before
* using the Pentaho-Reporting classes, to make sure that all subsystems are initialized correctly and in the correct
* order.
* <p/>
* Application developers should make sure, that the booting is done before any Pentaho-Reporting functions are used. If
* the system has not be initialized by booting this class, anything can happen and the results of all functionality of
* this reporting engine will be undefined.
* <p/>
* Additional modules can be specified by defining the system property <code>"org.pentaho.reporting.engine.classic.core.boot.Modules"</code>.
* The property expects a comma-separated list of {@link org.pentaho.reporting.libraries.base.boot.Module}
* implementations.
* <p/>
* Booting should be done by aquirering a new boot instance using {@link ClassicEngineBoot#getInstance()} and then
* starting the boot process with {@link ClassicEngineBoot#start()}.
*
* @author Thomas Morgner
*/
public class ClassicEngineBoot extends AbstractBoot
{
public static final String INDEX_COLUMN_PREFIX = "::column::";
public static final String METADATA_NAMESPACE =
"http://reporting.pentaho.org/namespaces/engine/classic/metadata/1.0";
public static final String DATASCHEMA_NAMESPACE =
"http://reporting.pentaho.org/namespaces/engine/classic/dataschema/1.0";
public static final String BUNDLE_TYPE = "application/vnd.pentaho.reporting.classic";
private static final Log logger = LogFactory.getLog(ClassicEngineBoot.class);
/**
* A wrappper around the user supplied global configuration.
*/
private static class UserConfigWrapper extends HierarchicalConfiguration
{
/**
* The wrapped configuration.
*/
private Configuration wrappedConfiguration;
/**
* Default constructor.
*/
protected UserConfigWrapper()
{
this(null);
}
/**
* Creates a new user-configuration wrapper for the given configuration.
*
* @param config the user-provided configuration that should be wrapped.
*/
protected UserConfigWrapper(final Configuration config)
{
this.wrappedConfiguration = config;
}
/**
* Sets a new configuration. This configuration will be inserted into the report configuration hierarchy. Set this
* property to null to disable the user defined configuration.
*
* @param wrappedConfiguration the wrapped configuration.
*/
public void setWrappedConfiguration(final Configuration wrappedConfiguration)
{
this.wrappedConfiguration = wrappedConfiguration;
}
/**
* Returns the user supplied global configuration, if exists.
*
* @return the user configuration.
*/
public Configuration getWrappedConfiguration()
{
return wrappedConfiguration;
}
/**
* Returns the configuration property with the specified key.
*
* @param key the property key.
* @return the property value.
*/
public String getConfigProperty(final String key)
{
if (wrappedConfiguration == null)
{
return getParentConfig().getConfigProperty(key);
}
final String retval = wrappedConfiguration.getConfigProperty(key);
if (retval != null)
{
return retval;
}
return getParentConfig().getConfigProperty(key);
}
/**
* Returns the configuration property with the specified key (or the specified default value if there is no such
* property).
* <p/>
* If the property is not defined in this configuration, the code will lookup the property in the parent
* configuration.
*
* @param key the property key.
* @param defaultValue the default value.
* @return the property value.
*/
public String getConfigProperty(final String key, final String defaultValue)
{
if (wrappedConfiguration == null)
{
return getParentConfig().getConfigProperty(key, defaultValue);
}
final String retval = wrappedConfiguration.getConfigProperty(key, null);
if (retval != null)
{
return retval;
}
return getParentConfig().getConfigProperty(key, defaultValue);
}
/**
* Sets a configuration property.
*
* @param key the property key.
* @param value the property value.
*/
public void setConfigProperty(final String key, final String value)
{
if (wrappedConfiguration instanceof ModifiableConfiguration)
{
final ModifiableConfiguration modConfiguration =
(ModifiableConfiguration) wrappedConfiguration;
modConfiguration.setConfigProperty(key, value);
}
}
/**
* Returns all defined configuration properties for the report. The enumeration contains all keys of the changed
* properties, properties set from files or the system properties are not included.
*
* @return all defined configuration properties for the report.
*/
public Enumeration getConfigProperties()
{
if (wrappedConfiguration instanceof ModifiableConfiguration)
{
final ModifiableConfiguration modConfiguration =
(ModifiableConfiguration) wrappedConfiguration;
return modConfiguration.getConfigProperties();
}
return super.getConfigProperties();
}
}
/**
* The singleton instance of the Boot class.
*/
private static ClassicEngineBoot instance;
/**
* The project info contains all meta data about the project.
*/
private ProjectInformation projectInfo;
/**
* Holds a possibly empty reference to a user-supplied Configuration implementation.
*/
private static final UserConfigWrapper configWrapper =
new UserConfigWrapper();
/**
* Creates a new instance.
*/
private ClassicEngineBoot()
{
projectInfo = ClassicEngineInfo.getInstance();
}
/**
* Returns the singleton instance of the boot utility class.
*
* @return the boot instance.
*/
public static synchronized ClassicEngineBoot getInstance()
{
if (instance == null)
{
instance = new ClassicEngineBoot();
}
return instance;
}
/**
* Returns the current global configuration as modifiable instance. This is exactly the same as casting the global
* configuration into a ModifableConfiguration instance.
* <p/>
* This is a convinience function, as all programmers are lazy.
*
* @return the global config as modifiable configuration.
*/
public ModifiableConfiguration getEditableConfig()
{
return (ModifiableConfiguration) getGlobalConfig();
}
/**
* Returns the project info.
*
* @return The project info.
*/
protected ProjectInformation getProjectInfo()
{
return projectInfo;
}
/**
* Loads the configuration. This will be called exactly once.
*
* @return The configuration.
*/
protected Configuration loadConfiguration()
{
final HierarchicalConfiguration globalConfig = new HierarchicalConfiguration(getClass());
final PropertyFileConfiguration rootProperty = new PropertyFileConfiguration();
rootProperty.load("/org/pentaho/reporting/engine/classic/core/classic-engine.properties", ClassicEngineBoot.class); // NON-NLS
globalConfig.insertConfiguration(rootProperty);
globalConfig.insertConfiguration(getPackageManager().getPackageConfiguration());
try
{
final String userConfigStripped = "classic-engine.properties"; // NON-NLS
final Enumeration userConfigs = ObjectUtilities.getClassLoader(ClassicEngineBoot.class).getResources(
userConfigStripped);
final ArrayList configs = new ArrayList();
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); // NON-NLS
}
}
for (int i = configs.size() - 1; i >= 0; i--)
{
final PropertyFileConfiguration baseProperty = (PropertyFileConfiguration) configs.get(i);
globalConfig.insertConfiguration(baseProperty);
}
}
catch (IOException e)
{
logger.warn("Failed to lookup the user configurations.", e); // NON-NLS
}
globalConfig.insertConfiguration(ClassicEngineBoot.configWrapper);
final SystemPropertyConfiguration systemConfig = new SystemPropertyConfiguration();
globalConfig.insertConfiguration(systemConfig);
return globalConfig;
}
/**
* Performs the actual boot process.
*/
protected void performBoot()
{
if (ClassicEngineBoot.isStrictFP() == false)
{
ClassicEngineBoot.logger.warn("The used VM seems to use a non-strict floating point arithmetics"); // NON-NLS
ClassicEngineBoot.logger.warn("Layouts computed with this Java Virtual Maschine may be invalid."); // NON-NLS
ClassicEngineBoot.logger.warn("JFreeReport and the library 'iText' depend on the strict floating point rules"); // NON-NLS
ClassicEngineBoot.logger.warn("of Java1.1 as implemented by the Sun Virtual Maschines."); // NON-NLS
ClassicEngineBoot.logger.warn("If you are using the BEA JRockit VM, start the Java VM with the option"); // NON-NLS
ClassicEngineBoot.logger.warn("'-Xstrictfp' to restore the default behaviour."); // NON-NLS
}
final PackageManager mgr = getPackageManager();
mgr.addModule(ClassicEngineCoreModule.class.getName());
mgr.load("org.pentaho.reporting.engine.classic.core.modules."); // NON-NLS
mgr.load("org.pentaho.reporting.engine.classic.extensions.modules."); // NON-NLS
mgr.load("org.pentaho.reporting.engine.classic.extensions.datasources."); // NON-NLS
mgr.load("org.pentaho.reporting.engine.classic.core.userdefined.modules."); // NON-NLS
bootAdditionalModules();
mgr.initializeModules();
if (mgr.isModuleAvailable(ClassicEngineCoreModule.class.getName()) == false)
{
throw new IllegalStateException("Booting the report-engine failed.");
}
StyleKey.lock();
}
/**
* Boots modules, which have been spcified in the "org.pentaho.reporting.engine.classic.core.boot.Modules"
* configuration parameter.
*/
private void bootAdditionalModules()
{
try
{
final String bootModules =
getGlobalConfig().getConfigProperty
("org.pentaho.reporting.engine.classic.core.boot.Modules"); // NON-NLS
if (bootModules != null)
{
final CSVTokenizer csvToken = new CSVTokenizer(bootModules, ",");
while (csvToken.hasMoreTokens())
{
final String token = csvToken.nextToken();
getPackageManager().load(token);
}
}
}
catch (SecurityException se)
{
// we'll ignore any Security exception ..
ClassicEngineBoot.logger.info("Security settings forbid to check the system properties for extension modules."); // NON-NLS
}
catch (Exception se)
{
ClassicEngineBoot.logger.error("An error occured while checking the system properties for extension modules.", // NON-NLS
se);
}
}
/**
* This method returns true on non-strict floating point systems.
* <p/>
* Since Java 1.2 Virtual Maschines may implement the floating point arithmetics in a more performant way, which does
* not put the old strict constraints on the floating point types <code>float</code> and <code>double</code>.
* <p/>
* As iText and this library requires strict (in the sense of Java1.1) floating point operations, we have to test for
* that feature here.
* <p/>
* The only known VM that seems to implement that feature is the JRockit VM. The strict mode can be restored on that
* VM by adding the "-Xstrictfp" VM parameter.
*
* @return true, if the VM uses strict floating points by default, false otherwise.
*/
private static boolean isStrictFP()
{
final double d = 8.0e+307;
final double result1 = 4.0 * d * 0.5;
final double result2 = 2.0 * d;
return (result1 != result2 && (result1 == Double.POSITIVE_INFINITY));
}
/**
* Returns the user supplied global configuration.
*
* @return the user configuration, if any.
*/
public static Configuration getUserConfig()
{
return configWrapper.getWrappedConfiguration();
}
/**
* Defines the global user configuration.
*
* @param config the user configuration.
*/
public static void setUserConfig(final Configuration config)
{
configWrapper.setWrappedConfiguration(config);
}
/**
* A helper method that checks, whether a given module is available. The result of this method is undefined if the
* system has no been booted yet.
*
* @param moduleClass the class-name of the module that should be tested.
* @return true, if the module is available and has been initialized correctly, false otherwise.
*/
public boolean isModuleAvailable(final String moduleClass)
{
return getPackageManager().isModuleAvailable
(new DefaultModuleInfo(moduleClass, null, null, null));
}
}