/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.queplix.core.modules.config.actions;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.Templates;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;

import org.apache.regexp.RE;
import org.apache.regexp.RESyntaxException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.jxb.entity.Chain;
import com.queplix.core.jxb.entity.ChainTable;
import com.queplix.core.jxb.entity.Chainref;
import com.queplix.core.jxb.entity.Chains;
import com.queplix.core.jxb.entity.Dataschema;
import com.queplix.core.jxb.entity.Dataschemas;
import com.queplix.core.jxb.entity.Dataset;
import com.queplix.core.jxb.entity.Dsref;
import com.queplix.core.jxb.entity.Efield;
import com.queplix.core.jxb.entity.Entity;
import com.queplix.core.jxb.entity.EntityConfig;
import com.queplix.core.jxb.entity.Fkeys;
import com.queplix.core.jxb.entity.Listref;
import com.queplix.core.jxb.entity.Nods;
import com.queplix.core.jxb.entity.Ref;
import com.queplix.core.jxb.entity.types.ControlSType;
import com.queplix.core.jxb.entity.types.DataSType;
import com.queplix.core.jxb.entity.types.RefSType;
import com.queplix.core.modules.config.ejb.EntityViewConfigManager;
import com.queplix.core.modules.config.ejb.EntityViewConfigManagerHome;
import com.queplix.core.modules.config.error.UnknownEfieldException;
import com.queplix.core.modules.config.error.UnknownEntityException;
import com.queplix.core.modules.config.utils.EntityDdobjectInfoVO;
import com.queplix.core.modules.config.utils.EntityFieldInfoVO;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.config.utils.EntitySchema;
import com.queplix.core.modules.config.utils.EntitySchemaFactory;
import com.queplix.core.modules.services.Action;
import com.queplix.core.utils.JNDINames;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.dao.AbstractPropertyFactory;
import com.queplix.core.utils.xml.TransletWrapper;
import com.queplix.core.utils.xml.XMLBinding;
import com.queplix.core.utils.xml.XMLFactory;
import com.queplix.core.utils.xml.XMLWrapper;


/**
 * <p>Action wich parses config XML files and fills EntityViewConfig EJBs</p>
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.2 $ $Date: 2005/12/27 18:47:15 $
 */

public class EntityInstallationAction
        extends Action {

    // constants
    public static final String CONFIG_FILES_EXTENSION = "xml";
    public static final String DATASCHEMA_SUB_FOLDER = "dataschema";
    public static final String CHAIN_SUB_FOLDER = "chain";
    public static final String ENTITY_XSL_RESOURCE = "xslt/entity.xsl";
    public static final String ENTITY_VAR = "\\$entity";
    public static final String DBOBJECT_VAR = "\\$dbobject";

    public static final String ENTITY_XML_DIRS_PARAM = "entityXmlDirs";

    // variables
    private Map<String, Entity> entityTable = new HashMap<String, Entity>();
    private List<String> entityNames = new ArrayList<String>();
    private Map<String, Dataschema> dataschemaTable
            = new HashMap<String, Dataschema>();
    private List<String> dataschemaNames = new ArrayList<String>();
    private List<Chain> chainList = new ArrayList<Chain>();

    private Map<String, EntityDdobjectInfoVO> dbInfoTable
            = new HashMap<String, EntityDdobjectInfoVO>();

    private XMLWrapper xmlWrapper;
    private XMLBinding xmlBind;


    // config params
    private Integer checkOnly;
    private String xmldir;
    private String logdir;
    private String xmlfile;

    private File entityDirFile;
    private File entityXslFile;
    private File entityOutXslFile;
    private File entityOutBindFile;
    private File entityOutDeployFile;
    private File entityXmlFile;


    // XSL translet implementation.
    private TransletWrapper transletWrapper;


    // initial entity in database
    private int INITIAL_ENTITY_SIZE = 0;


    // max entity config id
    private int ENTITY_COUNTER = 0;


    // current version
    private long CUR_VERSION = System.currentTimeMillis();

// --------------------- PUBLIC METHODS --------------------

    /*
     * No javadoc
     * @see Action#perform
     */

    public Serializable perform() {

        //
        // Read parameters
        //

        this.checkOnly = getContext().getParamAsInt("check");
        this.xmldir = getContext().getParamAsString("xmldir");
        this.logdir = getContext().getParamAsString("logdir");
        this.xmlfile = getContext().getParamAsString("xmlfile");

        String[] xmlDirs = (String[]) getContext().getParameter(
                ENTITY_XML_DIRS_PARAM);

        //
        // Checking
        //

        if(xmldir == null && (xmlDirs == null || xmlDirs.length == 0)) {
            throw new NullPointerException("Parameter XML dir is NULL");
        }

        init();

        initLogDirs();

        //
        // Config file initialization
        //
        if(xmldir != null && (xmlDirs == null || xmlDirs.length == 0)) {
            xmlDirs = new String[]{xmldir};
        }

        INFO("PROCESS STARTED...");
        long time = System.currentTimeMillis();


        for(int i = 0; i < xmlDirs.length; i++) {
            String xmlDir = xmlDirs[i];

            entityDirFile = new File(xmlDir);
            if(!entityDirFile.exists() || !entityDirFile.isDirectory()) {
                throw new IllegalStateException(
                        "Bad entity config dir: " + entityDirFile
                                .getAbsolutePath());
            }

//                if( checkOnly != null && checkOnly.intValue() > 0 ) {
//                    // do checking
//                    makeChecking();
//                    INFO( "Checking - ok" );
//
//                } else if( entityXmlFile != null ) {
//                    // do file process
//                    fileProcess();
//                    INFO( "Deploy process last(s): " + ( System.currentTimeMillis() - time ) / 1000 );
//
//                } else {
//                    // do global process
//                    globalProcess();
//                }

            parseEntityDir(entityDirFile);
            INFO("parse entity configs - ok");

            parseDataschemas();
            INFO("parse dataschema config - ok");

            parseChains();
            INFO("parse chain config - ok");

        }

        EntityConfig[] entityConfigs = parseEntities0();
        INFO("parse all entities - ok");

        buildEntityList(entityConfigs);
        INFO("build entity - ok");

        deployEntityViewConfigs();
        INFO("deploy entity configs - ok");

        INFO("Deploy process last(s): "
                + (System.currentTimeMillis() - time) / 1000);
        INFO("PROCESS DONE !!!");

//      if( xmlfile != null ) {
//      entityXmlFile = new File( entityDirFile, xmlfile );
//      if( !entityXmlFile.exists() || !entityXmlFile.isFile() ) {
//          throw new IllegalStateException( "Bad entity XML file: " +
//                                           entityXmlFile.getAbsolutePath() );
//      }

        return null;
    }

    private void initLogDirs() {
        if(logdir != null) {
            try {
                entityOutXslFile = new File(logdir, "entity-xsl");
                if(!entityOutXslFile.exists() || !entityOutXslFile
                        .isDirectory()) {
                    entityOutXslFile.mkdirs();
                }
            } catch (SecurityException ex) {
                entityOutXslFile = null;
                ERROR(ex);
            }

            try {
                entityOutBindFile = new File(logdir, "entity-bind");
                if(!entityOutBindFile.exists() || !entityOutBindFile
                        .isDirectory()) {
                    entityOutBindFile.mkdirs();
                }
            } catch (SecurityException ex) {
                entityOutBindFile = null;
                ERROR(ex);
            }

            try {
                entityOutDeployFile = new File(logdir, "entity-deploy");
                if(!entityOutDeployFile.exists() || !entityOutDeployFile
                        .isDirectory()) {
                    entityOutDeployFile.mkdirs();
                }
            } catch (SecurityException ex) {
                entityOutDeployFile = null;
                ERROR(ex);
            }
        }
    }


    private void init() {
        // get XML wrapper instance
        xmlWrapper = XMLFactory.getXMLWrapper();
        INFO("get XML wrapper: '" + xmlWrapper.getClass().getName() + "'");

        // get XSL translet wrapper instance
        transletWrapper = XMLFactory.getTransletWrapper();
        INFO("get XSL translet wrapper: '"
                + transletWrapper.getClass().getName() + "'");

        // get XML binder
        xmlBind = XMLFactory.getXMLBinding();
        INFO("get XML binding: '" + xmlBind.getClass().getName() + "'");
    }

// --------------------- PRIVATE METHODS -------------------

    /**
     * Main global function. Perform processing with XML configs
     */
    private void globalProcess() {

        EntityConfig[] entityConfigArray = parseEntities();
        INFO("parse entity config - ok");
        parseDataschemas();
        INFO("parse dataschema config - ok");
        parseChains();
        INFO("parse chain config - ok");
        buildEntityList(entityConfigArray);
        INFO("build entity - ok");
        deployEntityViewConfigs();
        INFO("deploy entity configs - ok");
    }


    /**
     * Main file function. Perform processing with one XML config
     */
    private void fileProcess() {

        loadEntities();
        INFO("load entity configs - ok");
        EntityConfig entityConfig = parseEntity(entityXmlFile);
        INFO("parse entity config - ok");
        parseDataschemas();
        INFO("parse dataschema config - ok");
        parseChains();
        INFO("parse chain config - ok");
        buildEntityList(new EntityConfig[]{entityConfig});
        INFO("build entity - ok");
        deployEntityViewConfigs();
        INFO("deploy entity configs - ok");
    }


    /**
     * Load entities from database
     */
    private void loadEntities() {

        try {
            Collection list = getEntityViewConfigManager()
                    .getEntityViewConfigs();
            Iterator it = list.iterator();
            while(it.hasNext()) {
                Entity entity = (Entity) it.next();
                String name = entity.getName();
                entityTable.put(name, entity);
                entityNames.add(name);

                // increment entity counters
                ENTITY_COUNTER++;
                INITIAL_ENTITY_SIZE++;
            }

        } catch (RemoteException ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "Remote exception: " + ex.getMessage(), ex);
        }
    }


    /**
     * Make checking confuig structure in database
     *
     * @return checking status (<code>true</code> - ok)
     */
    private boolean makeChecking() {

        boolean status = false;
        long time = System.currentTimeMillis();

        try {
            Collection list = getEntityViewConfigManager()
                    .getEntityViewConfigs();
            if(!list.isEmpty() && ((Entity) list.iterator().next()) != null) {
                status = true;
            }
            INFO("checking complete [count=" + list.size() + "]");

        } catch (Exception ex) {
            INFO("checking complete [error=" + ex.getMessage() + "]");
        }

        INFO("Checking process last: "
                + (System.currentTimeMillis() - time) / 1000 + ", status: "
                + status);

        return status;
    }


    /**
     * Parse entity xml configs and create array of EntityConfig objects
     *
     * @return array of EntityConfig objects
     */
    private EntityConfig[] parseEntities() {

        // get list of files
        File[] files = entityDirFile.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                String ext = CONFIG_FILES_EXTENSION;
                if(name.substring(name.length() - ext.length()).equals(ext)) {
                    return true;
                } else {
                    return false;
                }
            }
        });
        if(files.length == 0) {
            throw new IllegalStateException(
                    "Can't find any XML configs in path '" +
                            entityDirFile.getAbsolutePath() + "'");
        }

        // initialise array of EntityConfig objects
        EntityConfig[] entityConfigArray = new EntityConfig[files.length];

        // cycle read entity config XML files
        for(int i = 0; i < files.length; i++) {
            entityConfigArray[i] = parseEntity(files[i]);

        }
        return entityConfigArray;
    }


    /**
     * Parse entity xml config
     *
     * @param file XML config file
     * @return EntityConfig object
     */
    private EntityConfig parseEntity(File file) {

        String fileName = file.getName();
        EntityConfig entityConfig = null;

        try {
            // read entity config XML file
            INFO("  read from " + file.getCanonicalPath());

            // make XSLT
            ByteArrayOutputStream baos = (ByteArrayOutputStream)
                    transletWrapper.transform(entityXslFile, file, null);

            // write log
            if(entityOutXslFile != null) {
                try {
                    File outFile = new File(entityOutXslFile, fileName);
                    FileOutputStream fos = new FileOutputStream(outFile);
                    fos.write(baos.toByteArray());
                    fos.close();
                } catch (SecurityException ex) {
                    ERROR(ex);
                } catch (IOException ex) {
                    ERROR(ex);
                }
            }

            // build EntityConfig object (make binding)
            Document document = xmlWrapper.getDocument(new ByteArrayInputStream(
                    baos.toByteArray()), false);
            entityConfig = (EntityConfig) xmlBind.xmlToJava(EntityConfig.class,
                    document);

            // write log
            if(entityOutBindFile != null) {
                try {
                    File outFile = new File(entityOutBindFile, fileName);
                    FileOutputStream fos = new FileOutputStream(outFile);
                    xmlBind.javaToXml(entityConfig, new PrintWriter(
                            new OutputStreamWriter(fos, "UTF-8")));
                    fos.close();
                } catch (SecurityException ex) {
                    ERROR(ex);
                } catch (IOException ex) {
                    ERROR(ex);
                }
            }

        } catch (IOException ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "IO exception. Can't process parsing entity from file '" +
                            fileName + "': " + ex.getMessage(), ex);
        }

        return entityConfig;
    }


    private Map<Document, String> entityDocuments
            = new HashMap<Document, String>();
    private Map<String, Element> entityElements
            = new HashMap<String, Element>();
    private Map<String, Element> partialEntityElements
            = new HashMap<String, Element>();

    private EntityConfig[] parseEntities0() {

        processPartialEntities();

        List<EntityConfig> entityConfigs = new ArrayList<EntityConfig>();

        // Do entity transformation
        Templates templates = transletWrapper.loadTemplate(
                AbstractPropertyFactory.loadResourceAsStream(getClass(),
                        ENTITY_XSL_RESOURCE));
        DocumentBuilder builder = xmlWrapper.getDOMParser(false);

        for(Document document : entityDocuments.keySet()) {
            NodeList entities = document.getDocumentElement()
                    .getElementsByTagName(EntityHelper.ENTITY_TAG);
            if(entities.getLength() == 0) {
                continue;
            }

            DEBUG("Applying xslt transformation to xml document from '"
                    + entityDocuments.get(document) + "'");

            DOMResult result = new DOMResult(builder.newDocument());
            transletWrapper.transform(new DOMSource(document), result,
                    templates, null);

            EntityConfig entityConfig = (EntityConfig) xmlBind.xmlToJava(
                    EntityConfig.class, result.getNode());
            entityConfigs.add(entityConfig);
        }

        EntityConfig[] answer = (EntityConfig[]) entityConfigs.toArray(
                new EntityConfig[entityElements.size()]);

        entityElements.clear();
        entityDocuments.clear();

        return answer;
    }


    private void processPartialEntities() {
        List<String> partialNames = new ArrayList<String>(
                partialEntityElements.keySet());
        if(!partialNames.isEmpty()) {

            Collections.sort(partialNames);

            for(String partialName : partialNames) {
                INFO("Processing partial entity " + partialName);

                String[] names = StringHelper.split(partialName,
                        EntityHelper.NAME_SEPARATOR);
                String name = names[0];
                if(!entityElements.containsKey(name)) {
                    throw new GenericSystemException(
                            "Cannot process '" + partialName
                                    + "'. Parent entity '" + name
                                    + "' is undefined");
                }

                Element element = entityElements.get(name);
                boolean lastReached = false;
                for(int i = 1; i < names.length; i++) {
                    name = names[i];
                    boolean childFound = false;
                    lastReached = i == (names.length - 1);

                    NodeList childs = element.getElementsByTagName(
                            EntityHelper.ENTITY_TAG);
                    for(int j = 0; j < childs.getLength(); j++) {
                        Element _element = (Element) childs.item(j);
                        if(name.equals(_element.getAttribute(
                                EntityHelper.NAME_ATTRIBUTE))) {
                            element = _element;
                            childFound = true;
                            break;
                        }
                    }
                    if(!childFound && !lastReached) {
                        throw new GenericSystemException(
                                "Cannot find entity '" +
                                        StringHelper.join(Arrays.copyOfRange(
                                                names, 0, i + 1),
                                                EntityHelper.NAME_SEPARATOR) +
                                        "' to process partial entity '"
                                        + partialName + "'");
                    }
                }
                Element partialElem = partialEntityElements.get(partialName);
                partialElem = (Element) partialElem.getParentNode().removeChild(
                        partialElem);
                partialElem = (Element) element.getOwnerDocument().importNode(
                        partialElem, true);

                partialElem.setAttribute(EntityHelper.NAME_ATTRIBUTE,
                        names[names.length - 1]);

                if(lastReached) {
                    element.appendChild(partialElem);
                } else {
                    element.getParentNode().replaceChild(partialElem, element);
                }
            }
        }

        partialEntityElements.clear();
    }

    private void parseEntityDir(File entityDirFile) {
        INFO("Parse entity directory " + entityDirFile.getAbsolutePath());

        // get list of files
        File[] files = entityDirFile.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.substring(
                        name.length() - CONFIG_FILES_EXTENSION.length()).equals(
                        CONFIG_FILES_EXTENSION);
            }
        });
        String dirPath = entityDirFile.getAbsolutePath();
        if(files.length == 0) {
            //throw new IllegalStateException( "Can't find any XML configs in directory '" + dirPath + "'" );
            WARN("Can't find any XML configs in directory '" + dirPath + "'");
            return;
        }

        Map<String, Element> _entityNodes = new HashMap<String, Element>();
        Map<String, Element> _partialEntityNodes
                = new HashMap<String, Element>();

        // cycle read entity config XML files
        for(File file : files) {
            String filePath = file.getAbsolutePath();
            INFO("  read from " + filePath);

            Document document = xmlWrapper.getDocument(file, false);
            NodeList nodes = document.getDocumentElement().getChildNodes();
            for(int i = 0; i < nodes.getLength(); i++) {
                Node node = nodes.item(i);
                if(node.getNodeType() != Node.ELEMENT_NODE) {
                    continue;
                }

                Element element = (Element) node;
                String name = element.getAttribute(EntityHelper.NAME_ATTRIBUTE);

                if(name.contains(EntityHelper.NAME_SEPARATOR)) {
                    if(_partialEntityNodes.containsKey(name)) {
                        throw new GenericSystemException("Entity '" + name +
                                "' was found more than once in directory "
                                + dirPath);
                    }

                    _partialEntityNodes.put(name, element);
                } else {
                    if(_entityNodes.containsKey(name)) {
                        throw new GenericSystemException("Entity '" + name +
                                "' was found more than once in directory "
                                + dirPath);
                    }

                    _entityNodes.put(name, element);
                }
                entityDocuments.put(document, dirPath);
            }
        }

        collectEntityNodes(_entityNodes);
        collectPartialEntityNodes(_partialEntityNodes);
    }

    private void collectEntityNodes(Map<String, Element> elements) {
        for(String name : elements.keySet()) {
            if(entityElements.containsKey(name)) {
                Element oldElem = entityElements.get(name);

                Document oldDoc = oldElem.getOwnerDocument();
                String oldPath = entityDocuments.get(oldDoc);

                Document newDoc = elements.get(name).getOwnerDocument();
                String newPath = entityDocuments.get(newDoc);

                DEBUG(MessageFormat.format(
                        "Entity '{0}' from ({1}) will be replaced with entity from ({2})",
                        new Object[]{
                                name,
                                oldPath,
                                newPath
                        }));

                oldElem.getParentNode().removeChild(oldElem);
            }
        }
        entityElements.putAll(elements);
    }

    private void collectPartialEntityNodes(Map<String, Element> elements) {
        for(String name : elements.keySet()) {
            if(partialEntityElements.containsKey(name)) {
                Element oldElem = partialEntityElements.get(name);

                Document oldDoc = oldElem.getOwnerDocument();
                String oldPath = entityDocuments.get(oldDoc);

                Document newDoc = elements.get(name).getOwnerDocument();
                String newPath = entityDocuments.get(newDoc);

                DEBUG(MessageFormat.format(
                        "Partial entity '{0}' from ({1}) will be replaced with entity from ({2})",
                        new Object[]{
                                name,
                                oldPath,
                                newPath
                        }));

                oldElem.getParentNode().removeChild(oldElem);
            }
        }
        partialEntityElements.putAll(elements);
    }

    /**
     * Parse dataschema xml configs and fill the table of Dataschema objects
     */
    private void parseDataschemas() {
        File dataschemaDirFile = new File(entityDirFile, DATASCHEMA_SUB_FOLDER);
        if(!dataschemaDirFile.exists() || !dataschemaDirFile.isDirectory()) {
            return;
        }

        // get list of files
        File[] files = dataschemaDirFile.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                String ext = CONFIG_FILES_EXTENSION;
                if(name.substring(name.length() - ext.length()).equals(ext)) {
                    return true;
                } else {
                    return false;
                }
            }
        });

        try {
            // cycle read dataschema config XML files
            for(int i = 0; i < files.length; i++) {
                INFO("  read from " + files[i].getCanonicalPath());

                // build Dataschemas object
                Document document = xmlWrapper.getDocument(files[i], false);
                Dataschemas dss = (Dataschemas) xmlBind.xmlToJava(
                        Dataschemas.class, document);

                int dataschema_count = dss.getDataschemaCount();
                for(int j = 0; j < dataschema_count; j++) {
                    Dataschema ds = dss.getDataschema(j);
                    String name = ds.getName();

                    dataschemaNames.add(name);
                    dataschemaTable.put(name, ds);
                }
            }

        } catch (IOException ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "IO exception. Can't process parsing dataschemas: " +
                            ex.getMessage(), ex);
        }
    }


    /**
     * Parse chain xml configs and fill the table of Chain objects
     */
    private void parseChains() {
        File chainDirFile = new File(entityDirFile, CHAIN_SUB_FOLDER);
        if(!chainDirFile.exists() || !chainDirFile.isDirectory()) {
            return;
        }

        // get list of files
        File[] files = chainDirFile.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                String ext = CONFIG_FILES_EXTENSION;
                if(name.substring(name.length() - ext.length()).equals(ext)) {
                    return true;
                } else {
                    return false;
                }
            }
        });

        try {
            // cycle read chain config XML files
            for(int i = 0; i < files.length; i++) {
                INFO("  read from " + files[i].getCanonicalPath());

                // build Chains object
                Document document = xmlWrapper.getDocument(files[i], false);
                Chains chains = (Chains) xmlBind.xmlToJava(Chains.class,
                        document);

                int chain_count = chains.getChainCount();
                for(int j = 0; j < chain_count; j++) {
                    Chain chain = chains.getChain(j);
                    chainList.add(chain);
                }
            }

        } catch (IOException ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "IO exception. Can't process parsing chains: " +
                            ex.getMessage(), ex);
        }
    }


    /**
     * Build list of Entity object(s) and fill additional attributes
     *
     * @param entityConfigArray array of EntityConfig object(s)
     */
    private void buildEntityList(EntityConfig[] entityConfigArray) {

        for(int i = 0; i < entityConfigArray.length; i++) {
            buildEntity1(entityConfigArray[i]);
        }

        int entity_count = entityNames.size();

        DEBUG("BUILD PROCESS 1 - DONE");
        DEBUG("   initial entity size:" + INITIAL_ENTITY_SIZE);
        DEBUG("   current entity size:" + entity_count);

        for(int i = INITIAL_ENTITY_SIZE; i < entity_count; i++) {
            Entity entity = getEntityViewConfig((String) entityNames.get(i));
            buildEntity2(entity);
        }
        DEBUG("BUILD PROCESS 2 - DONE");

        for(int i = INITIAL_ENTITY_SIZE; i < entity_count; i++) {
            Entity entity = getEntityViewConfig((String) entityNames.get(i));
            buildEntity3(entity);
        }
        DEBUG("BUILD PROCESS 3 - DONE");

        for(int i = INITIAL_ENTITY_SIZE; i < entity_count; i++) {
            Entity entity = getEntityViewConfig((String) entityNames.get(i));
            buildEntity4(entity);
        }
        DEBUG("BUILD PROCESS 4 - DONE");

        for(int i = INITIAL_ENTITY_SIZE; i < entity_count; i++) {
            Entity entity = getEntityViewConfig((String) entityNames.get(i));
            buildEntity5(entity);
        }
        DEBUG("BUILD PROCESS 5 - DONE");

        for(int i = INITIAL_ENTITY_SIZE; i < entity_count; i++) {
            Entity entity = getEntityViewConfig((String) entityNames.get(i));
            buildEntity6(entity);
        }
        DEBUG("BUILD PROCESS 6 - DONE");
    }

    // #1:
    // fill attributes
    private void buildEntity1(EntityConfig entityConfig) {

        List<String> _entityNames = new ArrayList<String>();
        Map<String, Entity> _entityTable = new HashMap<String, Entity>();

        // cycle to select all Entity objects
        int entitySize = entityConfig.getEntityCount();
        for(int j = 0; j < entitySize; j++) {

            Entity entity = entityConfig.getEntity(j);
            String name = entity.getName();
            INFO("  load entity " + name);

            // add some specific Entity attributes
            driveEntityAttributes(entity);

            // sycle to select all Efield objects
            int efield_count = entity.getEfieldCount();
            for(int k = 0; k < efield_count; k++) {
                // add some specific Efield attribues
                driveEfieldAttributes(entity, entity.getEfield(k));
            }
/*
            if( entity.getPkeyfield() == null ) {
                throw new GenericSystemException(
                        "Entity '" + name + "' has no pkey.");                
            }
*/
            // check History attribute
            //driveHistoryAttribute( entity );

            if(_entityNames.contains(name)) {
                throw new GenericSystemException(
                        "Entity '" + name + "' was found more than once");
            }

            // add Entity to the temporary table
            _entityNames.add(name);
            _entityTable.put(name, entity);
        }

        // add temporary table to the table of entities
        entityNames.addAll(_entityNames);
        entityTable.putAll(_entityTable);
    }

    // #2:
    // update Efield Listref attributes
    private void buildEntity2(Entity entity) {
        // sycle to select all Efield objects
        for(int k = 0; k < entity.getEfieldCount(); k++) {
            // update some Efield attribues
            updateLrefEfieldAttributes(entity, entity.getEfield(k));
        }
    }


    // #3:
    // update Efield Ref attributes
    private void buildEntity3(Entity entity) {
        // sycle to select all Efield objects
        for(int k = 0; k < entity.getEfieldCount(); k++) {
            // update some Efield attribues
            updateRefEfieldAttributes(entity, entity.getEfield(k));
        }
    }


    // #4:
    // update Entity attributes
    private void buildEntity4(Entity entity) {
        // update some Entity attribues
        updateEntityAttributes(entity);
    }


    // #5:
    // build chains between entities
    private void buildEntity5(Entity entity1) {

        String e1 = entity1.getName();
        int entity_count = entityNames.size();

        // cycle to select all Entity objects
        for(int i = 0; i < entity_count; i++) {
            String e2 = (String) entityNames.get(i);

            // check are these entities relatives
            if(EntityHelper.isChild(e1, e2)) {
                continue;
            }
            if(EntityHelper.isChild(e2, e1)) {
                continue;
            }

            // check does Chain object exist
            ChainTable chainTab = getChainTable(e1, e2);
            if(chainTab != null) {
                // find !!!
                entity1.addChainTable(chainTab);
            }
        }
    }


    // #6:
    // update Dataset attributes
    // set fkeys elements
    private void buildEntity6(Entity entity) {
        // cycle to select all Dataset objects
        for(int k = 0; k < entity.getDatasetCount(); k++) {
            // update some Dataset attribues
            updateDatasetAttributes(entity, entity.getDataset(k));
        }

        // add db fkeys Entity attribues
        driveEntityFkeysAttributes(entity);
    }


    /**
     * Add some attributes into the Entity object
     *
     * @param entity Entity object
     */
    private void driveEntityAttributes(Entity entity) {

        // version id
        entity.setVersionId(new Long(CUR_VERSION));

        // build dbobjAlias attribute
        String dbobjAlias = "A" + ENTITY_COUNTER;
        entity.setDbobjAlias(dbobjAlias);

        ENTITY_COUNTER++;
    }


    /**
     * Check History attribute
     *
     * @param entity Entity object
     */
    private void driveHistoryAttribute(Entity entity) {

        String historyFieldName = entity.getHistoryfield();
        if(!StringHelper.isEmpty(historyFieldName)) {
            // Find the 'history' field.
            try {
                Efield historyField = EntityHelper.getEfield(historyFieldName,
                        entity);
                if(historyField.getDatatype() != DataSType.MEMO) {
                    throw new GenericSystemException("Field " + historyFieldName
                            + " could not be used as a History field in Entity '"
                            + entity.getName());
                } else {
                    entity.setHasHistoryFields(Boolean.TRUE);
                }
            } catch (UnknownEfieldException ex) {
                WARN(ex.getMessage());
            }
        }
    }

    /**
     * Add DB fkeys attributes into the Entity object
     *
     * @param entity Entity object
     */
    private void driveEntityFkeysAttributes(Entity entity) {

        if(entity.getInitialized().booleanValue()) {
            // entity already initialized - no use to find fkeys
            return;
        }

        // get table attributes
        EntityDdobjectInfoVO dbobjectInfo = getEntityDdobjectInfo(entity);

        // joined list
        List<Fkeys> joinedFkeyList = new ArrayList<Fkeys>();
        // array of already exists fkeys
        Fkeys[] existsFkeys = entity.getFkeys();

        // add found elements
        int size = dbobjectInfo.getFkeySize();
        for(int i = 0; i < size; i++) {
            EntityDdobjectInfoVO.FK foundFkey
                    = (EntityDdobjectInfoVO.FK) dbobjectInfo.getFkey(i);

            Fkeys newFkey = new Fkeys();
            newFkey.setFkTable(foundFkey.getFkDbobjectName());
            newFkey.setFkColumn(foundFkey.getFkDbcolumnName());
            newFkey.setPkColumn(foundFkey.getPkDbcolumnName());
            joinedFkeyList.add(newFkey);
        }

        // add exists elements
        for(int i = 0; i < existsFkeys.length; i++) {
            Fkeys existsFkey = existsFkeys[i];
            boolean hasFkEntity = (existsFkey.getFkEntity() != null);
            boolean hasFkTable = (existsFkey.getFkTable() != null);
            boolean hasFkColumn = (existsFkey.getFkColumn() != null);
            boolean hasPkColumn = (existsFkey.getPkColumn() != null);

            if(!hasFkTable) {
                // set foreign table name
                if(!hasFkEntity) {
                    throw new GenericSystemException(
                            "Please specify fkEntity or fkTable for fkey of entity '"
                                    +
                                    entity.getName() + "'");
                }

                Entity fkEntity = getEntityViewConfig(existsFkey.getFkEntity());
                existsFkey.setFkTable(fkEntity.getDbobject());
            }

            // add attributes got from DB
            size = joinedFkeyList.size();
            for(int j = 0; j < size; j++) {
                Fkeys _tmp = (Fkeys) joinedFkeyList.get(j);

                if(existsFkey.getFkTable().equalsIgnoreCase(_tmp.getFkTable())
                        &&
                        (!hasFkColumn || existsFkey.getFkColumn()
                                .equalsIgnoreCase(_tmp.getFkColumn())) &&
                        (!hasPkColumn || existsFkey.getPkColumn()
                                .equalsIgnoreCase(_tmp.getPkColumn()))) {

                    // exists. fill non mandatory attributes if needed
                    if(!hasFkColumn) {
                        existsFkey.setFkColumn(_tmp.getFkColumn());
                    }
                    if(!hasPkColumn) {
                        existsFkey.setPkColumn(_tmp.getPkColumn());

                        // remove old
                    }
                    joinedFkeyList.remove(j);
                    break;
                }
            }

            // add exists fkey in the end of list
            joinedFkeyList.add(existsFkey);
        }

        // remove all fkeys
        entity.clearFkeys();

        // add all fkeys from joined list
        size = joinedFkeyList.size();
        for(int i = 0; i < size; i++) {
            Fkeys fkey = (Fkeys) joinedFkeyList.get(i);

            if(fkey.getFkColumn() == null) {
                throw new GenericSystemException(
                        "Please specify 'fkColumn' for fkey." +
                                " Entity '" + entity.getName() + "'" +
                                " FkTableName '" + fkey.getFkTable() + "'");
            }
            if(fkey.getPkColumn() == null) {
                throw new GenericSystemException(
                        "Please specify 'pkColumn' for fkey." +
                                " Entity '" + entity.getName() + "'" +
                                " FkTableName '" + fkey.getFkTable() + "'");
            }
            entity.addFkeys(fkey);
        }
    }


    /**
     * Update (add) some Entity object attributes
     *
     * @param entity Entity object
     */
    private void updateEntityAttributes(Entity entity) {

        // build eql-constraint attribute
        String eqlConstraint = entity.getEqlConstraint();
        if(!StringHelper.isEmpty(eqlConstraint)) {
            entity.setEqlConstraint(makeVariableSubstitution(entity,
                    eqlConstraint));
        }

        // build eql-where attribute
        String eqlWhere = entity.getEqlWhere();
        if(!StringHelper.isEmpty(eqlWhere)) {
            entity.setEqlWhere(makeVariableSubstitution(entity, eqlWhere));
        }

        // build eql-order attribute
        String eqlOrder = entity.getEqlOrder();
        if(!StringHelper.isEmpty(eqlOrder)) {
            entity.setEqlOrder(makeVariableSubstitution(entity, eqlOrder));
        }

        // check cached entity
        if(entity.getCache().booleanValue()) {
            DEBUG("       Entity " + entity.getName() + " is cached...");

            Efield field1 = entity.getEfield(0);
            if(!field1.getPkey().booleanValue()) {
                throw new GenericSystemException(
                        "First field should be a public key. Entity " +
                                entity.getName());
            }
        }
    }


    /**
     * Add common attributes into the Efield object using Entity object
     *
     * @param entity Entity object
     * @param efield Efield object
     */
    private void driveEfieldAttributes(Entity entity, Efield efield) {

        String efieldName = efield.getName();
        String entityName = entity.getName();

        // check is history
//        if( efieldName.equals( HistoryConstants.HISTORY_FIELD ) ) {
//            // set hasHistoryFields attribute for entity
//            entity.setHasHistoryFields( Boolean.TRUE );
//        }

        // build id attr
        String id = EntityHelper.getFieldId(entityName, efieldName);
        efield.setId(id);

        // build entityName attr
        efield.setEntityName(entityName);

        String eqlSrc = efield.getEqlSrc();
        if(!StringHelper.isEmpty(eqlSrc)) {
            //
            // field has 'eql-src' attribute
            //
            efield.setVirtual(Boolean.TRUE);
            efield.setUpdatable(Boolean.FALSE);
            efield.setEqlSrc(makeVariableSubstitution(entity, eqlSrc));

        } else if(efield.getRef() != null) {
            //
            // field has Ref object
            //
            efield.setVirtual(Boolean.TRUE);
            efield.setUpdatable(Boolean.FALSE);

        } else {
            //
            // field exists in table !!!
            //
            boolean entityInitialized = entity.getInitialized().booleanValue();
            efield.setVirtual(Boolean.FALSE);

            if(!entityInitialized) {

                // add column attributes (only for non reference fields) using meta data
                EntityFieldInfoVO dbInfo = getEntityFieldInfo(entity, efield);
                if(dbInfo == null) {
                    throw new GenericSystemException(
                            "Cannot find info for field '" +
                                    efield.getId() + "'");
                }

                // build 'sqltype' attr
                if(efield.getSqltype() == null) {
                    efield.setSqltype(dbInfo.getSqlType());
                }

                // build 'datasize' attr
                if(efield.getDatasize() == null) {
                    efield.setDatasize(new Integer(dbInfo.getColumnSize()));
                }

                // build 'nullable' attribute
                if(efield.getNullable() == null) {
                    efield.setNullable(new Boolean(dbInfo.isIsNullable()));
                }

                // set pkey for efield from DB
/*
                EntityDdobjectInfoVO dbobjectInfo = getEntityDdobjectInfo(
                        entity);
                int pkeySize = dbobjectInfo.getPkeySize();
                for(int i = 0; i < pkeySize; i++) {
                    String pkey = dbobjectInfo.getPkey(i);
                    if(efieldName.equalsIgnoreCase(pkey)) {
                        efield.setPkey(Boolean.TRUE);
                        entity.setPkeyfield(efieldName);
                        break;
                    }
                }
*/
            }
        }

        // first check efields that don't have Ref
        // because method #updateRefEfieldAttributes will initialize some attrs later
        if(efield.getRef() == null) {
            // check 'sqltype'
            if(efield.getSqltype() == null) {
                // .. throw exception
                throw new GenericSystemException(
                        "Please specify 'sqltype' for field '" +
                                efield.getId() + "'");
            }

            // check 'datasize'
            if(efield.getDatasize() == null) {
                // .. print warning
                WARN("Datasize of field '" + efield.getId() + "' is empty!");
            }
        }

        // build 'hasHistoryField' attribute
        if(efield.getControl() != null) {
            if(efield.getControl().getType() == ControlSType.HISTORY_TYPE) {
                entity.setHasHistoryFields(Boolean.TRUE);
                entity.setHistoryfield(efield.getName());
            }
        }

        // build 'nullable' attribute
        if(efield.getNullable() == null) {
            // .. set to false if empty
            efield.setNullable(Boolean.TRUE);
        }

        // build 'eql-defsrc' attr
        String defEqlSrc = efield.getEqlDefsrc();
        if(!StringHelper.isEmpty(defEqlSrc)) {
            efield.setEqlDefsrc(makeVariableSubstitution(entity, defEqlSrc));
        }

        // build 'datatype' attr
        if(efield.getDatatype() == null) {
            if(efield.getSqltype() != null) {
                efield.setDatatype(DataSType.valueOf(
                        efield.getSqltype().toString()));
            }
        }

        // set 'required' to ~'nullable' if not defined
        if(efield.getRequired() == null) {
            efield.setRequired(new Boolean(
                    !efield.getNullable().booleanValue()));
        }

        // build 'pkey' attribute
        if(efield.getPkey() == null) {
            efield.setPkey(Boolean.FALSE);
        } else {
            if(entity.getPkeyfield() == null) {
                entity.setPkeyfield(efield.getName());
            } else {
                throw new GenericSystemException("Found more than one pkey " +
                    "in entity '" + entityName + "'");
            }
        }

        // check is field name unique
        if(entity.getObject(efieldName) != null) {
            throw new GenericSystemException("Duplicate name '" + efieldName +
                    "' in entity '" + entityName + "'");
        }

        // add in name list
        entity.putObject(efieldName, efield);
    }


    /**
     * Update (add) Efield object attributes using Ref object
     *
     * @param entity Entity object
     * @param efield Efield object
     */
    private void updateRefEfieldAttributes(Entity entity, Efield efield) {

        Ref ref = efield.getRef();
        if(ref == null) {
            return;
        }

        String e1 = entity.getName();
        String f1 = efield.getName();
        DEBUG("*** Update Ref. Entity " + e1 + ". Field " + f1);

        //
        // update Ref object
        //
        Entity entity2 = getEntityViewConfig(ref.getEntity());
        String f2 = ref.getEfield();
        Efield field2 = EntityHelper.getEfield(f2, entity2);

        // check ref field
        if(field2.getVirtual().booleanValue()) {
            throw new GenericSystemException(
                    "Ref field '" + f2 + "' must be none virtual. Entity '" +
                            e1 + "'. Field '" + f1 + "'");
        }

        // copy undefined attributes from reference field
        xmlBind.copyUndefinedAttributes(field2, efield, Efield.class);

        // copy Listref object
        if(field2.getListref() != null) {
            efield.setListref((Listref) xmlBind.clone(field2.getListref(),
                    Listref.class));
        }
    }


    /**
     * Update (add) Efield object attributes using Listref object
     *
     * @param entity Entity object
     * @param efield Efield object
     */
    private void updateLrefEfieldAttributes(Entity entity, Efield efield) {

        if(efield.getRef() != null) {
            return;
        }

        Listref lref = efield.getListref();
        if(lref == null) {
            return;
        }

        //
        // update Listref object
        //
        String e1 = entity.getName();
        String db1 = entity.getDbobject();
        String f1 = efield.getName();

        String e2 = lref.getEntity();
        Entity entity2 = getEntityViewConfig(e2);
        String db2 = entity2.getDbobject();
        String f2 = lref.getEfield();
        if(f2 == null) {
            f2 = entity2.getListfield();
        }
        if(f2 == null) {
            throw new GenericSystemException(
                    "Cannot find list field. Entity '" +
                            e1 + "'. Field '" + f1 + "'");
        }

        // check listref field
        Efield field2 = EntityHelper.getEfield(f2, entity2);
        if(field2.getVirtual().booleanValue()) {
            throw new GenericSystemException(
                    "List field '" + f2 + "' should be none virtual. Entity '" +
                            e1 + "'. Field '" + f1 + "'");
        }

        // set 'efield' attr
        lref.setEfield(f2);

        // set Dataschema object
        String refName = lref.getRefname();
        Dataschema ds = getDataschema(db1, f1, db2, refName);
        if(ds == null) {
            throw new GenericSystemException(
                    "Cannot find dataschema for entities '" + e1 +
                            "' and '" + e2 + "' with name '" + refName + "'");
        }
        lref.setDataschema(ds);

        // set 'refname' attr
        if(refName == null) {
            lref.setRefname(ds.getName());
        }
    }


    /**
     * Update (add) Dataset object attributes
     *
     * @param entity  Entity object
     * @param dataset Dataset object
     */
    private void updateDatasetAttributes(Entity entity, Dataset dataset) {

        String entityName = entity.getName();
        String dbobject = entity.getDbobject();
        String datasetName = dataset.getName();
        String refEntityName = dataset.getEntity();
        String refName = dataset.getRefname();
        Entity refEntity = getEntityViewConfig(refEntityName);
        String refDbobject = refEntity.getDbobject();

        // build id attr
        String id = EntityHelper.getDatasetId(entity, dataset);
        dataset.setId(id);

        DEBUG("   update dataset '" + id + "'");

        // set 'required' to false if not defined
        if(dataset.getRequired() == null) {
            dataset.setRequired(Boolean.FALSE);
        }

        // set parent entity name
        dataset.setEntityName(entityName);

        // find suited dataschema
        Dataschema ds = null;

        if(!StringHelper.isEmpty(refName)) {
            // get dataschema by the 'refname' attr
            ds = getDataschema(dbobject, null, refDbobject, refName);
            if(ds == null) {
                throw new GenericSystemException(
                        "Cannot find dataschema between '" +
                                dbobject + "' and '" + refDbobject +
                                "' tables by the name '" + refName + "'");
            }

        } else {
            // try to find suited chain
            ChainTable chtab = EntityHelper.getChainTable(refEntityName,
                    entity);
            if(chtab == null) {
                throw new GenericSystemException("Cannot find chain between '" +
                        refEntityName + "' and '" +
                        entityName + "' entites");
            }

            ds = chtab.getDataschema();
        }

        // set Dataschema element
        dataset.setDataschema(ds);

        // check is dataset name unique
        if(entity.getObject(datasetName) != null) {
            throw new GenericSystemException("Duplicate name '" + datasetName +
                    "' in entity '" + entityName + "'");
        }

        // add in name list
        entity.putObject(datasetName, dataset);

        // set EQL query
        Entity datasetEntity = getEntityViewConfig(dataset.getEntity());
        StringBuffer eql = new StringBuffer();
        eql.append("SELECT ");
        eql.append(datasetEntity.getName());
        eql.append(".* WHERE ");

        String[] datasetKeys = ds.getTable(1).getKey();
        for(int i = 0; i < datasetKeys.length; i++) {
            if(i > 0) {
                eql.append(" AND ");

            }
            Efield datasetEfield = EntityHelper.getEfieldBySrc(datasetKeys[i],
                    datasetEntity);
            eql.append(datasetEfield.getId());
            eql.append(" = ?");
        }

        dataset.setEql(eql.toString());

        // create fkey for this dataschema
        // find exists Fkey
        Fkeys fkeys = null;
        int size = entity.getFkeysCount();
        for(int i = 0; i < size; i++) {
            Fkeys _fkeys = entity.getFkeys(i);
            if(refDbobject.equalsIgnoreCase(_fkeys.getFkTable()) ||
                    refEntityName.equalsIgnoreCase(_fkeys.getFkEntity())) {
                // find!
                fkeys = _fkeys;
                break;
            }
        }

        if(fkeys == null) {
            // create new one
            fkeys = new Fkeys();
            fkeys.setCascadeDelete(Boolean.TRUE);
            entity.addFkeys(fkeys);
        }

        // set foreign entity name
        fkeys.setFkEntity(refEntityName);
    }


    /**
     * Get chain between two entities and return ChainTable object
     *
     * @param e1 entity 1 name
     * @param e2 entity 2 name
     * @return ChainTable object
     */
    private ChainTable getChainTable(String e1, String e2) {

        ChainTable chainTab = null;

        Entity entity1 = getEntityViewConfig(e1);
        Entity entity2 = getEntityViewConfig(e2);
        String dbobj1 = entity1.getDbobject();
        String dbobj2 = entity2.getDbobject();

        for(int i = 0; i < chainList.size(); i++) {
            Chain chain = (Chain) chainList.get(i);
            String _e1 = chain.getE1();
            String _e2 = chain.getE2();

            if(!(e1.equals(_e1) && e2.equals(_e2)) &&
                    !(e2.equals(_e1) && e1.equals(_e2))) {
                continue;
            }

            chainTab = new ChainTable();
            chainTab.setEntity(e2);

            Dsref dsref = chain.getDsref();
            Chainref chainRef = chain.getChainref();
            Nods noDs = chain.getNods();

            if(dsref != null) {
                // we have Dsref objects in Chain
                String name = dsref.getName();
                Object ref = dataschemaTable.get(name);
                if(ref == null) {
                    throw new GenericSystemException(
                            "Cannot find dataschema by the chain name '" +
                                    name + "'");
                }

                Dataschema ds = (Dataschema) ref;
                String t1 = ds.getTable(0).getName();
                String t2 = ds.getTable(1).getName();

                if(dbobj1.equals(t1) && dbobj2.equals(t2) && t1.equals(t2)) {
                    if(e2.equals(_e1) && e1.equals(_e2)) {
                        ds = invertDataschema(ds);
                    }
                } else if(dbobj1.equals(t1) && dbobj2.equals(t2)) {
                    // direct order
                    // (do nothing)

                } else if(dbobj1.equals(t2) && dbobj2.equals(t1)) {
                    // invert direct order
                    ds = invertDataschema(ds);

                } else {
                    // error (unknown table)
                    throw new GenericSystemException(
                            "Incorrect t1='" + t1 + "' and t2='" + t2 +
                                    "' attributes found in chain '" + name
                                    + "'");
                }
                chainTab.setDataschema(ds);

                // remember name
                chainTab.putObject(ds.getName(), ds);

            } else if(chainRef != null) {
                // we have Chainref object in Chain
                chainTab.setChainref(chainRef);

            } else {
                // we don't have physical links between entities
                chainTab.setNods(noDs);
            }

            break;
        }

        return chainTab;
    }


    /**
     * Get dataschema between two dbobjects
     *
     * @param db1    dbobject attribute of entity 1
     * @param src1   Efield object src attribute taken from entity 1 (can be NULL)
     * @param db2    dbobject attribute of entity 2
     * @param dsName dataschema name (can be NULL)
     * @return Dataschema object
     */
    private Dataschema getDataschema(String db1, String src1, String db2,
                                     String dsName) {

        Dataschema ds = null;

        // check input params
        if(src1 == null && dsName == null) {
            throw new GenericSystemException(
                    "Please specify 'src1' or 'dsName' attributes");
        }

        Iterator iter = dataschemaNames.iterator();
        while(iter.hasNext()) {
            String name = (String) iter.next();

            Dataschema dataschema = (Dataschema) dataschemaTable.get(name);
            String _dbobject1 = dataschema.getTable(0).getName();
            String _dbobject2 = dataschema.getTable(1).getName();
            String[] _keys1 = dataschema.getTable(0).getKey();
            String[] _keys2 = dataschema.getTable(1).getKey();

            if(dsName != null && !dsName.equals(name)) {
                continue;
            }

            if(db1.equalsIgnoreCase(_dbobject1) &&
                    db2.equalsIgnoreCase(_dbobject2) &&
                    (dsName != null || src1.equalsIgnoreCase(_keys1[0]))) {

                /** @todo search src1 in entire array _keys1 */

                // right order
                ds = dataschema;
                break;

            } else if(db2.equalsIgnoreCase(_dbobject1) &&
                    db1.equalsIgnoreCase(_dbobject2) &&
                    (dsName != null || src1.equalsIgnoreCase(_keys2[0]))) {

                // reverse order
                ds = invertDataschema(dataschema);
                break;
            }
        }
        return ds;
    }


    /**
     * Get EntitySchema object
     *
     * @param entity Entity object
     * @return EntitySchema
     */
    private EntitySchema getEntitySchema(Entity entity) {
        String schema = entity.getDbschema();
        if(schema == null) {
            return EntitySchemaFactory.getInstance().get();
        } else {
            return EntitySchemaFactory.getInstance().get(schema);
        }
    }


    /**
     * Get DB object information from schema
     *
     * @param entity Entity object
     * @return EntityDdobjectInfoVO object
     */
    private EntityDdobjectInfoVO getEntityDdobjectInfo(Entity entity) {

        if(entity.getInitialized().booleanValue()) {
            throw new IllegalStateException(
                    "Entity '" + entity.getName() + "' already initialized...");
        }

        String dbobject = entity.getDbobject();
        EntityDdobjectInfoVO tableInfo = (EntityDdobjectInfoVO) dbInfoTable.get(
                dbobject);
        if(tableInfo == null) {
            tableInfo = getEntitySchema(entity).getEntityDdobjectInfo(dbobject);
            dbInfoTable.put(dbobject, tableInfo);
        }
        return tableInfo;
    }


    /**
     * Get entity field information from schema
     *
     * @param entity Entity object
     * @param efield Efield object
     * @return EntityFieldInfoVO object
     */
    private EntityFieldInfoVO getEntityFieldInfo(Entity entity, Efield efield) {

        String dbcolumn = efield.getName();

        EntityDdobjectInfoVO dbobjectInfo = getEntityDdobjectInfo(entity);
        for(int i = 0; i < dbobjectInfo.getFieldSize(); i++) {
            EntityFieldInfoVO entityFieldInfo = dbobjectInfo.getField(i);
            if(dbcolumn.equalsIgnoreCase(entityFieldInfo.getDbcolumnName())) {
                return entityFieldInfo;
            }
        }
        return null;
    }


    /**
     * Invert dataschema table order
     *
     * @param ds Dataschema object
     * @return inverting dataschema
     */
    private Dataschema invertDataschema(Dataschema ds) {

        Dataschema newds = new Dataschema();
        xmlBind.copyAttributes(ds, newds, Dataschema.class);
        newds.addTable(ds.getTable(1));
        newds.addTable(ds.getTable(0));

        if(ds.getReftype().toString().equals("1:n")) {
            newds.setReftype(RefSType.valueOf("n:1"));
        } else if(ds.getReftype().toString().equals("n:1")) {
            newds.setReftype(RefSType.valueOf("1:n"));

        }
        return newds;
    }


    /**
     * Deploy entity view configs as Entity EJBs
     */
    private void deployEntityViewConfigs() {

        Entity[] entities = null;

        try {
            if(INITIAL_ENTITY_SIZE == 0) {
                // deploy all entities
                entities = (Entity[]) entityTable.values().toArray(
                        new Entity[0]);
                getEntityViewConfigManager().fillEntityViewConfigs(entities);

            } else {
                // deploy only new entities
                EntityViewConfigManager entityViewConfigManager
                        = getEntityViewConfigManager();
                int entity_count = entityNames.size();
                entities = new Entity[entity_count - INITIAL_ENTITY_SIZE];

                int count = 0;
                for(int i = INITIAL_ENTITY_SIZE; i < entity_count; i++) {
                    String entityName = (String) entityNames.get(i);
                    Entity entity = getEntityViewConfig(entityName);

                    entityViewConfigManager.deleteEntityViewConfig(entityName);
                    entityViewConfigManager.createEntityViewConfig(entity);

                    entities[count] = entity;
                    count++;
                }
            }

            if(entityOutDeployFile != null) {
                // write log
                try {
                    for(int i = 0; i < entities.length; i++) {
                        Entity entity = entities[i];
                        String name = entity.getName();

                        File outFile = new File(entityOutDeployFile,
                                name + ".xml");
                        FileOutputStream fos = new FileOutputStream(outFile);
                        xmlBind.javaToXml(entity, new PrintWriter(
                                new OutputStreamWriter(fos, "UTF-8")));
                        fos.close();
                    }
                } catch (SecurityException ex) {
                    ERROR(ex);
                } catch (IOException ex) {
                    ERROR(ex);
                }
            }

        } catch (RemoteException ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "Remote exception. Cannot deploy entity configs: " +
                            ex.getMessage(), ex);
        }
    }


    /**
     * Remove all variables ($entity, etc.) from string <code>s</code>
     *
     * @param entity Entity object
     * @param s      string
     * @return new string
     */
    private String makeVariableSubstitution(Entity entity, String s) {
        return makeVariableSubstitution(entity.getName(), entity.getDbobject(),
                s);
    }


    //
    // Substitute all variables
    //
    private String makeVariableSubstitution(String entityName, String dbObject,
                                            String s) {
        String ret = s;
        ret = makeEntityVariableSubstitution(entityName, ret);
        ret = makeDbObjectVariableSubstitution(dbObject, ret);
        return ret;
    }


    //
    // Substitute $entity variable
    //
    private String makeEntityVariableSubstitution(String entityName, String s) {
        try {
            return new RE(ENTITY_VAR).subst(s, entityName, RE.REPLACE_ALL);
        } catch (RESyntaxException ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "Cannot replace '" + ENTITY_VAR + "' variable", ex);
        }
    }


    //
    // Substitute $dbobject variable
    //
    private String makeDbObjectVariableSubstitution(String dbObject, String s) {
        try {
            return new RE(DBOBJECT_VAR).subst(s, dbObject, RE.REPLACE_ALL);
        } catch (RESyntaxException ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "Cannot replace '" + DBOBJECT_VAR + "' variable", ex);
        }
    }


    /**
     * Get Entity value object by the name
     *
     * @param entityName entity name
     * @return Entity object
     */
    private Entity getEntityViewConfig(String entityName) {

        Object o = entityTable.get(entityName);
        if(o == null) {
            throw new UnknownEntityException(entityName);
        }

        return (Entity) o;
    }


    /**
     * Get EntityViewConfigManager EJB reference
     *
     * @return EntityViewConfigManager remote interface
     */
    private EntityViewConfigManager getEntityViewConfigManager() {
        return (EntityViewConfigManager)
                getContext().getCOM().getRemoteObject(
                        JNDINames.EntityViewConfigManagerRemote,
                        EntityViewConfigManagerHome.class);
    }
}
