/* * 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 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core; 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.SystemPropertyConfiguration; import org.pentaho.reporting.libraries.base.util.CSVTokenizer; import org.pentaho.reporting.libraries.base.versioning.ProjectInformation; import org.pentaho.reporting.libraries.xmlns.common.ParserUtil; import java.util.Enumeration; import java.util.StringTokenizer; /** * 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 int VERSION_TRUNK = ClassicEngineBoot.computeVersionId( 999, 999, 999 ); public static final int VERSION_3_8 = ClassicEngineBoot.computeVersionId( 3, 8, 0 ); public static final int VERSION_3_9 = ClassicEngineBoot.computeVersionId( 3, 9, 0 ); public static final int VERSION_4_0 = ClassicEngineBoot.computeVersionId( 4, 0, 0 ); 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<String> 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 = createDefaultHierarchicalConfiguration( "/org/pentaho/reporting/engine/classic/core/classic-engine.properties", "/classic-engine.properties", false, ClassicEngineBoot.class ); 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 ) ); } public enum VersionValidity { VALID, INVALID_RELEASE, INVALID_PATCH } public static int parseVersionId( final String text ) { final StringTokenizer strtok = new StringTokenizer( text, "." ); if ( strtok.countTokens() == 3 ) { final int major = ParserUtil.parseInt( strtok.nextToken(), -1 ); final int minor = ParserUtil.parseInt( strtok.nextToken(), -1 ); final int patch = ParserUtil.parseInt( strtok.nextToken(), -1 ); if ( major == -1 || minor == -1 || patch == -1 ) { return -1; } else { return ( ClassicEngineBoot.computeVersionId( major, minor, patch ) ); } } else { return -1; } } public static String printVersion( final int versionId ) { if ( versionId <= 0 || versionId > 999000000 ) { return "TRUNK"; } final int patch = versionId % 1000; final int minor = ( versionId / 1000 ) % 1000; final int major = ( versionId / 1000000 ); return String.format( "%d.%d.%d", major, minor, patch ); } public static int computeCurrentVersionId() { final int releaseMajor = ParserUtil.parseInt( ClassicEngineInfo.getInstance().getReleaseMajor(), 999 ); final int releaseMinor = ParserUtil.parseInt( ClassicEngineInfo.getInstance().getReleaseMinor(), 999 ); final int releasePatch = ParserUtil.parseInt( ClassicEngineInfo.getInstance().getReleaseMilestone(), 999 ); final int version = computeVersionId( releaseMajor, releaseMinor, releasePatch ); if ( version == 0 ) { return VERSION_TRUNK; } return version; } public static int computeVersionId( final int prptVersionMajorRaw, final int prptVersionMinorRaw, final int prptVersionPatchRaw ) { return prptVersionMajorRaw * 1000000 + prptVersionMinorRaw * 1000 + prptVersionPatchRaw; } public static VersionValidity isValidVersion( final int prptVersionMajorRaw, final int prptVersionMinorRaw, final int prptVersionPatchRaw ) { return getInstance().isValidVersion( prptVersionMajorRaw, prptVersionMinorRaw, prptVersionPatchRaw, ClassicEngineInfo.getInstance() ); } protected VersionValidity isValidVersion( final int prptVersionMajorRaw, final int prptVersionMinorRaw, final int prptVersionPatchRaw, final ProjectInformation info ) { final int releaseMajor = ParserUtil.parseInt( info.getReleaseMajor(), 999 ); final int releaseMinor = ParserUtil.parseInt( info.getReleaseMinor(), 999 ); final int releasePatch = ParserUtil.parseInt( info.getReleaseMilestone(), 999 ); if ( computeVersionId( prptVersionMajorRaw, prptVersionMinorRaw, prptVersionPatchRaw ) == VERSION_TRUNK ) { return VersionValidity.VALID; } if ( ( prptVersionMajorRaw * 1000 + prptVersionMinorRaw ) > ( releaseMajor * 1000 + releaseMinor ) ) { return VersionValidity.INVALID_RELEASE; } if ( ( prptVersionMajorRaw * 1000 + prptVersionMinorRaw ) == ( releaseMajor * 1000 + releaseMinor ) ) { if ( prptVersionPatchRaw > releasePatch ) { return VersionValidity.INVALID_PATCH; } } return VersionValidity.VALID; } public static boolean isEnforceCompatibilityFor( final int level, final int prptVersionMajorRaw, final int prptVersionMinorRaw ) { return isEnforceCompatibilityFor( level, prptVersionMajorRaw, prptVersionMinorRaw, 999 ); } public static boolean isEnforceCompatibilityFor( final int level, final int prptVersionMajorRaw, final int prptVersionMinorRaw, final int prptVersionPatchRaw ) { if ( level == -1 ) { return false; } return level <= computeVersionId( prptVersionMajorRaw, prptVersionMinorRaw, prptVersionPatchRaw ); } }