com.flexive.core.storage.GenericDivisionImporter.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.core.storage.GenericDivisionImporter.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.core.storage;

import com.flexive.core.Database;
import com.flexive.core.DatabaseConst;
import com.flexive.core.flatstorage.FxFlatStorage;
import com.flexive.core.flatstorage.FxFlatStorageInfo;
import com.flexive.core.flatstorage.FxFlatStorageManager;
import com.flexive.core.storage.binary.FxBinaryUtils;
import com.flexive.shared.*;
import com.flexive.shared.configuration.SystemParameters;
import com.flexive.shared.exceptions.FxApplicationException;
import com.flexive.shared.exceptions.FxDbException;
import com.flexive.shared.exceptions.FxInvalidParameterException;
import com.flexive.shared.exceptions.FxNotFoundException;
import com.flexive.shared.impex.FxDivisionExportInfo;
import com.flexive.shared.impex.FxImportExportConstants;
import com.flexive.shared.structure.FxDataType;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.*;
import java.text.ParseException;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Division Importer
 *
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
public class GenericDivisionImporter implements FxImportExportConstants {
    /*
        
    //test script for flatstorage as hierarchical:
        
    // Default [fleXive] imports for Groovy
    import com.flexive.shared.*
    import com.flexive.shared.interfaces.*
    import com.flexive.shared.value.*
    import com.flexive.shared.content.*
    import com.flexive.shared.search.*
    import com.flexive.shared.search.query.*
    import com.flexive.shared.tree.*
    import com.flexive.shared.workflow.*
    import com.flexive.shared.media.*
    import com.flexive.shared.scripting.groovy.*
    import com.flexive.shared.structure.*
    import com.flexive.shared.security.*
    import com.flexive.shared.impex.*
    import com.flexive.core.*
    import com.flexive.core.storage.*
    import java.util.zip.*
        
    File data = new File("/home/mplesser/impex/export_pg.zip")
    ZipFile zip = new ZipFile(data)
    java.sql.Connection con = Database.getDbConnection()
    try {
      FxDivisionExportInfo ei = GenericDivisionImporter.getInstance().getDivisionExportInfo(zip)
      GenericDivisionImporter.getInstance().importFlatStoragesHierarchical(con, zip, ei)
    } finally {
      con.close()
    }
     */
    private static final Log LOG = LogFactory.getLog(GenericDivisionImporter.class);

    @SuppressWarnings({ "FieldCanBeLocal" })
    private static GenericDivisionImporter INSTANCE = new GenericDivisionImporter();

    private static boolean DBG = false;

    /**
     * Getter for the importer singleton
     *
     * @return exporter
     */
    public static GenericDivisionImporter getInstance() {
        return INSTANCE;
    }

    /**
     * Does importing a division require a non-transactional connection?
     *
     * @return need non-TX Connection or use "regular"?
     */
    public boolean importRequiresNonTXConnection() {
        return false;
    }

    /**
     * Get a file from the zip archive
     *
     * @param zip  zip archive containing the file
     * @param file name of the file
     * @return ZipEntry
     * @throws FxNotFoundException if the archive does not contain the file
     */
    protected ZipEntry getZipEntry(ZipFile zip, String file) throws FxNotFoundException {
        ZipEntry ze = zip.getEntry(file);
        if (ze == null)
            throw new FxNotFoundException("ex.import.missingFile", file, zip.getName());
        return ze;
    }

    /**
     * Get division export information from an exported archive
     *
     * @param zip zip file containing the export
     * @return FxDivisionExportInfo
     * @throws FxApplicationException on errors
     */
    public FxDivisionExportInfo getDivisionExportInfo(ZipFile zip) throws FxApplicationException {
        ZipEntry ze = getZipEntry(zip, FILE_BUILD_INFOS);
        FxDivisionExportInfo exportInfo;
        try {
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = builder.parse(zip.getInputStream(ze));

            XPath xPath = XPathFactory.newInstance().newXPath();

            String[] drops;
            String dropsRaw = xPath.evaluate("/flexive/drops", document);
            if (dropsRaw == null || !dropsRaw.startsWith("["))
                drops = new String[0];
            else {
                dropsRaw = dropsRaw.substring(1, dropsRaw.length() - 1);
                drops = dropsRaw.split(", ");
            }
            exportInfo = new FxDivisionExportInfo(Integer.parseInt(xPath.evaluate("/flexive/division", document)),
                    Integer.parseInt(xPath.evaluate("/flexive/schema", document)),
                    Integer.parseInt(xPath.evaluate("/flexive/build", document)),
                    xPath.evaluate("/flexive/verbose", document), xPath.evaluate("/flexive/appserver", document),
                    xPath.evaluate("/flexive/database", document), xPath.evaluate("/flexive/dbdriver", document),
                    xPath.evaluate("/flexive/domain", document), Arrays.asList(drops),
                    xPath.evaluate("/flexive/user", document),
                    FxFormatUtils.getDateTimeFormat().parse(xPath.evaluate("/flexive/date", document)));
        } catch (Exception e) {
            throw new FxApplicationException(e, "ex.import.parseInfoFailed", e.getMessage());
        }
        return exportInfo;
    }

    /**
     * Wipe all tables of a division
     *
     * @param con an open and valid connection for the division to wipe
     * @throws Exception on errors
     */
    public void wipeDivisionData(Connection con) throws Exception {
        Statement stmt = con.createStatement();
        dropTableData(stmt, DatabaseConst.TBL_CONTENT_DATA_FT);
        dropTableData(stmt, DatabaseConst.TBL_CONTENT_DATA);
        dropTableData(stmt, DatabaseConst.TBL_CONTENT_ACLS);
        dropTableData(stmt, DatabaseConst.TBL_BINARY_TRANSIT);
        dropTableData(stmt, DatabaseConst.TBL_SEARCHCACHE_PERM);
        dropTableData(stmt, DatabaseConst.TBL_SEARCHCACHE_MEMORY);
        dropTableData(stmt, DatabaseConst.TBL_BRIEFCASE_DATA_ITEM);
        dropTableData(stmt, DatabaseConst.TBL_BRIEFCASE_DATA);
        dropTableData(stmt, DatabaseConst.TBL_BRIEFCASE);
        setFieldNull(stmt, DatabaseConst.TBL_TREE + "_LIVE", "PARENT");
        dropTableData(stmt, DatabaseConst.TBL_TREE + "_LIVE");
        setFieldNull(stmt, DatabaseConst.TBL_TREE, "PARENT");
        dropTableData(stmt, DatabaseConst.TBL_TREE);
        boolean hasFlatStorages = false;
        for (FxFlatStorageInfo flat : FxFlatStorageManager.getInstance().getFlatStorageInfos()) {
            hasFlatStorages = true;
            dropTableData(stmt, flat.getName());
            dropTable(stmt, flat.getName());
        }
        if (hasFlatStorages) {
            dropTableData(stmt, DatabaseConst.TBL_STRUCT_FLATSTORE_INFO);
            dropTableData(stmt, DatabaseConst.TBL_STRUCT_FLATSTORE_MAPPING);
        }
        dropTableData(stmt, DatabaseConst.TBL_RESOURCES);
        dropTableData(stmt, DatabaseConst.TBL_PHRASE_MAP);
        dropTableData(stmt, DatabaseConst.TBL_PHRASE_TREE);
        dropTableData(stmt, DatabaseConst.TBL_PHRASE_VALUES);
        dropTableData(stmt, DatabaseConst.TBL_PHRASE);
        dropTableData(stmt, DatabaseConst.TBL_HISTORY);
        dropTableData(stmt, DatabaseConst.TBL_ACCOUNT_DETAILS);
        dropTableData(stmt, DatabaseConst.TBL_LOCKS);
        setFieldNull(stmt, DatabaseConst.TBL_STRUCT_TYPES, "ICON_REF");
        dropTableData(stmt, DatabaseConst.TBL_CONTENT);

        dropTableData(stmt, DatabaseConst.TBL_ACLS + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_ASSIGNMENTS + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_SELECTLIST_ITEM + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_SELECTLIST + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_TYPES + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_GROUPS + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_PROPERTIES + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_WORKFLOW_STEPDEFINITION + DatabaseConst.ML);

        dropTableData(stmt, DatabaseConst.TBL_SCRIPT_MAPPING_TYPES);
        dropTableData(stmt, DatabaseConst.TBL_SCRIPT_MAPPING_ASSIGN);
        dropTableData(stmt, DatabaseConst.TBL_ROLE_MAPPING);

        dropTableData(stmt, DatabaseConst.TBL_STRUCT_GROUP_OPTIONS);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_PROPERTY_OPTIONS);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_TYPES_OPTIONS);

        stmt.execute(StorageManager.getStorageImpl().getReferentialIntegrityChecksStatement(false));
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_ASSIGNMENTS);
        stmt.execute(StorageManager.getStorageImpl().getReferentialIntegrityChecksStatement(true));
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_PROPERTIES);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_GROUPS);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_TYPERELATIONS);
        setFieldNull(stmt, DatabaseConst.TBL_STRUCT_TYPES, "PARENT");
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_TYPES);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_DATATYPES + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_DATATYPES);

        dropTableData(stmt, DatabaseConst.TBL_STRUCT_SELECTLIST_ITEM);
        dropTableData(stmt, DatabaseConst.TBL_STRUCT_SELECTLIST);
        dropTableData(stmt, DatabaseConst.TBL_SCRIPTS);

        setFieldNull(stmt, DatabaseConst.TBL_CONTENT_BINARY, "PREVIEW_REF");
        dropTableData(stmt, DatabaseConst.TBL_CONTENT_BINARY);

        dropTableData(stmt, DatabaseConst.TBL_WORKFLOW_ROUTES);
        dropTableData(stmt, DatabaseConst.TBL_WORKFLOW_STEP);
        dropTableData(stmt, DatabaseConst.TBL_WORKFLOW_STEPDEFINITION);
        dropTableData(stmt, DatabaseConst.TBL_WORKFLOW);

        dropTableData(stmt, DatabaseConst.TBL_CONFIG_APPLICATION);
        dropTableData(stmt, DatabaseConst.TBL_CONFIG_DIVISION);
        dropTableData(stmt, DatabaseConst.TBL_CONFIG_NODE);
        dropTableData(stmt, DatabaseConst.TBL_CONFIG_USER);

        dropTableData(stmt, DatabaseConst.TBL_ASSIGN_GROUPS);
        dropTableData(stmt, DatabaseConst.TBL_ACLS_ASSIGNMENT);
        dropTableData(stmt, DatabaseConst.TBL_USERGROUPS);
        dropTableData(stmt, DatabaseConst.TBL_ACLS);
        dropTableData(stmt, DatabaseConst.TBL_ACCOUNTS);
        dropTableData(stmt, DatabaseConst.TBL_MANDATORS);

        dropTableData(stmt, DatabaseConst.TBL_LANG + DatabaseConst.ML);
        dropTableData(stmt, DatabaseConst.TBL_LANG);

        final String divisionPath = File.separatorChar + String.valueOf(FxContext.get().getDivisionId());
        FxFileUtils.removeDirectory(FxBinaryUtils.getBinaryDirectory() + divisionPath);
        FxFileUtils.removeDirectory(FxBinaryUtils.getTransitDirectory() + divisionPath);
    }

    /**
     * Set a column of a table to <code>null</code>
     *
     * @param stmt   statement to operate on
     * @param table  name of the table
     * @param column name of the column
     * @throws SQLException on errors
     */
    private void setFieldNull(Statement stmt, String table, String column) throws SQLException {
        int count = stmt.executeUpdate("UPDATE " + table + " SET " + column + "=NULL");
        LOG.info("Set [" + count + "] entries in [" + table + "." + column + "] to NULL");
    }

    /**
     * Drop a table
     *
     * @param stmt  statement to operate on
     * @param table name of the table to drop
     * @throws SQLException on errors
     */
    protected void dropTable(Statement stmt, String table) throws SQLException {
        stmt.executeUpdate("DROP TABLE " + table);
        LOG.info("Dropped table [" + table + "]");
    }

    /**
     * Drop all data of a table
     *
     * @param stmt  statement to operate on
     * @param table name of the table
     * @throws SQLException on errors
     */
    private void dropTableData(Statement stmt, String table) throws SQLException {
        int count = stmt.executeUpdate("DELETE FROM " + table);
        LOG.info("Removed [" + count + "] entries from table [" + table + "]");
    }

    /**
     * Helper class to keep information about columns in a prepared statement
     */
    class ColumnInfo {
        ColumnInfo(int columnType, int index) {
            this.columnType = columnType;
            this.index = index;
        }

        int columnType;
        int index;
    }

    /**
     * Import data from a zip archive to a database table
     *
     * @param stmt  statement to use
     * @param zip   zip archive containing the zip entry
     * @param ze    zip entry within the archive
     * @param xpath xpath containing the entries to import
     * @param table name of the table
     * @throws Exception on errors
     */
    protected void importTable(Statement stmt, final ZipFile zip, final ZipEntry ze, final String xpath,
            final String table) throws Exception {
        importTable(stmt, zip, ze, xpath, table, true, false);
    }

    /**
     * Import data from a zip archive to a database table
     *
     * @param stmt               statement to use
     * @param zip                zip archive containing the zip entry
     * @param ze                 zip entry within the archive
     * @param xpath              xpath containing the entries to import
     * @param table              name of the table
     * @param executeInsertPhase execute the insert phase?
     * @param executeUpdatePhase execute the update phase?
     * @param updateColumns      columns that should be set to <code>null</code> in a first pass (insert)
     *                           and updated to the provided values in a second pass (update),
     *                           columns that should be used in the where clause have to be prefixed
     *                           with "KEY:", to assign a default value use the expression "columnname:default value",
     *                           if the default value is "@", it will be a negative counter starting at 0, decreasing.
     *                           If the default value starts with "%", it will be set to the column following the "%"
     *                           character in the first pass
     * @throws Exception on errors
     */
    protected void importTable(Statement stmt, final ZipFile zip, final ZipEntry ze, final String xpath,
            final String table, final boolean executeInsertPhase, final boolean executeUpdatePhase,
            final String... updateColumns) throws Exception {
        //analyze the table
        final ResultSet rs = stmt.executeQuery("SELECT * FROM " + table + " WHERE 1=2");
        StringBuilder sbInsert = new StringBuilder(500);
        StringBuilder sbUpdate = updateColumns.length > 0 ? new StringBuilder(500) : null;
        if (rs == null)
            throw new IllegalArgumentException("Can not analyze table [" + table + "]!");
        sbInsert.append("INSERT INTO ").append(table).append(" (");
        final ResultSetMetaData md = rs.getMetaData();
        final Map<String, ColumnInfo> updateClauseColumns = updateColumns.length > 0
                ? new HashMap<String, ColumnInfo>(md.getColumnCount())
                : null;
        final Map<String, ColumnInfo> updateSetColumns = updateColumns.length > 0
                ? new LinkedHashMap<String, ColumnInfo>(md.getColumnCount())
                : null;
        final Map<String, String> presetColumns = updateColumns.length > 0 ? new HashMap<String, String>(10) : null;
        //preset to a referenced column (%column syntax)
        final Map<String, String> presetRefColumns = updateColumns.length > 0 ? new HashMap<String, String>(10)
                : null;
        final Map<String, Integer> counters = updateColumns.length > 0 ? new HashMap<String, Integer>(10) : null;
        final Map<String, ColumnInfo> insertColumns = new HashMap<String, ColumnInfo>(
                md.getColumnCount() + (counters != null ? counters.size() : 0));
        int insertIndex = 1;
        int updateSetIndex = 1;
        int updateClauseIndex = 1;
        boolean first = true;
        for (int i = 0; i < md.getColumnCount(); i++) {
            final String currCol = md.getColumnName(i + 1).toLowerCase();
            if (updateColumns.length > 0) {
                boolean abort = false;
                for (String col : updateColumns) {
                    if (col.indexOf(':') > 0 && !col.startsWith("KEY:")) {
                        String value = col.substring(col.indexOf(':') + 1);
                        col = col.substring(0, col.indexOf(':'));
                        if ("@".equals(value)) {
                            if (currCol.equalsIgnoreCase(col)) {
                                counters.put(col, 0);
                                insertColumns.put(col, new ColumnInfo(md.getColumnType(i + 1), insertIndex++));
                                sbInsert.append(',').append(currCol);
                            }
                        } else if (value.startsWith("%")) {
                            if (currCol.equalsIgnoreCase(col)) {
                                presetRefColumns.put(col, value.substring(1));
                                insertColumns.put(col, new ColumnInfo(md.getColumnType(i + 1), insertIndex++));
                                sbInsert.append(',').append(currCol);
                                //                                System.out.println("==> adding presetRefColumn "+col+" with value of "+value.substring(1));
                            }
                        } else if (!presetColumns.containsKey(col))
                            presetColumns.put(col, value);
                    }
                    if (currCol.equalsIgnoreCase(col)) {
                        abort = true;
                        updateSetColumns.put(currCol, new ColumnInfo(md.getColumnType(i + 1), updateSetIndex++));
                        break;
                    }
                }
                if (abort)
                    continue;
            }
            if (first) {
                first = false;
            } else
                sbInsert.append(',');
            sbInsert.append(currCol);
            insertColumns.put(currCol, new ColumnInfo(md.getColumnType(i + 1), insertIndex++));
        }
        if (updateColumns.length > 0 && executeUpdatePhase) {
            sbUpdate.append("UPDATE ").append(table).append(" SET ");
            int counter = 0;
            for (String updateColumn : updateSetColumns.keySet()) {
                if (counter++ > 0)
                    sbUpdate.append(',');
                sbUpdate.append(updateColumn).append("=?");
            }
            sbUpdate.append(" WHERE ");
            boolean hasKeyColumn = false;
            for (String col : updateColumns) {
                if (!col.startsWith("KEY:"))
                    continue;
                hasKeyColumn = true;
                String keyCol = col.substring(4);
                for (int i = 0; i < md.getColumnCount(); i++) {
                    if (!md.getColumnName(i + 1).equalsIgnoreCase(keyCol))
                        continue;
                    updateClauseColumns.put(keyCol, new ColumnInfo(md.getColumnType(i + 1), updateClauseIndex++));
                    sbUpdate.append(keyCol).append("=? AND ");
                    break;
                }

            }
            if (!hasKeyColumn)
                throw new IllegalArgumentException("Update columns require a KEY!");
            sbUpdate.delete(sbUpdate.length() - 5, sbUpdate.length()); //remove trailing " AND "
            //"shift" clause indices
            for (String col : updateClauseColumns.keySet()) {
                GenericDivisionImporter.ColumnInfo ci = updateClauseColumns.get(col);
                ci.index += (updateSetIndex - 1);
            }
        }
        if (presetColumns != null) {
            for (String key : presetColumns.keySet())
                sbInsert.append(',').append(key);
        }
        sbInsert.append(")VALUES(");
        for (int i = 0; i < insertColumns.size(); i++) {
            if (i > 0)
                sbInsert.append(',');
            sbInsert.append('?');
        }
        if (presetColumns != null) {
            for (String key : presetColumns.keySet())
                sbInsert.append(',').append(presetColumns.get(key));
        }
        sbInsert.append(')');
        if (DBG) {
            LOG.info("Insert statement:\n" + sbInsert.toString());
            if (updateColumns.length > 0)
                LOG.info("Update statement:\n" + sbUpdate.toString());
        }
        //build a map containing all nodes that require attributes
        //this allows for matching simple xpath queries like "flatstorages/storage[@name='FX_FLAT_STORAGE']/data"
        final Map<String, List<String>> queryAttributes = new HashMap<String, List<String>>(5);
        for (String pElem : xpath.split("/")) {
            if (!(pElem.indexOf('@') > 0 && pElem.indexOf('[') > 0))
                continue;
            List<String> att = new ArrayList<String>(5);
            for (String pAtt : pElem.split("@")) {
                if (!(pAtt.indexOf('=') > 0))
                    continue;
                att.add(pAtt.substring(0, pAtt.indexOf('=')));
            }
            queryAttributes.put(pElem.substring(0, pElem.indexOf('[')), att);
        }
        final PreparedStatement psInsert = stmt.getConnection().prepareStatement(sbInsert.toString());
        final PreparedStatement psUpdate = updateColumns.length > 0 && executeUpdatePhase
                ? stmt.getConnection().prepareStatement(sbUpdate.toString())
                : null;
        try {
            final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
            final DefaultHandler handler = new DefaultHandler() {
                private String currentElement = null;
                private Map<String, String> data = new HashMap<String, String>(10);
                private StringBuilder sbData = new StringBuilder(10000);
                boolean inTag = false;
                boolean inElement = false;
                int counter;
                List<String> path = new ArrayList<String>(10);
                StringBuilder currPath = new StringBuilder(100);
                boolean insertMode = true;

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void startDocument() throws SAXException {
                    counter = 0;
                    inTag = false;
                    inElement = false;
                    path.clear();
                    currPath.setLength(0);
                    sbData.setLength(0);
                    data.clear();
                    currentElement = null;
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void processingInstruction(String target, String data) throws SAXException {
                    if (target != null && target.startsWith("fx_")) {
                        if (target.equals("fx_mode"))
                            insertMode = "insert".equals(data);
                    } else
                        super.processingInstruction(target, data);
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void endDocument() throws SAXException {
                    if (insertMode)
                        LOG.info("Imported [" + counter + "] entries into [" + table + "] for xpath [" + xpath
                                + "]");
                    else
                        LOG.info("Updated [" + counter + "] entries in [" + table + "] for xpath [" + xpath + "]");
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes)
                        throws SAXException {
                    pushPath(qName, attributes);
                    if (currPath.toString().equals(xpath)) {
                        inTag = true;
                        data.clear();
                        for (int i = 0; i < attributes.getLength(); i++) {
                            String name = attributes.getLocalName(i);
                            if (StringUtils.isEmpty(name))
                                name = attributes.getQName(i);
                            data.put(name, attributes.getValue(i));
                        }
                    } else {
                        currentElement = qName;
                    }
                    inElement = true;
                    sbData.setLength(0);
                }

                /**
                 * Push a path element from the stack
                 *
                 * @param qName element name to push
                 * @param att attributes
                 */
                private void pushPath(String qName, Attributes att) {
                    if (att.getLength() > 0 && queryAttributes.containsKey(qName)) {
                        String curr = qName + "[";
                        boolean first = true;
                        final List<String> attList = queryAttributes.get(qName);
                        for (int i = 0; i < att.getLength(); i++) {
                            if (!attList.contains(att.getQName(i)))
                                continue;
                            if (first)
                                first = false;
                            else
                                curr += ',';
                            curr += "@" + att.getQName(i) + "='" + att.getValue(i) + "'";
                        }
                        curr += ']';
                        path.add(curr);
                    } else
                        path.add(qName);
                    buildPath();
                }

                /**
                 * Pop the top path element from the stack
                 */
                private void popPath() {
                    path.remove(path.size() - 1);
                    buildPath();
                }

                /**
                 * Rebuild the current path
                 */
                private synchronized void buildPath() {
                    currPath.setLength(0);
                    for (String s : path)
                        currPath.append(s).append('/');
                    if (currPath.length() > 1)
                        currPath.delete(currPath.length() - 1, currPath.length());
                    //                    System.out.println("currPath: " + currPath);
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException {
                    if (currPath.toString().equals(xpath)) {
                        if (DBG)
                            LOG.info("Insert [" + xpath + "]: [" + data + "]");
                        inTag = false;
                        try {
                            if (insertMode) {
                                if (executeInsertPhase) {
                                    processColumnSet(insertColumns, psInsert);
                                    counter += psInsert.executeUpdate();
                                }
                            } else {
                                if (executeUpdatePhase) {
                                    if (processColumnSet(updateSetColumns, psUpdate)) {
                                        processColumnSet(updateClauseColumns, psUpdate);
                                        counter += psUpdate.executeUpdate();
                                    }
                                }
                            }
                        } catch (SQLException e) {
                            throw new SAXException(e);
                        } catch (ParseException e) {
                            throw new SAXException(e);
                        }
                    } else {
                        if (inTag) {
                            data.put(currentElement, sbData.toString());
                        }
                        currentElement = null;
                    }
                    popPath();
                    inElement = false;
                    sbData.setLength(0);
                }

                /**
                 * Process a column set
                 *
                 * @param columns the columns to process
                 * @param ps prepared statement to use
                 * @return if data other than <code>null</code> has been set
                 * @throws SQLException on errors
                 * @throws ParseException on date/time conversion errors
                 */
                private boolean processColumnSet(Map<String, ColumnInfo> columns, PreparedStatement ps)
                        throws SQLException, ParseException {
                    boolean dataSet = false;
                    for (String col : columns.keySet()) {
                        ColumnInfo ci = columns.get(col);
                        String value = data.get(col);
                        if (insertMode && counters != null && counters.get(col) != null) {
                            final int newVal = counters.get(col) - 1;
                            value = String.valueOf(newVal);
                            counters.put(col, newVal);
                            //                            System.out.println("new value for " + col + ": " + newVal);
                        }
                        if (insertMode && presetRefColumns != null && presetRefColumns.get(col) != null) {
                            value = data.get(presetRefColumns.get(col));
                            //                            System.out.println("Set presetRefColumn for "+col+" to ["+value+"] from column ["+presetRefColumns.get(col)+"]");
                        }

                        if (value == null)
                            ps.setNull(ci.index, ci.columnType);
                        else {
                            dataSet = true;
                            switch (ci.columnType) {
                            case Types.BIGINT:
                            case Types.NUMERIC:
                                if (DBG)
                                    LOG.info("BigInt " + ci.index + "->" + new BigDecimal(value));
                                ps.setBigDecimal(ci.index, new BigDecimal(value));
                                break;
                            case java.sql.Types.DOUBLE:
                                if (DBG)
                                    LOG.info("Double " + ci.index + "->" + Double.parseDouble(value));
                                ps.setDouble(ci.index, Double.parseDouble(value));
                                break;
                            case java.sql.Types.FLOAT:
                            case java.sql.Types.REAL:
                                if (DBG)
                                    LOG.info("Float " + ci.index + "->" + Float.parseFloat(value));
                                ps.setFloat(ci.index, Float.parseFloat(value));
                                break;
                            case java.sql.Types.TIMESTAMP:
                            case java.sql.Types.DATE:
                                if (DBG)
                                    LOG.info("Timestamp/Date " + ci.index + "->"
                                            + FxFormatUtils.getDateTimeFormat().parse(value));
                                ps.setTimestamp(ci.index,
                                        new Timestamp(FxFormatUtils.getDateTimeFormat().parse(value).getTime()));
                                break;
                            case Types.TINYINT:
                            case Types.SMALLINT:
                                if (DBG)
                                    LOG.info("Integer " + ci.index + "->" + Integer.valueOf(value));
                                ps.setInt(ci.index, Integer.valueOf(value));
                                break;
                            case Types.INTEGER:
                            case Types.DECIMAL:
                                try {
                                    if (DBG)
                                        LOG.info("Long " + ci.index + "->" + Long.valueOf(value));
                                    ps.setLong(ci.index, Long.valueOf(value));
                                } catch (NumberFormatException e) {
                                    //Fallback (temporary) for H2 if the reported long is a big decimal (tree...)
                                    ps.setBigDecimal(ci.index, new BigDecimal(value));
                                }
                                break;
                            case Types.BIT:
                            case Types.CHAR:
                            case Types.BOOLEAN:
                                if (DBG)
                                    LOG.info("Boolean " + ci.index + "->" + value);
                                if ("1".equals(value) || "true".equals(value))
                                    ps.setBoolean(ci.index, true);
                                else
                                    ps.setBoolean(ci.index, false);
                                break;
                            case Types.LONGVARBINARY:
                            case Types.VARBINARY:
                            case Types.BLOB:
                            case Types.BINARY:
                                ZipEntry bin = zip.getEntry(value);
                                if (bin == null) {
                                    LOG.error("Failed to lookup binary [" + value + "]!");
                                    ps.setNull(ci.index, ci.columnType);
                                    break;
                                }
                                try {
                                    ps.setBinaryStream(ci.index, zip.getInputStream(bin), (int) bin.getSize());
                                } catch (IOException e) {
                                    LOG.error("IOException importing binary [" + value + "]: " + e.getMessage(), e);
                                }
                                break;
                            case Types.CLOB:
                            case Types.LONGVARCHAR:
                            case Types.VARCHAR:
                            case SQL_LONGNVARCHAR:
                            case SQL_NCHAR:
                            case SQL_NCLOB:
                            case SQL_NVARCHAR:
                                if (DBG)
                                    LOG.info("String " + ci.index + "->" + value);
                                ps.setString(ci.index, value);
                                break;
                            default:
                                LOG.warn("Unhandled type [" + ci.columnType + "] for column [" + col + "]");
                            }
                        }
                    }
                    return dataSet;
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void characters(char[] ch, int start, int length) throws SAXException {
                    if (inElement)
                        sbData.append(ch, start, length);
                }

            };
            handler.processingInstruction("fx_mode", "insert");
            parser.parse(zip.getInputStream(ze), handler);
            if (updateColumns.length > 0 && executeUpdatePhase) {
                handler.processingInstruction("fx_mode", "update");
                parser.parse(zip.getInputStream(ze), handler);
            }
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, psInsert, psUpdate);
        }
    }

    /**
     * Import language settings
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importLanguages(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_LANGUAGES);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "languages/lang", DatabaseConst.TBL_LANG);
            importTable(stmt, zip, ze, "languages/lang_t", DatabaseConst.TBL_LANG + DatabaseConst.ML);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import mandators
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importMandators(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_MANDATORS);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "mandators/mandator", DatabaseConst.TBL_MANDATORS);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import security data
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importSecurity(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_SECURITY);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "security/accounts/data/account", DatabaseConst.TBL_ACCOUNTS);
            importTable(stmt, zip, ze, "security/accounts/details/detail", DatabaseConst.TBL_ACCOUNT_DETAILS);
            importTable(stmt, zip, ze, "security/acls/acl", DatabaseConst.TBL_ACLS);
            importTable(stmt, zip, ze, "security/acls/acl_t", DatabaseConst.TBL_ACLS + DatabaseConst.ML);
            importTable(stmt, zip, ze, "security/groups/group", DatabaseConst.TBL_USERGROUPS);
            importTable(stmt, zip, ze, "security/assignments/assignment", DatabaseConst.TBL_ACLS_ASSIGNMENT);
            importTable(stmt, zip, ze, "security/groupAssignments/assignment", DatabaseConst.TBL_ASSIGN_GROUPS);
            importTable(stmt, zip, ze, "security/roleAssignments/assignment", DatabaseConst.TBL_ROLE_MAPPING);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import workflow data
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importWorkflows(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_WORKFLOWS);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "workflow/workflows/workflow", DatabaseConst.TBL_WORKFLOW);
            importTable(stmt, zip, ze, "workflow/stepDefinitions/stepdef",
                    DatabaseConst.TBL_WORKFLOW_STEPDEFINITION, true, true, "unique_target", "KEY:id");
            importTable(stmt, zip, ze, "workflow/stepDefinitions/stepdef_t",
                    DatabaseConst.TBL_WORKFLOW_STEPDEFINITION + DatabaseConst.ML);
            importTable(stmt, zip, ze, "workflow/steps/step", DatabaseConst.TBL_WORKFLOW_STEP);
            importTable(stmt, zip, ze, "workflow/routes/route", DatabaseConst.TBL_WORKFLOW_ROUTES);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import configurations
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importConfigurations(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_CONFIGURATIONS);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "configurations/application/entry", DatabaseConst.TBL_CONFIG_APPLICATION);
            importTable(stmt, zip, ze, "configurations/division/entry", DatabaseConst.TBL_CONFIG_DIVISION);
            importTable(stmt, zip, ze, "configurations/node/entry", DatabaseConst.TBL_CONFIG_NODE);
            importTable(stmt, zip, ze, "configurations/user/entry", DatabaseConst.TBL_CONFIG_USER);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import structural data
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importStructures(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_STRUCTURES);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "structures/selectlists/list", DatabaseConst.TBL_STRUCT_SELECTLIST);
            importTable(stmt, zip, ze, "structures/selectlists/list_t",
                    DatabaseConst.TBL_STRUCT_SELECTLIST + DatabaseConst.ML);
            importTable(stmt, zip, ze, "structures/datatypes/type", DatabaseConst.TBL_STRUCT_DATATYPES);
            importTable(stmt, zip, ze, "structures/datatypes/type_t",
                    DatabaseConst.TBL_STRUCT_DATATYPES + DatabaseConst.ML);
            importTable(stmt, zip, ze, "structures/types/tdef", DatabaseConst.TBL_STRUCT_TYPES, true, false,
                    "icon_ref");
            importTable(stmt, zip, ze, "structures/types/tdef_t",
                    DatabaseConst.TBL_STRUCT_TYPES + DatabaseConst.ML);
            importTable(stmt, zip, ze, "structures/types/trel", DatabaseConst.TBL_STRUCT_TYPERELATIONS);
            importTable(stmt, zip, ze, "structures/types/topts", DatabaseConst.TBL_STRUCT_TYPES_OPTIONS);
            importTable(stmt, zip, ze, "structures/properties/property", DatabaseConst.TBL_STRUCT_PROPERTIES);
            importTable(stmt, zip, ze, "structures/properties/property_t",
                    DatabaseConst.TBL_STRUCT_PROPERTIES + DatabaseConst.ML);
            importTable(stmt, zip, ze, "structures/groups/group", DatabaseConst.TBL_STRUCT_GROUPS);
            importTable(stmt, zip, ze, "structures/groups/group_t",
                    DatabaseConst.TBL_STRUCT_GROUPS + DatabaseConst.ML);
            importAssignments(con, zip, ze, stmt);
            importTable(stmt, zip, ze, "structures/assignments/assignment_t",
                    DatabaseConst.TBL_STRUCT_ASSIGNMENTS + DatabaseConst.ML);
            importTable(stmt, zip, ze, "structures/selectlists/item", DatabaseConst.TBL_STRUCT_SELECTLIST_ITEM);
            importTable(stmt, zip, ze, "structures/selectlists/item_t",
                    DatabaseConst.TBL_STRUCT_SELECTLIST_ITEM + DatabaseConst.ML);
            importTable(stmt, zip, ze, "structures/groups/goption", DatabaseConst.TBL_STRUCT_GROUP_OPTIONS);
            importTable(stmt, zip, ze, "structures/properties/poption", DatabaseConst.TBL_STRUCT_PROPERTY_OPTIONS);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    protected void importAssignments(Connection con, ZipFile zip, ZipEntry ze, Statement stmt) throws Exception {
        DBStorage storage = StorageManager.getStorageImpl();
        try {
            stmt.execute(storage.getReferentialIntegrityChecksStatement(false));
            importTable(stmt, zip, ze, "structures/assignments/assignment", DatabaseConst.TBL_STRUCT_ASSIGNMENTS,
                    true, true, "base:0", "parentgroup:%id", "pos:@", "KEY:id");
        } finally {
            stmt.execute(storage.getReferentialIntegrityChecksStatement(true));
        }
    }

    /**
     * Import database and filesystem binaries
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importBinaries(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_BINARIES);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "binaries/binary", DatabaseConst.TBL_CONTENT_BINARY);
            ZipEntry curr;
            final String nodeBinDir = FxBinaryUtils.getBinaryDirectory();
            File binDir = new File(
                    nodeBinDir + File.separatorChar + String.valueOf(FxContext.get().getDivisionId()));
            if (!binDir.exists())
                //noinspection ResultOfMethodCallIgnored
                binDir.mkdirs();
            if (!binDir.exists() && binDir.isDirectory()) {
                LOG.error("Failed to create binary directory [" + binDir.getAbsolutePath() + "]!");
                return;
            }
            int count = 0;
            for (Enumeration e = zip.entries(); e.hasMoreElements();) {
                curr = (ZipEntry) e.nextElement();
                if (curr.getName().startsWith(FOLDER_FS_BINARY) && !curr.isDirectory()) {
                    File out = new File(binDir.getAbsolutePath() + File.separatorChar
                            + curr.getName().substring(FOLDER_FS_BINARY.length() + 1));
                    String path = out.getAbsolutePath();
                    path = path.replace('\\', '/'); //normalize separator chars
                    path = path.replace('/', File.separatorChar);
                    path = path.substring(0, out.getAbsolutePath().lastIndexOf(File.separatorChar));
                    File fPath = new File(path);
                    if (!fPath.exists()) {
                        if (!fPath.mkdirs()) {
                            LOG.error("Failed to create path [" + path + "!]");
                            continue;
                        }
                    }
                    if (!out.createNewFile()) {
                        LOG.error("Failed to create file [" + out.getAbsolutePath() + "]!");
                        continue;
                    }
                    if (FxFileUtils.copyStream2File(curr.getSize(), zip.getInputStream(curr), out))
                        count++;
                    else
                        LOG.error("Failed to write zip stream to file [" + out.getAbsolutePath() + "]!");
                }
            }
            FxContext.get().runAsSystem();
            try {
                EJBLookup.getNodeConfigurationEngine().put(SystemParameters.NODE_BINARY_PATH, nodeBinDir);
            } finally {
                FxContext.get().stopRunAsSystem();
            }
            LOG.info("Imported [" + count + "] files to filesystem binary storage located at ["
                    + binDir.getAbsolutePath() + "]");
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import hierarchical contents
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importHierarchicalContents(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_DATA_HIERARCHICAL);
        ZipEntry ze_struct = getZipEntry(zip, FILE_STRUCTURES);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "hierarchical/content", DatabaseConst.TBL_CONTENT);
            importTable(stmt, zip, ze, "hierarchical/data", DatabaseConst.TBL_CONTENT_DATA);
            importTable(stmt, zip, ze, "hierarchical/acl", DatabaseConst.TBL_CONTENT_ACLS);
            importTable(stmt, zip, ze_struct, "structures/types/tdef", DatabaseConst.TBL_STRUCT_TYPES, false, true,
                    "icon_ref", "KEY:id");
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import scripting data
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importScripts(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_SCRIPTS);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "scripts/script", DatabaseConst.TBL_SCRIPTS);
            importTable(stmt, zip, ze, "scripts/assignmap", DatabaseConst.TBL_SCRIPT_MAPPING_ASSIGN);
            importTable(stmt, zip, ze, "scripts/typemap", DatabaseConst.TBL_SCRIPT_MAPPING_TYPES);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import tree data
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importTree(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_TREE);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "tree/edit/node", DatabaseConst.TBL_TREE, true, true, "parent", "KEY:id");
            importTable(stmt, zip, ze, "tree/live/node", DatabaseConst.TBL_TREE + "_LIVE", true, true, "parent",
                    "KEY:id");
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import history data
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importHistory(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_HISTORY);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "history/entry", DatabaseConst.TBL_HISTORY);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import resource data
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importResources(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_RESOURCES);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "resources/entry", DatabaseConst.TBL_RESOURCES);
            importTable(stmt, zip, ze, "resources/phrase", DatabaseConst.TBL_PHRASE);
            importTable(stmt, zip, ze, "resources/phraseVal", DatabaseConst.TBL_PHRASE_VALUES);
            importTable(stmt, zip, ze, "resources/phraseTree", DatabaseConst.TBL_PHRASE_TREE);
            importTable(stmt, zip, ze, "resources/phraseMap", DatabaseConst.TBL_PHRASE_MAP);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import briefcases
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importBriefcases(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_BRIEFCASES);
        Statement stmt = con.createStatement();
        try {
            importTable(stmt, zip, ze, "briefcases/briefcase", DatabaseConst.TBL_BRIEFCASE);
            importTable(stmt, zip, ze, "briefcases/data", DatabaseConst.TBL_BRIEFCASE_DATA);
            importTable(stmt, zip, ze, "briefcases/dataItem", DatabaseConst.TBL_BRIEFCASE_DATA_ITEM);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Import flat storages to the hierarchical storage
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    protected void importFlatStoragesHierarchical(Connection con, ZipFile zip) throws Exception {
        //mapping: storage->level->columnname->assignment id
        final Map<String, Map<Integer, Map<String, Long>>> flatAssignmentMapping = new HashMap<String, Map<Integer, Map<String, Long>>>(
                5);
        //mapping: assignment id->position index
        final Map<Long, Integer> assignmentPositions = new HashMap<Long, Integer>(100);
        //mapping: flatstorage->column sizes [string,bigint,double,select,text]
        final Map<String, Integer[]> flatstoragesColumns = new HashMap<String, Integer[]>(5);
        ZipEntry zeMeta = getZipEntry(zip, FILE_FLATSTORAGE_META);
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = builder.parse(zip.getInputStream(zeMeta));
        XPath xPath = XPathFactory.newInstance().newXPath();

        //calculate column sizes
        NodeList nodes = (NodeList) xPath.evaluate("/flatstorageMeta/storageMeta", document,
                XPathConstants.NODESET);
        Node currNode;
        for (int i = 0; i < nodes.getLength(); i++) {
            currNode = nodes.item(i);
            int cbigInt = Integer.parseInt(currNode.getAttributes().getNamedItem("bigInt").getNodeValue());
            int cdouble = Integer.parseInt(currNode.getAttributes().getNamedItem("double").getNodeValue());
            int cselect = Integer.parseInt(currNode.getAttributes().getNamedItem("select").getNodeValue());
            int cstring = Integer.parseInt(currNode.getAttributes().getNamedItem("string").getNodeValue());
            int ctext = Integer.parseInt(currNode.getAttributes().getNamedItem("text").getNodeValue());
            String tableName = null;
            if (currNode.hasChildNodes()) {
                for (int j = 0; j < currNode.getChildNodes().getLength(); j++)
                    if (currNode.getChildNodes().item(j).getNodeName().equals("name")) {
                        tableName = currNode.getChildNodes().item(j).getTextContent();
                    }
            }
            if (tableName != null) {
                flatstoragesColumns.put(tableName, new Integer[] { cstring, cbigInt, cdouble, cselect, ctext });
            }
        }

        //parse mappings
        nodes = (NodeList) xPath.evaluate("/flatstorageMeta/mapping", document, XPathConstants.NODESET);
        for (int i = 0; i < nodes.getLength(); i++) {
            currNode = nodes.item(i);
            long assignment = Long.valueOf(currNode.getAttributes().getNamedItem("assid").getNodeValue());
            int level = Integer.valueOf(currNode.getAttributes().getNamedItem("lvl").getNodeValue());
            String storage = null;
            String columnname = null;
            final NodeList childNodes = currNode.getChildNodes();
            for (int c = 0; c < childNodes.getLength(); c++) {
                Node child = childNodes.item(c);
                if ("tblname".equals(child.getNodeName()))
                    storage = child.getTextContent();
                else if ("colname".equals(child.getNodeName()))
                    columnname = child.getTextContent();
            }
            if (storage == null || columnname == null)
                throw new Exception("Invalid flatstorage export: could not read storage or column name!");
            if (!flatAssignmentMapping.containsKey(storage))
                flatAssignmentMapping.put(storage, new HashMap<Integer, Map<String, Long>>(20));
            Map<Integer, Map<String, Long>> levelMap = flatAssignmentMapping.get(storage);
            if (!levelMap.containsKey(level))
                levelMap.put(level, new HashMap<String, Long>(30));
            Map<String, Long> columnMap = levelMap.get(level);
            if (!columnMap.containsKey(columnname))
                columnMap.put(columnname, assignment);
            //calculate position
            assignmentPositions.put(assignment,
                    getAssignmentPosition(flatstoragesColumns.get(storage), columnname));
        }
        if (flatAssignmentMapping.size() == 0) {
            LOG.warn("No flatstorage assignments found to process!");
            return;
        }
        ZipEntry zeData = getZipEntry(zip, FILE_DATA_FLAT);

        final String xpathStorage = "flatstorages/storage";
        final String xpathData = "flatstorages/storage/data";

        final PreparedStatement psGetAssInfo = con.prepareStatement(
                "SELECT DISTINCT a.APROPERTY,a.XALIAS,p.DATATYPE FROM " + DatabaseConst.TBL_STRUCT_ASSIGNMENTS
                        + " a, " + DatabaseConst.TBL_STRUCT_PROPERTIES + " p WHERE a.ID=? AND p.ID=a.APROPERTY");
        final Map<Long, Object[]> assignmentPropAlias = new HashMap<Long, Object[]>(assignmentPositions.size());
        final String insert1 = "INSERT INTO " + DatabaseConst.TBL_CONTENT_DATA +
        //1  2   3   4    5     6      =1     =1    =1     =1          7         8          9
                "(ID,VER,POS,LANG,TPROP,ASSIGN,XDEPTH,XMULT,XINDEX,PARENTXMULT,ISMAX_VER,ISLIVE_VER,ISMLDEF,";
        final String insert2 = "(?,?,?,?,1,?,?,1,1,1,?,?,?,";
        final PreparedStatement psString = con
                .prepareStatement(insert1 + "FTEXT1024,UFTEXT1024,FSELECT,FINT)VALUES" + insert2 + "?,?,0,?)");
        final PreparedStatement psText = con
                .prepareStatement(insert1 + "FCLOB,UFCLOB,FSELECT,FINT)VALUES" + insert2 + "?,?,0,?)");
        final PreparedStatement psDouble = con
                .prepareStatement(insert1 + "FDOUBLE,FSELECT,FINT)VALUES" + insert2 + "?,0,?)");
        final PreparedStatement psNumber = con
                .prepareStatement(insert1 + "FINT,FSELECT,FBIGINT)VALUES" + insert2 + "?,0,?)");
        final PreparedStatement psLargeNumber = con
                .prepareStatement(insert1 + "FBIGINT,FSELECT,FINT)VALUES" + insert2 + "?,0,?)");
        final PreparedStatement psFloat = con
                .prepareStatement(insert1 + "FFLOAT,FSELECT,FINT)VALUES" + insert2 + "?,0,?)");
        final PreparedStatement psBoolean = con
                .prepareStatement(insert1 + "FBOOL,FSELECT,FINT)VALUES" + insert2 + "?,0,?)");
        final PreparedStatement psReference = con
                .prepareStatement(insert1 + "FREF,FSELECT,FINT)VALUES" + insert2 + "?,0,?)");
        final PreparedStatement psSelectOne = con
                .prepareStatement(insert1 + "FSELECT,FINT)VALUES" + insert2 + "?,?)");
        try {
            final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
            final DefaultHandler handler = new DefaultHandler() {
                private String currentElement = null;
                private String currentStorage = null;
                private Map<String, String> data = new HashMap<String, String>(10);
                private StringBuilder sbData = new StringBuilder(10000);
                boolean inTag = false;
                boolean inElement = false;
                List<String> path = new ArrayList<String>(10);
                StringBuilder currPath = new StringBuilder(100);
                int insertCount = 0;

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void startDocument() throws SAXException {
                    inTag = false;
                    inElement = false;
                    path.clear();
                    currPath.setLength(0);
                    sbData.setLength(0);
                    data.clear();
                    currentElement = null;
                    currentStorage = null;
                    insertCount = 0;
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void endDocument() throws SAXException {
                    LOG.info("Imported [" + insertCount + "] flatstorage entries into the hierarchical storage");
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes)
                        throws SAXException {
                    pushPath(qName, attributes);
                    if (currPath.toString().equals(xpathData)) {
                        inTag = true;
                        data.clear();
                        for (int i = 0; i < attributes.getLength(); i++) {
                            String name = attributes.getLocalName(i);
                            if (StringUtils.isEmpty(name))
                                name = attributes.getQName(i);
                            data.put(name, attributes.getValue(i));
                        }
                    } else if (currPath.toString().equals(xpathStorage)) {
                        currentStorage = attributes.getValue("name");
                        LOG.info("Processing storage: " + currentStorage);
                    } else {
                        currentElement = qName;
                    }
                    inElement = true;
                    sbData.setLength(0);
                }

                /**
                 * Push a path element from the stack
                 *
                 * @param qName element name to push
                 * @param att attributes
                 */
                @SuppressWarnings({ "UnusedDeclaration" })
                private void pushPath(String qName, Attributes att) {
                    path.add(qName);
                    buildPath();
                }

                /**
                 * Pop the top path element from the stack
                 */
                private void popPath() {
                    path.remove(path.size() - 1);
                    buildPath();
                }

                /**
                 * Rebuild the current path
                 */
                private synchronized void buildPath() {
                    currPath.setLength(0);
                    for (String s : path)
                        currPath.append(s).append('/');
                    if (currPath.length() > 1)
                        currPath.delete(currPath.length() - 1, currPath.length());
                    //                    System.out.println("currPath: " + currPath);
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException {
                    if (currPath.toString().equals(xpathData)) {
                        //                        LOG.info("Insert [" + xpathData + "]: [" + data + "]");
                        inTag = false;
                        processData();
                        /*try {
                        if (insertMode) {
                            if (executeInsertPhase) {
                                processColumnSet(insertColumns, psInsert);
                                counter += psInsert.executeUpdate();
                            }
                        } else {
                            if (executeUpdatePhase) {
                                if (processColumnSet(updateSetColumns, psUpdate)) {
                                    processColumnSet(updateClauseColumns, psUpdate);
                                    counter += psUpdate.executeUpdate();
                                }
                            }
                        }
                        } catch (SQLException e) {
                        throw new SAXException(e);
                        } catch (ParseException e) {
                        throw new SAXException(e);
                        }*/
                    } else {
                        if (inTag) {
                            data.put(currentElement, sbData.toString());
                        }
                        currentElement = null;
                    }
                    popPath();
                    inElement = false;
                    sbData.setLength(0);
                }

                void processData() {
                    //                    System.out.println("processing " + currentStorage + " -> " + data);
                    final String[] cols = { "string", "bigint", "double", "select", "text" };
                    for (String column : data.keySet()) {
                        if (column.endsWith("_mld"))
                            continue;
                        for (String check : cols) {
                            if (column.startsWith(check)) {
                                if ("select".equals(check) && "0".equals(data.get(column)))
                                    continue; //dont insert 0-referencing selects
                                try {
                                    insertData(column);
                                } catch (SQLException e) {
                                    //noinspection ThrowableInstanceNeverThrown
                                    throw new FxDbException(e, "ex.db.sqlError", e.getMessage())
                                            .asRuntimeException();
                                }
                            }
                        }
                    }
                }

                private void insertData(String column) throws SQLException {
                    final int level = Integer.parseInt(data.get("lvl"));
                    long assignment = flatAssignmentMapping.get(currentStorage).get(level)
                            .get(column.toUpperCase());
                    int pos = FxArrayUtils.getIntElementAt(data.get("positions"), ',',
                            assignmentPositions.get(assignment));
                    String _valueData = data.get("valuedata");
                    Integer valueData = _valueData == null ? null
                            : FxArrayUtils.getHexIntElementAt(data.get("valuedata"), ',',
                                    assignmentPositions.get(assignment));
                    Object[] propXP = getPropertyXPathDataType(assignment);
                    long prop = (Long) propXP[0];
                    String xpath = (String) propXP[1];
                    FxDataType dataType;
                    try {
                        dataType = FxDataType.getById((Long) propXP[2]);
                    } catch (FxNotFoundException e) {
                        throw e.asRuntimeException();
                    }
                    long id = Long.parseLong(data.get("id"));
                    int ver = Integer.parseInt(data.get("ver"));
                    long lang = Integer.parseInt(data.get("lang"));
                    boolean isMaxVer = "1".equals(data.get("ismax_ver"));
                    boolean isLiveVer = "1".equals(data.get("islive_ver"));
                    boolean mlDef = "1".equals(data.get(column + "_mld"));
                    PreparedStatement ps;
                    int vdPos;
                    switch (dataType) {
                    case String1024:
                        ps = psString;
                        ps.setString(10, data.get(column));
                        ps.setString(11, data.get(column).toUpperCase());
                        vdPos = 12;
                        break;
                    case Text:
                    case HTML:
                        ps = psText;
                        ps.setString(10, data.get(column));
                        ps.setString(11, data.get(column).toUpperCase());
                        vdPos = 12;
                        break;
                    case Number:
                        ps = psNumber;
                        ps.setLong(10, Long.valueOf(data.get(column)));
                        vdPos = 11;
                        break;
                    case LargeNumber:
                        ps = psLargeNumber;
                        ps.setLong(10, Long.valueOf(data.get(column)));
                        vdPos = 11;
                        break;
                    case Reference:
                        ps = psReference;
                        ps.setLong(10, Long.valueOf(data.get(column)));
                        vdPos = 11;
                        break;
                    case Float:
                        ps = psFloat;
                        ps.setFloat(10, Float.valueOf(data.get(column)));
                        vdPos = 11;
                        break;
                    case Double:
                        ps = psDouble;
                        ps.setDouble(10, Double.valueOf(data.get(column)));
                        vdPos = 11;
                        break;
                    case Boolean:
                        ps = psBoolean;
                        ps.setBoolean(10, "1".equals(data.get(column)));
                        vdPos = 11;
                        break;
                    case SelectOne:
                        ps = psSelectOne;
                        ps.setLong(10, Long.valueOf(data.get(column)));
                        vdPos = 11;
                        break;
                    default:
                        //noinspection ThrowableInstanceNeverThrown
                        throw new FxInvalidParameterException("assignment",
                                "ex.structure.flatstorage.datatype.unsupported", dataType.name())
                                        .asRuntimeException();
                    }
                    ps.setLong(1, id);
                    ps.setInt(2, ver);
                    ps.setInt(3, pos);
                    ps.setLong(4, lang);
                    ps.setLong(5, prop);
                    ps.setLong(6, assignment);
                    ps.setBoolean(7, isMaxVer);
                    ps.setBoolean(8, isLiveVer);
                    ps.setBoolean(9, mlDef);
                    if (valueData == null)
                        ps.setNull(vdPos, java.sql.Types.NUMERIC);
                    else
                        ps.setInt(vdPos, valueData);
                    ps.executeUpdate();
                    insertCount++;
                }

                /**
                 * Get property id, xpath and data type for an assignment
                 *
                 * @param assignment assignment id
                 * @return Object[] {propertyId, xpath, datatype}
                 */
                private Object[] getPropertyXPathDataType(long assignment) {
                    if (assignmentPropAlias.get(assignment) != null)
                        return assignmentPropAlias.get(assignment);
                    try {
                        psGetAssInfo.setLong(1, assignment);
                        ResultSet rs = psGetAssInfo.executeQuery();
                        if (rs != null && rs.next()) {
                            Object[] data = new Object[] { rs.getLong(1), rs.getString(2), rs.getLong(3) };
                            assignmentPropAlias.put(assignment, data);
                            return data;
                        }
                    } catch (SQLException e) {
                        throw new IllegalArgumentException(
                                "Could not load data for assignment " + assignment + ": " + e.getMessage());
                    }
                    throw new IllegalArgumentException("Could not load data for assignment " + assignment + "!");
                }

                /**
                 * {@inheritDoc}
                 */
                @Override
                public void characters(char[] ch, int start, int length) throws SAXException {
                    if (inElement)
                        sbData.append(ch, start, length);
                }

            };
            parser.parse(zip.getInputStream(zeData), handler);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, psGetAssInfo, psString, psBoolean, psDouble,
                    psFloat, psLargeNumber, psNumber, psReference, psSelectOne, psText);
        }
    }

    /**
     * Get the assignment position based on the column name
     *
     * @param columnSizes number of columns o feach type
     * @param columnname  name of the column to evaluate
     * @return position
     */
    private int getAssignmentPosition(Integer[] columnSizes, String columnname) {
        if (columnSizes == null)
            throw new IllegalArgumentException("No columnSizes available!");
        //[string,bigint,double,select,text]
        int start;
        if (columnname.startsWith("STRING"))
            start = 0;
        else if (columnname.startsWith("BIGINT"))
            start = columnSizes[0];
        else if (columnname.startsWith("DOUBLE"))
            start = columnSizes[0] + columnSizes[1];
        else if (columnname.startsWith("SELECT"))
            start = columnSizes[0] + columnSizes[1] + columnSizes[2];
        else if (columnname.startsWith("TEXT"))
            start = columnSizes[0] + columnSizes[1] + columnSizes[2] + columnSizes[3];
        else
            throw new IllegalArgumentException("Unknown column: " + columnname);
        return start + Integer.parseInt(columnname.substring(columnname.length() - 3)); //format COLUMNxxx, xxx=number
    }

    /**
     * Import briefcases
     *
     * @param con        an open and valid connection to store imported data
     * @param zip        zip file containing the data
     * @param exportInfo information about the exported data
     * @throws Exception on errors
     */
    public void importFlatStorages(Connection con, ZipFile zip, FxDivisionExportInfo exportInfo) throws Exception {
        if (!canImportFlatStorages(exportInfo)) {
            importFlatStoragesHierarchical(con, zip);
            return;
        }
        ZipEntry ze = getZipEntry(zip, FILE_FLATSTORAGE_META);
        Statement stmt = con.createStatement();
        try {
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = builder.parse(zip.getInputStream(ze));
            XPath xPath = XPathFactory.newInstance().newXPath();

            NodeList nodes = (NodeList) xPath.evaluate("/flatstorageMeta/storageMeta", document,
                    XPathConstants.NODESET);
            Node currNode;
            List<String> storages = new ArrayList<String>(5);
            for (int i = 0; i < nodes.getLength(); i++) {
                currNode = nodes.item(i);
                int cbigInt = Integer.parseInt(currNode.getAttributes().getNamedItem("bigInt").getNodeValue());
                int cdouble = Integer.parseInt(currNode.getAttributes().getNamedItem("double").getNodeValue());
                int cselect = Integer.parseInt(currNode.getAttributes().getNamedItem("select").getNodeValue());
                int cstring = Integer.parseInt(currNode.getAttributes().getNamedItem("string").getNodeValue());
                int ctext = Integer.parseInt(currNode.getAttributes().getNamedItem("text").getNodeValue());
                String tableName = null;
                String description = null;
                if (currNode.hasChildNodes()) {
                    for (int j = 0; j < currNode.getChildNodes().getLength(); j++)
                        if (currNode.getChildNodes().item(j).getNodeName().equals("name")) {
                            tableName = currNode.getChildNodes().item(j).getTextContent();
                        } else if (currNode.getChildNodes().item(j).getNodeName().equals("description")) {
                            description = currNode.getChildNodes().item(j).getTextContent();
                        }
                }
                if (tableName != null) {
                    if (description == null)
                        description = "FlatStorage " + tableName;
                    final FxFlatStorage flatStorage = FxFlatStorageManager.getInstance();
                    for (FxFlatStorageInfo fi : flatStorage.getFlatStorageInfos()) {
                        if (fi.getName().equals(tableName)) {
                            flatStorage.removeFlatStorage(tableName);
                            break;
                        }
                    }
                    // TODO - flat storage type detection
                    flatStorage.createFlatStorage(con, tableName, FxFlatStorageInfo.Type.TypeNormal, description,
                            cstring, ctext, cbigInt, cdouble, cselect);
                    storages.add(tableName);
                }
            }

            importTable(stmt, zip, ze, "flatstorageMeta/mapping", DatabaseConst.TBL_STRUCT_FLATSTORE_MAPPING);

            ZipEntry zeData = getZipEntry(zip, FILE_DATA_FLAT);
            for (String storage : storages)
                importTable(stmt, zip, zeData, "flatstorages/storage[@name='" + storage + "']/data", storage);
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }

    /**
     * Can this importer import flatstorages from the given export?
     *
     * @param exportInfo info about the export
     * @return flatstorages can be imported
     */
    protected boolean canImportFlatStorages(FxDivisionExportInfo exportInfo) {
        return true;
    }

    /**
     * Import sequencer settings
     *
     * @param con an open and valid connection to store imported data
     * @param zip zip file containing the data
     * @throws Exception on errors
     */
    public void importSequencers(Connection con, ZipFile zip) throws Exception {
        ZipEntry ze = getZipEntry(zip, FILE_SEQUENCERS);
        Statement stmt = con.createStatement();
        try {
            SequencerStorage seq = StorageManager.getSequencerStorage();
            for (CustomSequencer cust : seq.getCustomSequencers())
                seq.removeSequencer(cust.getName());
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = builder.parse(zip.getInputStream(ze));
            XPath xPath = XPathFactory.newInstance().newXPath();

            NodeList nodes = (NodeList) xPath.evaluate("/sequencers/syssequence", document, XPathConstants.NODESET);
            Node currNode;
            String seqName;
            long value;
            for (int i = 0; i < nodes.getLength(); i++) {
                currNode = nodes.item(i);
                if (currNode.hasChildNodes()) {
                    seqName = null;
                    value = -1L;
                    for (int j = 0; j < currNode.getChildNodes().getLength(); j++)
                        if (currNode.getChildNodes().item(j).getNodeName().equals("name")) {
                            seqName = currNode.getChildNodes().item(j).getTextContent();
                        } else if (currNode.getChildNodes().item(j).getNodeName().equals("value")) {
                            value = Long.parseLong(currNode.getChildNodes().item(j).getTextContent());
                        }
                    if (value != -1L && seqName != null) {
                        try {
                            if (value <= 0)
                                value = 1; //make sure we have a valid value
                            FxSystemSequencer sseq = FxSystemSequencer.valueOf(seqName); //check if this is really a system sequencer
                            seq.setSequencerId(sseq.getSequencerName(), value);
                            LOG.info("Set sequencer [" + seqName + "] to [" + value + "]");
                        } catch (IllegalArgumentException e) {
                            LOG.error("Could not find system sequencer named [" + seqName + "]!");
                        }

                    }
                }
            }

            nodes = (NodeList) xPath.evaluate("/sequencers/usrsequence", document, XPathConstants.NODESET);
            boolean rollOver = false;
            for (int i = 0; i < nodes.getLength(); i++) {
                currNode = nodes.item(i);
                if (currNode.hasChildNodes()) {
                    seqName = null;
                    value = -1L;
                    for (int j = 0; j < currNode.getChildNodes().getLength(); j++)
                        if (currNode.getChildNodes().item(j).getNodeName().equals("name")) {
                            seqName = currNode.getChildNodes().item(j).getTextContent();
                        } else if (currNode.getChildNodes().item(j).getNodeName().equals("value")) {
                            value = Long.parseLong(currNode.getChildNodes().item(j).getTextContent());
                        } else if (currNode.getChildNodes().item(j).getNodeName().equals("rollover")) {
                            rollOver = "1".equals(currNode.getChildNodes().item(j).getTextContent());
                        }
                    if (value != -1L && seqName != null) {
                        seq.createSequencer(seqName, rollOver, value);
                        LOG.info("Created sequencer [" + seqName + "] with start value [" + value + "], rollover: "
                                + rollOver);
                    }
                }
            }
        } finally {
            Database.closeObjects(GenericDivisionImporter.class, stmt);
        }
    }
}