001: /*
002: *
003: * The DbUnit Database Testing Framework
004: * Copyright (C)2002-2004, DbUnit.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: */
021: package org.dbunit.dataset.xml;
022:
023: import org.slf4j.Logger;
024: import org.slf4j.LoggerFactory;
025:
026: import org.dbunit.dataset.Column;
027: import org.dbunit.dataset.DataSetException;
028: import org.dbunit.dataset.DefaultTableMetaData;
029: import org.dbunit.dataset.datatype.DataType;
030: import org.dbunit.dataset.stream.DefaultConsumer;
031: import org.dbunit.dataset.stream.IDataSetConsumer;
032: import org.dbunit.dataset.stream.IDataSetProducer;
033: import org.xml.sax.*;
034: import org.xml.sax.ext.DeclHandler;
035: import org.xml.sax.ext.LexicalHandler;
036:
037: import javax.xml.parsers.ParserConfigurationException;
038: import javax.xml.parsers.SAXParser;
039: import javax.xml.parsers.SAXParserFactory;
040: import java.io.IOException;
041: import java.io.StringReader;
042: import java.util.*;
043:
044: /**
045: * @author Manuel Laflamme
046: * @since Apr 27, 2003
047: * @version $Revision: 559 $
048: */
049: public class FlatDtdProducer implements IDataSetProducer,
050: EntityResolver, DeclHandler, LexicalHandler {
051:
052: /**
053: * Logger for this class
054: */
055: private static final Logger logger = LoggerFactory
056: .getLogger(FlatDtdProducer.class);
057:
058: private static final IDataSetConsumer EMPTY_CONSUMER = new DefaultConsumer();
059:
060: private static final String XML_CONTENT = "<?xml version=\"1.0\"?>"
061: + "<!DOCTYPE dataset SYSTEM \"urn:/dummy.dtd\">"
062: + "<dataset/>";
063: private static final String DECL_HANDLER_PROPERTY_NAME = "http://xml.org/sax/properties/declaration-handler";
064: private static final String LEXICAL_HANDLER_PROPERTY_NAME = "http://xml.org/sax/properties/lexical-handler";
065:
066: private static final String REQUIRED = "#REQUIRED";
067: private static final String IMPLIED = "#IMPLIED";
068:
069: private InputSource _inputSource;
070: private IDataSetConsumer _consumer = EMPTY_CONSUMER;
071:
072: private String _rootName;
073: private String _rootModel;
074: private final Map _columnListMap = new HashMap();
075:
076: public FlatDtdProducer() {
077: }
078:
079: public FlatDtdProducer(InputSource inputSource) {
080: _inputSource = inputSource;
081: }
082:
083: public static void setDeclHandler(XMLReader xmlReader,
084: DeclHandler handler) throws SAXNotRecognizedException,
085: SAXNotSupportedException {
086: logger.debug("setDeclHandler(xmlReader=" + xmlReader
087: + ", handler=" + handler + ") - start");
088:
089: xmlReader.setProperty(DECL_HANDLER_PROPERTY_NAME, handler);
090: }
091:
092: public static void setLexicalHandler(XMLReader xmlReader,
093: LexicalHandler handler) throws SAXNotRecognizedException,
094: SAXNotSupportedException {
095: logger.debug("setLexicalHandler(xmlReader=" + xmlReader
096: + ", handler=" + handler + ") - start");
097:
098: xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY_NAME, handler);
099: }
100:
101: private List createColumnList() {
102: logger.debug("createColumnList() - start");
103:
104: return new LinkedList();
105: }
106:
107: ////////////////////////////////////////////////////////////////////////////
108: // IDataSetProducer interface
109:
110: public void setConsumer(IDataSetConsumer consumer)
111: throws DataSetException {
112: logger.debug("setConsumer(consumer) - start");
113:
114: _consumer = consumer;
115: }
116:
117: public void produce() throws DataSetException {
118: logger.debug("produce() - start");
119:
120: try {
121: SAXParser saxParser = SAXParserFactory.newInstance()
122: .newSAXParser();
123: XMLReader xmlReader = saxParser.getXMLReader();
124:
125: setDeclHandler(xmlReader, this );
126: setLexicalHandler(xmlReader, this );
127: xmlReader.setEntityResolver(this );
128: xmlReader.parse(new InputSource(new StringReader(
129: XML_CONTENT)));
130: } catch (ParserConfigurationException e) {
131: logger.error("produce()", e);
132:
133: throw new DataSetException(e);
134: } catch (SAXException e) {
135: logger.error("produce()", e);
136:
137: Exception exception = e.getException() == null ? e : e
138: .getException();
139: throw new DataSetException(exception);
140: } catch (IOException e) {
141: logger.error("produce()", e);
142:
143: throw new DataSetException(e);
144: }
145: }
146:
147: ////////////////////////////////////////////////////////////////////////////
148: // EntityResolver interface
149:
150: public InputSource resolveEntity(String publicId, String systemId)
151: throws SAXException {
152: logger.debug("resolveEntity(publicId=" + publicId
153: + ", systemId=" + systemId + ") - start");
154:
155: return _inputSource;
156: }
157:
158: ////////////////////////////////////////////////////////////////////////////
159: // DeclHandler interface
160:
161: public void elementDecl(String name, String model)
162: throws SAXException {
163: logger.debug("elementDecl(name=" + name + ", model=" + model
164: + ") - start");
165:
166: // Root element
167: if (name.equals(_rootName)) {
168: // The root model defines the table sequence. Keep it for later used!
169: _rootModel = model;
170: } else if (!_columnListMap.containsKey(name)) {
171: _columnListMap.put(name, createColumnList());
172: }
173: }
174:
175: public void attributeDecl(String elementName, String attributeName,
176: String type, String mode, String value) throws SAXException {
177: logger.debug("attributeDecl(elementName=" + elementName
178: + ", attributeName=" + attributeName + ", type=" + type
179: + ", mode=" + mode + ", value=" + value + ") - start");
180:
181: // Each element attribute represent a table column
182: Column.Nullable nullable = (REQUIRED.equals(mode)) ? Column.NO_NULLS
183: : Column.NULLABLE;
184: Column column = new Column(attributeName, DataType.UNKNOWN,
185: nullable);
186:
187: if (!_columnListMap.containsKey(elementName)) {
188: _columnListMap.put(elementName, createColumnList());
189: }
190: List columnList = (List) _columnListMap.get(elementName);
191: columnList.add(column);
192: }
193:
194: public void internalEntityDecl(String name, String value)
195: throws SAXException {
196: // Not used!
197: }
198:
199: public void externalEntityDecl(String name, String publicId,
200: String systemId) throws SAXException {
201: // Not used!
202: }
203:
204: ////////////////////////////////////////////////////////////////////////////
205: // LexicalHandler interface
206:
207: public void startDTD(String name, String publicId, String systemId)
208: throws SAXException {
209: logger.debug("startDTD(name=" + name + ", publicId=" + publicId
210: + ", systemId=" + systemId + ") - start");
211:
212: try {
213: _rootName = name;
214: _consumer.startDataSet();
215: } catch (DataSetException e) {
216: logger.error("startDTD()", e);
217:
218: throw new SAXException(e);
219: }
220: }
221:
222: public void endDTD() throws SAXException {
223: logger.debug("endDTD() - start");
224:
225: try {
226: if (_rootModel != null) {
227: // Remove enclosing model parenthesis
228: String rootModel = _rootModel.substring(1, _rootModel
229: .length() - 1);
230:
231: // Parse the root element model to determine the table sequence.
232: // Support all sequence or choices model but not the mix of both.
233: String delim = (rootModel.indexOf(",") != -1) ? ","
234: : "|";
235: StringTokenizer tokenizer = new StringTokenizer(
236: rootModel, delim);
237: while (tokenizer.hasMoreTokens()) {
238: String tableName = tokenizer.nextToken();
239:
240: // Prune ending occurrence operator
241: if (tableName.endsWith("*")
242: || tableName.endsWith("?")
243: || tableName.endsWith("+")) {
244: tableName = tableName.substring(0, tableName
245: .length() - 1);
246: }
247:
248: // Interestingly, sometimes a table comes in brackets, e.g. "(mytablename)"
249: // These must be removed because otherwise the list of columns associated with the table
250: // can not be retrieved from the map, i.e. the list of columns (columnList) is null.
251: // This leads to a NullPointerException in DB-Unit 2.2 when columnList is being accessed.
252: // Patch for NullPointerException starts here
253: if (tableName.startsWith("(")) {
254: tableName = tableName.substring(1, tableName
255: .length() - 1);
256: }
257:
258: if (tableName.endsWith(")")) {
259: tableName = tableName.substring(0, tableName
260: .length() - 1);
261: }
262: // Patch for NullPointerException ends here
263:
264: List columnList = (List) _columnListMap
265: .get(tableName);
266: Column[] columns = (Column[]) columnList
267: .toArray(new Column[0]);
268:
269: _consumer.startTable(new DefaultTableMetaData(
270: tableName, columns));
271: _consumer.endTable();
272: }
273: }
274:
275: _consumer.endDataSet();
276: } catch (DataSetException e) {
277: logger.error("endDTD()", e);
278:
279: throw new SAXException(e);
280: }
281: }
282:
283: public void startEntity(String name) throws SAXException {
284: // Not used!
285: }
286:
287: public void endEntity(String name) throws SAXException {
288: // Not used!
289: }
290:
291: public void startCDATA() throws SAXException {
292: // Not used!
293: }
294:
295: public void endCDATA() throws SAXException {
296: // Not used!
297: }
298:
299: public void comment(char ch[], int start, int length)
300: throws SAXException {
301: // Not used!
302: }
303: }
|