001: /*
002: * Copyright 2004-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.compass.core.config;
018:
019: import java.io.File;
020: import java.io.InputStream;
021: import java.net.URL;
022: import java.util.HashMap;
023: import java.util.Iterator;
024:
025: import org.apache.commons.logging.Log;
026: import org.apache.commons.logging.LogFactory;
027: import org.compass.annotations.config.binding.AnnotationsMappingBinding;
028: import org.compass.annotations.config.binding.OverrideAnnotationsWithCpmMappingBinding;
029: import org.compass.core.Compass;
030: import org.compass.core.CompassException;
031: import org.compass.core.config.binding.XmlMappingBinding;
032: import org.compass.core.config.binding.XmlMetaDataBinding;
033: import org.compass.core.config.builder.ConfigurationBuilder;
034: import org.compass.core.config.builder.SmartConfigurationBuilder;
035: import org.compass.core.config.process.MappingProcessor;
036: import org.compass.core.converter.Converter;
037: import org.compass.core.converter.ConverterLookup;
038: import org.compass.core.converter.DefaultConverterLookup;
039: import org.compass.core.engine.naming.DefaultPropertyNamingStrategyFactory;
040: import org.compass.core.engine.naming.PropertyNamingStrategy;
041: import org.compass.core.engine.naming.PropertyNamingStrategyFactory;
042: import org.compass.core.executor.DefaultExecutorManager;
043: import org.compass.core.impl.DefaultCompass;
044: import org.compass.core.mapping.CompassMapping;
045: import org.compass.core.mapping.ResourceMapping;
046: import org.compass.core.metadata.CompassMetaData;
047: import org.compass.core.metadata.impl.DefaultCompassMetaData;
048: import org.compass.core.util.ClassUtils;
049:
050: /**
051: * Used to configure <code>Compass</code> instances.
052: * <p/>
053: * An instance of it allows the application to specify settings and mapping
054: * files to be used when creating <code>Compass</code>.
055: * </p>
056: * <p/>
057: * There are several options to configure a <code>Compass</code> instance,
058: * programmatically using the <code>CompassConfiguration</code> class, using
059: * the xml configuration file (compass.cfg.xml), or a combination of both.
060: * </p>
061: * <p/>
062: * Usually the application will create a single
063: * <code>CompassConfiguration</code>, use it to configure and than build a
064: * <code>Compass</code> instance, and than instantiate
065: * <code>CompassSession</code>s in threads servicing client requests.
066: * </p>
067: * <p/>
068: * The <code>CompassConfiguration</code> is meant only as an
069: * initialization-time object. <code>Compass</code> is immutable and do not
070: * affect the <code>CompassConfiguration</code> that created it.
071: * </p>
072: *
073: * @author kimchy
074: * @see org.compass.core.Compass
075: */
076: public class CompassConfiguration {
077:
078: protected static final Log log = LogFactory
079: .getLog(CompassConfiguration.class);
080:
081: private CompassMetaData metaData;
082:
083: private CompassMapping mapping;
084:
085: private CompassSettings settings;
086:
087: private ClassLoader classLoader;
088:
089: protected CompassMappingBinding mappingBinding;
090:
091: protected ConfigurationBuilder configurationBuilder = new SmartConfigurationBuilder();
092:
093: private HashMap<String, Converter> temporaryConvertersByName = new HashMap<String, Converter>();
094:
095: public CompassConfiguration() {
096: mapping = new CompassMapping();
097: metaData = new DefaultCompassMetaData();
098:
099: settings = new CompassSettings();
100:
101: mappingBinding = new CompassMappingBinding();
102: addMappingBindings(mappingBinding);
103: mappingBinding.setUpBinding(mapping, metaData, settings);
104: }
105:
106: protected void addMappingBindings(
107: CompassMappingBinding mappingBinding) {
108: mappingBinding.addMappingBinding(new XmlMetaDataBinding());
109: mappingBinding.addMappingBinding(new XmlMappingBinding());
110: mappingBinding
111: .addMappingBinding(new AnnotationsMappingBinding());
112: mappingBinding
113: .addMappingBinding(new OverrideAnnotationsWithCpmMappingBinding());
114: }
115:
116: /**
117: * Sets the class loader that will be used to load classes and resources.
118: */
119: public CompassConfiguration setClassLoader(ClassLoader classLoader) {
120: this .classLoader = classLoader;
121: this .settings.setClassLoader(classLoader);
122: return this ;
123: }
124:
125: /**
126: * Returns the class loader that will be used to load classes and resources. If directly
127: * set, will return it. If not, will return the therad local context class loader.
128: */
129: public ClassLoader getClassLoader() {
130: if (this .classLoader == null) {
131: return Thread.currentThread().getContextClassLoader();
132: }
133: return this .classLoader;
134: }
135:
136: /**
137: * Returns the current set of settings associated with the configuration.
138: *
139: * @return The settings used by the configuration
140: */
141: public CompassSettings getSettings() {
142: return settings;
143: }
144:
145: /**
146: * Sets a specific setting in the compass configuration settings.
147: *
148: * @param setting The setting name
149: * @param value The setting value
150: * @return <code>CompassConfiguration</code> for method chaining
151: */
152: public CompassConfiguration setSetting(String setting, String value) {
153: settings.setSetting(setting, value);
154: return this ;
155: }
156:
157: /**
158: * Sets the connection for the compass instance.
159: *
160: * @param connection The connection for compass to use
161: * @return <code>CompassConfiguration</code> for method chaining
162: */
163: public CompassConfiguration setConnection(String connection) {
164: settings.setSetting(CompassEnvironment.CONNECTION, connection);
165: return this ;
166: }
167:
168: /**
169: * Registers a {@link Converter} under the given name. The name can then be used in the mapping
170: * definitions as a logical name to the converter.
171: *
172: * @param converterName the converter name the converter will be registered under
173: * @param converter The converter to use
174: * @return The configuration
175: */
176: public CompassConfiguration registerConverter(String converterName,
177: Converter converter) {
178: this .temporaryConvertersByName.put(converterName, converter);
179: return this ;
180: }
181:
182: /**
183: * Build compass with the configurations set. Creates a copy of all the
184: * current settings and mappings, configures a {@link Compass} instance and
185: * starts it.
186: * <p/>
187: * Note that the <code>CompassConfiguration</code> class can be used to
188: * create more Compass objects after the method has been called.
189: * </p>
190: *
191: * @return the Compass
192: */
193: public Compass buildCompass() throws CompassException {
194:
195: CompassSettings copySettings = settings.copy();
196:
197: copySettings.setClassLoader(getClassLoader());
198:
199: // add any mappings set in the properties
200: for (Iterator it = settings.keySet().iterator(); it.hasNext();) {
201: String setting = (String) it.next();
202: if (setting.startsWith(CompassEnvironment.MAPPING_PREFIX)) {
203: String mapping = settings.getSetting(setting);
204: if (mapping.endsWith("cpm.xml")
205: || mapping.endsWith("cmd.xml")) {
206: addResource(mapping);
207: } else {
208: try {
209: addClass(ClassUtils.forName(mapping,
210: copySettings.getClassLoader()));
211: } catch (ClassNotFoundException e) {
212: throw new CompassException(
213: "Failed to find class [" + mapping
214: + "]");
215: }
216: }
217: }
218: }
219:
220: ConverterLookup converterLookup = new DefaultConverterLookup();
221: registerExtraConverters(converterLookup);
222: converterLookup.configure(copySettings);
223: for (String converterName : temporaryConvertersByName.keySet()) {
224: Converter converter = temporaryConvertersByName
225: .get(converterName);
226: converterLookup.registerConverter(converterName, converter);
227: }
228:
229: CompassMapping copyCompassMapping = mapping
230: .copy(converterLookup);
231:
232: PropertyNamingStrategyFactory propertyNamingStrategyFactory = new DefaultPropertyNamingStrategyFactory();
233: PropertyNamingStrategy propertyNamingStrategy = propertyNamingStrategyFactory
234: .createNamingStrategy(copySettings);
235:
236: MappingProcessor mappingProcessor = new CompassMappingProcessor();
237: mappingProcessor.process(copyCompassMapping,
238: propertyNamingStrategy, converterLookup, copySettings);
239:
240: CompassMetaData copyMetaData = metaData.copy();
241:
242: DefaultExecutorManager executorManager = new DefaultExecutorManager();
243: executorManager.configure(settings);
244:
245: return new DefaultCompass(copyCompassMapping, converterLookup,
246: copyMetaData, propertyNamingStrategy, executorManager,
247: copySettings);
248: }
249:
250: protected void registerExtraConverters(
251: ConverterLookup converterLookup) {
252:
253: }
254:
255: /**
256: * Use the mappings and properties specified in an application resource with
257: * the path <code>/compass.cfg.xml</code>.
258: *
259: * @return <code>CompassConfiguration</code> for method chaining
260: */
261: public CompassConfiguration configure()
262: throws ConfigurationException {
263: configure("/compass.cfg.xml");
264: return this ;
265: }
266:
267: /**
268: * Use the mappings and properties specified in the given application
269: * resource.
270: *
271: * @param resource The compass configuration resource path
272: * @return <code>CompassConfiguration</code> for method chaining
273: */
274: public CompassConfiguration configure(String resource)
275: throws ConfigurationException {
276: log.info("Configuring from resource [" + resource + "]");
277: configurationBuilder.configure(resource, this );
278: return this ;
279: }
280:
281: /**
282: * Use the mappings and properties specified in the given document.
283: *
284: * @param url URL from which you wish to load the configuration
285: * @return A configuration configured via the file
286: * @throws ConfigurationException
287: */
288: public CompassConfiguration configure(URL url)
289: throws ConfigurationException {
290: log.info("Configuring from url [" + url.toExternalForm() + "]");
291: configurationBuilder.configure(url, this );
292: return this ;
293: }
294:
295: /**
296: * Use the mappings and properties specified in the given application file.
297: *
298: * @param configFile <code>File</code> from which you wish to load the
299: * configuration
300: * @return A configuration configured via the file
301: * @throws ConfigurationException
302: */
303: public CompassConfiguration configure(File configFile)
304: throws ConfigurationException {
305: log.info("Configuring from file ["
306: + configFile.getAbsolutePath() + "]");
307: configurationBuilder.configure(configFile, this );
308: return this ;
309: }
310:
311: /**
312: * Advance: Add mappings based on {@link org.compass.core.mapping.ResourceMapping}
313: * implementation which allows for adding pre built mapping constructs.
314: */
315: public CompassConfiguration addResourceMapping(
316: ResourceMapping resourceMapping) {
317: boolean hasAddedResource = mappingBinding
318: .addResoruceMapping(resourceMapping);
319: if (!hasAddedResource) {
320: throw new ConfigurationException(
321: "No mapping match resource mapping ["
322: + resourceMapping.getAlias() + "]");
323: }
324: if (log.isInfoEnabled()) {
325: log.info("Resource Mapping [" + resourceMapping.getAlias()
326: + "]");
327: }
328: return this ;
329: }
330:
331: /**
332: * Uses a class that implements the {@link InputStreamMappingResolver} for auto
333: * generation of mapping definitions.
334: *
335: * @param mappingResolver The mapping resolver
336: */
337: public CompassConfiguration addMappingResover(
338: InputStreamMappingResolver mappingResolver)
339: throws ConfigurationException {
340: boolean hasAddedResource = mappingBinding
341: .addMappingResolver(mappingResolver);
342: if (!hasAddedResource) {
343: throw new ConfigurationException(
344: "No mapping match mapping resolver ["
345: + mappingResolver + "]");
346: }
347: if (log.isInfoEnabled()) {
348: log.info("Mapping resolver [" + mappingResolver + "]");
349: }
350: return this ;
351: }
352:
353: /**
354: * Read mappings from an application resource
355: *
356: * @param path a resource
357: * @param classLoader a <code>ClassLoader</code> to use
358: */
359: public CompassConfiguration addResource(String path,
360: ClassLoader classLoader) throws ConfigurationException {
361: boolean hasAddedResource = mappingBinding.addResource(path,
362: classLoader);
363: if (!hasAddedResource) {
364: throw new ConfigurationException(
365: "No mapping match resource [" + path
366: + "] and class loader [" + classLoader
367: + "]");
368: }
369: if (log.isInfoEnabled()) {
370: log.info("Mapping resource [" + path
371: + "] from class loader [" + classLoader + "]");
372: }
373: return this ;
374: }
375:
376: /**
377: * Read mappings from an application resource trying different classloaders.
378: * This method will try to load the resource first from the thread context
379: * classloader and then from the classloader that loaded Compass.
380: *
381: * @param path The path of the resource
382: */
383: public CompassConfiguration addResource(String path)
384: throws ConfigurationException {
385: boolean hasAddedResource = mappingBinding.addResource(path,
386: getClassLoader());
387: if (!hasAddedResource) {
388: throw new ConfigurationException(
389: "No mapping match resource [" + path + "]");
390: }
391: if (log.isInfoEnabled()) {
392: log.info("Mapping resource [" + path + "] in class loader");
393: }
394: return this ;
395: }
396:
397: /**
398: * Read mappings from a particular file.
399: *
400: * @param filePath a path to a file
401: */
402: public CompassConfiguration addFile(String filePath)
403: throws ConfigurationException {
404: boolean hasAddedResource = mappingBinding.addFile(filePath);
405: if (!hasAddedResource) {
406: throw new ConfigurationException("No mapping match file ["
407: + filePath + "]");
408: }
409: if (log.isInfoEnabled()) {
410: log.info("Mapping file [" + filePath + "]");
411: }
412: return this ;
413: }
414:
415: /**
416: * Read mappings from a particular file.
417: *
418: * @param file a path to a file
419: */
420: public CompassConfiguration addFile(File file)
421: throws ConfigurationException {
422: boolean hasAddedResource = mappingBinding.addFile(file);
423: if (!hasAddedResource) {
424: throw new ConfigurationException("No mapping match file ["
425: + file.getAbsolutePath() + "]");
426: }
427: if (log.isInfoEnabled()) {
428: log.info("Mapping file [" + file.getAbsolutePath() + "]");
429: }
430: return this ;
431: }
432:
433: /**
434: * Read annotated package definitions.
435: *
436: * @param packageName The package name to load
437: */
438: public CompassConfiguration addPackage(String packageName)
439: throws ConfigurationException {
440: boolean hasAddedResource = mappingBinding
441: .addPackage(packageName);
442: if (!hasAddedResource) {
443: throw new ConfigurationException(
444: "No mapping match package [" + packageName + "]");
445: }
446: if (log.isInfoEnabled()) {
447: log.info("Mapping package [" + packageName + "]");
448: }
449: return this ;
450: }
451:
452: /**
453: * Read a mapping from an application resource, using a convention. The
454: * class <code>foo.bar.Foo</code> is mapped by the file
455: * <code>foo/bar/Foo.cpm.xml</code> (in the case of Xml binding).
456: *
457: * @param searchableClass the mapped class
458: */
459: public CompassConfiguration addClass(Class searchableClass)
460: throws ConfigurationException {
461: boolean hasAddedResource = mappingBinding
462: .addClass(searchableClass);
463: if (!hasAddedResource) {
464: throw new ConfigurationException("No mapping match class ["
465: + searchableClass.getName() + "]");
466: }
467: if (log.isInfoEnabled()) {
468: log.info("Mapping class [" + searchableClass + "]");
469: }
470: return this ;
471: }
472:
473: /**
474: * Tries to add a class and returns a boolean indicator if it was added or not.
475: *
476: * @param searchableClass The searchable class to add
477: * @return <code>true</code> if the class was added, <code>false</code> otherwise
478: * @throws ConfigurationException
479: */
480: public boolean tryAddClass(Class searchableClass)
481: throws ConfigurationException {
482: boolean hasAddedResource = mappingBinding
483: .addClass(searchableClass);
484: if (log.isInfoEnabled() && hasAddedResource) {
485: log.info("Mapping class [" + searchableClass + "]");
486: }
487: return hasAddedResource;
488: }
489:
490: /**
491: * Read all mapping and meta-data documents from a directory tree. Assume
492: * that any file named <code>*.cpm.xml</code> or <code>*.cmd.xml</code>
493: * is a mapping document.
494: *
495: * @param dir a directory
496: */
497: public CompassConfiguration addDirectory(File dir)
498: throws ConfigurationException {
499: boolean hasAddedResource = mappingBinding.addDirectory(dir);
500: if (!hasAddedResource) {
501: throw new ConfigurationException(
502: "No mapping match directory ["
503: + dir.getAbsolutePath() + "]");
504: }
505: if (log.isInfoEnabled()) {
506: log.info("Mapping directory [" + dir.getAbsolutePath()
507: + "]");
508: }
509: return this ;
510: }
511:
512: /**
513: * Read all mappings and meta-data from a jar file. Assume that any file
514: * named <code>*.cpm.xml</code> or <code>*.cmd.xml</code> is a mapping
515: * document.
516: *
517: * @param jar a jar file
518: */
519: public CompassConfiguration addJar(File jar)
520: throws ConfigurationException {
521: boolean hasAddedResource = mappingBinding.addJar(jar);
522: if (!hasAddedResource) {
523: throw new ConfigurationException("No mapping match jar ["
524: + jar.getAbsolutePath() + "]");
525: }
526: if (log.isInfoEnabled()) {
527: log.info("Mapping jar [" + jar.getName() + "]");
528: }
529: return this ;
530: }
531:
532: /**
533: * Read mappings from a <code>URL</code>.
534: *
535: * @param url the URL
536: */
537: public CompassConfiguration addURL(URL url)
538: throws ConfigurationException {
539: boolean hasAddedResource = mappingBinding.addURL(url);
540: if (!hasAddedResource) {
541: throw new ConfigurationException("No mapping match URL ["
542: + url.toExternalForm() + "]");
543: }
544: if (log.isInfoEnabled()) {
545: log.info("Mapping URL [" + url.toExternalForm() + "]");
546: }
547: return this ;
548: }
549:
550: /**
551: * Read mappings from an <code>InputStream</code>.
552: *
553: * @param inputStream an <code>InputStream</code> containing
554: */
555: public CompassConfiguration addInputStream(InputStream inputStream,
556: String resourceName) throws ConfigurationException {
557: boolean hasAddedResource = mappingBinding.addInputStream(
558: inputStream, resourceName);
559: if (!hasAddedResource) {
560: throw new ConfigurationException(
561: "No mapping match input stream [" + resourceName
562: + "]");
563: }
564: if (log.isInfoEnabled()) {
565: log.info("Mapping InputStream [" + resourceName + "]");
566: }
567: return this;
568: }
569: }
|