View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2004, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  package org.dbunit.dataset.xml;
22  
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.StringTokenizer;
31  
32  import javax.xml.parsers.ParserConfigurationException;
33  import javax.xml.parsers.SAXParser;
34  import javax.xml.parsers.SAXParserFactory;
35  
36  import org.dbunit.dataset.Column;
37  import org.dbunit.dataset.DataSetException;
38  import org.dbunit.dataset.DefaultTableMetaData;
39  import org.dbunit.dataset.datatype.DataType;
40  import org.dbunit.dataset.stream.DefaultConsumer;
41  import org.dbunit.dataset.stream.IDataSetConsumer;
42  import org.dbunit.dataset.stream.IDataSetProducer;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  import org.xml.sax.EntityResolver;
46  import org.xml.sax.InputSource;
47  import org.xml.sax.SAXException;
48  import org.xml.sax.SAXNotRecognizedException;
49  import org.xml.sax.SAXNotSupportedException;
50  import org.xml.sax.XMLReader;
51  import org.xml.sax.ext.DeclHandler;
52  import org.xml.sax.ext.LexicalHandler;
53  
54  /**
55   * Produces a DataSet from a flat DTD.
56   *
57   * Only external DTDs are supported and for the root element only the following
58   * declarations are supported.
59   * <ul>
60   *   <li>ANY: like &lt;!Element dataset ANY&gt;</li>
61   *   <li>sequences: like &lt;!Element dataset (first*,second,third?)gt;</li>
62   *   <li>choices: like &lt;!Element dataset (first|second+|third)&gt;</li>
63   * </ul>
64   * Combinations of sequences and choices are not support nor are #PCDATA or
65   * EMPTY declarations.
66   * 
67   * @author Manuel Laflamme
68   * @author Last changed by: $Author$
69   * @version $Revision$ $Date$
70   * @since Apr 27, 2003
71   */
72  public class FlatDtdProducer implements IDataSetProducer, EntityResolver, DeclHandler, LexicalHandler
73  {
74      /**
75       * Constant for the value {@value}
76       */
77      public static final String REQUIRED = "#REQUIRED";
78      
79      /**
80       * Constant for the value {@value}
81       */
82      public static final String IMPLIED = "#IMPLIED";
83  
84      /**
85       * Constant for the value {@value}
86       */
87      public static final String ANY = "ANY";
88  
89      /**
90       * Logger for this class
91       */
92      private static final Logger logger = LoggerFactory.getLogger(FlatDtdProducer.class);
93  
94      private static final IDataSetConsumer EMPTY_CONSUMER = new DefaultConsumer();
95  
96      private static final String XML_CONTENT =
97              "<?xml version=\"1.0\"?>" +
98              "<!DOCTYPE dataset SYSTEM \"urn:/dummy.dtd\">" +
99              "<dataset/>";
100     private static final String DECL_HANDLER_PROPERTY_NAME =
101             "http://xml.org/sax/properties/declaration-handler";
102     private static final String LEXICAL_HANDLER_PROPERTY_NAME =
103             "http://xml.org/sax/properties/lexical-handler";
104 
105     private InputSource _inputSource;
106     private IDataSetConsumer _consumer = EMPTY_CONSUMER;
107 
108     private String _rootName;
109     private String _rootModel;
110     private final Map _columnListMap = new HashMap();
111 
112     public FlatDtdProducer()
113     {
114     }
115 
116     public FlatDtdProducer(InputSource inputSource)
117     {
118         _inputSource = inputSource;
119     }
120 
121     public static void setDeclHandler(XMLReader xmlReader, DeclHandler handler)
122             throws SAXNotRecognizedException, SAXNotSupportedException
123     {
124         logger.debug("setDeclHandler(xmlReader={}, handler={}) - start", xmlReader, handler);
125         xmlReader.setProperty(DECL_HANDLER_PROPERTY_NAME, handler);
126     }
127 
128     public static void setLexicalHandler(XMLReader xmlReader, LexicalHandler handler)
129             throws SAXNotRecognizedException, SAXNotSupportedException
130     {
131         logger.debug("setLexicalHandler(xmlReader={}, handler={}) - start", xmlReader, handler);
132         xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY_NAME, handler);
133     }
134 
135     private List createColumnList()
136     {
137         return new LinkedList();
138     }
139 
140     ////////////////////////////////////////////////////////////////////////////
141     // IDataSetProducer interface
142 
143     public void setConsumer(IDataSetConsumer consumer) throws DataSetException
144     {
145         _consumer = consumer;
146     }
147 
148     public void produce() throws DataSetException
149     {
150         logger.debug("produce() - start");
151 
152         try
153         {
154             SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
155             XMLReader xmlReader = saxParser.getXMLReader();
156 
157             setDeclHandler(xmlReader, this);
158             setLexicalHandler(xmlReader, this);
159             xmlReader.setEntityResolver(this);
160             xmlReader.parse(new InputSource(new StringReader(XML_CONTENT)));
161         }
162         catch (ParserConfigurationException e)
163         {
164             throw new DataSetException(e);
165         }
166         catch (SAXException e)
167         {
168             Exception exception = e.getException() == null ? e : e.getException();
169             if(exception instanceof DataSetException)
170             {
171                 throw (DataSetException)exception;
172             }
173             else
174             {
175                 throw new DataSetException(exception);
176             }
177         }
178         catch (IOException e)
179         {
180             throw new DataSetException(e);
181         }
182     }
183 
184     ////////////////////////////////////////////////////////////////////////////
185     // EntityResolver interface
186 
187     public InputSource resolveEntity(String publicId, String systemId)
188             throws SAXException
189     {
190         return _inputSource;
191     }
192 
193     ////////////////////////////////////////////////////////////////////////////
194     // DeclHandler interface
195 
196     public void elementDecl(String name, String model) throws SAXException
197     {
198         logger.debug("elementDecl(name={}, model={}) - start", name, model);
199 
200         // Root element
201         if (name.equals(_rootName))
202         {
203             // The root model defines the table sequence. Keep it for later used!
204             _rootModel = model;
205         }
206         else if (!_columnListMap.containsKey(name))
207         {
208             _columnListMap.put(name, createColumnList());
209         }
210     }
211 
212     public void attributeDecl(String elementName, String attributeName,
213             String type, String mode, String value) throws SAXException
214     {
215     	if (logger.isDebugEnabled())
216     	{
217     		logger.debug("attributeDecl(elementName={}, attributeName={}, type={}, mode={}, value={}) - start",
218     				new Object[]{ elementName, attributeName, type, mode, value });
219     	}
220 
221         // Each element attribute represent a table column
222         Column.Nullable nullable = (REQUIRED.equals(mode)) ?
223                 Column.NO_NULLS : Column.NULLABLE;
224         Column column = new Column(attributeName, DataType.UNKNOWN, nullable);
225 
226         if (!_columnListMap.containsKey(elementName))
227         {
228             _columnListMap.put(elementName, createColumnList());
229         }
230         List columnList = (List)_columnListMap.get(elementName);
231         columnList.add(column);
232     }
233 
234     public void internalEntityDecl(String name, String value) throws SAXException
235     {
236         // Not used!
237     }
238 
239     public void externalEntityDecl(String name, String publicId,
240             String systemId) throws SAXException
241     {
242         // Not used!
243     }
244 
245     ////////////////////////////////////////////////////////////////////////////
246     // LexicalHandler interface
247 
248     public void startDTD(String name, String publicId, String systemId)
249             throws SAXException
250     {
251     	if (logger.isDebugEnabled())
252     	{
253     		logger.debug("startDTD(name={}, publicId={}, systemId={}) - start",
254     				new Object[]{ name, publicId, systemId });
255     	}
256 
257         try
258         {
259             _rootName = name;
260             _consumer.startDataSet();
261         }
262         catch (DataSetException e)
263         {
264             throw new SAXException(e);
265         }
266     }
267 
268     public void endDTD() throws SAXException
269     {
270         logger.debug("endDTD() - start");
271 
272         try
273         {
274             if(_rootModel == null)
275             {
276                 logger.info("The rootModel is null. Cannot add tables.");
277             }
278             else
279             {
280                 if (ANY.equalsIgnoreCase(_rootModel))
281                 {
282                     Iterator i = _columnListMap.keySet().iterator();
283                     while (i.hasNext()) {
284                         String tableName = (String) i.next();
285                         addTable(tableName);
286                     }
287                 }
288                 else {
289                     // Remove enclosing model parenthesis
290                     String rootModel = _rootModel.substring(1, _rootModel.length() - 1);
291     
292                     // Parse the root element model to determine the table sequence.
293                     // Support all sequence or choices model but not the mix of both.
294                     String delim = (rootModel.indexOf(",") != -1) ? "," : "|";
295                     StringTokenizer tokenizer = new StringTokenizer(rootModel, delim);
296                     while (tokenizer.hasMoreTokens()) {
297                         String tableName = tokenizer.nextToken();
298                         tableName = cleanupTableName(tableName);
299                         addTable(tableName);
300                     }
301                 }
302             }
303 
304             _consumer.endDataSet();
305         }
306         catch (DataSetException e)
307         {
308             throw new SAXException(e);
309         }
310     }
311     
312     private void addTable(String tableName) throws DataSetException
313     {
314         Column[] columns = getColumns(tableName);
315         _consumer.startTable(new DefaultTableMetaData(tableName, columns));
316         _consumer.endTable();
317     }
318 
319     private Column[] getColumns(String tableName) throws DataSetException 
320     {
321         List columnList = (List)_columnListMap.get(tableName);
322         if(columnList==null){
323             throw new DataSetException("ELEMENT/ATTRIBUTE declaration for '" + tableName + "' is missing. " +
324                     "Every table must have an element describing the table.");
325         }
326         Column[] columns = (Column[])columnList.toArray(new Column[0]);
327         return columns;
328     }
329 
330     protected String cleanupTableName(String tableName)
331     {
332         String cleaned = tableName;
333         // Remove beginning parenthesis.
334         while (cleaned.startsWith("(")) {
335             cleaned = cleaned.substring(1);
336         }
337         // Remove ending parenthesis and occurrence operators
338         while (cleaned.endsWith(")")
339                 || cleaned.endsWith("*")
340                 || cleaned.endsWith("?")
341                 || cleaned.endsWith("+")) {
342             cleaned = cleaned.substring(0, cleaned.length() - 1);
343         }
344         return cleaned;
345     }
346 
347     public void startEntity(String name) throws SAXException
348     {
349         // Not used!
350     }
351 
352     public void endEntity(String name) throws SAXException
353     {
354         // Not used!
355     }
356 
357     public void startCDATA() throws SAXException
358     {
359         // Not used!
360     }
361 
362     public void endCDATA() throws SAXException
363     {
364         // Not used!
365     }
366 
367     public void comment(char ch[], int start, int length) throws SAXException
368     {
369         // Not used!
370     }
371 }