001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
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: package org.kuali.core.util.properties;
017:
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.net.URL;
021: import java.util.ArrayList;
022: import java.util.Iterator;
023:
024: import org.apache.commons.digester.AbstractObjectCreationFactory;
025: import org.apache.commons.digester.Digester;
026: import org.apache.commons.digester.ObjectCreationFactory;
027: import org.apache.commons.lang.StringUtils;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.kuali.core.exceptions.PropertiesException;
031: import org.xml.sax.Attributes;
032: import org.xml.sax.SAXException;
033:
034: /**
035: * This class assembles a PropertyHolder containing all Properties from a set of PropertySources listed in an XML configuration
036: * file.
037: *
038: *
039: */
040: public class KualiPropertiesFactory {
041: // config-file tags
042: private static final String CONFIG_ROOT = "configuration/";
043: private static final String CONFIG_PROPERTIES = CONFIG_ROOT
044: + "properties";
045:
046: // config-file tag attribute
047: private static final String CONFIG_PROPERTIES_ATTR_FILENAME = "fileName";
048:
049: private static Log log = LogFactory
050: .getLog(KualiPropertiesFactory.class);
051:
052: private String configurationFileName;
053:
054: /**
055: * Constructor with ConfigurationFile Name passed
056: *
057: * @param configurationFileName The path to the configuration file
058: * @throws IllegalArgumentException if the configurationFileName is blank
059: */
060: public KualiPropertiesFactory(String configurationFileName) {
061: this .configurationFileName = configurationFileName;
062: }
063:
064: /**
065: * Load the XML configuration file, processes all of the (known) PropertySources declared in that file, and returns a
066: * PropertyHolder containing all of the properties from all of those sources.
067: *
068: * @param startingProperties a PropertyHolder containing predefined properties, which will be used as the starting point for the
069: * returned PropertyHolder; may be null
070: * @return a PropertyHolder containing all properties from all sources listed in the config file
071: * @throws DuplicateKeyException if any source defines a key which has already been defined by an earlier source
072: * @throws PropertiesException if the config file can't be loaded, or if a PropertySource can't load its properties
073: */
074: public PropertyHolder getProperties(
075: PropertyHolder startingProperties) {
076:
077: // open stream to configFile
078: InputStream input = null;
079: try {
080: ClassLoader loader = Thread.currentThread()
081: .getContextClassLoader();
082: URL url = loader.getResource(getConfigurationFileName());
083: if (url != null) {
084: input = url.openStream();
085: }
086: } catch (IOException e) {
087: throw new PropertiesException(
088: "exception caught opening configFile '"
089: + getConfigurationFileName() + "'", e);
090: }
091: if (input != null) {
092:
093: // create and init digester
094: PropertyHolderBuilder builder = new PropertyHolderBuilder(
095: startingProperties);
096: Digester digester = buildDigester(builder);
097:
098: // populate the PropertyHolderBuilder with all sources listed in the
099: // config file
100: try {
101: digester.parse(input);
102: input.close();
103: } catch (SAXException saxe) {
104: log.error("SAX Exception caught", saxe);
105: throw new PropertiesException("SAX Exception caught",
106: saxe);
107: } catch (IOException ioe) {
108: log.error("IO Exception caught", ioe);
109: throw new PropertiesException("IO Exception caught",
110: ioe);
111: }
112:
113: // create and return the merged PropertyHolder
114: return builder.mergeProperties();
115: } else {
116: return new PropertyHolder();
117: }
118: }
119:
120: private Digester buildDigester(Object rootObject) {
121: Digester digester = new Digester();
122:
123: digester.setNamespaceAware(false);
124: digester.setValidating(false);
125: digester.setUseContextClassLoader(true);
126:
127: // set parsing rules
128: setupDigesterInstance(digester, CONFIG_PROPERTIES,
129: new FilePropertySourceFactory(FilePropertySource.class));
130:
131: digester.push(rootObject);
132:
133: return digester;
134: }
135:
136: /**
137: * @return name of the configurationFile
138: */
139: public String getConfigurationFileName() {
140: return configurationFileName;
141: }
142:
143: /**
144: * Sets up digester rules used to process the config file. Should be called once for each distinct PropertySource tag/type.
145: *
146: * @param digester the current digester
147: * @param matchString the pattern to match with this rule
148: * @param factory an ObjectCreationFactory instance to use for creating new objects
149: */
150: protected void setupDigesterInstance(Digester digester,
151: String matchString, ObjectCreationFactory factory) {
152: digester.addFactoryCreate(matchString, factory);
153: digester.addSetProperties(matchString);
154: digester.addSetNext(matchString, "addPropertySource",
155: PropertySource.class.getName());
156: }
157:
158: /**
159: * A tiny inner class that allows the digester to construct properly-initialized FilePropertySource objects. You'll need one of
160: * these foreach different type of PropertySource.
161: */
162: public static class FilePropertySourceFactory extends
163: AbstractObjectCreationFactory {
164: private Class clazz;
165:
166: public FilePropertySourceFactory(Class clazz) {
167: this .clazz = clazz;
168: }
169:
170: public Object createObject(Attributes attributes)
171: throws Exception {
172: FilePropertySource source = (FilePropertySource) clazz
173: .newInstance();
174: source.setFileName(attributes
175: .getValue(CONFIG_PROPERTIES_ATTR_FILENAME));
176: log.info("Created FilePropertySource '"
177: + source.getFileName() + "'");
178:
179: return source;
180: }
181: }
182:
183: /**
184: * An internally used helper class for accumulating PropertySources and merging their contents
185: */
186: public static class PropertyHolderBuilder {
187: PropertyHolder startingProperties;
188: private ArrayList sourceList;
189:
190: /**
191: * Default constructor.
192: */
193: public PropertyHolderBuilder(PropertyHolder startingProperties) {
194: this .sourceList = new ArrayList();
195: this .startingProperties = startingProperties;
196: }
197:
198: /**
199: * Adds the given PropertySource to the list. Called by Digester.
200: *
201: * @param source
202: */
203: public void addPropertySource(PropertySource source) {
204: this .sourceList.add(source);
205: }
206:
207: /**
208: * Assembles and returns the complete PropertyHolder
209: *
210: * @return PropertyHolder containing properties from the accumulated PropertySources
211: */
212: public PropertyHolder mergeProperties() {
213: PropertyHolder mergedProperties = startingProperties;
214: if (mergedProperties == null) {
215: mergedProperties = new PropertyHolder();
216: }
217:
218: for (Iterator i = this .sourceList.iterator(); i.hasNext();) {
219: PropertySource source = (PropertySource) i.next();
220: mergedProperties.loadProperties(source);
221: }
222:
223: return mergedProperties;
224: }
225: }
226: }
|