org.vivoweb.harvester.fetch.JDBCFetch.java Source code

Java tutorial

Introduction

Here is the source code for org.vivoweb.harvester.fetch.JDBCFetch.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2011 VIVO Harvester Team. For full list of contributors, please see the AUTHORS file provided.
 * All rights reserved.
 * This program and the accompanying materials are made available under the terms of the new BSD license which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.html
 ******************************************************************************/
package org.vivoweb.harvester.fetch;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vivoweb.harvester.util.InitLog;
import org.vivoweb.harvester.util.SpecialEntities;
import org.vivoweb.harvester.util.args.ArgDef;
import org.vivoweb.harvester.util.args.ArgList;
import org.vivoweb.harvester.util.args.ArgParser;
import org.vivoweb.harvester.util.args.UsageException;
import org.vivoweb.harvester.util.repo.RecordHandler;

/**
 * Fetches rdf data from a JDBC database placing the data in the supplied record handler.
 * @author Christopher Haines (hainesc@ctrip.ufl.edu)
 */
public class JDBCFetch {
    /**
     * SLF4J Logger
     */
    private static Logger log = LoggerFactory.getLogger(JDBCFetch.class);
    /**
     * Record Handler to write records to
     */
    private RecordHandler rh;
    /**
     * Statement processor for the database
     */
    private Statement cursor;
    /**
     * Mapping of tablename to idField name
     */
    private Map<String, List<String>> idFields = null;
    /**
     * Mapping of tablename to mapping of fieldname to tablename
     */
    private Map<String, Map<String, String>> fkRelations = null;
    /**
     * Mapping of tablename to list of datafields
     */
    private Map<String, List<String>> dataFields = null;
    /**
     * Set of table names
     */
    private Set<String> tableNames = null;
    /**
     * List of conditions
     */
    private Map<String, List<String>> whereClauses;
    /**
     * Mapping of extra tables for the from section
     */
    private Map<String, String> fromClauses;
    /**
     * Namespace for RDF made from this database
     */
    private String uriNS;
    /**
     * Prefix each field in query with this
     */
    private String queryPre;
    /**
     * Suffix each field in query with this
     */
    private String querySuf;
    /**
     * The user defined SQL Query string
     */
    private Map<String, String> queryStrings;
    /**
     * accepted table types
     */
    private String[] tableTypes;

    /**
     * Constructor
     * @param dbConn connection to the database
     * @param output RecordHandler to write data to
     * @param uriNameSpace namespace base for rdf records
     * @throws IOException error talking with database
     */
    public JDBCFetch(Connection dbConn, RecordHandler output, String uriNameSpace) throws IOException {
        this(dbConn, output, uriNameSpace, null, null, null, null, null, null, null, null, null);
    }

    /**
     * Constructor
     * @param driverClass the jdbc driver
     * @param connLine the jdbc connection line
     * @param username the username
     * @param password the password
     * @param output RecordHandler to write data to
     * @param uriNameSpace namespace base for rdf records
     * @throws IOException error talking with database
     */
    public JDBCFetch(String driverClass, String connLine, String username, String password, RecordHandler output,
            String uriNameSpace) throws IOException {
        this(driverClass, connLine, username, password, output, uriNameSpace, null, null, null, null, null, null,
                null, null, null);
    }

    /**
     * Command line Constructor
     * @param args commandline arguments
     * @throws IOException error creating task
     * @throws UsageException user requested usage message
     */
    private JDBCFetch(String[] args) throws IOException, UsageException {
        this(getParser().parse(args));
    }

    /**
     * Arglist Constructor
     * @param args option set of parsed args
     * @throws IOException error creating task
     */
    private JDBCFetch(ArgList args) throws IOException {
        this(args.get("d"), args.get("c"), args.get("u"), args.get("p"),
                RecordHandler.parseConfig(args.get("o"), args.getValueMap("O")),
                (args.has("n") ? args.get("n") : (args.get("c") + "/")), args.get("delimiterPrefix"),
                args.get("delimiterSuffix"), new TreeSet<String>(args.getAll("t")),
                (args.has("T") ? args.getValueMap("T") : null),
                (args.has("F") ? splitCommaList(args.getValueMap("F")) : null),
                (args.has("I") ? splitCommaList(args.getValueMap("I")) : null),
                (args.has("W") ? splitCommaList(args.getValueMap("W")) : null),
                (args.has("R") ? splitTildeMap(args.getValueMap("R")) : null),
                (args.has("Q") ? args.getValueMap("Q") : null), args.getAll("v").toArray(new String[] {}));
    }

    /**
     * Library style Constructor
     * @param dbConn The database Connection
     * @param rh Record Handler to write records to
     * @param uriNS the uri namespace to use
     * @param queryPre Query prefix often "["
     * @param querySuf Query suffix often "]"
     * @param tableNames set of the table names
     * @param fromClauses Mapping of extra tables for the from section
     * @param dataFields Mapping of tablename to list of datafields
     * @param idFields Mapping of tablename to idField name
     * @param whereClauses List of conditions
     * @param relations Mapping of tablename to mapping of fieldname to tablename
     * @param queryStrings Mapping of tablename to query
     * @param tableTypes accepted table types
     * @throws IOException error accessing database
     */
    public JDBCFetch(Connection dbConn, RecordHandler rh, String uriNS, String queryPre, String querySuf,
            Set<String> tableNames, Map<String, String> fromClauses, Map<String, List<String>> dataFields,
            Map<String, List<String>> idFields, Map<String, List<String>> whereClauses,
            Map<String, Map<String, String>> relations, Map<String, String> queryStrings, String... tableTypes)
            throws IOException {
        try {
            this.cursor = dbConn.createStatement();
        } catch (SQLException e) {
            throw new IOException(e);
        }
        this.rh = rh;
        Set<String> argTables = tableNames;
        this.fromClauses = fromClauses;
        this.dataFields = dataFields;
        this.idFields = idFields;
        this.whereClauses = whereClauses;
        this.fkRelations = relations;
        this.uriNS = uriNS;
        this.queryPre = queryPre;
        this.querySuf = querySuf;
        this.queryStrings = queryStrings;
        if (tableTypes.length > 0 && tableTypes[0] != null) {
            this.tableTypes = tableTypes;
            for (int x = 0; x < this.tableTypes.length; x++) {
                this.tableTypes[x] = this.tableTypes[x].toUpperCase();
            }
        } else {
            this.tableTypes = new String[] { "TABLE" };
        }

        if (this.rh == null) {
            throw new IllegalArgumentException("Must provide output recordhandler!");
        }

        if (argTables == null) {
            argTables = new TreeSet<String>();
        }

        if (this.fromClauses != null) {
            argTables.addAll(this.fromClauses.keySet());
        }

        if (this.dataFields != null) {
            argTables.addAll(this.dataFields.keySet());
        }

        if (this.idFields != null) {
            argTables.addAll(this.idFields.keySet());
        }

        if (this.whereClauses != null) {
            argTables.addAll(this.whereClauses.keySet());
        }

        if (this.fkRelations != null) {
            argTables.addAll(this.fkRelations.keySet());
        }

        if (this.queryStrings != null) {
            // tablenames for queries are arbitrary
            argTables.removeAll(this.queryStrings.keySet());
        }

        this.tableNames = new TreeSet<String>();
        try {
            this.tableNames = getTableNames();
        } catch (SQLException e) {
            throw new IOException(e);
        }
        Set<String> realDBTables = this.tableNames;

        this.tableNames = new TreeSet<String>();
        //TODO: this is required as getTableNames loads data in and we want this to be a fresh start
        // there should be a nicer way to do all of this, as this is very hacky at the moment
        for (String argTable : argTables) {
            boolean found = false;
            for (String realTableName : realDBTables) {
                realTableName = realTableName.trim();
                if (argTable.trim().equalsIgnoreCase(realTableName)) {
                    this.tableNames.add(realTableName);
                    // fix the tablename in all the other structures >.>
                    if (this.fromClauses != null) {
                        Map<String, String> tempMap = new HashMap<String, String>();
                        for (String fromClausesTable : this.fromClauses.keySet()) {
                            if (fromClausesTable.trim().equalsIgnoreCase(realTableName)) {
                                tempMap.put(realTableName, this.fromClauses.get(fromClausesTable));
                            } else {
                                tempMap.put(fromClausesTable, this.fromClauses.get(fromClausesTable));
                            }
                        }
                        this.fromClauses = tempMap;
                    }
                    if (this.dataFields != null) {
                        Map<String, List<String>> tempMap = this.dataFields;
                        for (String dataFieldsTable : this.dataFields.keySet()) {
                            if (dataFieldsTable.trim().equalsIgnoreCase(realTableName)) {
                                tempMap.put(realTableName, this.dataFields.get(dataFieldsTable));
                            } else {
                                tempMap.put(dataFieldsTable, this.dataFields.get(dataFieldsTable));
                            }
                        }
                        this.dataFields = tempMap;
                    }
                    if (this.idFields != null) {
                        Map<String, List<String>> tempMap = this.idFields;
                        for (String idFieldsTable : this.idFields.keySet()) {
                            if (idFieldsTable.trim().equalsIgnoreCase(realTableName)) {
                                tempMap.put(realTableName, this.idFields.get(idFieldsTable));
                            } else {
                                tempMap.put(idFieldsTable, this.idFields.get(idFieldsTable));
                            }
                        }
                        this.idFields = tempMap;
                    }
                    if (this.whereClauses != null) {
                        Map<String, List<String>> tempMap = this.whereClauses;
                        for (String whereClausesTable : this.whereClauses.keySet()) {
                            if (whereClausesTable.trim().equalsIgnoreCase(realTableName)) {
                                tempMap.put(realTableName, this.whereClauses.get(whereClausesTable));
                            } else {
                                tempMap.put(whereClausesTable, this.whereClauses.get(whereClausesTable));
                            }
                        }
                        this.whereClauses = tempMap;
                    }
                    if (this.fkRelations != null) {
                        Map<String, Map<String, String>> tempMap = this.fkRelations;
                        for (String fkRelationsTable : this.fkRelations.keySet()) {
                            if (fkRelationsTable.trim().equalsIgnoreCase(realTableName)) {
                                tempMap.put(realTableName, this.fkRelations.get(fkRelationsTable));
                            } else {
                                tempMap.put(fkRelationsTable, this.fkRelations.get(fkRelationsTable));
                            }
                        }
                        this.fkRelations = tempMap;
                    }
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new IllegalArgumentException("Database Does Not Contain A Table Named '" + argTable + "'");
            }
        }

        if (this.queryStrings != null) {
            this.tableNames.addAll(this.queryStrings.keySet());
        }
    }

    /**
     * Library style Constructor
     * @param driverClass the jdbc driver
     * @param connLine the jdbc connection line
     * @param username the username
     * @param password the password
     * @param rh Record Handler to write records to
     * @param uriNS the uri namespace to use
     * @param queryPre Query prefix often "["
     * @param querySuf Query suffix often "]"
     * @param tableNames set of the table names
     * @param fromClauses Mapping of extra tables for the from section
     * @param dataFields Mapping of tablename to list of datafields
     * @param idFields Mapping of tablename to idField name
     * @param whereClauses List of conditions
     * @param relations Mapping of tablename to mapping of fieldname to tablename
     * @param queryStrings Mapping of tablename to query
     * @param tableTypes accepted table types
     * @throws IOException error accessing database
     */
    public JDBCFetch(String driverClass, String connLine, String username, String password, RecordHandler rh,
            String uriNS, String queryPre, String querySuf, Set<String> tableNames, Map<String, String> fromClauses,
            Map<String, List<String>> dataFields, Map<String, List<String>> idFields,
            Map<String, List<String>> whereClauses, Map<String, Map<String, String>> relations,
            Map<String, String> queryStrings, String... tableTypes) throws IOException {
        this(createConnection(driverClass, connLine, username, password), rh, uriNS, queryPre, querySuf, tableNames,
                fromClauses, dataFields, idFields, whereClauses, relations, queryStrings, tableTypes);
    }

    /**
     * Split the values of a comma separated list mapping
     * @param list the original mapping
     * @return the split mapping
     */
    private static Map<String, List<String>> splitCommaList(Map<String, String> list) {
        Map<String, List<String>> splitList = new HashMap<String, List<String>>();
        for (String tableName : list.keySet()) {
            splitList.put(tableName, Arrays.asList(list.get(tableName).split("\\s?,\\s?")));
        }
        return splitList;
    }

    /**
     * Split the values of comma separated ~ maps mapping
     * @param list the original mapping
     * @return the split mappings
     */
    private static Map<String, Map<String, String>> splitTildeMap(Map<String, String> list) {
        Map<String, List<String>> splitList = splitCommaList(list);
        Map<String, Map<String, String>> splitMaps = new HashMap<String, Map<String, String>>();
        for (String tableName : splitList.keySet()) {
            if (!splitMaps.containsKey(tableName)) {
                splitMaps.put(tableName, new HashMap<String, String>());
            }
            for (String relLine : splitList.get(tableName)) {
                String[] relPair = relLine.split("\\s?~\\s?", 2);
                if (relPair.length != 2) {
                    throw new IllegalArgumentException("Bad Relation Line: " + relLine);
                }
                splitMaps.get(tableName).put(relPair[0], relPair[1]);
            }
        }
        return splitMaps;
    }

    /**
     * Create a connection
     * @param driverClass the jdbc driver
     * @param connLine the jdbc connection line
     * @param username the username
     * @param password the password
     * @return the connection
     * @throws IOException error connecting or loading driver
     */
    private static Connection createConnection(String driverClass, String connLine, String username,
            String password) throws IOException {
        try {
            Class.forName(driverClass);
            return DriverManager.getConnection(connLine, username, password);
        } catch (SQLException e) {
            throw new IOException(e);
        } catch (ClassNotFoundException e) {
            throw new IOException(e);
        }
    }

    /**
     * Get the field prefix
     * @return the field prefix
     */
    private String getFieldPrefix() {
        if (this.queryPre == null) {
            this.queryPre = getParser().getOptMap().get("delimiterPrefix").getDefaultValue();
        }
        return this.queryPre;
    }

    /**
     * Set the field prefix
     * @param fieldPrefix the field prefix to use
     */
    public void setFieldPrefix(String fieldPrefix) {
        this.queryPre = fieldPrefix;
    }

    /**
     * Get the field suffix
     * @return the field suffix
     */
    private String getFieldSuffix() {
        if (this.querySuf == null) {
            this.querySuf = getParser().getOptMap().get("delimiterSuffix").getDefaultValue();
        }
        return this.querySuf;
    }

    /**
     * Set the field suffix
     * @param fieldSuffix the field suffix to use
     */
    public void setFieldSuffix(String fieldSuffix) {
        this.querySuf = fieldSuffix;
    }

    /**
     * Get the data field information for a table from the database
     * @param tableName the table to get the data field information for
     * @return the data field list
     * @throws SQLException error connecting to DB
     */
    private List<String> getDataFields(String tableName) throws SQLException {
        // TODO: the part after the OR looks like it should be on the next if statement, look into this
        if ((this.dataFields == null)
                || ((this.queryStrings != null) && this.queryStrings.containsKey(tableName))) {
            this.dataFields = new HashMap<String, List<String>>();
        }
        if (!this.dataFields.containsKey(tableName)) {
            log.debug("Finding data column names for table: " + tableName);
            this.dataFields.put(tableName, new LinkedList<String>());
            if ((this.queryStrings == null) || !this.queryStrings.containsKey(tableName)) {
                ResultSet columnData = this.cursor.getConnection().getMetaData()
                        .getColumns(this.cursor.getConnection().getCatalog(), null, tableName, "%");
                while (columnData.next()) {
                    String colName = columnData.getString("COLUMN_NAME");
                    log.trace("Found data column: " + colName);
                    if (!getFkRelationFields(tableName).containsKey(colName)) {
                        this.dataFields.get(tableName).add(colName);
                    }
                }
            }
        }
        return this.dataFields.get(tableName);
    }

    /**
     * Get the relation field information for a table from the database
     * @param tableName the table to get the relation field information for
     * @return the relation field mapping
     * @throws SQLException error connecting to DB
     */
    private Map<String, String> getFkRelationFields(String tableName) throws SQLException {
        // TODO: the part after the OR looks like it should be on the next if statement, look into this
        if (this.fkRelations == null) {
            this.fkRelations = new HashMap<String, Map<String, String>>();
        }
        if (!this.fkRelations.containsKey(tableName)) {
            this.fkRelations.put(tableName, new HashMap<String, String>());
        }
        if ((this.queryStrings == null || !this.queryStrings.containsKey(tableName))
                && this.fkRelations.get(tableName).isEmpty()) {
            log.debug("Finding relation column names for table: " + tableName);
            if ((this.queryStrings == null) || !this.queryStrings.containsKey(tableName)) {
                ResultSet foreignKeys = this.cursor.getConnection().getMetaData()
                        .getImportedKeys(this.cursor.getConnection().getCatalog(), null, tableName);
                while (foreignKeys.next()) {
                    String colName = foreignKeys.getString("FKCOLUMN_NAME");
                    String foreignTable = foreignKeys.getString("PKTABLE_NAME");
                    log.trace("Found relation column: " + colName);
                    log.trace("links to table: " + foreignTable);
                    this.fkRelations.get(tableName).put(colName, foreignTable);
                }
            }
        }
        return this.fkRelations.get(tableName);
    }

    /**
     * Get the where clauses for a table from the database
     * @param tableName the table to get the where clauses for
     * @return the where clauses
     */
    private List<String> getWhereClauses(String tableName) {
        if (this.whereClauses == null) {
            this.whereClauses = new HashMap<String, List<String>>();
        }
        if (!this.whereClauses.containsKey(tableName)) {
            this.whereClauses.put(tableName, new LinkedList<String>());
        }
        return this.whereClauses.get(tableName);
    }

    /**
     * Get the id field list for a table from the database
     * @param tableName the table to get the id field list for
     * @return the id field list
     * @throws SQLException error connecting to DB
     */
    private List<String> getIDFields(String tableName) throws SQLException {
        if (this.idFields == null) {
            this.idFields = new HashMap<String, List<String>>();
        }
        if (!this.idFields.containsKey(tableName)) {
            log.debug("Finding id column names for table: " + tableName);
            this.idFields.put(tableName, new LinkedList<String>());
            ResultSet primaryKeys = this.cursor.getConnection().getMetaData()
                    .getPrimaryKeys(this.cursor.getConnection().getCatalog(), null, tableName);
            while (primaryKeys.next()) {
                String colName = primaryKeys.getString("COLUMN_NAME");
                log.trace("Found id column: " + colName);
                this.idFields.get(tableName).add(colName);
            }
        }
        if (this.idFields.get(tableName).isEmpty()) {
            throw new IllegalArgumentException("ID fields for table '" + tableName
                    + "' were not provided and no primary keys are present... please provide an ID field set for this table");
        }
        return this.idFields.get(tableName);
    }

    /**
     * Gets the tablenames in database
     * @return set of tablenames
     * @throws SQLException error connecting to DB
     */
    private Set<String> getTableNames() throws SQLException {
        if (this.tableNames.isEmpty()) {
            ResultSet tableData = this.cursor.getConnection().getMetaData()
                    .getTables(this.cursor.getConnection().getCatalog(), null, "%", this.tableTypes);
            while (tableData.next()) {
                this.tableNames.add(tableData.getString("TABLE_NAME"));
            }
        }
        return this.tableNames;
    }

    /**
     * Builds a select statement against the table using configured fields
     * @param tableName the table to build the select statement for
     * @return the select statement
     * @throws SQLException error connecting to db
     */
    private String buildSelect(String tableName) throws SQLException {
        if ((this.queryStrings != null) && this.queryStrings.containsKey(tableName)) {
            String query = this.queryStrings.get(tableName);
            log.trace("User defined SQL Query:\n" + query);
            return query;
        }
        boolean multiTable = (this.fromClauses != null) && this.fromClauses.containsKey(tableName);
        StringBuilder sb = new StringBuilder();
        sb.append("SELECT ");
        for (String dataField : getDataFields(tableName)) {
            sb.append(getFieldPrefix());
            if (multiTable && (dataField.split("\\.").length <= 1)) {
                sb.append(tableName);
                sb.append(".");
            }
            sb.append(dataField);
            sb.append(getFieldSuffix());
            sb.append(", ");
        }
        for (String relField : getFkRelationFields(tableName).keySet()) {
            sb.append(getFieldPrefix());
            if (multiTable && (relField.split("\\.").length <= 1)) {
                sb.append(tableName);
                sb.append(".");
            }
            sb.append(relField);
            sb.append(getFieldSuffix());
            sb.append(", ");
        }
        for (String idField : getIDFields(tableName)) {
            sb.append(getFieldPrefix());
            if (multiTable && (idField.split("\\.").length <= 1)) {
                sb.append(tableName);
                sb.append(".");
            }
            sb.append(idField);
            sb.append(getFieldSuffix());
            sb.append(", ");
        }
        sb.delete(sb.lastIndexOf(", "), sb.length());
        sb.append(" FROM ");
        sb.append(tableName);
        if (multiTable) {
            sb.append(", ");
            sb.append(this.fromClauses.get(tableName));
        }

        if (getWhereClauses(tableName).size() > 0) {
            sb.append(" WHERE ");
            sb.append(StringUtils.join(getWhereClauses(tableName), " AND "));
        }
        log.trace("Generated SQL Query:\n" + sb.toString());
        return sb.toString();
    }

    /**
     * Builds a table's record namespace
     * @param tableName the table to build the namespace for
     * @return the namespace
     */
    private String buildTableRecordNS(String tableName) {
        return this.uriNS + tableName;
    }

    /**
     * Builds a table's field description namespace
     * @param tableName the table to build the namespace for
     * @return the namespace
     */
    private String buildTableFieldNS(String tableName) {
        return this.uriNS + "fields/" + tableName + "/";
    }

    /**
     * Builds a table's type description namespace
     * @param tableName the table to build the namespace for
     * @return the namespace
     */
    private String buildTableType(String tableName) {
        return this.uriNS + "types#" + tableName;
    }

    /**
     * Get the fields from a result set
     * @param rs the resultset
     * @return the list of fields
     * @throws SQLException error reading resultset
     */
    private List<String> getResultSetFields(ResultSet rs) throws SQLException {
        ResultSetMetaData rsmd = rs.getMetaData();
        int count = rsmd.getColumnCount();
        List<String> fields = new ArrayList<String>(count);
        for (int x = 1; x <= count; x++) {
            fields.add(rsmd.getColumnLabel(x));
        }
        return fields;
    }

    /**
     * Executes the task
     * @throws IOException error processing record handler or jdbc connection
     */
    public void execute() throws IOException {
        int count = 0;
        // For each Table
        try {
            for (String tableName : getTableNames()) {
                StringBuilder sb = new StringBuilder();
                // For each Record
                ResultSet rs = this.cursor.executeQuery(buildSelect(tableName));
                //            ResultSetMetaData rsmd = rs.getMetaData();
                //            int ColumnCount = rsmd.getColumnCount();
                //            Map<String,String> columnData = new HashMap<String,String>();
                while (rs.next()) {
                    StringBuilder recID = new StringBuilder();
                    recID.append("id");
                    for (String idField : getIDFields(tableName)) {
                        recID.append("_-_");
                        String id = rs.getString(idField);
                        if (id != null) {
                            id = id.trim();
                        }
                        id = SpecialEntities.xmlEncode(id);
                        recID.append(id);
                    }
                    // log.trace("Creating RDF for "+tableName+": "+recID);
                    // Build RDF BEGIN
                    // Header info
                    String tableNS = "db-" + tableName;
                    sb = new StringBuilder();
                    sb.append("<?xml version=\"1.0\"?>\n");
                    sb.append("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n");
                    sb.append("         xmlns:");
                    sb.append(tableNS);
                    sb.append("=\"");
                    sb.append(buildTableFieldNS(tableName));
                    sb.append("\"\n");
                    sb.append("         xml:base=\"");
                    sb.append(buildTableRecordNS(tableName));
                    sb.append("\">\n");

                    // Record info BEGIN
                    sb.append("  <rdf:Description rdf:ID=\"");
                    sb.append(recID);
                    sb.append("\">\n");

                    // insert type value
                    sb.append("    <rdf:type rdf:resource=\"");
                    sb.append(buildTableType(tableName));
                    sb.append("\"/>\n");

                    // DataFields
                    List<String> dataFieldList;
                    if ((this.queryStrings != null) && this.queryStrings.containsKey(tableName)) {
                        dataFieldList = getResultSetFields(rs);
                    } else {
                        dataFieldList = getDataFields(tableName);
                    }
                    for (String dataField : dataFieldList) {
                        // Field BEGIN
                        String field = tableNS + ":" + dataField.replaceAll(" ", "_");
                        sb.append("    <");
                        sb.append(SpecialEntities.xmlEncode(field));
                        sb.append(">");

                        // insert field value
                        if (rs.getString(dataField) != null) {
                            sb.append(SpecialEntities.xmlEncode(rs.getString(dataField).trim()));
                        }

                        // Field END
                        sb.append("</");
                        sb.append(SpecialEntities.xmlEncode(field));
                        sb.append(">\n");
                    }

                    // Relation Fields
                    for (String relationField : getFkRelationFields(tableName).keySet()) {
                        // Field BEGIN
                        String field = tableNS + ":" + relationField.replaceAll(" ", "_");
                        sb.append("    <");
                        sb.append(SpecialEntities.xmlEncode(field));
                        sb.append(" rdf:resource=\"");
                        sb.append(buildTableRecordNS(getFkRelationFields(tableName).get(relationField)));

                        // insert field value
                        sb.append("#id_-_" + rs.getString(relationField).trim());

                        // Field END
                        sb.append("\"/>\n");
                    }

                    // Record info END
                    sb.append("  </rdf:Description>\n");

                    // Footer info
                    sb.append("</rdf:RDF>");
                    // Build RDF END

                    // Write RDF to RecordHandler
                    log.trace("Adding record: " + tableName + "_" + recID);
                    this.rh.addRecord(tableName + "_" + recID, sb.toString(), this.getClass());
                    count++;
                }
            }
        } catch (SQLException e) {
            throw new IOException(e);
        }
        log.info("Added " + count + " Records");
    }

    /**
     * Get the ArgParser for this task
     * @return the ArgParser
     */
    private static ArgParser getParser() {
        ArgParser parser = new ArgParser("JDBCFetch");
        parser.addArgument(new ArgDef().setShortOption('d').setLongOpt("driver").withParameter(true, "JDBC_DRIVER")
                .setDescription("jdbc driver class").setRequired(true));
        parser.addArgument(new ArgDef().setShortOption('c').setLongOpt("connection")
                .withParameter(true, "JDBC_CONN").setDescription("jdbc connection string").setRequired(true));
        parser.addArgument(new ArgDef().setShortOption('u').setLongOpt("username").withParameter(true, "USERNAME")
                .setDescription("database username").setRequired(true));
        parser.addArgument(new ArgDef().setShortOption('p').setLongOpt("password").withParameter(true, "PASSWORD")
                .setDescription("database password").setRequired(true));
        parser.addArgument(new ArgDef().setShortOption('o').setLongOpt("output").withParameter(true, "CONFIG_FILE")
                .setDescription("RecordHandler config file path").setRequired(false));
        parser.addArgument(
                new ArgDef().setShortOption('t').setLongOpt("tableName").withParameters(true, "TABLE_NAME")
                        .setDescription("a single database table name [have multiple -t for more table names]")
                        .setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('v').setLongOpt("validTableType")
                .withParameters(true, "TABLE_TYPE")
                .setDescription("a single table type (TABLE, VIEW, etc) [have multiple -v for more table types]")
                .setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('Q').setLongOpt("query")
                .withParameterValueMap("TABLE_NAME", "SQL_QUERY")
                .setDescription("use SQL_QUERY to select from TABLE_NAME").setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('I').setLongOpt("id")
                .withParameterValueMap("TABLE_NAME", "ID_FIELD_LIST")
                .setDescription("use columns in ID_FIELD_LIST[comma separated] as identifier for TABLE_NAME")
                .setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('F').setLongOpt("fields")
                .withParameterValueMap("TABLE_NAME", "FIELD_LIST")
                .setDescription("fetch columns in FIELD_LIST[comma separated] for TABLE_NAME").setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('R').setLongOpt("relations")
                .withParameterValueMap("TABLE_NAME", "RELATION_PAIR_LIST")
                .setDescription("fetch columns in RELATION_PAIR_LIST[comma separated] for TABLE_NAME")
                .setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('W').setLongOpt("whereClause")
                .withParameterValueMap("TABLE_NAME", "CLAUSE_LIST")
                .setDescription("filter TABLE_NAME records based on conditions in CLAUSE_LIST[comma separated]")
                .setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('T').setLongOpt("tableFromClause")
                .withParameterValueMap("TABLE_NAME", "TABLE_LIST")
                .setDescription("add tables to use in from clauses for TABLE_NAME").setRequired(false));
        parser.addArgument(new ArgDef().setShortOption('O').setLongOpt("outputOverride")
                .withParameterValueMap("RH_PARAM", "VALUE")
                .setDescription("override the RH_PARAM of output recordhandler using VALUE").setRequired(false));
        parser.addArgument(
                new ArgDef().setShortOption('n').setLongOpt("namespaceBase").withParameter(true, "NAMESPACE_BASE")
                        .setDescription("the base namespace to use for each node created").setRequired(false));
        parser.addArgument(new ArgDef().setLongOpt("delimiterPrefix").withParameter(true, "DELIMITER")
                .setDescription("Prefix each field in the query with this character").setDefaultValue("")
                .setRequired(false));
        parser.addArgument(new ArgDef().setLongOpt("delimiterSuffix").withParameter(true, "DELIMITER")
                .setDescription("Suffix each field in the query with this character").setDefaultValue("")
                .setRequired(false));
        return parser;
    }

    /**
     * Main method
     * @param args commandline arguments
     */
    public static void main(String... args) {
        Exception error = null;
        try {
            InitLog.initLogger(args, getParser());
            log.info(getParser().getAppName() + ": Start");
            new JDBCFetch(args).execute();
        } catch (IllegalArgumentException e) {
            log.error(e.getMessage());
            log.debug("Stacktrace:", e);
            System.out.println(getParser().getUsage());
            error = e;
        } catch (UsageException e) {
            log.info("Printing Usage:");
            System.out.println(getParser().getUsage());
            error = e;
        } catch (Exception e) {
            log.error(e.getMessage());
            log.debug("Stacktrace:", e);
            error = e;
        } finally {
            log.info(getParser().getAppName() + ": End");
            if (error != null) {
                System.exit(1);
            }
        }
    }
}