Source code: org/objectstyle/cayenne/conf/Configuration.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56 package org.objectstyle.cayenne.conf;
57
58 import java.io.InputStream;
59 import java.net.URL;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.Iterator;
63
64 import org.apache.log4j.BasicConfigurator;
65 import org.apache.log4j.Level;
66 import org.apache.log4j.Logger;
67 import org.apache.log4j.PropertyConfigurator;
68 import org.objectstyle.cayenne.CayenneRuntimeException;
69 import org.objectstyle.cayenne.ConfigurationException;
70 import org.objectstyle.cayenne.access.DataDomain;
71 import org.objectstyle.cayenne.util.CayenneMap;
72 import org.objectstyle.cayenne.util.ResourceLocator;
73
74 /**
75 * This class is an entry point to Cayenne. It loads all
76 * configuration files and instantiates main Cayenne objects. Used as a
77 * singleton via the {@link #getSharedConfiguration} method.
78 *
79 * <p>To use a custom subclass of Configuration, Java applications must
80 * call {@link #initializeSharedConfiguration} with the subclass as argument.
81 * This will create and initialize a Configuration singleton instance of the
82 * specified class. By default {@link DefaultConfiguration} is instantiated.
83 * </p>
84 *
85 * @author Andrei Adamchik
86 * @author Holger Hoffstaette
87 */
88 public abstract class Configuration {
89 private static Logger logObj = Logger.getLogger(Configuration.class);
90
91 public static final String DEFAULT_LOGGING_PROPS_FILE = ".cayenne/cayenne-log.properties";
92 public static final String DEFAULT_DOMAIN_FILE = "cayenne.xml";
93 public static final Class DEFAULT_CONFIGURATION_CLASS = DefaultConfiguration.class;
94
95 protected static Configuration sharedConfiguration = null;
96 private static boolean loggingConfigured = false;
97
98 /**
99 * Defines a ClassLoader to use for resource lookup.
100 * Configuration objects that are using ClassLoaders
101 * to locate resources may need to be bootstrapped
102 * explicitly.
103 */
104 protected static ClassLoader resourceLoader = Configuration.class.getClassLoader();
105
106 /** Lookup map that stores DataDomains with names as keys. */
107 protected CayenneMap dataDomains = new CayenneMap(this);
108 protected Collection dataDomainsRef = Collections.unmodifiableCollection(dataDomains.values());
109 protected DataSourceFactory overrideFactory;
110 protected ConfigStatus loadStatus = new ConfigStatus();
111 protected String domainConfigurationName = DEFAULT_DOMAIN_FILE;
112 protected boolean ignoringLoadFailures = false;
113 protected ConfigurationShutdownHook configurationShutdownHook = new ConfigurationShutdownHook();
114
115 /**
116 * Sets <code>cl</code> class's ClassLoader to serve
117 * as shared configuration resource ClassLoader.
118 * If shared Configuration object does not use ClassLoader,
119 * this method call will have no effect on how resources are loaded.
120 */
121 public static void bootstrapSharedConfiguration(Class cl) {
122 resourceLoader = cl.getClassLoader();
123 }
124
125 /**
126 * Configures Cayenne logging properties.
127 * Search for the properties file called <code>cayenne-log.properties</code>
128 * is first done in $HOME/.cayenne, then in CLASSPATH.
129 */
130 public synchronized static void configureCommonLogging() {
131 if (!Configuration.isLoggingConfigured()) {
132 // create a simple CLASSPATH/$HOME locator
133 ResourceLocator locator = new ResourceLocator();
134 locator.setSkipAbsolutePath(true);
135 locator.setSkipClasspath(false);
136 locator.setSkipCurrentDirectory(true);
137 locator.setSkipHomeDirectory(false);
138
139 // and load the default logging config file
140 URL configURL = locator.findResource(DEFAULT_LOGGING_PROPS_FILE);
141 Configuration.configureCommonLogging(configURL);
142 }
143 }
144
145 /**
146 * Configures Cayenne logging properties using properties found at the specified URL.
147 */
148 public synchronized static void configureCommonLogging(URL propsFile) {
149 if (!Configuration.isLoggingConfigured()) {
150 if (propsFile != null) {
151 PropertyConfigurator.configure(propsFile);
152 logObj.debug("configured log4j from: " + propsFile);
153 } else {
154 BasicConfigurator.configure();
155 logObj.debug("configured log4j with BasicConfigurator.");
156 }
157
158 // remember configuration success
159 Configuration.setLoggingConfigured(true);
160 }
161 }
162
163 /**
164 * Indicates whether Log4j has been initialized, either by cayenne
165 * or otherwise. If an external setup has been detected,
166 * {@link #setLoggingConfigured} will be called to remember this.
167 */
168 public static boolean isLoggingConfigured() {
169 if (!loggingConfigured) {
170 // check for existing log4j setup
171 if (Logger.getRootLogger().getAllAppenders().hasMoreElements()) {
172 Configuration.setLoggingConfigured(true);
173 }
174 }
175
176 return loggingConfigured;
177 }
178
179 /**
180 * Indicate whether Log4j has been initialized. Can be used when
181 * subclasses customize the initialization process, or to configure
182 * Log4J outside of Cayenne.
183 */
184 public synchronized static void setLoggingConfigured(boolean state) {
185 loggingConfigured = state;
186 }
187
188 /**
189 * Use this method as an entry point to all Cayenne access objects.
190 * <p>Note that if you want to provide a custom Configuration,
191 * make sure you call one of the {@link #initializeSharedConfiguration} methods
192 * before your application code has a chance to call this method.
193 */
194 public synchronized static Configuration getSharedConfiguration() {
195 if (Configuration.sharedConfiguration == null) {
196 Configuration.initializeSharedConfiguration();
197 }
198
199 return Configuration.sharedConfiguration;
200 }
201
202 /**
203 * Returns the ClassLoader used to load resources.
204 */
205 public static ClassLoader getResourceLoader() {
206 return Configuration.resourceLoader;
207 }
208
209 /**
210 * Returns default log level for loading configuration.
211 * Log level is made static so that applications can set it
212 * before shared Configuration object is instantiated.
213 */
214 public static Level getLoggingLevel() {
215 Level l = logObj.getLevel();
216 return (l != null ? l : Level.DEBUG);
217 }
218
219 /**
220 * Sets the default log level for loading a configuration.
221 */
222 public static void setLoggingLevel(Level logLevel) {
223 logObj.setLevel(logLevel);
224 }
225
226 /**
227 * Creates and initializes shared Configuration object.
228 * By default {@link DefaultConfiguration} will be
229 * instantiated and assigned to a singleton instance of
230 * Configuration.
231 */
232 public static void initializeSharedConfiguration() {
233 Configuration.initializeSharedConfiguration(DEFAULT_CONFIGURATION_CLASS);
234 }
235
236 /**
237 * Creates and initializes a shared Configuration object of a
238 * custom Configuration subclass.
239 */
240 public static void initializeSharedConfiguration(Class configurationClass) {
241 Configuration conf = null;
242
243 try {
244 conf = (Configuration)configurationClass.newInstance();
245 } catch (Exception ex) {
246 logObj.error("Error creating shared Configuration: ", ex);
247 throw new ConfigurationException("Error creating shared Configuration." + ex.getMessage(), ex);
248 }
249
250 Configuration.initializeSharedConfiguration(conf);
251 }
252
253 /**
254 * Sets the shared Configuration object to a new Configuration object.
255 * First calls {@link #canInitialize} and - if permitted -
256 * {@link #initialize} followed by {@link #didInitialize}.
257 */
258 public static void initializeSharedConfiguration(Configuration conf) {
259 // check to see whether we can proceed
260 if (!conf.canInitialize()) {
261 throw new ConfigurationException("Configuration of class "
262 + conf.getClass().getName()
263 + " refused to be initialized.");
264 }
265
266 try {
267 // initialize configuration
268 conf.initialize();
269
270 // call post-initialization hook
271 conf.didInitialize();
272
273 // set the initialized Configuration only after success
274 Configuration.sharedConfiguration = conf;
275 } catch (Exception ex) {
276 throw new ConfigurationException("Error during Configuration initialization. " + ex.getMessage(), ex);
277 }
278 }
279
280 /**
281 * Default constructor for new Configuration instances.
282 * Simply calls {@link Configuration#Configuration(String)}.
283 * @see Configuration#Configuration(String)
284 */
285 protected Configuration() {
286 this(DEFAULT_DOMAIN_FILE);
287 }
288
289 /**
290 * Default constructor for new Configuration instances using the
291 * given resource name as the main domain file.
292 * First calls {@link #configureLogging}, then {@link #setDomainConfigurationName}
293 * with the given domain configuration resource name.
294 */
295 protected Configuration(String domainConfigurationName) {
296 super();
297
298 // set up logging
299 this.configureLogging();
300
301 // set domain configuration name
302 this.setDomainConfigurationName(domainConfigurationName);
303 }
304
305 /**
306 * Indicates whether {@link #initialize} can be called.
307 * Returning <code>false</code> allows new instances to delay
308 * or refuse the initialization process.
309 */
310 public abstract boolean canInitialize();
311
312 /**
313 * Initializes the new instance.
314 * @throws Exception
315 */
316 public abstract void initialize() throws Exception;
317
318 /**
319 * Called after successful completion of {@link #initialize}.
320 */
321 public abstract void didInitialize();
322
323 /**
324 * Returns the resource locator used for finding and loading resources.
325 */
326 protected abstract ResourceLocator getResourceLocator();
327
328 /**
329 * Returns a DataDomain as a stream or <code>null</code>
330 * if it cannot be found.
331 */
332 protected abstract InputStream getDomainConfiguration();
333
334 /**
335 * Returns a DataMap with the given name or <code>null</code>
336 * if it cannot be found.
337 */
338 protected abstract InputStream getMapConfiguration(String name);
339
340
341 /**
342 * Configures log4J. This implementation calls
343 * {@link Configuration#configureCommonLogging}.
344 */
345 protected void configureLogging() {
346 Configuration.configureCommonLogging();
347 }
348
349 /**
350 * Returns the name of the main domain configuration resource.
351 * Defaults to {@link Configuration#DEFAULT_DOMAIN_FILE}.
352 */
353 public String getDomainConfigurationName() {
354 return this.domainConfigurationName;
355 }
356
357 /**
358 * Sets the name of the main domain configuration resource.
359 * @param domainConfigurationName the name of the resource that contains
360 * this Configuration's domain(s).
361 */
362 protected void setDomainConfigurationName(String domainConfigurationName) {
363 this.domainConfigurationName = domainConfigurationName;
364 }
365
366 /**
367 * Returns an internal property for the DataSource factory that
368 * will override any settings configured in XML.
369 * Subclasses may override this method to provide a special factory for
370 * DataSource creation that will take precedence over any factories
371 * configured in a cayenne project.
372 */
373 public DataSourceFactory getDataSourceFactory() {
374 return this.overrideFactory;
375 }
376
377 public void setDataSourceFactory(DataSourceFactory overrideFactory) {
378 this.overrideFactory = overrideFactory;
379 }
380
381 /**
382 * Adds new DataDomain to the list of registered domains.
383 */
384 public void addDomain(DataDomain domain) {
385 this.dataDomains.put(domain.getName(), domain);
386 logObj.debug("added domain: " + domain.getName());
387 }
388
389 /**
390 * Returns registered domain matching <code>name</code>
391 * or <code>null</code> if no such domain is found.
392 */
393 public DataDomain getDomain(String name) {
394 return (DataDomain)this.dataDomains.get(name);
395 }
396
397 /**
398 * Returns default domain of this configuration. If no domains are
399 * configured, <code>null</code> is returned. If more than one domain
400 * exists in this configuration, a CayenneRuntimeException is thrown,
401 * indicating that the domain name must be explicitly specified.
402 * In such cases {@link #getDomain(String name)} must be used instead.
403 */
404 public DataDomain getDomain() {
405 int size = this.dataDomains.size();
406 if (size == 0) {
407 return null;
408 } else if (size == 1) {
409 return (DataDomain)this.dataDomains.values().iterator().next();
410 } else {
411 throw new CayenneRuntimeException("More than one domain is configured; use 'getDomain(String name)' instead.");
412 }
413 }
414
415 /**
416 * Unregisters DataDomain matching <code>name<code> from
417 * this Configuration object. Note that any domain database
418 * connections remain open, and it is a responsibility of a
419 * caller to clean it up.
420 */
421 public void removeDomain(String name) {
422 this.dataDomains.remove(name);
423 logObj.debug("removed domain: " + name);
424 }
425
426 /**
427 * Returns an unmodifiable collection of registered {@link DataDomain} objects.
428 */
429 public Collection getDomains() {
430 return this.dataDomainsRef;
431 }
432
433 /**
434 * Returns whether to ignore any failures during map loading or not.
435 * @return boolean
436 */
437 public boolean isIgnoringLoadFailures() {
438 return this.ignoringLoadFailures;
439 }
440
441 /**
442 * Sets whether to ignore any failures during map loading or not.
443 * @param ignoringLoadFailures <code>true</code> or <code>false</code>
444 */
445 protected void setIgnoringLoadFailures(boolean ignoringLoadFailures) {
446 this.ignoringLoadFailures = ignoringLoadFailures;
447 }
448
449 /**
450 * Returns the load status.
451 * @return ConfigStatus
452 */
453 public ConfigStatus getLoadStatus() {
454 return this.loadStatus;
455 }
456
457 /**
458 * Sets the load status.
459 */
460 protected void setLoadStatus(ConfigStatus status) {
461 this.loadStatus = status;
462 }
463
464 /**
465 * Returns a delegate used for controlling the loading of
466 * configuration elements.
467 * By default a {@link RuntimeLoadDelegate} is used.
468 */
469 public ConfigLoaderDelegate getLoaderDelegate() {
470 return new RuntimeLoadDelegate(this, this.getLoadStatus(), Configuration.getLoggingLevel());
471 }
472
473 /**
474 * Shutdowns all owned domains. Invokes DataDomain.shutdown().
475 */
476 public void shutdown() {
477 Collection domains = getDomains();
478 for (Iterator i = domains.iterator(); i.hasNext(); ) {
479 DataDomain domain = (DataDomain)i.next();
480 domain.shutdown();
481 }
482 }
483
484 private class ConfigurationShutdownHook extends Thread {
485 public void run() {
486 shutdown();
487 }
488 }
489
490 public void installConfigurationShutdownHook() {
491 uninstallConfigurationShutdownHook();
492 Runtime.getRuntime().addShutdownHook(configurationShutdownHook);
493 }
494
495 public void uninstallConfigurationShutdownHook() {
496 Runtime.getRuntime().removeShutdownHook(configurationShutdownHook);
497 }
498 }