org.apache.cayenne.conf.Configuration Maven / Gradle / Ivy
Show all versions of cayenne-client-nodeps
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.conf;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.ConfigurationException;
import org.apache.cayenne.access.DataDomain;
import org.apache.cayenne.dataview.DataView;
import org.apache.cayenne.event.EventManager;
import org.apache.cayenne.util.CayenneMap;
import org.apache.cayenne.util.ResourceLocator;
/**
* 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.
*
* 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.
*
*
* @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;
}
};
/**
* Lookup map that stores DataDomains with names as keys.
*/
protected CayenneMap dataDomains = new CayenneMap(this);
protected DataSourceFactory overrideFactory;
protected ConfigStatus loadStatus = new ConfigStatus();
protected String domainConfigurationName = DEFAULT_DOMAIN_FILE;
protected boolean ignoringLoadFailures;
protected ConfigLoaderDelegate loaderDelegate;
protected ConfigSaverDelegate saverDelegate;
protected ConfigurationShutdownHook configurationShutdownHook = new ConfigurationShutdownHook();
protected Map dataViewLocations = new HashMap();
protected String projectVersion;
/**
* @since 1.2
*/
protected EventManager eventManager;
/**
* @deprecated since 1.2. Use Thread.currentThread().setContextClassLoader() instead.
*/
public static void bootstrapSharedConfiguration(Class cl) {
logObj.warn("This method does nothing.");
}
/**
* Configures Cayenne logging properties. Search for the properties file called
* cayenne-log.properties
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.
*
* 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;
}
/**
* @deprecated since 1.2 use Thread.currentThread().getContextClassLoader(). This is
* what Cayenne uses internally.
*/
public static ClassLoader getResourceLoader() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = Configuration.class.getClassLoader();
}
return loader;
}
/**
* Returns default log level for loading configuration. Log level is made static so
* that applications can set it before shared Configuration object is instantiated.
*
* @deprecated since 1.2 unused
*/
public static Level getLoggingLevel() {
Level l = logObj.getLevel();
return (l != null ? l : Level.DEBUG);
}
/**
* Sets the default log level for loading a configuration.
*
* @deprecated since 1.2 unused.
*/
public static void setLoggingLevel(Level logLevel) {
logObj.setLevel(logLevel);
}
/**
* Returns EventManager used by this configuration.
*
* @since 1.2
*/
public EventManager getEventManager() {
return eventManager;
}
/**
* Sets EventManager used by this configuration.
*
* @since 1.2
*/
public void setEventManager(EventManager eventManager) {
this.eventManager = eventManager;
}
/**
* 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) {
// set up logging
this.configureLogging();
// set domain configuration name
this.setDomainConfigurationName(domainConfigurationName);
this.eventManager = new EventManager();
}
/**
* Indicates whether {@link #initialize}can be called. Returning false
* 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 null
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 null
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. Injects EventManager used by
* this configuration into the domain.
*/
public void addDomain(DataDomain domain) {
this.dataDomains.put(domain.getName(), domain);
// inject EventManager
if (domain != null) {
domain.setEventManager(getEventManager());
}
logObj.debug("added domain: " + domain.getName());
}
/**
* Returns registered domain matching name
or null
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,
* null
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 name 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) {
DataDomain domain = (DataDomain) dataDomains.remove(name);
if (domain != null) {
domain.setEventManager(null);
}
logObj.debug("removed domain: " + name);
}
/**
* Returns an unmodifiable collection of registered {@link DataDomain}objects.
*/
public Collection getDomains() {
return Collections.unmodifiableCollection(dataDomains.values());
}
/**
* 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 true
or false
*/
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
*/
public void setLoaderDelegate(ConfigLoaderDelegate loaderDelegate) {
this.loaderDelegate = loaderDelegate;
}
/**
* @since 1.2
*/
public ConfigSaverDelegate getSaverDelegate() {
return saverDelegate;
}
/**
* @since 1.2
*/
public void setSaverDelegate(ConfigSaverDelegate saverDelegate) {
this.saverDelegate = saverDelegate;
}
/**
* 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 {
if (dataView == null) {
throw new IllegalArgumentException("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);
}
}