/*
* 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 );
}
}