/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: RdbMappingBuilder.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */


package org.ow2.jonas.lib.ejb21.jorm;

import java.util.ArrayList;
import java.util.Iterator;

import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.ejb.BeanDesc;
import org.ow2.jonas.deployment.ejb.DeploymentDescEjb2;
import org.ow2.jonas.deployment.ejb.EjbRelationDesc;
import org.ow2.jonas.deployment.ejb.EjbRelationshipRoleDesc;
import org.ow2.jonas.deployment.ejb.EntityJdbcCmp2Desc;
import org.ow2.jonas.deployment.ejb.FieldJdbcDesc;
import org.ow2.jonas.lib.util.Log;



import org.objectweb.jorm.api.PException;
import org.objectweb.jorm.metainfo.api.Class;
import org.objectweb.jorm.metainfo.api.ClassProject;
import org.objectweb.jorm.metainfo.api.ClassRef;
import org.objectweb.jorm.metainfo.api.CompositeName;
import org.objectweb.jorm.metainfo.api.GenClassRef;
import org.objectweb.jorm.metainfo.api.Manager;
import org.objectweb.jorm.metainfo.api.NameDef;
import org.objectweb.jorm.metainfo.api.NameRef;
import org.objectweb.jorm.metainfo.api.PrimitiveElement;
import org.objectweb.jorm.metainfo.api.ScalarField;
import org.objectweb.jorm.metainfo.api.PrimitiveElementMapping;
import org.objectweb.jorm.metainfo.lib.JormManager;
import org.objectweb.jorm.metainfo.lib.MetaInfoPrinter;
import org.objectweb.jorm.type.api.PType;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbMappingPrinter;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbMappingFactory;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbMapping;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbClassMultiMapping;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbTable;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbGenClassMapping;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbExternalTable;
import org.objectweb.jorm.mapper.rdb.metainfo.RdbJoin;
import org.objectweb.util.monolog.api.Logger;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.wrapper.printwriter.PrintStreamImpl;

/**
 * Class to hold meta-information for Jorm
 * @author Sebastien Chassande-Barrioz for INRIA : Initial developper
 * @author Philippe Durieux
 */
public class RdbMappingBuilder {

    /**
     * Logger for tracing
     */
    private Logger logger = null;


    /**
     * The JORM meta-information manager
     */
    private JormManager manager = null;

    /**
     * JOnAS map Entity beans on relational Databases :
     * The JORM mapper is "rdb".
     * It could be a good idea to put all the mapping information in another Class
     */
    public static final String MAPPER_NAME = "rdb";

    /**
     * Jorm need a project name to store the mapping information (Jorm Meta Information)
     */
    public static final String PROJECT_NAME = "jonas";

    /**
     * MetaInfo printer (debug)
     */
    private PrintStreamImpl pstream;
    private MetaInfoPrinter mip;

    private ArrayList cmp2List = new ArrayList();

    /**
     * Constructor
     * @param dd Deployment Descriptor for the ejb-jar
     * @throws DeploymentDescException Cannot build Jorm Meta Information
     */
    public RdbMappingBuilder(DeploymentDescEjb2 dd) throws DeploymentDescException {
        logger = Log.getLogger("org.ow2.jonas.lib.ejb21.mijorm");
        pstream = new PrintStreamImpl(this.logger);
        mip = new MetaInfoPrinter();
        mip.addMappingPrinter(new RdbMappingPrinter());

        // Init a Jorm manager mapped on Rdb. Rdb is the only mapping
        // supported by JOnAS today.
        //System.out.println("Build JORM Meta Information");
        manager = new JormManager();
        manager.setLogger(logger);
        manager.init();
        manager.addMappingFactory("rdb", new RdbMappingFactory());

        try {
            // Create the jorm meta information for entity beans CMP2
            for (Iterator i = dd.getBeanDescIterator(); i.hasNext(); ) {
                BeanDesc bd = (BeanDesc) i.next();
                if (bd instanceof EntityJdbcCmp2Desc) {
                    CMP2Bean cmp2 = createJormEntityMI((EntityJdbcCmp2Desc) bd);
                    cmp2List.add(cmp2);
                }
            }

            // Create the jorm meta information for Relations
            for (Iterator i = dd.getEjbRelationDescIterator(); i.hasNext(); ) {
                EjbRelationDesc relation = (EjbRelationDesc) i.next();
                createJormRelationMI(relation);
            }
        } catch (PException e) {
            logger.log(BasicLevel.ERROR, "Jorm Exception raised: " + e);
            throw new DeploymentDescException(e);
        }
    }

    public CMP2Bean getCmp2Bean(String beanName) {
        for (Iterator it = cmp2List.iterator(); it.hasNext(); ) {
            CMP2Bean cmp2 = (CMP2Bean) it.next();
            if (cmp2.getName().equals(beanName)) {
                return cmp2;
            }
        }
        return null;
    }

    /**
     * @return the Jorm MetaInfo Manager
     */
    public Manager getJormMIManager() {
        return manager;
    }

    /**
     * @return the Jorm Project Name
     */
    public static String getProjectName() {
        return PROJECT_NAME;
    }

    /**
     * Creates the Jorm Meta Information for an entity bean (CMP2)
     * @param bd the Bean Descriptor
     * @throws PException Jorm Manager reported an exception
     */
    private CMP2Bean createJormEntityMI(EntityJdbcCmp2Desc bd) throws PException {
        CMP2Bean cmp = new CMP2Bean(bd);
        String asn = bd.getJormClassName();
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Create Jorm MI for the bean: " + asn);
        }

        // --------------------------------------------------------------------
        // create persistent class and add it to the list for Jorm Compiler
        // --------------------------------------------------------------------
        Class myejb = manager.createClass(asn);
        cmp.addToJormList(myejb);
        ClassProject acp = myejb.createClassProject(PROJECT_NAME);
        RdbMapping mapping = (RdbMapping) acp.createMapping(MAPPER_NAME);
        RdbClassMultiMapping classmapping = mapping.createClassMultiMapping("to-table");
        String tablename = bd.getJdbcTableName();
        RdbTable mytable = classmapping.createRdbTable(tablename);

        // --------------------------------------------------------------------
        // CMP fields
        // --------------------------------------------------------------------
        for (Iterator i = bd.getCmpFieldDescIterator(); i.hasNext();) {
            FieldJdbcDesc fd = (FieldJdbcDesc) i.next();
            String fieldname = fd.getName();
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Create PrimitiveElement for the CMP field: " + fieldname);
            }
            PType pt = JormType.getPType(fd.getFieldType(), fd.isPrimaryKey());
            PrimitiveElement pe = myejb.createPrimitiveElement(fieldname, pt, PType.NOSIZE, PType.NOSIZE);
            mytable.createPrimitiveElementMapping(pe, fd.getJdbcFieldName(), fd.getSqlType(), fd.isPrimaryKey());
        }

        // --------------------------------------------------------------------
        // Primary Key
        // --------------------------------------------------------------------
        NameDef nds = myejb.createNameDef();
        if (bd.hasSimplePkField()) {
            // PK is a single field
            nds.setFieldName(bd.getSimplePkField().getName());
        } else {
            // Define a Composite Name (name = PK Class Name) and a NameRef
            // Sometimes, the CompositeName already exists, for example if 2 beans
            // have the same PK Class.
            boolean newcn = false;
            CompositeName cn = manager.getCompositeName(bd.getJormPKClassName());
            if (cn == null) {
                cn = manager.createCompositeName(bd.getJormPKClassName());
                cmp.addToJormList(cn);
                newcn = true;
            }
            // Use the CompositeName into the NameDef
            NameRef nrs = nds.createNameRef(cn);
            // fill the composite name with pk fields and link these fields to the class fields
            for (Iterator it = bd.getCmpFieldDescIterator(); it.hasNext();) {
                FieldJdbcDesc fd = (FieldJdbcDesc) it.next();
                if (fd.isPrimaryKey()) {
                    String fieldName = fd.getName();
                    if (newcn) {
                        cn.createCompositeNameField(fieldName, JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                                    PType.NOSIZE, PType.NOSIZE);
                    }
                    nrs.addProjection(fieldName, fieldName);
                }
            }
        }
        classmapping.createIdentifierMapping(nds);

        mip.print("Jorm MI for " + asn + ": ", myejb, pstream);
        mip.print("JORM MI Mapping for " + asn + ": ", classmapping, pstream);

        return cmp;
    }

    /**
     * Creates the Jorm Meta Information for a relation between CMP2 entity beans
     * @param relation the Relation Descriptor
     * @throws PException Jorm Manager reported an exception
     */
    private void createJormRelationMI(EjbRelationDesc relation) throws PException {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "Create Jorm MI for the relation: " + relation.getName());
        }
        EjbRelationshipRoleDesc role1 = relation.getRelationshipRole1();
        EjbRelationshipRoleDesc role2 = relation.getRelationshipRole2();
        // role1
        if (role1.isTargetMultiple())
            createJormRoleMIMulti(role1, role2);
        else
            createJormRoleMIOne(role1, role2);
        // role2
        if (role2.isTargetMultiple())
            createJormRoleMIMulti(role2, role1);
        else
            createJormRoleMIOne(role2, role1);
    }

    /**
     * Creates the Jorm Meta Information for a relationship role (CMR field)
     * when the target is mono.
     * @param sRole source EjbRelationship Role Descriptor
     * @param tRole target EjbRelationship Role Descriptor
     * @throws PException Jorm Manager reported an exception
     */
    private void createJormRoleMIOne(EjbRelationshipRoleDesc sRole, EjbRelationshipRoleDesc tRole)
        throws PException {

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, sRole.getName());
        }

        // source bean in the relation
        EntityJdbcCmp2Desc sEntity = (EntityJdbcCmp2Desc) sRole.getSourceBean();
        String sAsn = sEntity.getJormClassName();
        Class sClass = manager.getClass(sAsn);

        // target bean in the relation.
        EntityJdbcCmp2Desc tEntity = (EntityJdbcCmp2Desc) sRole.getTargetBean();
        String tAsn = tEntity.getJormClassName();
        Class tClass = manager.getClass(tAsn);

        ClassProject cpro = sClass.getClassProject(PROJECT_NAME);
        RdbMapping rdbmapping = (RdbMapping) cpro.getMapping(MAPPER_NAME);
        RdbClassMultiMapping classmapping = (RdbClassMultiMapping) rdbmapping.getClassMapping();
        RdbTable rdbtable = classmapping.getRdbTable();

        // There is always a cmr-field, sometimes added by JOnAS, because we
        // only deal with bidirectional relations.
        String sCmr = sRole.getCmrFieldName();

        // In case we have the same cmr field used in both roles, nothing to do
        if (tClass == sClass && sClass.getTypedElement(sCmr) != null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Nothing to do, same CMR field.");
            }
            return;
        }

        // add Dependencies between beans (= same cluster of beans)
        // avoids dropping tables at each loading of beans in the same cluster.
        if (tClass != sClass) {
            classmapping.addDependency(tClass.getFQName());
        }

        // Mono-value cmr: create the Class Ref. and its NameDef
        ClassRef cr = sClass.createClassRef(sCmr, tClass);
        NameDef rndf = cr.createRefNameDef();

        if (sRole.hasJdbcMapping() || tClass == sClass) {
            // source bean has a JDBC mapping for its CMR field.
            // 1 <-> N case (N side), or 1 <-> 1 case (Jdbc side)
            classmapping.createReferenceMapping("embedded-target-reference", rndf);
            if (tClass != sClass) {
                rdbtable.setColocated(true);
                rdbtable.setColocatedMaster(true);
            }
            if (tEntity.hasSimplePkField()) {
                // target bean has a Simple PK.
                FieldJdbcDesc fd = (FieldJdbcDesc) tEntity.getSimplePkField();
                String cmrmappingname = sRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
                PrimitiveElement pe = null;
                String clafn = sCmr + "_" + fd.getName();
                boolean peHasMapping = false;
                if (cmrmappingname != null) {
                    //check if the CMR is mapper over a CMP
                    pe = getPrimitiveElementByColumn(classmapping, cmrmappingname);
                }
                if (pe == null) {
                    //No CMP has been found ==> create an hidden field
                    pe = sClass.createHiddenField(clafn,
                                                  JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                                  PType.NOSIZE, PType.NOSIZE);
                } else {
                    peHasMapping = true;
                }
                //Register the field into the namedef
                rndf.setFieldName(pe.getName());
                if (cmrmappingname != null) { // ???
                    if (!peHasMapping) {
                        rdbtable.createPrimitiveElementMapping(pe, cmrmappingname, fd.getSqlType(), false);
                    }
                } else if (tClass == sClass) {
                    // tClass == sClass : unidirectional cmr on same bean
                    cmrmappingname = tRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
                    PrimitiveElementMapping pem = classmapping.getPrimitiveElementMapping(tRole.getCmrFieldName() + "_" + fd.getName());
                    classmapping.addPrimitiveElementMapping(clafn, pem);
                }
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "CMR " + sCmr + " is mapped over the field: " + clafn + " / columname: " + cmrmappingname);
                }
            } else {
                // target bean has a Composite PK.
                NameRef cpk = rndf.createNameRef(tClass.getNameDef("").getNameRef().getCompositeName());
                for (Iterator i = tEntity.getCmpFieldDescIterator(); i.hasNext(); ) {
                    FieldJdbcDesc fd = (FieldJdbcDesc) i.next();
                    if (fd.isPrimaryKey()) {
                        String cmrmappingname = sRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
                        PrimitiveElement pe = null;
                        String clafn = sCmr + "_" + fd.getName();
                        boolean peHasMapping = false;
                        if (cmrmappingname != null) {
                            //check if the CMR is mapper over a CMP
                            pe = getPrimitiveElementByColumn(classmapping, cmrmappingname);
                        }
                        if (pe == null) {
                            //No CMP has been found ==> create an hidden field
                            pe = sClass.createHiddenField(clafn,
                                                          JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                                          PType.NOSIZE, PType.NOSIZE);
                        } else {
                            peHasMapping = true;
                        }
                        cpk.addProjection(fd.getName(), pe.getName());
                        if (cmrmappingname != null) {
                            if (!peHasMapping) {
                                rdbtable.createPrimitiveElementMapping(pe, cmrmappingname, fd.getSqlType(), false);
                            }
                        } else if (tClass == sClass) {
                            // tClass == sClass : unidirectional cmr on same bean (???)
                            cmrmappingname = tRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
                            PrimitiveElementMapping pem = classmapping.getPrimitiveElementMapping(tRole.getCmrFieldName() + "_" + fd.getName());
                            classmapping.addPrimitiveElementMapping(clafn, pem);
                        }
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "CMR " + sCmr + " is mapped over the field: " + clafn + " / columname: " + cmrmappingname);
                        }
                    }
                }
            }
        } else {
            // The JDBC mapping for the source CMR field is done on the target bean.
            // 1 <-> 1 case (non-jdbc side)
            classmapping.createReferenceMapping("multi-table", rndf);
            RdbExternalTable table2 = classmapping.createRdbExternalTable(tEntity.getJdbcTableName());
            table2.setColocated(true); // Table used by 2 beans => update before insert
            table2.setColocatedMaster(false);
            table2.setReadOnly(true);
            RdbJoin joinTo2 = table2.createRdbJoin(sCmr);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "CMR " + sCmr + " is mapped over the external table: " + tEntity.getJdbcTableName());
            }
            // source pk fields compose the join
            if (sEntity.hasSimplePkField()) {
                FieldJdbcDesc spk = (FieldJdbcDesc) sEntity.getSimplePkField();
                String cmrmappingname = tRole.getForeignKeyJdbcName(spk.getJdbcFieldName());
                joinTo2.addJoinColumnNames(spk.getJdbcFieldName(), cmrmappingname);
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "CMR " + sCmr + ": join: " + spk.getJdbcFieldName() + " = " + cmrmappingname);
                }
            } else {
                for (Iterator i = sEntity.getCmpFieldDescIterator(); i.hasNext(); ) {
                    FieldJdbcDesc fd = (FieldJdbcDesc) i.next();
                    if (fd.isPrimaryKey()) {
                        String cmrmappingname = tRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
                        joinTo2.addJoinColumnNames(fd.getJdbcFieldName(), cmrmappingname);
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "CMR " + sCmr + ": join: " + fd.getJdbcFieldName() + " = " + cmrmappingname);
                        }
                    }
                }
            }
            // target pk fields compose the name def of reference
            if (tEntity.hasSimplePkField()) {
                FieldJdbcDesc tpk = (FieldJdbcDesc) tEntity.getSimplePkField();
                String clafn = sCmr + "_" + tpk.getName();
                rndf.setFieldName(clafn);
                ScalarField sf = sClass.createHiddenField(clafn,
                                                          JormType.getPType(tpk.getFieldType(), tpk.isPrimaryKey()),
                                                          PType.NOSIZE, PType.NOSIZE);
                table2.createPrimitiveElementMapping(sf, tpk.getJdbcFieldName(), tpk.getSqlType(), false, joinTo2);
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "CMR " + sCmr + ": mapped over the field : " + clafn + " column: " + tpk.getJdbcFieldName());
                }
            } else {
                NameRef cpk = rndf.createNameRef(tClass.getNameDef("").getNameRef().getCompositeName());
                for (Iterator i = tEntity.getCmpFieldDescIterator(); i.hasNext(); ) {
                    FieldJdbcDesc fd = (FieldJdbcDesc) i.next();
                    if (fd.isPrimaryKey()) {
                        String clafn = sCmr + "_" + fd.getName();
                        ScalarField sf = sClass.createHiddenField(clafn,
                                                                  JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                                                  PType.NOSIZE, PType.NOSIZE);
                        table2.createPrimitiveElementMapping(sf, fd.getJdbcFieldName(), fd.getSqlType(), false, joinTo2);
                        cpk.addProjection(fd.getName(), sf.getName());
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "CMR " + sCmr + ": mapped over the field : " + clafn + " column: " + fd.getJdbcFieldName());
                        }
                    }
                }
            }
        }
        mip.print("JORM MI for " + sAsn + ": ", sClass, pstream);
        mip.print("JORM MI Mapping for " + sAsn + ": ", classmapping, pstream);
    }

    /**
     * Creates the Jorm Meta Information for a relationship role (Multivalued CMR field)
     * @param sRole source EjbRelationship Role Descriptor
     * @param tRole target EjbRelationship Role Descriptor
     * @throws PException Jorm Manager reported an exception
     */
    private void createJormRoleMIMulti(EjbRelationshipRoleDesc sRole, EjbRelationshipRoleDesc tRole)
        throws PException {

        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, sRole.getName());
        }

        // source bean in the relation
        EntityJdbcCmp2Desc sEntity = (EntityJdbcCmp2Desc) sRole.getSourceBean();
        String sAsn = sEntity.getJormClassName();
        Class sClass = manager.getClass(sAsn);

        // target bean in the relation.
        EntityJdbcCmp2Desc tEntity = (EntityJdbcCmp2Desc) sRole.getTargetBean();
        String tAsn = tEntity.getJormClassName();
        Class tClass = manager.getClass(tAsn);

        ClassProject cpro = sClass.getClassProject(PROJECT_NAME);
        RdbMapping rdbmapping = (RdbMapping) cpro.getMapping(MAPPER_NAME);
        RdbClassMultiMapping classmapping = (RdbClassMultiMapping) rdbmapping.getClassMapping();

        // There is always a cmr-field, sometimes added by JOnAS, because we
        // only deal with bidirectional relations.
        String sCmr = sRole.getCmrFieldName();

        // In case we have the same cmr field used in both roles, nothing to do
        if (tClass == sClass && sClass.getTypedElement(sCmr) != null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Nothing to do, same CMR field.");
            }
            return;
        }

        // add Dependencies between beans (= same cluster of beans)
        // avoids dropping tables at each loading of beans in the same cluster.
        if (tClass != sClass) {
            classmapping.addDependency(tClass.getFQName());
        }

        // Multi-value cmr: create the gen class and its Reference NameDef.
        GenClassRef gcr = sClass.createGenClassRef(sCmr, sRole.getCmrFieldType().getName());
        NameDef ndsg = gcr.createRefNameDef();

        RdbGenClassMapping gcm = null;
        RdbTable tableGC = null;
        if (sRole.isSourceMultiple()) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Create GenClassReference for the CMR field (Many-Many): " + sCmr);
            }
            // M <-> N case
            gcm = rdbmapping.createGenClassMapping("embedded-target-references", gcr);
            // We need a jointure table in that case.
            tableGC = gcm.createRdbTable(sRole.getRelation().getJdbcTableName());
            if (sRole.isSlave()) {
                tableGC.setReadOnly(true);
            }
        } else {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Create GenClassReference for the CMR field (One-Many): " + sCmr);
            }
            // 1 <-> N case (1 side) : store CMR on N side.
            gcm = rdbmapping.createGenClassMapping("embedded-target-objects", gcr);
            tableGC = gcm.createRdbTable(tEntity.getJdbcTableName());
            tableGC.setReadOnly(true);
            tableGC.setColocated(true);
        }

        // How the current bean references the gen class
        // = the identifier of the source class
        // no mapping, because already done for the bean pk.
        classmapping.createReferenceMapping("embedded-target-reference", ndsg);
        if (sEntity.hasSimplePkField()) {
            // source bean has a Simple PK.
            FieldJdbcDesc fd = (FieldJdbcDesc) sEntity.getSimplePkField();
            ndsg.setFieldName(fd.getName());
        } else {
            // source bean has a Composite PK.
            NameRef cpk = ndsg.createNameRef(sClass.getNameDef("").getNameRef().getCompositeName());
            for (Iterator i = sEntity.getCmpFieldDescIterator(); i.hasNext(); ) {
                FieldJdbcDesc fd = (FieldJdbcDesc) i.next();
                if (fd.isPrimaryKey()) {
                    // field names in the Composite are field names of the bean.
                    cpk.addProjection(fd.getName(), fd.getName());
                }
            }
        }

        // How the gen class is identified (the gen class name)
        // = the identifier of the source class
        String fieldsuffix;
        if (sClass == tClass) {
            fieldsuffix = (sRole.isSlave() ? "2" : "1");
        } else {
            fieldsuffix = "";
        }
        NameDef ndid = gcr.createIdNameDef();
        if (sEntity.hasSimplePkField()) {
            // source bean has a Simple PK.
            FieldJdbcDesc fd = (FieldJdbcDesc) sEntity.getSimplePkField();
            String clafn = sAsn + "_" + fd.getName() + fieldsuffix;
            ndid.setFieldName(clafn);
            ScalarField pe = gcr.createHiddenField(clafn,
                                                   JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                                   PType.NOSIZE, PType.NOSIZE);

            String fkname = tRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
            tableGC.createPrimitiveElementMapping(pe, fkname, fd.getSqlType(), false);
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "GC id is mapped over the field: " + clafn + " / column:" + fkname);
            }
        } else {
            // source bean has a Composite PK.
            NameRef cpk = ndid.createNameRef(sClass.getNameDef("").getNameRef().getCompositeName());
            for (Iterator i = sEntity.getCmpFieldDescIterator(); i.hasNext(); ) {
                FieldJdbcDesc fd = (FieldJdbcDesc) i.next();
                if (fd.isPrimaryKey()) {
                    String clafn = sAsn + "_" + fd.getName() + fieldsuffix;
                    ScalarField sf = gcr.createHiddenField(clafn,
                                                           JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                                           PType.NOSIZE, PType.NOSIZE);
                    cpk.addProjection(fd.getName(), sf.getName());
                    String fkname = tRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
                    tableGC.createPrimitiveElementMapping(sf, fkname, fd.getSqlType(), false);
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "GC id is mapped over the field: " + clafn + " / column:" + fkname);
                    }
                }
            }
        }
        gcm.createIdentifierMapping(ndid);

        // How the referenced class is referenced from the gen class.
        // Describes an Element of the GenClass.
        // = pk of the target bean
        if (sClass == tClass) {
            fieldsuffix = (tRole.isSlave() ? "2" : "1");
        } else {
            fieldsuffix = "";
        }
        // Create the ClassRef and its NameDef.
        NameDef ndr2 = gcr.createClassRef(tClass).createRefNameDef();
        gcm.createReferenceMapping("embedded-target-object", ndr2);

        if (tEntity.hasSimplePkField()) {
            // target bean has a Simple PK.
            FieldJdbcDesc fd = (FieldJdbcDesc) tEntity.getSimplePkField();
            String fkname;
            if (sRole.isSourceMultiple()) {
                // M <-> N case
                fkname = sRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
            } else {
                // 1 <-> N case
                fkname = fd.getJdbcFieldName();
            }
            ScalarField pe = getPrimitiveElementByColumn(gcm, fkname);
            if (pe == null) {
                String clafn = tAsn + "_" + fd.getName() + fieldsuffix;
                pe = gcr.createHiddenField(clafn,
                                           JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                           PType.NOSIZE, PType.NOSIZE);
                tableGC.createPrimitiveElementMapping(pe, fkname, fd.getSqlType(), false);
            }
            ndr2.setFieldName(pe.getName());
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "GC elem is mapped over the field: " + pe.getName() + " / column:" + fkname);
            }
        } else {
            // target Bean has a Composite PK.
            NameRef cpk = ndr2.createNameRef(tClass.getNameDef("").getNameRef().getCompositeName());
            for (Iterator i = tEntity.getCmpFieldDescIterator(); i.hasNext(); ) {
                FieldJdbcDesc fd = (FieldJdbcDesc) i.next();
                if (fd.isPrimaryKey()) {
                    String fkname;
                    if (sRole.isSourceMultiple()) {
                        // M <-> N case
                        fkname = sRole.getForeignKeyJdbcName(fd.getJdbcFieldName());
                    } else {
                        // 1 <-> N case
                        fkname = fd.getJdbcFieldName();
                    }
                    ScalarField pe = getPrimitiveElementByColumn(gcm, fkname);
                    if (pe == null) {
                        String clafn = tAsn + "_" + fd.getName() + fieldsuffix;
                        pe = gcr.createHiddenField(clafn,
                                                   JormType.getPType(fd.getFieldType(), fd.isPrimaryKey()),
                                                   PType.NOSIZE, PType.NOSIZE);
                        tableGC.createPrimitiveElementMapping(pe, fkname, fd.getSqlType(), false);
                    }
                    cpk.addProjection(fd.getName(), pe.getName());
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "GC elem is mapped over the field: " + pe.getName() + " / column:" + fkname);
                    }
                }
            }
        }
        mip.print("JORM MI for " + sAsn + ": ", sClass, pstream);
        mip.print("JORM MI Mapping for " + sAsn + ": ", classmapping, pstream);
    }

    private PrimitiveElement getPrimitiveElementByColumn(RdbClassMultiMapping cm, String col) {
        PrimitiveElementMapping pem = cm.getRdbTable().getPrimitiveElementMappingByCol(col);
        if (pem == null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "no " + col);
            }
            return null;
        }
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "yes " + col);
        }
        return (PrimitiveElement) pem.getLinkedMO();
    }

    private ScalarField getPrimitiveElementByColumn(RdbGenClassMapping cm, String col) {
        PrimitiveElementMapping pem = cm.getRdbTable().getPrimitiveElementMappingByCol(col);
        if (pem == null) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "no " + col);
            }
            return null;
        }
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "yes " + col);
        }
        return (ScalarField) pem.getLinkedMO();
    }
}
