/*! * 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.pentaho.reporting.libraries.base.config.Configuration; import org.pentaho.reporting.libraries.base.config.DefaultConfiguration; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import org.pentaho.reporting.libraries.base.util.StringUtils; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; /** * The abstract module provides a default implementation of the module interface. * <p/> * The module can be specified in an external property file. The file name of this specification defaults to * "module.properties". * <p/> * The first and mandatory section is always the module info and contains the basic module properties like name, version * and a short description. * <p/> * <pre> * module.name: xls-export-gui * module.producer: The JFreeReport project - www.jfree.org/jfreereport * module.description: A dialog component for the Excel table export. * module.version.major: 0 * module.version.minor: 84 * module.version.patchlevel: 0 * </pre> * The properties name, producer and description are simple strings. They may span multiple lines, but may not contain a * colon (':'). The version properties are integer values. * <p/> * This section may be followed by one or more "depends" sections. These sections describe the base modules that are * required to be active to make this module work. The package manager will enforce this policy and will deactivate this * module if one of the base modules is missing. * <p/> * <pre> * dependency.module-id.module: org.pentaho.reporting.engine.classic.core.modules.output.table.xls.XLSTableModule * dependency.module-id.version.major: 0 * dependency.module-id.version.minor: 84 * </pre> * <p/> * The property *.module references to the module implementation of the module package. The module-id is a * per-module-definition-unique identifier and it is recommended to set it to the referenced module's name for * documentary purposes. * * @author Thomas Morgner * @noinspection HardCodedStringLiteral */ public abstract class AbstractModule extends DefaultModuleInfo implements Module { /** * An empty array for performance reasons. */ private static final ModuleInfo[] EMPTY_MODULEINFO = new ModuleInfo[ 0 ]; /** * The list of required modules. */ private ModuleInfo[] requiredModules; /** * The list of optional modules. */ private ModuleInfo[] optionalModules; /** * The name of the module. */ private String name; /** * A short description of the module. */ private String description; /** * The name of the module producer. */ private String producer; /** * The modules subsystem. */ private String subsystem; /** * Default Constructor. */ protected AbstractModule() { setModuleClass( this.getClass().getName() ); } /** * Loads the default module description from the file "module.properties". This file must be in the same package as * the implementing class. * * @throws ModuleInitializeException if an error occurs. */ protected void loadModuleInfo() throws ModuleInitializeException { final InputStream in = ObjectUtilities.getResourceRelativeAsStream( "module.properties", getClass() ); if ( in == null ) { throw new ModuleInitializeException ( "File 'module.properties' not found in module package." ); } loadModuleInfo( in ); } /** * Loads the module description from the given input stream. The module description must conform to the rules define * in the class description. The file must be encoded with "ISO-8859-1" (like property files). * * @param in the input stream from where to read the file * @throws ModuleInitializeException if an error occurs. */ protected void loadModuleInfo( final InputStream in ) throws ModuleInitializeException { if ( in == null ) { throw new NullPointerException ( "Given InputStream is null." ); } try { final DefaultConfiguration props = new DefaultConfiguration(); props.load( in ); readModuleInfo( props ); final ArrayList<ModuleInfo> optionalModules = new ArrayList<ModuleInfo>(); final ArrayList<ModuleInfo> dependentModules = new ArrayList<ModuleInfo>(); final Iterator<String> keys = props.findPropertyKeys( "dependency." ); while ( keys.hasNext() ) { final String key = keys.next(); if ( key.endsWith( ".dependency-type" ) ) { final String moduleHandle = key.substring( 0, key.length() - ".dependency-type".length() ); final DefaultModuleInfo module = readExternalModule( props, moduleHandle ); if ( "optional".equals( props.getConfigProperty( key ) ) ) { optionalModules.add( module ); } else { dependentModules.add( module ); } } } this.optionalModules = optionalModules.toArray( new ModuleInfo[ optionalModules.size() ] ); this.requiredModules = dependentModules.toArray( new ModuleInfo[ dependentModules.size() ] ); } catch ( IOException ioe ) { throw new ModuleInitializeException( "Failed to load properties", ioe ); } finally { try { in.close(); } catch ( IOException e ) { // ignore .. } } } /** * Reads the module definition header. This header contains information about the module itself. * * @param config the properties from where to read the content. */ private void readModuleInfo( final Configuration config ) { setName( config.getConfigProperty( "module.name" ) ); setProducer( config.getConfigProperty( "module.producer" ) ); setDescription( config.getConfigProperty( "module.description" ) ); setMajorVersion( config.getConfigProperty( "module.version.major" ) ); setMinorVersion( config.getConfigProperty( "module.version.minor" ) ); setPatchLevel( config.getConfigProperty( "module.version.patchlevel" ) ); setSubSystem( config.getConfigProperty( "module.subsystem" ) ); } /** * Reads an external module description. This describes either an optional or a required module. * * @param reader the reader from where to read the module * @param prefix the property-key prefix. * @return the read module, never null */ private DefaultModuleInfo readExternalModule( final Configuration reader, final String prefix ) { final DefaultModuleInfo mi = new DefaultModuleInfo(); mi.setModuleClass( reader.getConfigProperty( prefix + ".module" ) ); mi.setMajorVersion( reader.getConfigProperty( prefix + ".version.major" ) ); mi.setMinorVersion( reader.getConfigProperty( prefix + ".version.minor" ) ); mi.setPatchLevel( reader.getConfigProperty( prefix + ".version.patchlevel" ) ); return mi; } /** * Returns the name of this module. * * @return the module name * @see Module#getName() */ public String getName() { return this.name; } /** * Defines the name of the module. * * @param name the module name. */ protected void setName( final String name ) { this.name = name; } /** * Returns the module description. * * @return the description of the module. * @see Module#getDescription() */ public String getDescription() { return this.description; } /** * Defines the description of the module. * * @param description the module's desciption. */ protected void setDescription( final String description ) { this.description = description; } /** * Returns the producer of the module. * * @return the producer. * @see Module#getProducer() */ public String getProducer() { return this.producer; } /** * Defines the producer of the module. * * @param producer the producer. */ protected void setProducer( final String producer ) { this.producer = producer; } /** * Returns a copy of the required modules array. This array contains all description of the modules that need to be * present to make this module work. * * @return an array of all required modules. * @see Module#getRequiredModules() */ public ModuleInfo[] getRequiredModules() { if ( this.requiredModules == null ) { return EMPTY_MODULEINFO; } final ModuleInfo[] retval = new ModuleInfo[ this.requiredModules.length ]; System.arraycopy( this.requiredModules, 0, retval, 0, this.requiredModules.length ); return retval; } /** * Returns a copy of the required modules array. This array contains all description of the optional modules that may * improve the modules functonality. * * @return an array of all required modules. * @see Module#getRequiredModules() */ public ModuleInfo[] getOptionalModules() { if ( this.optionalModules == null ) { return EMPTY_MODULEINFO; } final ModuleInfo[] retval = new ModuleInfo[ this.optionalModules.length ]; System.arraycopy( this.optionalModules, 0, retval, 0, this.optionalModules.length ); return retval; } /** * Defines the required module descriptions for this module. * * @param requiredModules the required modules. */ protected void setRequiredModules( final ModuleInfo[] requiredModules ) { this.requiredModules = new ModuleInfo[ requiredModules.length ]; System.arraycopy( requiredModules, 0, this.requiredModules, 0, requiredModules.length ); } /** * Defines the optional module descriptions for this module. * * @param optionalModules the optional modules. */ public void setOptionalModules( final ModuleInfo[] optionalModules ) { this.optionalModules = new ModuleInfo[ optionalModules.length ]; System.arraycopy( optionalModules, 0, this.optionalModules, 0, optionalModules.length ); } /** * Returns a string representation of this module. * * @return the string representation of this module for debugging purposes. * @see Object#toString() */ public String toString() { final String lineSeparator = StringUtils.getLineSeparator(); final StringBuilder buffer = new StringBuilder( 120 ); buffer.append( "Module : " ); buffer.append( getName() ); buffer.append( lineSeparator ); buffer.append( "ModuleClass : " ); buffer.append( getModuleClass() ); buffer.append( lineSeparator ); buffer.append( "Version: " ); buffer.append( getMajorVersion() ); buffer.append( '.' ); buffer.append( getMinorVersion() ); buffer.append( '.' ); buffer.append( getPatchLevel() ); buffer.append( lineSeparator ); buffer.append( "Producer: " ); buffer.append( getProducer() ); buffer.append( lineSeparator ); buffer.append( "Description: " ); buffer.append( getDescription() ); buffer.append( lineSeparator ); return buffer.toString(); } /** * Tries to load a class to indirectly check for the existence of a certain library. * * @param name the name of the library class. * @param context the context class to get a classloader from. * @return true, if the class could be loaded, false otherwise. */ protected static boolean isClassLoadable( final String name, final Class<?> context ) { try { final ClassLoader loader = ObjectUtilities.getClassLoader( context ); Class.forName( name, false, loader ); return true; } catch ( Exception e ) { return false; } } /** * Configures the module by loading the configuration properties and adding them to the package configuration. * * @param subSystem the subsystem. */ public void configure( final SubSystem subSystem ) { final InputStream in = ObjectUtilities.getResourceRelativeAsStream( "configuration.properties", getClass() ); if ( in == null ) { return; } try { subSystem.getPackageManager().getPackageConfiguration().load( in ); } finally { try { in.close(); } catch ( IOException e ) { // can be ignored ... } } } /** * Tries to load an module initializer and uses this initializer to initialize the module. * * @param classname the class name of the initializer. * @throws ModuleInitializeException if an error occures * @deprecated Use the method that provides a class-context instead. */ protected void performExternalInitialize( final String classname ) throws ModuleInitializeException { try { final ModuleInitializer mi = ObjectUtilities.loadAndInstantiate( classname, AbstractModule.class, ModuleInitializer.class ); if ( mi == null ) { throw new ModuleInitializeException( "Failed to load specified initializer class." ); } mi.performInit(); } catch ( ModuleInitializeException mie ) { throw mie; } catch ( Exception e ) { throw new ModuleInitializeException( "Failed to load specified initializer class.", e ); } } /** * Executes an weakly referenced external initializer. The initializer will be loaded using reflection and will be * executed once. If the initializing fails with any exception, the module will become unavailable. * * @param classname the classname of the <code>ModuleInitializer</code> implementation * @param context the class-loader context from where to load the module's classes. * @throws ModuleInitializeException if an error occured or the initializer could not be found. */ protected void performExternalInitialize( final String classname, final Class<?> context ) throws ModuleInitializeException { try { final ModuleInitializer mi = ObjectUtilities.loadAndInstantiate( classname, context, ModuleInitializer.class ); if ( mi == null ) { throw new ModuleInitializeException( "Failed to load specified initializer class." ); } mi.performInit(); } catch ( ModuleInitializeException mie ) { throw mie; } catch ( Exception e ) { throw new ModuleInitializeException( "Failed to load specified initializer class.", e ); } } /** * Returns the modules subsystem. If this module is not part of an subsystem then return the modules name, but never * null. * * @return the name of the subsystem. */ public String getSubSystem() { if ( this.subsystem == null ) { return getName(); } return this.subsystem; } /** * Defines the subsystem name for this module. If no sub-system name is set, the sub-system defaults to the module's * name. * * @param name the new name of the subsystem. */ protected void setSubSystem( final String name ) { this.subsystem = name; } }