public MetadataRecordFactory(Map<String, String> namespaces) {
   this.namespaces = namespaces;
   try {
     documentBuilder = XMLToolFactory.documentBuilderFactory().newDocumentBuilder();
   } catch (ParserConfigurationException e) {
     throw new RuntimeException("Parser config?", e);
   }
 }
/**
 * Create a hierarchical dump based on a profile that is initially generated by introspecting the
 * database and then adjusted to determine the contents of the dump.
 *
 * @author Gerald de Jong <*****@*****.**>
 */
public class TableExtractor {
  private static final String DUMP_ENVELOP_TAG = "delving-db-dump";
  private XMLOutputFactory outputFactory = XMLToolFactory.xmlOutputFactory();
  private XMLEventFactory event = XMLToolFactory.xmlEventFactory();
  private Stack<ResultSet> resultSets = new Stack<ResultSet>();
  private XMLEventWriter out;
  private Connection connection;
  private RelationalProfile profile;
  private int maxRows;

  public TableExtractor(Connection connection, RelationalProfile profile) {
    this.connection = connection;
    this.profile = profile;
  }

  public void setMaxRows(int maxRows) {
    this.maxRows = maxRows;
  }

  public void fillCaches() throws SQLException {
    for (RelationalProfile.Table table : profile.tables) {
      if (!table.cached) continue;
      table.cache = new HashMap<String, Map<String, String>>();
      Statement statement = connection.createStatement();
      ResultSet resultSet = statement.executeQuery(table.toQuery(null));
      while (resultSet.next()) {
        String key = null;
        Map<String, String> value = new TreeMap<String, String>();
        for (RelationalProfile.Column column : table.columns) {
          if (column.key) key = resultSet.getString(column.name);
          String fieldValue = getValue(column, resultSet);
          if (fieldValue != null) value.put(column.name, fieldValue);
        }
        if (key == null) throw new RuntimeException("Table missing key column: " + table.name);
        table.cache.put(key, value);
      }
      statement.close();
    }
  }

  public void dumpTo(OutputStream outputStream)
      throws FileNotFoundException, UnsupportedEncodingException, XMLStreamException, SQLException {
    out = outputFactory.createXMLEventWriter(new OutputStreamWriter(outputStream, "UTF-8"));
    startDocument();
    dumpTo(profile.rootTable(), 0);
    endDocument();
  }

  private void dumpTo(RelationalProfile.Table table, int indent)
      throws SQLException, XMLStreamException {
    String key =
        resultSets.isEmpty() ? null : resultSets.peek().getString(table.linkColumn.link.name);
    if (table.cached) {
      if (key == null) return;
      Map<String, String> result = table.cache.get(key);
      startWrapper(table.name, indent);
      for (RelationalProfile.Column column : table.columns) {
        putValue(column, result, indent + 1);
      }
      endWrapper(table.name, indent);
    } else {
      Statement statement = connection.createStatement();
      if (maxRows > 0) statement.setMaxRows(maxRows);
      String sql = table.toQuery(key);
      resultSets.push(statement.executeQuery(sql));
      int count = 0;
      while (resultSets.peek().next()) {
        if (resultSets.size() == 1) {
          System.out.println("=== Record " + (++count));
        }
        startWrapper(table.name, indent);
        for (RelationalProfile.Column column : table.columns) {
          putValue(column, resultSets.peek(), indent + 1);
        }
        for (RelationalProfile.Table childTable : profile.childTables(table)) {
          if (childTable.wrap != null) {
            startWrapper(childTable.wrap, indent + 1);
            dumpTo(childTable, indent + 2);
            endWrapper(table.name, indent + 1);
          } else {
            dumpTo(childTable, indent + 1);
          }
        }
        endWrapper(table.name, indent);
      }
      resultSets.pop();
      statement.close();
    }
  }

  private String getValue(RelationalProfile.Column column, ResultSet resultSet)
      throws SQLException {
    String value = resultSet.getString(column.name); // todo: improve this if necessary
    if (value == null) return null;
    return checkEmpty(column, value);
  }

  private String getValue(RelationalProfile.Column column, Map<String, String> result) {
    String value = result.get(column.name);
    if (value == null) return null;
    return checkEmpty(column, value);
  }

  private String checkEmpty(RelationalProfile.Column column, String value) {
    value = value.trim();
    return column.isEmpty(value) ? null : value;
  }

  private void putValue(RelationalProfile.Column column, Map<String, String> result, int indent)
      throws XMLStreamException, SQLException {
    String value = getValue(column, result);
    if (value == null) return;
    while (indent-- > 0) out.add(event.createCharacters("   "));
    out.add(event.createStartElement("", "", column.name, null, null));
    out.add(event.createCharacters(value));
    out.add(event.createEndElement("", "", column.name));
    out.add(event.createCharacters("\n"));
  }

  private void putValue(RelationalProfile.Column column, ResultSet resultSet, int indent)
      throws XMLStreamException, SQLException {
    String value = getValue(column, resultSet);
    if (value == null) return;
    while (indent-- > 0) out.add(event.createCharacters("   "));
    out.add(event.createStartElement("", "", column.name, null, null));
    out.add(event.createCharacters(value));
    out.add(event.createEndElement("", "", column.name));
    out.add(event.createCharacters("\n"));
  }

  private void startWrapper(String tag, int indent) throws XMLStreamException {
    while (indent-- > 0) out.add(event.createCharacters("   "));
    out.add(event.createStartElement("", "", tag, null, null));
    out.add(event.createCharacters("\n"));
  }

  private void endWrapper(String tag, int indent) throws XMLStreamException {
    while (indent-- > 0) out.add(event.createCharacters("   "));
    out.add(event.createEndElement("", "", tag));
    out.add(event.createCharacters("\n"));
  }

  private void startDocument() throws XMLStreamException {
    out.add(event.createStartDocument());
    out.add(event.createCharacters("\n"));
    out.add(event.createStartElement("", "", DUMP_ENVELOP_TAG, null, null));
    out.add(event.createCharacters("\n"));
  }

  private void endDocument() throws XMLStreamException {
    out.add(event.createEndElement("", "", DUMP_ENVELOP_TAG));
    out.add(event.createCharacters("\n"));
    out.add(event.createEndDocument());
  }
}
/**
 * When the MetadataRecord instances are not coming from the parse of an input file using the
 * MetadataParser, they can be produced one by one using the metadataRecordFrom method, which first
 * cleverly wraps the record and then parses it.
 */
public class MetadataRecordFactory {
  private XMLInputFactory inputFactory = XMLToolFactory.xmlInputFactory();
  private DocumentBuilder documentBuilder;
  private Map<String, String> namespaces;

  public MetadataRecordFactory(Map<String, String> namespaces) {
    this.namespaces = namespaces;
    try {
      documentBuilder = XMLToolFactory.documentBuilderFactory().newDocumentBuilder();
    } catch (ParserConfigurationException e) {
      throw new RuntimeException("Parser config?", e);
    }
  }

  public MetadataRecord fromGroovyNode(GroovyNode rootNode, int recordNumber, int recordCount) {
    return MetadataRecord.create(rootNode, recordNumber, recordCount);
  }

  public MetadataRecord metadataRecordFrom(String recordContents) throws XMLStreamException {
    try {
      Reader reader = new StringReader(recordContents);
      XMLStreamReader2 input = (XMLStreamReader2) inputFactory.createXMLStreamReader(reader);
      GroovyNode rootNode = null, node = null;
      StringBuilder value = new StringBuilder();
      while (true) {
        switch (input.getEventType()) {
          case XMLEvent.START_DOCUMENT:
            break;
          case XMLEvent.START_ELEMENT:
            node =
                new GroovyNode(
                    node, input.getNamespaceURI(), input.getLocalName(), input.getPrefix());
            if (rootNode == null) {
              rootNode = node;
            }
            if (input.getAttributeCount() > 0) {
              for (int walk = 0; walk < input.getAttributeCount(); walk++) {
                QName attributeName = input.getAttributeName(walk);
                if (attributeName.getPrefix() == null || attributeName.getPrefix().isEmpty()) {
                  node.attributes()
                      .put(attributeName.getLocalPart(), input.getAttributeValue(walk));
                } else {
                  node.attributes()
                      .put(
                          String.format(
                              "%s:%s", attributeName.getPrefix(), attributeName.getLocalPart()),
                          input.getAttributeValue(walk));
                }
              }
            }
            value.setLength(0);
            break;
          case XMLEvent.CHARACTERS:
            value.append(input.getText());
            break;
          case XMLEvent.CDATA:
            value.append(String.format("<![CDATA[%s]]>", input.getText()));
            break;
          case XMLEvent.END_ELEMENT:
            if (node == null) throw new RuntimeException("Node cannot be null");
            String valueString = value.toString().trim();
            value.setLength(0);
            if (valueString.length() > 0) node.setNodeValue(valueString);
            node = node.parent();
            break;
          case XMLEvent.END_DOCUMENT:
            {
              break;
            }
        }
        if (!input.hasNext()) {
          break;
        }
        input.next();
      }
      return MetadataRecord.create(rootNode, -1, -1);
    } catch (WstxParsingException e) {
      throw new XMLStreamException("Problem parsing record:\n" + recordContents, e);
    }
  }
}