001: /*
002: * The contents of this file are subject to the Sapient Public License
003: * Version 1.0 (the "License"); you may not use this file except in compliance
004: * with the License. You may obtain a copy of the License at
005: * http://carbon.sf.net/License.html.
006: *
007: * Software distributed under the License is distributed on an "AS IS" basis,
008: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
009: * the specific language governing rights and limitations under the License.
010: *
011: * The Original Code is The Carbon Component Framework.
012: *
013: * The Initial Developer of the Original Code is Sapient Corporation
014: *
015: * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
016: */
017:
018: package org.sape.carbon.core.config.format.jdom;
019:
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.InputStreamReader;
023: import java.io.OutputStream;
024: import java.io.StringReader;
025: import java.util.Iterator;
026: import java.util.List;
027:
028: import javax.xml.parsers.SAXParser;
029: import javax.xml.parsers.SAXParserFactory;
030:
031: import org.sape.carbon.core.config.format.ConfigurationDataFormatService;
032: import org.sape.carbon.core.config.format.ConfigurationFormatException;
033: import org.sape.carbon.core.exception.ExceptionUtility;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.jdom.Document;
038: import org.jdom.Element;
039: import org.jdom.JDOMException;
040: import org.jdom.Namespace;
041: import org.jdom.input.SAXBuilder;
042: import org.jdom.output.XMLOutputter;
043:
044: /**
045: * <p>This factory implementation constructs the appropriate Configuration
046: * implementation, through the Dynamic Proxy facility of the JDK, for the
047: * particular Configuration interface requested.
048: * </p>
049: *
050: * Copyright 2002 Sapient
051: * @since carbon 1.0
052: * @author Greg Hinkle, January 2002
053: * @version $Revision: 1.21 $($Author: dvoet $ / $Date: 2003/05/05 21:21:17 $)
054: */
055: public class JDOMConfigurationFactory implements
056: ConfigurationDataFormatService {
057:
058: /**
059: * Provides a handle to Apache-commons logger
060: */
061: private Log log = LogFactory.getLog(this .getClass());
062:
063: /** Name of the feature URI to turn on schema validation in xerces. */
064: private static final String SCHEMA_URI = "http://www.w3.org/2001/XMLSchema-instance";
065:
066: /** Name of the xerces sax parser used to do schema validation. */
067: private static final String XERCES_SAX_PARSER = "org.apache.xerces.jaxp.SAXParserImpl";
068:
069: /**
070: * <P>Loads a <code>org.jdom.Document</code> object from the given
071: * <code>InputStream</code>. This node object will represent
072: * the full object-graph depiction of a live configuration.</P>
073: *
074: * @param name The name of the configuration node
075: * @param in the {@link java.io.InputStream} from which
076: * the configuration will be read
077: * @throws ConfigurationFormatException when there is a formatting error
078: * with the input stream
079: * @return The Document object representing a live
080: * object graph of the data from the input stream
081: */
082: public Document readConfigurationStreamToData(String name,
083: InputStream in) throws ConfigurationFormatException {
084:
085: Document document = null;
086: ConfigEntityResolver configEntityResolver = new ConfigEntityResolver(
087: name);
088:
089: // Configuration may be read twice if it fails to validate
090: // the first time. Therefore this method must read
091: // over the input stream twice. Because JDOM closes
092: // the input stream after reading it, it must be
093: // copied out of the normal stream into a form where it
094: // can be used to build a new input stream for the
095: // two reads by JDOM.
096: String inputStreamString = convertStreamToString(name, in);
097:
098: try {
099: // First use a validating builder. Ideally all
100: // the JDOM XML should validate.
101: SAXBuilder validatingBuilder = new SAXBuilder(true);
102: validatingBuilder.setEntityResolver(configEntityResolver);
103:
104: // turn on schema validation
105: if (XERCES_SAX_PARSER.equals(getSaxParserClassName())) {
106: validatingBuilder
107: .setFeature(
108: "http://apache.org/xml/features/validation/schema",
109: true);
110: }
111:
112: StringReader stringReader = new StringReader(
113: inputStreamString);
114: document = validatingBuilder.build(stringReader);
115:
116: if (log.isTraceEnabled()) {
117: log.trace("Validated configuration [" + name + "]");
118: }
119:
120: } catch (JDOMException jde) {
121: // Likely indicates validation of the document failed.
122: // Try again with a non-validating parser.
123:
124: try {
125: SAXBuilder nonvalidatingBuilder = new SAXBuilder(false);
126: nonvalidatingBuilder
127: .setEntityResolver(configEntityResolver);
128: StringReader stringReader = new StringReader(
129: inputStreamString);
130: document = nonvalidatingBuilder.build(stringReader);
131:
132: // The document has been read and is well-formed, so
133: // if the document contained information it was suppose
134: // to validate against, give a warning.
135: // If there was no validating document, merely log
136: // a trace message.
137: if (containsValidatingDocument(document)) {
138: if (log.isWarnEnabled()) {
139: log
140: .warn("Validation failed for configuration ["
141: + name + "]: " + jde);
142: }
143: } else {
144: if (log.isTraceEnabled()) {
145: log
146: .trace("No validating document supplied for "
147: + "configuration ["
148: + name
149: + "]");
150: }
151: }
152:
153: } catch (JDOMException jde2) {
154: throw new ConfigurationFormatException(
155: this .getClass(),
156: "Unable to parse Configuration Data from input stream.",
157: jde);
158: }
159: }
160: return document;
161: }
162:
163: /**
164: * Checks if the given JDOM Document contains a document for validation
165: * against. This can be a inline/external DTD or a Schema.
166: *
167: * @param document the document to check against
168: * @return if the document has a validation document associated
169: */
170: protected boolean containsValidatingDocument(Document document) {
171: return (containsDtd(document) || containsSchema(document));
172: }
173:
174: /**
175: * Checks if the given JDOM Document contains a DTD for validation
176: * against.
177: *
178: * @param document the document to check against
179: * @return if the document has a DTD associated
180: */
181: protected boolean containsDtd(Document document) {
182: return (document.getDocType() != null);
183: }
184:
185: /**
186: * Checks if the given JDOM Document contains a Schema for validation
187: * against.
188: *
189: * @param document the document to check against
190: * @return if the document has a Schema associated
191: */
192: protected boolean containsSchema(Document document) {
193: boolean containsSchema = false;
194:
195: Element rootElement = document.getRootElement();
196: List additionalNamespaces = rootElement
197: .getAdditionalNamespaces();
198: if (additionalNamespaces != null) {
199: Iterator additionalNamespacesIterator = additionalNamespaces
200: .iterator();
201:
202: while (additionalNamespacesIterator.hasNext()) {
203: Namespace namespace = (Namespace) additionalNamespacesIterator
204: .next();
205:
206: if (SCHEMA_URI.equals(namespace.getURI())) {
207: // If a schema URI exists check for an internal
208: // and external schema which this document tried
209: // to validate against
210:
211: String noNamespaceSchemaLocation = rootElement
212: .getAttributeValue(
213: "noNamespaceSchemaLocation",
214: namespace);
215:
216: String schemaLocation = rootElement
217: .getAttributeValue("schemaLocation",
218: namespace);
219:
220: if (noNamespaceSchemaLocation != null
221: || schemaLocation != null) {
222:
223: containsSchema = true;
224: }
225: }
226: }
227: }
228:
229: return containsSchema;
230: }
231:
232: /**
233: * Converts a given input stream into a String.
234: *
235: * @param name the name of the input string to convert. Used for logging.
236: * @param in the input stream to convert
237: * @return a string representation of the InputStream
238: * @throws ConfigurationFormatException indicates an error converting
239: * the input stream into a string. Generally used to wrap
240: * an IOException
241: */
242: protected String convertStreamToString(String name, InputStream in)
243: throws ConfigurationFormatException {
244:
245: InputStreamReader inputStreamReader = new InputStreamReader(in);
246:
247: StringBuffer inputStreamStringBuffer = new StringBuffer();
248:
249: try {
250: char[] cbuf = new char[8096];
251: int charRead;
252: String characters;
253:
254: charRead = inputStreamReader.read(cbuf, 0, cbuf.length);
255: while (charRead >= 0) {
256: characters = new String(cbuf, 0, charRead);
257: inputStreamStringBuffer.append(characters);
258: charRead = inputStreamReader.read(cbuf, 0, cbuf.length);
259: }
260: } catch (IOException ioe) {
261: throw new ConfigurationFormatException(this .getClass(),
262: "Unable to convert configuration ["
263: + name
264: + "] into a StringReader: "
265: + ExceptionUtility
266: .printStackTracesToString(ioe));
267:
268: }
269:
270: return inputStreamStringBuffer.toString();
271: }
272:
273: /**
274: * Returns a string representation of the parser in the system or
275: * null if an error occurs.
276: *
277: * @return name of the system sax parser or null for an error
278: */
279: protected static String getSaxParserClassName() {
280: try {
281: SAXParserFactory saxParserFactory = SAXParserFactory
282: .newInstance();
283: SAXParser saxParser = saxParserFactory.newSAXParser();
284: return saxParser.getClass().getName();
285: } catch (Exception e) {
286: return null;
287: }
288: }
289:
290: /**
291: * <P>Stores the raw version of the provided <code>org.jdom.Document</code>
292: * object in the format that this format service implementation
293: * understands. The format of this implementation is strict XML.</P>
294: *
295: * @param out The output stream to which the raw configuration
296: * data should be written
297: * @param document the document to output
298: * @throws ConfigurationFormatException When unable to write a
299: * node's raw format to the output stream
300: */
301: public void writeConfigurationStreamToData(Document document,
302: OutputStream out) throws ConfigurationFormatException {
303:
304: try {
305: XMLOutputter outputter = new XMLOutputter(" ", true);
306:
307: outputter.output(document, out);
308: } catch (IOException ioe) {
309: throw new ConfigurationFormatException(this .getClass(),
310: "Failed to write configuration document to output stream "
311: + "in xml format.", ioe);
312: }
313: }
314:
315: }
|