001: // $Id: ParserImpl.java 282 2007-07-19 22:46:27Z jg_hamburg $
002: /********************************************************************************
003: * DDTUnit, a Datadriven Approach to Unit- and Moduletesting
004: * Copyright (c) 2004, Joerg and Kai Gellien
005: * All rights reserved.
006: *
007: * The Software is provided under the terms of the Common Public License 1.0
008: * as provided with the distribution of DDTUnit in the file cpl-v10.html.
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: * + Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * + Redistributions in binary form must reproduce the above
017: * copyright notice, this list of conditions and the following
018: * disclaimer in the documentation and/or other materials provided
019: * with the distribution.
020: *
021: * + Neither the name of the authors or DDTUnit, nor the
022: * names of its contributors may be used to endorse or promote
023: * products derived from this software without specific prior
024: * written permission.
025: *
026: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
027: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
028: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
029: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
030: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
031: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
032: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
033: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
036: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: ********************************************************************************/package junitx.ddtunit.data.processing.parser;
038:
039: import java.io.File;
040: import java.io.IOException;
041: import java.io.InputStream;
042: import java.io.Reader;
043: import java.io.StringReader;
044: import java.net.MalformedURLException;
045:
046: import javax.xml.parsers.ParserConfigurationException;
047: import javax.xml.parsers.SAXParser;
048: import javax.xml.parsers.SAXParserFactory;
049:
050: import junitx.ddtunit.DDTException;
051: import junitx.ddtunit.data.DDTTestDataException;
052: import junitx.ddtunit.data.TestClusterDataSet;
053: import junitx.ddtunit.data.TypedObject;
054: import junitx.ddtunit.data.processing.IParser;
055: import junitx.ddtunit.util.DDTConfiguration;
056:
057: import org.apache.log4j.Logger;
058: import org.xml.sax.InputSource;
059: import org.xml.sax.Locator;
060: import org.xml.sax.SAXException;
061: import org.xml.sax.SAXNotRecognizedException;
062: import org.xml.sax.SAXParseException;
063: import org.xml.sax.XMLReader;
064: import org.xml.sax.helpers.DefaultHandler;
065: import org.xml.sax.helpers.LocatorImpl;
066:
067: /**
068: * XML SAX parser converting xml testdatea resourse into object structure used
069: * by DDTestCase class execution. <br/>Using JAXP 1.1 to specify and configure
070: * SAX conformant parser. <br/>As project default Apache Crimson provided in the
071: * distribution of JDK 1.4.2 is used.
072: *
073: * @author jg
074: */
075: public final class ParserImpl implements IParser {
076: private static final String XML_XERCES_NONAMESPACE_SCHEMA_LOCATION_URI = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation";
077:
078: private static final String XML_XERCES_SCHEMA_FULL_CHECK_URI = "http://apache.org/xml/features/validation/schema-full-checking";
079:
080: private static final String XML_VALIDATE_URI = "http://xml.org/sax/features/validation";
081:
082: private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
083:
084: private static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
085:
086: private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
087:
088: private static final String XML_LEXICAL_HANDLER_URI = "http://xml.org/sax/properties/lexical-handler";
089:
090: private static final String XML_XERCES_VALIDATE_URI = "http://apache.org/xml/features/validation/schema";
091:
092: /**
093: * URL of xml schema for validation
094: */
095: public final static String XSD_URL = "http://ddtunit.sourceforge.net/ddtunit.xsd";
096:
097: /**
098: * resource path of xml schema used for validation
099: */
100: public final static String XSD_RESOURCE_PATH = "/junitx/ddtunit/data/processing/parser/ddtunit.xsd";
101:
102: private final Logger log = Logger.getLogger(ParserImpl.class);
103:
104: private XMLReader producer;
105:
106: private ContentHandler consumer;
107:
108: private final static String XML_NAMESPACE_URI = "http://xml.org/sax/features/namespaces";
109:
110: private final static String XML_NAMESPACE_PREFIX_URI = "http://xml.org/sax/features/namespace-prefixes";
111:
112: private static final String LF = System
113: .getProperty("line.separator");
114:
115: private static final String XML_HEADER = "<?xml version=\"1.0\" ?>";
116:
117: /**
118: *
119: */
120: public ParserImpl() {
121: log.debug("DDTParser - constructor START");
122: try {
123: // the SAX way
124: // this.producer = XMLReaderFactory.createXMLReader();
125: // the jaxp1.1 way
126: SAXParserFactory factory = SAXParserFactory.newInstance();
127: factory.setNamespaceAware(true);
128: // factory.setValidating(DDTConfiguration.getInstance()
129: // .isActiveXmlValidation());
130: SAXParser saxParser = factory.newSAXParser();
131: // Assert.assertTrue("SAX parser should be validating", saxParser
132: // .isValidating());
133: this .producer = saxParser.getXMLReader();
134: this .producer.setFeature(XML_NAMESPACE_PREFIX_URI, false);
135: this .producer.setFeature(XML_NAMESPACE_URI, true);
136: } catch (SAXNotRecognizedException e) {
137: throw DDTException
138: .create(
139: new StringBuffer(
140: "XML ParserImpl does not support schema validation as of JAXP 1.2"),
141: e);
142: } catch (ParserConfigurationException e) {
143: throw DDTException.create(new StringBuffer(
144: "Error configuring parser."), e);
145: } catch (SAXException e) {
146: throw DDTException.create(new StringBuffer(
147: "Error configuring parser."), e);
148: }
149: this .producer.setErrorHandler(new ErrorHandler());
150: this .producer.setEntityResolver(new EntityResolver());
151: log.debug("DDTParser - constructor END");
152: }
153:
154: /*
155: * (non-Javadoc)
156: *
157: * @see junitx.ddtunit.data.processing.parser.IParser#parse(java.lang.String,
158: * java.lang.String)
159: */
160: public TestClusterDataSet parse(String resource, boolean byName,
161: String clusterId, TestClusterDataSet baseDataSet) {
162: log.debug("parse(" + resource + ", " + clusterId + ")-START");
163: this .consumer = new ContentHandler(resource, baseDataSet);
164: Locator locator = new LocatorImpl();
165: this .consumer.setDocumentLocator(locator);
166:
167: try {
168: // add classId to consumer as a processing filter criterion
169: this .consumer.setClusterId(clusterId);
170: // process reource
171: InputSource iSource = createInputSource(resource, true);
172: if (validateSource(iSource)) {
173: iSource = createInputSource(resource, byName);
174: }
175: this .producer.setContentHandler(consumer);
176: this .producer.parse(iSource);
177: } catch (IOException e) {
178: log.error("Error on behalf of xml test resource.", e);
179: throw DDTTestDataException.create(new StringBuffer(
180: "Error on behalf of xml test resource."), e);
181: } catch (SAXException e) {
182: StringBuffer sb = new StringBuffer(
183: "Error during parsing of xml testresource");
184: if (SAXParseException.class.isInstance(e)) {
185: sb
186: .append(LF)
187: .append("Resource \'")
188: .append(resource)
189: .append("\' line/column ")
190: .append(((SAXParseException) e).getLineNumber())
191: .append("/").append(
192: ((SAXParseException) e)
193: .getColumnNumber());
194: }
195: log.error(sb.toString(), e);
196: throw DDTTestDataException.create(sb, e);
197: } finally {
198: log.debug("parse(" + resource + ", " + clusterId + ")-END");
199: }
200: if (baseDataSet.size() == 0) {
201: StringBuffer sb = new StringBuffer(
202: "No testdata provided for class id \'")
203: .append(baseDataSet.getId())
204: .append("\' in testresource \'")
205: .append(resource)
206: .append("\'\n")
207: .append(
208: "Check if referred class id in xml resources matches definition")
209: .append(
210: " of \n initTestData(resource, classId) inside of your testclass.");
211: throw new DDTTestDataException(sb.toString());
212: }
213:
214: return baseDataSet;
215: }
216:
217: /**
218: * Parse object definition and use provided object as base class. If set to
219: * null a new instance of required type will be created during processing.
220: *
221: * @param xmlObjectDef
222: * string defining object to instanciate
223: * @param addXMLHeader -
224: * set true if xml header should be added
225: * @return object to instanciate as defined
226: */
227: public TypedObject parseElement(String xmlObjectDef,
228: boolean addXMLHeader) {
229: TypedObject myObj = null;
230: String defaultCluster = "singleton";
231: log.debug("parse(\"" + xmlObjectDef + "\", " + addXMLHeader
232: + ")-START");
233: TestClusterDataSet dataSet = new TestClusterDataSet(
234: defaultCluster, null);
235: this .consumer = new ContentHandler(null, dataSet);
236: Locator locator = new LocatorImpl();
237: this .consumer.setDocumentLocator(locator);
238: this .producer.setContentHandler(consumer);
239: // dataSet = (TestClusterDataSet) parse(xmlObjectDef, false,
240: // defaultCluster, dataSet);
241: try {
242: InputSource iSource = createInputSource(xmlObjectDef, false);
243: validateSource(iSource);
244: iSource = createInputSource(xmlObjectDef, false);
245: this .producer.parse(iSource);
246: } catch (IOException e) {
247: log.error("Error on behalf of xml test resource.", e);
248: throw DDTTestDataException.create(new StringBuffer(
249: "Error on behalf of xml test resource."), e);
250: } catch (SAXException e) {
251: StringBuffer sb = new StringBuffer(
252: "Error during parsing of xml testresource from reader.");
253: throw DDTTestDataException.create(sb, e);
254: } finally {
255: log.debug("parse(reader)-END");
256: }
257: myObj = dataSet.getObject("singleton");
258: return myObj;
259: }
260:
261: /**
262: * @param resource
263: * to process
264: * @param byName
265: * set to true if resource is a name of resource to process,
266: * false if it is the resource itself.
267: * @return
268: * @throws MalformedURLException
269: */
270: private InputSource createInputSource(String resource,
271: boolean byName) throws MalformedURLException {
272: // check if resource is file or resource
273: InputSource iSource = null;
274: if (byName) {
275: InputStream in = this .getClass().getResourceAsStream(
276: resource);
277: if (in == null) {
278: File inFile = new File(resource);
279:
280: iSource = new InputSource(resource);
281: iSource.setSystemId(inFile.toURL().toExternalForm());
282: } else {
283: iSource = new InputSource(in);
284: iSource.setSystemId(this .getClass().getResource(
285: resource).toExternalForm());
286: }
287: } else {
288: String xmlDef = null;
289: xmlDef = XML_HEADER + resource;
290: Reader reader = new StringReader(xmlDef);
291: iSource = new InputSource(reader);
292: }
293: return iSource;
294: }
295:
296: /**
297: * @param iSource
298: * input source to validate
299: * @return true if validation was activated and processed
300: */
301: private boolean validateSource(InputSource iSource) {
302: boolean validated = false;
303: try {
304: if (this .producer.getFeature(XML_XERCES_VALIDATE_URI)) {
305: DDTConfiguration.getInstance()
306: .setActiveParserValidation(true);
307: }
308: } catch (SAXException ex) {
309: // no Apache Xerces found, so just ignore validation
310: }
311: if (DDTConfiguration.getInstance().isActiveXmlValidation()
312: && DDTConfiguration.getInstance()
313: .isActiveParserValidation()) {
314: try {
315: validated = true;
316: this .producer.setContentHandler(new DefaultHandler());
317: // validation properties and features must be activated
318: this .producer.setFeature(XML_VALIDATE_URI, true);
319: this .producer.setFeature(XML_XERCES_VALIDATE_URI, true);
320: this .producer.setFeature(
321: XML_XERCES_SCHEMA_FULL_CHECK_URI, true);
322: this .producer.setProperty(
323: XML_XERCES_NONAMESPACE_SCHEMA_LOCATION_URI,
324: ParserImpl.XSD_URL);
325: this .producer.setProperty(JAXP_SCHEMA_LANGUAGE,
326: W3C_XML_SCHEMA);
327: this .producer.setProperty(JAXP_SCHEMA_SOURCE,
328: ParserImpl.XSD_URL);
329:
330: this .producer.parse(iSource);
331: } catch (DDTException ex) {
332: if (ex.getMessage() != null
333: && ex.getMessage().startsWith(
334: "Error during parsing")) {
335: throw ex;
336: }
337: log.warn("Error on validation", ex);
338: } catch (IOException e) {
339: log.error("Error on behalf of xml test resource.", e);
340: throw DDTException.create(new StringBuffer(
341: "Error on behalf of xml test resource."), e);
342: } catch (SAXException e) {
343: StringBuffer sb = new StringBuffer(
344: "Error during parsing of xml testresource");
345: if (SAXParseException.class.isInstance(e)) {
346: sb.append(LF).append("Resource \'").append(
347: iSource.getSystemId()).append(
348: "\' line/column ").append(
349: ((SAXParseException) e).getLineNumber())
350: .append("/").append(
351: ((SAXParseException) e)
352: .getColumnNumber());
353: }
354: log.error(sb.toString(), e);
355: throw DDTException.create(sb, e);
356: }
357: }
358: return validated;
359: }
360:
361: public TypedObject parseElement(String xmlObjectDef) {
362: return parseElement(xmlObjectDef, true);
363: }
364:
365: }
|