FlatDtdProducer.java

  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. import java.io.IOException;
  23. import java.io.StringReader;
  24. import java.util.HashMap;
  25. import java.util.Iterator;
  26. import java.util.LinkedList;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.StringTokenizer;

  30. import javax.xml.parsers.ParserConfigurationException;
  31. import javax.xml.parsers.SAXParser;
  32. import javax.xml.parsers.SAXParserFactory;

  33. import org.dbunit.dataset.Column;
  34. import org.dbunit.dataset.DataSetException;
  35. import org.dbunit.dataset.DefaultTableMetaData;
  36. import org.dbunit.dataset.datatype.DataType;
  37. import org.dbunit.dataset.stream.DefaultConsumer;
  38. import org.dbunit.dataset.stream.IDataSetConsumer;
  39. import org.dbunit.dataset.stream.IDataSetProducer;
  40. import org.slf4j.Logger;
  41. import org.slf4j.LoggerFactory;
  42. import org.xml.sax.EntityResolver;
  43. import org.xml.sax.InputSource;
  44. import org.xml.sax.SAXException;
  45. import org.xml.sax.SAXNotRecognizedException;
  46. import org.xml.sax.SAXNotSupportedException;
  47. import org.xml.sax.XMLReader;
  48. import org.xml.sax.ext.DeclHandler;
  49. import org.xml.sax.ext.LexicalHandler;

  50. /**
  51.  * Produces a DataSet from a flat DTD.
  52.  *
  53.  * Only external DTDs are supported and for the root element only the following
  54.  * declarations are supported.
  55.  * <ul>
  56.  *   <li>ANY: like &lt;!Element dataset ANY&gt;</li>
  57.  *   <li>sequences: like &lt;!Element dataset (first*,second,third?)gt;</li>
  58.  *   <li>choices: like &lt;!Element dataset (first|second+|third)&gt;</li>
  59.  * </ul>
  60.  * Combinations of sequences and choices are not support nor are #PCDATA or
  61.  * EMPTY declarations.
  62.  *
  63.  * @author Manuel Laflamme
  64.  * @author Last changed by: $Author$
  65.  * @version $Revision$ $Date$
  66.  * @since Apr 27, 2003
  67.  */
  68. public class FlatDtdProducer implements IDataSetProducer, EntityResolver, DeclHandler, LexicalHandler
  69. {
  70.     /**
  71.      * Constant for the value {@value}
  72.      */
  73.     public static final String REQUIRED = "#REQUIRED";

  74.     /**
  75.      * Constant for the value {@value}
  76.      */
  77.     public static final String IMPLIED = "#IMPLIED";

  78.     /**
  79.      * Constant for the value {@value}
  80.      */
  81.     public static final String ANY = "ANY";

  82.     /**
  83.      * Logger for this class
  84.      */
  85.     private static final Logger logger = LoggerFactory.getLogger(FlatDtdProducer.class);

  86.     private static final IDataSetConsumer EMPTY_CONSUMER = new DefaultConsumer();

  87.     private static final String XML_CONTENT =
  88.             "<?xml version=\"1.0\"?>" +
  89.                     "<!DOCTYPE dataset SYSTEM \"urn:/dummy.dtd\">" +
  90.                     "<dataset/>";
  91.     private static final String DECL_HANDLER_PROPERTY_NAME =
  92.             "http://xml.org/sax/properties/declaration-handler";
  93.     private static final String LEXICAL_HANDLER_PROPERTY_NAME =
  94.             "http://xml.org/sax/properties/lexical-handler";

  95.     private InputSource _inputSource;
  96.     private IDataSetConsumer _consumer = EMPTY_CONSUMER;

  97.     private String _rootName;
  98.     private String _rootModel;
  99.     private final Map _columnListMap = new HashMap();

  100.     public FlatDtdProducer()
  101.     {
  102.     }

  103.     public FlatDtdProducer(final InputSource inputSource)
  104.     {
  105.         _inputSource = inputSource;
  106.     }

  107.     public static void setDeclHandler(final XMLReader xmlReader, final DeclHandler handler)
  108.             throws SAXNotRecognizedException, SAXNotSupportedException
  109.     {
  110.         logger.debug("setDeclHandler(xmlReader={}, handler={}) - start", xmlReader, handler);
  111.         xmlReader.setProperty(DECL_HANDLER_PROPERTY_NAME, handler);
  112.     }

  113.     public static void setLexicalHandler(final XMLReader xmlReader, final LexicalHandler handler)
  114.             throws SAXNotRecognizedException, SAXNotSupportedException
  115.     {
  116.         logger.debug("setLexicalHandler(xmlReader={}, handler={}) - start", xmlReader, handler);
  117.         xmlReader.setProperty(LEXICAL_HANDLER_PROPERTY_NAME, handler);
  118.     }

  119.     private List createColumnList()
  120.     {
  121.         return new LinkedList();
  122.     }

  123.     ////////////////////////////////////////////////////////////////////////////
  124.     // IDataSetProducer interface

  125.     @Override
  126.     public void setConsumer(final IDataSetConsumer consumer) throws DataSetException
  127.     {
  128.         _consumer = consumer;
  129.     }

  130.     @Override
  131.     public void produce() throws DataSetException
  132.     {
  133.         logger.debug("produce() - start");

  134.         try
  135.         {

  136.             final SAXParser saxParser = SAXParserFactory.newInstance("com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl", null).newSAXParser();
  137.             final XMLReader xmlReader = saxParser.getXMLReader();

  138.             setDeclHandler(xmlReader, this);
  139.             setLexicalHandler(xmlReader, this);
  140.             xmlReader.setEntityResolver(this);
  141.             xmlReader.parse(new InputSource(new StringReader(XML_CONTENT)));
  142.         }
  143.         catch (final ParserConfigurationException e)
  144.         {
  145.             throw new DataSetException(e);
  146.         }
  147.         catch (final SAXException e)
  148.         {
  149.             final Exception exception = e.getException() == null ? e : e.getException();
  150.             if(exception instanceof DataSetException)
  151.             {
  152.                 throw (DataSetException)exception;
  153.             }
  154.             else
  155.             {
  156.                 throw new DataSetException(exception);
  157.             }
  158.         }
  159.         catch (final IOException e)
  160.         {
  161.             throw new DataSetException(e);
  162.         }
  163.     }

  164.     ////////////////////////////////////////////////////////////////////////////
  165.     // EntityResolver interface

  166.     @Override
  167.     public InputSource resolveEntity(final String publicId, final String systemId)
  168.             throws SAXException
  169.     {
  170.         return _inputSource;
  171.     }

  172.     ////////////////////////////////////////////////////////////////////////////
  173.     // DeclHandler interface

  174.     @Override
  175.     public void elementDecl(final String name, final String model) throws SAXException
  176.     {
  177.         logger.debug("elementDecl(name={}, model={}) - start", name, model);

  178.         // Root element
  179.         if (name.equals(_rootName))
  180.         {
  181.             // The root model defines the table sequence. Keep it for later used!
  182.             _rootModel = model;
  183.         }
  184.         else if (!_columnListMap.containsKey(name))
  185.         {
  186.             _columnListMap.put(name, createColumnList());
  187.         }
  188.     }

  189.     @Override
  190.     public void attributeDecl(final String elementName, final String attributeName,
  191.             final String type, final String mode, final String value) throws SAXException
  192.     {
  193.         if (logger.isDebugEnabled())
  194.         {
  195.             logger.debug("attributeDecl(elementName={}, attributeName={}, type={}, mode={}, value={}) - start",
  196.                     new Object[]{ elementName, attributeName, type, mode, value });
  197.         }

  198.         // Each element attribute represent a table column
  199.         final Column.Nullable nullable = (REQUIRED.equals(mode)) ?
  200.                 Column.NO_NULLS : Column.NULLABLE;
  201.         final Column column = new Column(attributeName, DataType.UNKNOWN, nullable);

  202.         if (!_columnListMap.containsKey(elementName))
  203.         {
  204.             _columnListMap.put(elementName, createColumnList());
  205.         }
  206.         final List columnList = (List)_columnListMap.get(elementName);
  207.         columnList.add(column);
  208.     }

  209.     @Override
  210.     public void internalEntityDecl(final String name, final String value) throws SAXException
  211.     {
  212.         // Not used!
  213.     }

  214.     @Override
  215.     public void externalEntityDecl(final String name, final String publicId,
  216.             final String systemId) throws SAXException
  217.     {
  218.         // Not used!
  219.     }

  220.     ////////////////////////////////////////////////////////////////////////////
  221.     // LexicalHandler interface

  222.     @Override
  223.     public void startDTD(final String name, final String publicId, final String systemId)
  224.             throws SAXException
  225.     {
  226.         if (logger.isDebugEnabled())
  227.         {
  228.             logger.debug("startDTD(name={}, publicId={}, systemId={}) - start",
  229.                     new Object[]{ name, publicId, systemId });
  230.         }

  231.         try
  232.         {
  233.             _rootName = name;
  234.             _consumer.startDataSet();
  235.         }
  236.         catch (final DataSetException e)
  237.         {
  238.             throw new SAXException(e);
  239.         }
  240.     }

  241.     @Override
  242.     public void endDTD() throws SAXException
  243.     {
  244.         logger.debug("endDTD() - start");

  245.         try
  246.         {
  247.             if(_rootModel == null)
  248.             {
  249.                 logger.info("The rootModel is null. Cannot add tables.");
  250.             }
  251.             else
  252.             {
  253.                 if (ANY.equalsIgnoreCase(_rootModel))
  254.                 {
  255.                     final Iterator i = _columnListMap.keySet().iterator();
  256.                     while (i.hasNext()) {
  257.                         final String tableName = (String) i.next();
  258.                         addTable(tableName);
  259.                     }
  260.                 }
  261.                 else {
  262.                     // Remove enclosing model parenthesis
  263.                     final String rootModel = _rootModel.substring(1, _rootModel.length() - 1);

  264.                     // Parse the root element model to determine the table sequence.
  265.                     // Support all sequence or choices model but not the mix of both.
  266.                     final String delim = (rootModel.indexOf(",") != -1) ? "," : "|";
  267.                     final StringTokenizer tokenizer = new StringTokenizer(rootModel, delim);
  268.                     while (tokenizer.hasMoreTokens()) {
  269.                         String tableName = tokenizer.nextToken();
  270.                         tableName = cleanupTableName(tableName);
  271.                         addTable(tableName);
  272.                     }
  273.                 }
  274.             }

  275.             _consumer.endDataSet();
  276.         }
  277.         catch (final DataSetException e)
  278.         {
  279.             throw new SAXException(e);
  280.         }
  281.     }

  282.     private void addTable(final String tableName) throws DataSetException
  283.     {
  284.         final Column[] columns = getColumns(tableName);
  285.         _consumer.startTable(new DefaultTableMetaData(tableName, columns));
  286.         _consumer.endTable();
  287.     }

  288.     private Column[] getColumns(final String tableName) throws DataSetException
  289.     {
  290.         final List columnList = (List)_columnListMap.get(tableName);
  291.         if(columnList==null){
  292.             throw new DataSetException("ELEMENT/ATTRIBUTE declaration for '" + tableName + "' is missing. " +
  293.                     "Every table must have an element describing the table.");
  294.         }
  295.         final Column[] columns = (Column[])columnList.toArray(new Column[0]);
  296.         return columns;
  297.     }

  298.     protected String cleanupTableName(final String tableName)
  299.     {
  300.         String cleaned = tableName;
  301.         // Remove beginning parenthesis.
  302.         while (cleaned.startsWith("(")) {
  303.             cleaned = cleaned.substring(1);
  304.         }
  305.         // Remove ending parenthesis and occurrence operators
  306.         while (cleaned.endsWith(")")
  307.                 || cleaned.endsWith("*")
  308.                 || cleaned.endsWith("?")
  309.                 || cleaned.endsWith("+")) {
  310.             cleaned = cleaned.substring(0, cleaned.length() - 1);
  311.         }
  312.         return cleaned;
  313.     }

  314.     @Override
  315.     public void startEntity(final String name) throws SAXException
  316.     {
  317.         // Not used!
  318.     }

  319.     @Override
  320.     public void endEntity(final String name) throws SAXException
  321.     {
  322.         // Not used!
  323.     }

  324.     @Override
  325.     public void startCDATA() throws SAXException
  326.     {
  327.         // Not used!
  328.     }

  329.     @Override
  330.     public void endCDATA() throws SAXException
  331.     {
  332.         // Not used!
  333.     }

  334.     @Override
  335.     public void comment(final char ch[], final int start, final int length) throws SAXException
  336.     {
  337.         // Not used!
  338.     }
  339. }