org.objectstyle.cayenne.conf.Configuration.java Source code

Java tutorial

Introduction

Here is the source code for org.objectstyle.cayenne.conf.Configuration.java

Source

/* ====================================================================
 *
 * The ObjectStyle Group Software License, version 1.1
 * ObjectStyle Group - http://objectstyle.org/
 * 
 * Copyright (c) 2002-2004, Andrei (Andrus) Adamchik and individual authors
 * of the software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if any,
 *    must include the following acknowlegement:
 *    "This product includes software developed by independent contributors
 *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
 *    or promote products derived from this software without prior written
 *    permission. For written permission, email
 *    "andrus at objectstyle dot org".
 * 
 * 5. Products derived from this software may not be called "ObjectStyle"
 *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
 *    names without prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals and hosted on ObjectStyle Group web site.  For more
 * information on the ObjectStyle Group, please see
 * <http://objectstyle.org/>.
 */
package org.objectstyle.cayenne.conf;

import java.io.InputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

import org.apache.commons.lang.Validate;
import org.apache.commons.collections.Predicate;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.objectstyle.cayenne.CayenneRuntimeException;
import org.objectstyle.cayenne.ConfigurationException;
import org.objectstyle.cayenne.access.DataDomain;
import org.objectstyle.cayenne.util.CayenneMap;
import org.objectstyle.cayenne.util.ResourceLocator;
import org.objectstyle.cayenne.dataview.DataView;

/**
 * This class is an entry point to Cayenne. It loads all
 * configuration files and instantiates main Cayenne objects. Used as a
 * singleton via the {@link #getSharedConfiguration} method.
 *
 * <p>To use a custom subclass of Configuration, Java applications must
 * call {@link #initializeSharedConfiguration} with the subclass as argument.
 * This will create and initialize a Configuration singleton instance of the
 * specified class. By default {@link DefaultConfiguration} is instantiated.
 * </p>
 *
 * @author Andrei Adamchik
 * @author Holger Hoffstaette
 */
public abstract class Configuration {
    private static Logger logObj = Logger.getLogger(Configuration.class);

    public static final String DEFAULT_LOGGING_PROPS_FILE = ".cayenne/cayenne-log.properties";
    public static final String DEFAULT_DOMAIN_FILE = "cayenne.xml";
    public static final Class DEFAULT_CONFIGURATION_CLASS = DefaultConfiguration.class;

    protected static Configuration sharedConfiguration = null;
    private static boolean loggingConfigured = false;

    public static final Predicate ACCEPT_ALL_DATAVIEWS = new Predicate() {
        public boolean evaluate(Object dataViewName) {
            return true;
        }
    };

    /**
     * Defines a ClassLoader to use for resource lookup.
     * Configuration objects that are using ClassLoaders
     * to locate resources may need to be bootstrapped
     * explicitly.
     */
    protected static ClassLoader resourceLoader = Configuration.class.getClassLoader();

    static {
        if (Configuration.resourceLoader == null) {
            Configuration.resourceLoader = ClassLoader.getSystemClassLoader();
        }
    }

    /** Lookup map that stores DataDomains with names as keys. */
    protected CayenneMap dataDomains = new CayenneMap(this);
    protected Collection dataDomainsRef = Collections.unmodifiableCollection(dataDomains.values());
    protected DataSourceFactory overrideFactory;
    protected ConfigStatus loadStatus = new ConfigStatus();
    protected String domainConfigurationName = DEFAULT_DOMAIN_FILE;
    protected boolean ignoringLoadFailures;
    protected ConfigLoaderDelegate loaderDelegate;
    protected ConfigurationShutdownHook configurationShutdownHook = new ConfigurationShutdownHook();
    protected Map dataViewLocations = new HashMap();
    protected String projectVersion;

    /**
     * Sets <code>cl</code> class's ClassLoader to serve
     * as shared configuration resource ClassLoader.
     * If shared Configuration object does not use ClassLoader,
     * this method call will have no effect on how resources are loaded.
     */
    public static void bootstrapSharedConfiguration(Class cl) {
        if (cl.getClassLoader() != null) {
            resourceLoader = cl.getClassLoader();
        } else {
            logObj.debug("An attempt to bootstrap configuration with null class loader for class " + cl.getName());
        }
    }

    /**
     * Configures Cayenne logging properties.
     * Search for the properties file called <code>cayenne-log.properties</code>
     * is first done in $HOME/.cayenne, then in CLASSPATH.
     */
    public synchronized static void configureCommonLogging() {
        if (!Configuration.isLoggingConfigured()) {
            // create a simple CLASSPATH/$HOME locator
            ResourceLocator locator = new ResourceLocator();
            locator.setSkipAbsolutePath(true);
            locator.setSkipClasspath(false);
            locator.setSkipCurrentDirectory(true);
            locator.setSkipHomeDirectory(false);

            // and load the default logging config file
            URL configURL = locator.findResource(DEFAULT_LOGGING_PROPS_FILE);
            Configuration.configureCommonLogging(configURL);
        }
    }

    /**
     * Configures Cayenne logging properties using properties found at the specified URL.
     */
    public synchronized static void configureCommonLogging(URL propsFile) {
        if (!Configuration.isLoggingConfigured()) {
            if (propsFile != null) {
                PropertyConfigurator.configure(propsFile);
                logObj.debug("configured log4j from: " + propsFile);
            } else {
                BasicConfigurator.configure();
                logObj.debug("configured log4j with BasicConfigurator.");
            }

            // remember configuration success
            Configuration.setLoggingConfigured(true);
        }
    }

    /**
     * Indicates whether Log4j has been initialized, either by cayenne
     * or otherwise. If an external setup has been detected,
     * {@link #setLoggingConfigured} will be called to remember this.
     */
    public static boolean isLoggingConfigured() {
        if (!loggingConfigured) {
            // check for existing log4j setup
            if (Logger.getRootLogger().getAllAppenders().hasMoreElements()) {
                Configuration.setLoggingConfigured(true);
            }
        }

        return loggingConfigured;
    }

    /**
     * Indicate whether Log4j has been initialized. Can be used when
     * subclasses customize the initialization process, or to configure
      * Log4J outside of Cayenne.
     */
    public synchronized static void setLoggingConfigured(boolean state) {
        loggingConfigured = state;
    }

    /**
     * Use this method as an entry point to all Cayenne access objects.
     * <p>Note that if you want to provide a custom Configuration,
     * make sure you call one of the {@link #initializeSharedConfiguration} methods
     * before your application code has a chance to call this method.
     */
    public synchronized static Configuration getSharedConfiguration() {
        if (Configuration.sharedConfiguration == null) {
            Configuration.initializeSharedConfiguration();
        }

        return Configuration.sharedConfiguration;
    }

    /**
     * Returns the ClassLoader used to load resources.
     */
    public static ClassLoader getResourceLoader() {
        return Configuration.resourceLoader;
    }

    /**
     * Returns default log level for loading configuration.
     * Log level is made static so that applications can set it
     * before shared Configuration object is instantiated.
     */
    public static Level getLoggingLevel() {
        Level l = logObj.getLevel();
        return (l != null ? l : Level.DEBUG);
    }

    /**
     * Sets the default log level for loading a configuration.
     */
    public static void setLoggingLevel(Level logLevel) {
        logObj.setLevel(logLevel);
    }

    /**
     * Creates and initializes shared Configuration object.
     * By default {@link DefaultConfiguration} will be
     * instantiated and assigned to a singleton instance of
     * Configuration.
     */
    public static void initializeSharedConfiguration() {
        Configuration.initializeSharedConfiguration(DEFAULT_CONFIGURATION_CLASS);
    }

    /**
     * Creates and initializes a shared Configuration object of a
     * custom Configuration subclass.
     */
    public static void initializeSharedConfiguration(Class configurationClass) {
        Configuration conf = null;

        try {
            conf = (Configuration) configurationClass.newInstance();
        } catch (Exception ex) {
            logObj.error("Error creating shared Configuration: ", ex);
            throw new ConfigurationException("Error creating shared Configuration." + ex.getMessage(), ex);
        }

        Configuration.initializeSharedConfiguration(conf);
    }

    /**
     * Sets the shared Configuration object to a new Configuration object.
     * First calls {@link #canInitialize} and - if permitted -
     * {@link #initialize} followed by {@link #didInitialize}.
     */
    public static void initializeSharedConfiguration(Configuration conf) {
        // check to see whether we can proceed
        if (!conf.canInitialize()) {
            throw new ConfigurationException(
                    "Configuration of class " + conf.getClass().getName() + " refused to be initialized.");
        }

        try {
            // initialize configuration
            conf.initialize();

            // call post-initialization hook
            conf.didInitialize();

            // set the initialized Configuration only after success
            Configuration.sharedConfiguration = conf;
        } catch (Exception ex) {
            throw new ConfigurationException("Error during Configuration initialization. " + ex.getMessage(), ex);
        }
    }

    /**
     * Default constructor for new Configuration instances.
     * Simply calls {@link Configuration#Configuration(String)}.
     * @see Configuration#Configuration(String)
     */
    protected Configuration() {
        this(DEFAULT_DOMAIN_FILE);
    }

    /**
     * Default constructor for new Configuration instances using the
     * given resource name as the main domain file.
     * First calls {@link #configureLogging}, then {@link #setDomainConfigurationName}
     * with the given domain configuration resource name.
     */
    protected Configuration(String domainConfigurationName) {
        super();

        // set up logging
        this.configureLogging();

        // set domain configuration name
        this.setDomainConfigurationName(domainConfigurationName);
    }

    /**
     * Indicates whether {@link #initialize} can be called.
     * Returning <code>false</code> allows new instances to delay
     * or refuse the initialization process.
     */
    public abstract boolean canInitialize();

    /**
     * Initializes the new instance.
     * @throws Exception
     */
    public abstract void initialize() throws Exception;

    /**
     * Called after successful completion of {@link #initialize}.
     */
    public abstract void didInitialize();

    /**
     * Returns the resource locator used for finding and loading resources.
     */
    protected abstract ResourceLocator getResourceLocator();

    /**
     * Returns a DataDomain as a stream or <code>null</code>
     * if it cannot be found.
     */
    // TODO: this method is only used in sublcass (DefaultConfiguration),
    // should we remove it from here?
    protected abstract InputStream getDomainConfiguration();

    /**
     * Returns a DataMap with the given name or <code>null</code>
     * if it cannot be found.
     */
    protected abstract InputStream getMapConfiguration(String name);

    protected abstract InputStream getViewConfiguration(String location);

    /**
     * Configures log4J. This implementation calls
     * {@link Configuration#configureCommonLogging}.
     */
    protected void configureLogging() {
        Configuration.configureCommonLogging();
    }

    /**
     * Returns the name of the main domain configuration resource.
     * Defaults to {@link Configuration#DEFAULT_DOMAIN_FILE}.
     */
    public String getDomainConfigurationName() {
        return this.domainConfigurationName;
    }

    /**
     * Sets the name of the main domain configuration resource.
     * @param domainConfigurationName the name of the resource that contains
     * this Configuration's domain(s).
     */
    protected void setDomainConfigurationName(String domainConfigurationName) {
        this.domainConfigurationName = domainConfigurationName;
    }

    /**
     * @since 1.1
     */
    public String getProjectVersion() {
        return projectVersion;
    }

    /**
     * @since 1.1
     */
    public void setProjectVersion(String projectVersion) {
        this.projectVersion = projectVersion;
    }

    /**
     * Returns an internal property for the DataSource factory that
     * will override any settings configured in XML.
     * Subclasses may override this method to provide a special factory for
     * DataSource creation that will take precedence over any factories
     * configured in a cayenne project.
     */
    public DataSourceFactory getDataSourceFactory() {
        return this.overrideFactory;
    }

    public void setDataSourceFactory(DataSourceFactory overrideFactory) {
        this.overrideFactory = overrideFactory;
    }

    /**
     * Adds new DataDomain to the list of registered domains.
     */
    public void addDomain(DataDomain domain) {
        this.dataDomains.put(domain.getName(), domain);
        logObj.debug("added domain: " + domain.getName());
    }

    /**
     * Returns registered domain matching <code>name</code>
     * or <code>null</code> if no such domain is found.
     */
    public DataDomain getDomain(String name) {
        return (DataDomain) this.dataDomains.get(name);
    }

    /**
     * Returns default domain of this configuration. If no domains are
     * configured, <code>null</code> is returned. If more than one domain
     * exists in this configuration, a CayenneRuntimeException is thrown,
     * indicating that the domain name must be explicitly specified.
     * In such cases {@link #getDomain(String name)} must be used instead.
     */
    public DataDomain getDomain() {
        int size = this.dataDomains.size();
        if (size == 0) {
            return null;
        } else if (size == 1) {
            return (DataDomain) this.dataDomains.values().iterator().next();
        } else {
            throw new CayenneRuntimeException(
                    "More than one domain is configured; use 'getDomain(String name)' instead.");
        }
    }

    /**
     * Unregisters DataDomain matching <code>name<code> from
     * this Configuration object. Note that any domain database
     * connections remain open, and it is a responsibility of a
     * caller to clean it up.
     */
    public void removeDomain(String name) {
        this.dataDomains.remove(name);
        logObj.debug("removed domain: " + name);
    }

    /**
     * Returns an unmodifiable collection of registered {@link DataDomain} objects.
     */
    public Collection getDomains() {
        return this.dataDomainsRef;
    }

    /**
     * Returns whether to ignore any failures during map loading or not.
     * @return boolean
     */
    public boolean isIgnoringLoadFailures() {
        return this.ignoringLoadFailures;
    }

    /**
     * Sets whether to ignore any failures during map loading or not.
     * @param ignoringLoadFailures <code>true</code> or <code>false</code>
     */
    protected void setIgnoringLoadFailures(boolean ignoringLoadFailures) {
        this.ignoringLoadFailures = ignoringLoadFailures;
    }

    /**
     * Returns the load status.
     * @return ConfigStatus
     */
    public ConfigStatus getLoadStatus() {
        return this.loadStatus;
    }

    /**
     * Sets the load status.
     */
    protected void setLoadStatus(ConfigStatus status) {
        this.loadStatus = status;
    }

    /**
     * Returns a delegate used for controlling the loading of configuration elements.
     */
    public ConfigLoaderDelegate getLoaderDelegate() {
        return loaderDelegate;
    }

    /**
     * @since 1.1
     * @param loaderDelegate
     */
    public void setLoaderDelegate(ConfigLoaderDelegate loaderDelegate) {
        this.loaderDelegate = loaderDelegate;
    }

    /**
     * Initializes configuration with the location of data views.
     * 
     * @since 1.1
     * @param dataViewLocations Map of DataView locations.
     */
    public void setDataViewLocations(Map dataViewLocations) {
        if (dataViewLocations == null)
            this.dataViewLocations = new HashMap();
        else
            this.dataViewLocations = dataViewLocations;
    }

    /**
     * @since 1.1
     */
    public Map getDataViewLocations() {
        return dataViewLocations;
    }

    /**
     * @since 1.1
     */
    public boolean loadDataView(DataView dataView) throws IOException {
        return loadDataView(dataView, Configuration.ACCEPT_ALL_DATAVIEWS);
    }

    /**
     * @since 1.1
     */
    public boolean loadDataView(DataView dataView, Predicate dataViewNameFilter) throws IOException {

        Validate.notNull(dataView, "DataView cannot be null.");

        if (dataViewLocations.size() == 0 || dataViewLocations.size() > 512) {
            return false;
        }

        if (dataViewNameFilter == null)
            dataViewNameFilter = Configuration.ACCEPT_ALL_DATAVIEWS;

        List viewXMLSources = new ArrayList(dataViewLocations.size());
        int index = 0;
        for (Iterator i = dataViewLocations.entrySet().iterator(); i.hasNext(); index++) {
            Map.Entry entry = (Map.Entry) i.next();
            String name = (String) entry.getKey();
            if (!dataViewNameFilter.evaluate(name))
                continue;
            String location = (String) entry.getValue();
            InputStream in = getViewConfiguration(location);
            if (in != null)
                viewXMLSources.add(in);
        }

        if (viewXMLSources.isEmpty())
            return false;

        dataView.load((InputStream[]) viewXMLSources.toArray(new InputStream[viewXMLSources.size()]));
        return true;
    }

    /**
     * Shutdowns all owned domains. Invokes DataDomain.shutdown().
     */
    public void shutdown() {
        Collection domains = getDomains();
        for (Iterator i = domains.iterator(); i.hasNext();) {
            DataDomain domain = (DataDomain) i.next();
            domain.shutdown();
        }
    }

    private class ConfigurationShutdownHook extends Thread {
        public void run() {
            shutdown();
        }
    }

    public void installConfigurationShutdownHook() {
        uninstallConfigurationShutdownHook();
        Runtime.getRuntime().addShutdownHook(configurationShutdownHook);
    }

    public void uninstallConfigurationShutdownHook() {
        Runtime.getRuntime().removeShutdownHook(configurationShutdownHook);
    }
}