001: package net.myvietnam.mvncore.configuration;
002:
003: /* ====================================================================
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowledgement:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowledgement may appear in the software itself,
026: * if and wherever such third-party acknowledgements normally appear.
027: *
028: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
029: * Foundation" must not be used to endorse or promote products derived
030: * from this software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache"
034: * nor may "Apache" appear in their names without prior written
035: * permission of the Apache Software Foundation.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: */
056:
057: import java.io.BufferedOutputStream;
058: import java.io.File;
059: import java.io.FileOutputStream;
060: import java.io.IOException;
061: import java.io.OutputStream;
062: import java.net.URL;
063: import java.util.Iterator;
064: import java.util.List;
065:
066: import org.dom4j.Attribute;
067: import org.dom4j.Document;
068: import org.dom4j.Element;
069: import org.dom4j.io.OutputFormat;
070: import org.dom4j.io.SAXReader;
071: import org.dom4j.io.XMLWriter;
072:
073: import org.apache.commons.lang.StringUtils;
074: import org.apache.commons.lang.exception.NestableRuntimeException;
075:
076: /**
077: * Reads a XML configuration file.
078: *
079: * To retrieve the value of an attribute of an element, use
080: * <code>X.Y.Z[@attribute]</code>. The '@' symbol was chosen for
081: * consistency with XPath.
082: *
083: * Setting property values will <b>NOT</b> automatically persist
084: * changes to disk, unless <code>autoSave=true</code>.
085: *
086: * @author <a href="mailto:kelvint@apache.org">Kelvin Tan</a>
087: * @author <a href="mailto:dlr@apache.org">Daniel Rall</a>
088: * @since 0.8.1
089: */
090: public class DOM4JConfiguration extends XMLConfiguration {
091: // For conformance with xpath
092: private static final char ATTRIB_MARKER = '@';
093: private static final String ATTRIB_START_MARKER = "["
094: + ATTRIB_MARKER;
095:
096: /**
097: * For consistency with properties files. Access nodes via an
098: * "A.B.C" notation.
099: */
100: private static final String NODE_DELIMITER = ".";
101:
102: /**
103: * A handle to our data source.
104: */
105: private String fileName;
106:
107: /**
108: * The XML document from our data source.
109: */
110: private Document document;
111:
112: /**
113: * If true, modifications are immediately persisted.
114: */
115: private boolean autoSave = false;
116:
117: /**
118: * Empty construtor. You must provide a file/fileName
119: * and call the load method
120: *
121: */
122: public DOM4JConfiguration() {
123: }
124:
125: /**
126: * Attempts to load the XML file as a resource from the
127: * classpath. The XML file must be located somewhere in the
128: * classpath.
129: *
130: * @param resource Name of the resource
131: * @exception Exception If error reading data source.
132: * @see DOM4JConfiguration#DOM4JConfiguration(File)
133: */
134: public DOM4JConfiguration(String resource) throws Exception {
135: setFile(resourceURLToFile(resource));
136: load();
137: }
138:
139: /**
140: * Attempts to load the XML file.
141: *
142: * @param file File object representing the XML file.
143: * @exception Exception If error reading data source.
144: */
145: public DOM4JConfiguration(File file) throws Exception {
146: setFile(file);
147: load();
148: }
149:
150: public void load() throws Exception {
151:
152: document = new SAXReader().read(ConfigurationUtils.getURL(
153: getBasePath(), getFileName()));
154: initProperties(document.getRootElement(), new StringBuffer());
155:
156: }
157:
158: private static File resourceURLToFile(String resource) {
159: URL confURL = DOM4JConfiguration.class.getClassLoader()
160: .getResource(resource);
161: if (confURL == null) {
162: confURL = ClassLoader.getSystemResource(resource);
163: }
164: return new File(confURL.getFile());
165: }
166:
167: /**
168: * Loads and initializes from the XML file.
169: *
170: * @param element The element to start processing from. Callers
171: * should supply the root element of the document.
172: * @param hierarchy
173: */
174: private void initProperties(Element element, StringBuffer hierarchy) {
175: for (Iterator it = element.elementIterator(); it.hasNext();) {
176: StringBuffer subhierarchy = new StringBuffer(hierarchy
177: .toString());
178: Element child = (Element) it.next();
179: String nodeName = child.getName();
180: String nodeValue = child.getTextTrim();
181: subhierarchy.append(nodeName);
182: if (nodeValue.length() > 0) {
183: super .addProperty(subhierarchy.toString(), nodeValue);
184: }
185:
186: // Add attributes as x.y{ATTRIB_START_MARKER}att{ATTRIB_END_MARKER}
187: List attributes = child.attributes();
188: for (int j = 0, k = attributes.size(); j < k; j++) {
189: Attribute a = (Attribute) attributes.get(j);
190: String attName = subhierarchy.toString() + '['
191: + ATTRIB_MARKER + a.getName() + ']';
192: String attValue = a.getValue();
193: super .addProperty(attName, attValue);
194: }
195: StringBuffer buf = new StringBuffer(subhierarchy.toString());
196: initProperties(child, buf.append('.'));
197: }
198: }
199:
200: /**
201: * Calls super method, and also ensures the underlying {@link
202: * Document} is modified so changes are persisted when saved.
203: *
204: * @param name
205: * @param value
206: */
207: public void addProperty(String name, Object value) {
208: super .addProperty(name, value);
209: setXmlProperty(name, value);
210: possiblySave();
211: }
212:
213: /**
214: * Calls super method, and also ensures the underlying {@link
215: * Document} is modified so changes are persisted when saved.
216: *
217: * @param name
218: * @param value
219: */
220: public void setProperty(String name, Object value) {
221: super .setProperty(name, value);
222: setXmlProperty(name, value);
223: possiblySave();
224: }
225:
226: /**
227: * Sets the property value in our document tree, auto-saving if
228: * appropriate.
229: *
230: * @param name The name of the element to set a value for.
231: * @param value The value to set.
232: */
233: private void setXmlProperty(String name, Object value) {
234: String[] nodes = StringUtils.split(name, NODE_DELIMITER);
235: String attName = null;
236: Element element = document.getRootElement();
237: for (int i = 0; i < nodes.length; i++) {
238: String eName = nodes[i];
239: int index = eName.indexOf(ATTRIB_START_MARKER);
240: if (index > -1) {
241: attName = eName.substring(index
242: + ATTRIB_START_MARKER.length(),
243: eName.length() - 1);
244: eName = eName.substring(0, index);
245: }
246: // If we don't find this part of the property in the XML heirarchy
247: // we add it as a new node
248: if (element.element(eName) == null && attName == null) {
249: element.addElement(eName);
250: }
251: element = element.element(eName);
252: }
253:
254: if (attName == null) {
255: element.setText((String) value);
256: } else {
257: element.addAttribute(attName, (String) value);
258: }
259: }
260:
261: /**
262: * Calls super method, and also ensures the underlying {@link
263: * Document} is modified so changes are persisted when saved.
264: *
265: * @param name The name of the property to clear.
266: */
267: public void clearProperty(String name) {
268: super .clearProperty(name);
269: clearXmlProperty(name);
270: possiblySave();
271: }
272:
273: private void clearXmlProperty(String name) {
274: String[] nodes = StringUtils.split(name, NODE_DELIMITER);
275: String attName = null;
276: Element element = document.getRootElement();
277: for (int i = 0; i < nodes.length; i++) {
278: String eName = nodes[i];
279: int index = eName.indexOf(ATTRIB_START_MARKER);
280: if (index > -1) {
281: attName = eName.substring(index
282: + ATTRIB_START_MARKER.length(),
283: eName.length() - 1);
284: eName = eName.substring(0, index);
285: }
286: element = element.element(eName);
287: if (element == null) {
288: return;
289: }
290: }
291:
292: if (attName == null) {
293: element.remove(element.element(nodes[nodes.length - 1]));
294: } else {
295: element.remove(element.attribute(attName));
296: }
297: }
298:
299: /**
300: */
301: private void possiblySave() {
302: if (autoSave) {
303: try {
304: save();
305: } catch (IOException e) {
306: throw new NestableRuntimeException(
307: "Failed to auto-save", e);
308: }
309: }
310: }
311:
312: /**
313: * If true, changes are automatically persisted.
314: * @param autoSave
315: */
316: public void setAutoSave(boolean autoSave) {
317: this .autoSave = autoSave;
318: }
319:
320: public synchronized void save() throws IOException {
321: XMLWriter writer = null;
322: OutputStream out = null;
323: try {
324: OutputFormat outputter = OutputFormat.createPrettyPrint();
325: out = new BufferedOutputStream(new FileOutputStream(
326: getFile()));
327: writer = new XMLWriter(out, outputter);
328: writer.write(document);
329: } finally {
330: if (out != null) {
331: out.close();
332: }
333:
334: if (writer != null) {
335: writer.close();
336: }
337: }
338: }
339:
340: /**
341: * Returns the file.
342: * @return File
343: */
344: public File getFile() {
345: return ConfigurationUtils.constructFile(getBasePath(),
346: getFileName());
347: }
348:
349: /**
350: * Sets the file.
351: * @param file The file to set
352: */
353: public void setFile(File file) {
354: this .fileName = file.getAbsolutePath();
355: }
356:
357: public void setFileName(String fileName) {
358:
359: this .fileName = fileName;
360:
361: }
362:
363: /**
364: * Returns the fileName.
365: * @return String
366: */
367: public String getFileName() {
368: return fileName;
369: }
370: }
|