org.pentaho.reporting.tools.configeditor.model.ModuleNodeFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.reporting.tools.configeditor.model.ModuleNodeFactory.java

Source

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