/* * 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.tools.configeditor.model; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.libraries.base.boot.AbstractBoot; import org.pentaho.reporting.libraries.base.boot.Module; import org.pentaho.reporting.libraries.base.config.metadata.ConfigurationDomain; import org.pentaho.reporting.libraries.base.config.metadata.ConfigurationMetaData; import org.pentaho.reporting.libraries.base.config.metadata.ConfigurationMetaDataEntry; import org.pentaho.reporting.libraries.base.config.metadata.ConfigurationMetaDataParser; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import org.pentaho.reporting.tools.configeditor.Messages; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; /** * The module node factory is used to build the lists of modules and their assigned keys for the ConfigTreeModel. * * @author Thomas Morgner */ public class ModuleNodeFactory { private static class ConfigTreeModuleNodeComparator implements Comparator<ConfigTreeModuleNode> { public int compare( final ConfigTreeModuleNode n1, final ConfigTreeModuleNode n2 ) { return ( n1.getName().compareTo( n2.getName() ) ); } } /** * Sorts the given modules by their class package names. * * @author Thomas Morgner */ private static class ModuleSorter implements Comparator<Module>, Serializable { /** * DefaultConstructor. */ protected ModuleSorter() { } /** * Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first * argument is less than, equal to, or greater than the second.<p> * * @param o1 the first object to be compared. * @param o2 the second object to be compared. * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater * than the second. * @throws ClassCastException if the arguments' types prevent them from being compared by this Comparator. */ public int compare( final Module o1, final Module o2 ) { final String name1; final String name2; if ( o1.getClass().getPackage() == null || o2.getClass().getPackage() == null ) { name1 = ModuleNodeFactory.getPackage( o1.getClass() ); name2 = ModuleNodeFactory.getPackage( o2.getClass() ); } else { name1 = o1.getClass().getPackage().getName(); name2 = o2.getClass().getPackage().getName(); } return name1.compareTo( name2 ); } } private static final Log logger = LogFactory.getLog( ModuleNodeFactory.class ); /** * Provides access to externalized strings */ private final Messages messages; /** * All known modules as known at construction time. */ private final Module[] activeModules; /** * A list of global module nodes. */ private final ArrayList<ConfigTreeModuleNode> globalNodes; /** * A list of local module nodes. */ private final ArrayList<ConfigTreeModuleNode> localNodes; /** * A hashtable of all defined config description entries. */ private final HashMap<String, ConfigDescriptionEntry> configEntryLookup; private AbstractBoot packageManager; /** * Create a new and uninitialized module node factory. */ public ModuleNodeFactory( final AbstractBoot packageManager ) { this.packageManager = packageManager; messages = Messages.getInstance(); activeModules = packageManager.getPackageManager().getAllModules(); Arrays.sort( activeModules, new ModuleSorter() ); globalNodes = new ArrayList<ConfigTreeModuleNode>(); localNodes = new ArrayList<ConfigTreeModuleNode>(); configEntryLookup = new HashMap<String, ConfigDescriptionEntry>(); } public void load( final boolean append ) throws IOException { final String configurationDomain = packageManager.getConfigurationDomain(); final ConfigurationDomain domain = ConfigurationMetaData.getInstance().createDomain( configurationDomain ); final ConfigurationMetaDataParser parser = new ConfigurationMetaDataParser(); parser.parseConfiguration( this.packageManager ); if ( append == false ) { configEntryLookup.clear(); } final ConfigurationMetaDataEntry[] entries = domain.getAll(); for ( int i = 0; i < entries.length; i++ ) { final ConfigurationMetaDataEntry entry = entries[ i ]; if ( entry.getClassName() != null ) { final ClassConfigDescriptionEntry value = new ClassConfigDescriptionEntry( entry.getKey() ); try { value.setBaseClass( Class.forName( entry.getClassName(), false, ObjectUtilities.getClassLoader( packageManager.getClass() ) ) ); value.setHidden( entry.isHidden() ); value.setGlobal( entry.isGlobal() ); value.setDescription( entry.getDescription() ); configEntryLookup.put( entry.getKey(), value ); continue; } catch ( Exception e ) { logger.info( "Failed to load defined class '" + entry.getClassName() + "' , fall back to plain-text entry for key " + entry.getKey() ); } } final String[] tags = entry.getTags(); if ( tags.length > 0 ) { final EnumConfigDescriptionEntry value = new EnumConfigDescriptionEntry( entry.getKey() ); value.setOptions( tags ); value.setHidden( entry.isHidden() ); value.setGlobal( entry.isGlobal() ); value.setDescription( entry.getDescription() ); configEntryLookup.put( entry.getKey(), value ); continue; } final TextConfigDescriptionEntry value = new TextConfigDescriptionEntry( entry.getKey() ); value.setHidden( entry.isHidden() ); value.setGlobal( entry.isGlobal() ); value.setDescription( entry.getDescription() ); configEntryLookup.put( entry.getKey(), value ); } } public void load( final InputStream in, final boolean append ) throws IOException { if ( append == false ) { configEntryLookup.clear(); } final ConfigDescriptionModel model = new ConfigDescriptionModel(); try { model.load( in ); } catch ( SAXException saxException ) { final String error = messages.getString( "ModuleNodeFactory.ERROR_0001_PARSE_FAILURE", saxException.getMessage() ); //$NON-NLS-1$ ModuleNodeFactory.logger.error( error, saxException ); throw new IOException( error ); } catch ( ParserConfigurationException pE ) { final String error = messages.getString( "ModuleNodeFactory.ERROR_0002_PARSER_CONFIG_ERROR", pE.getMessage() ); //$NON-NLS-1$ ModuleNodeFactory.logger.error( error, pE ); throw new IOException( error ); } final ConfigDescriptionEntry[] entries = model.toArray(); for ( int i = 0; i < entries.length; i++ ) { //Log.debug ("Entry: " + entries[i].getKeyName() + " registered"); configEntryLookup.put( entries[ i ].getKeyName(), entries[ i ] ); } } /** * (Re)Initializes the factory from the given report configuration. This will assign all keys frmo the report * configuration to the model and assignes the definition from the configuration description if possible. */ public void init() { globalNodes.clear(); localNodes.clear(); //Iterator enum = config.findPropertyKeys(""); final Iterator keys = configEntryLookup.keySet().iterator(); while ( keys.hasNext() ) { final String key = (String) keys.next(); processKey( key ); } } /** * Processes a single report configuration key and tries to find a definition for that key. * * @param key the name of the report configuration key */ private void processKey( final String key ) { ConfigDescriptionEntry cde = configEntryLookup.get( key ); //Log.debug ("ActiveModule: " + mod.getClass() + " for key " + key); if ( cde == null ) { // check whether the system properties define such an key. // if they do, then we can assume, that it is just a sys-prop // and we ignore the key. // // if this is no system property, then this is a new entry, we'll // assume that it is a local text key. // // Security restrictions are handled as if the key is not defined // in the system properties. It is safer to add too much than to add // less properties ... try { if ( System.getProperties().containsKey( key ) ) { ModuleNodeFactory.logger.debug( "Ignored key from the system properties: " + key ); //$NON-NLS-1$ return; } else { ModuleNodeFactory.logger.debug( "Undefined key added on the fly: " + key ); //$NON-NLS-1$ cde = new TextConfigDescriptionEntry( key ); } } catch ( final SecurityException se ) { ModuleNodeFactory.logger .debug( "Unsafe key-definition due to security restrictions: " + key, se ); //$NON-NLS-1$ cde = new TextConfigDescriptionEntry( key ); } } // We ignore hidden keys. if ( cde.isHidden() ) { return; } final Module mod = lookupModule( key ); if ( mod == null ) { return; } if ( cde.isGlobal() == false ) { ConfigTreeModuleNode node = lookupNode( mod, localNodes ); if ( node == null ) { node = new ConfigTreeModuleNode( mod ); localNodes.add( node ); } node.addAssignedKey( cde ); } // The global configuration provides defaults for the local // settings... ConfigTreeModuleNode node = lookupNode( mod, globalNodes ); if ( node == null ) { node = new ConfigTreeModuleNode( mod ); globalNodes.add( node ); } node.addAssignedKey( cde ); } /** * Tries to find a module node for the given module in the given list. * * @param key the module that is searched. * @param nodeList the list with all known modules. * @return the node containing the given module, or null if not found. */ private ConfigTreeModuleNode lookupNode( final Module key, final ArrayList nodeList ) { if ( key == null ) { return null; } for ( int i = 0; i < nodeList.size(); i++ ) { final ConfigTreeModuleNode node = (ConfigTreeModuleNode) nodeList.get( i ); if ( key == node.getModule() ) { return node; } } return null; } /** * Returns the name of the package for the given class. This is a workaround for the classloader behaviour of JDK1.2.2 * where no package objects are created. * * @param c the class for which we search the package. * @return the name of the package, never null. */ public static String getPackage( final Class c ) { final String className = c.getName(); final int idx = className.lastIndexOf( '.' ); if ( idx <= 0 ) { // the default package return ""; //$NON-NLS-1$ } else { return className.substring( 0, idx ); } } /** * Looks up the module for the given key. If no module is responsible for the key, then it will be assigned to the * core module. * <p/> * If the core is not defined, then a ConfigTreeModelException is thrown. The core is the base for all modules, and is * always defined in a sane environment. * * @param key the name of the configuration key * @return the module that most likely defines that key */ private Module lookupModule( final String key ) { Module retval = null; int confidence = -1; for ( int i = 0; i < activeModules.length; i++ ) { final String modPackage = ModuleNodeFactory.getPackage( activeModules[ i ].getClass() ); // Log.debug ("Module package: " + modPackage + " for " + activeModules[i].getClass()); if ( key.startsWith( modPackage ) ) { if ( confidence < modPackage.length() ) { confidence = modPackage.length(); retval = activeModules[ i ]; } } } return retval; } /** * Returns all global nodes. You have to initialize the factory before using this method. * * @return the list of all global nodes. */ public ConfigTreeModuleNode[] getGlobalNodes() { final ConfigTreeModuleNode[] retval = globalNodes.toArray( new ConfigTreeModuleNode[ globalNodes.size() ] ); Arrays.sort( retval, new ConfigTreeModuleNodeComparator() ); return retval; } /** * Returns all local nodes. You have to initialize the factory before using this method. * * @return the list of all global nodes. */ public ConfigTreeModuleNode[] getLocalNodes() { final ConfigTreeModuleNode[] retval = localNodes.toArray( new ConfigTreeModuleNode[ localNodes.size() ] ); Arrays.sort( retval, new ConfigTreeModuleNodeComparator() ); return retval; } /** * Returns the entry for the given key or null, if the key has no metadata. * * @param key the name of the key * @return the entry or null if not found. */ public ConfigDescriptionEntry getEntryForKey( final String key ) { return configEntryLookup.get( key ); } }