/**
 * 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$
 * --------------------------------------------------------------------------
 */


package org.ow2.jonas.deployment.ejb;

import java.util.ArrayList;
import java.util.Iterator;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.ow2.jonas.deployment.common.DeploymentDescException;
import org.ow2.jonas.deployment.common.xml.JLinkedList;
import org.ow2.jonas.deployment.ejb.ejbql.ParseException;
import org.ow2.jonas.deployment.ejb.xml.AssemblyDescriptor;
import org.ow2.jonas.deployment.ejb.xml.Entity;
import org.ow2.jonas.deployment.ejb.xml.JonasEntity;
import org.ow2.jonas.deployment.ejb.xml.Query;
import org.ow2.jonas.lib.util.BeanNaming;





/**
 * Class to hold meta-information related to an entity of type CMP version 2.
 * Created on Jun 24, 2002
 * @author Christophe Ney [cney@batisseurs.com] : Initial developper
 * @author Helene Joanin : code cleanup on May 2003
 * @author Helene Joanin : complement for legacy first version on May 2003
 */
public abstract class EntityCmp2Desc extends EntityCmpDesc {

    private ArrayList ejbRelationshipRoleDesc = new ArrayList();
    protected String abstractSchemaName;

    private static final String JORM_PACKAGE = "jorm";

    private String jormClassName = null;
    private String jormFQClassName = null;
    private String jormAccessorClassName = null;
    private String jormPKClassName = null;
    private String jormPNameGetterClassName = null;
    private String jormBinderClassName = null;


    protected String factoryClassName = null;
    protected DeploymentDescEjb2 dc2d = null;

    /**
     * constructor: called when the DeploymentDescriptor is read.
     * Currently, called by both GenIC and createContainer.
     */
    public EntityCmp2Desc(ClassLoader classLoader,
                          Entity ent,
                          AssemblyDescriptor asd,
                          JonasEntity jEnt,
                          DeploymentDescEjb2 dc2d,
                          JLinkedList jMDRList,
			              String fileName)
        throws DeploymentDescException {

        super(classLoader, ent, asd, jEnt, jMDRList, fileName);
        this.dc2d = dc2d;

        // An abstract schema name is required
        if ((ent.getAbstractSchemaName() == null)
            || (ent.getAbstractSchemaName().length() == 0)) {
            throw new DeploymentDescException("abstract-schema-name must be provided for bean " + this.ejbName);
        }
        abstractSchemaName = ent.getAbstractSchemaName();

        // check if persistent fields exist
        if (fieldDesc.isEmpty()) {
            throw new DeploymentDescException("No cmp-field defined in bean " + this.ejbName);
        }
        // check if persistent fields map to getter and setter
        for (Iterator i = fieldDesc.keySet().iterator(); i.hasNext();) {
            String fn = (String) i.next();

            // field should not be defined
            try {
                Field f = ejbClass.getField(fn);
                throw new DeploymentDescException("In cmp-version 2.x, field-name " + fn + " should not be defined in bean " + this.ejbName);
            } catch (NoSuchFieldException e) {
                // this is what we expect: nothing to do
            } catch (SecurityException e) {
                throw new DeploymentDescException("Cannot use java reflexion on " + this.ejbClass.getName());
            }

            try {
                Method getter = null;
                try {
                    getter = ejbClass.getMethod(FieldDesc.getGetterName(fn), (Class[]) null);
                    ((FieldDesc) (fieldDesc.get(fn))).setFieldType(getter.getReturnType());
                } catch (NoSuchMethodException e) {
                    throw new DeploymentDescException("Getter method not found for field-name " + fn + " in bean " + this.ejbName, e);
                }
                try {
                    ejbClass.getMethod(FieldDesc.getSetterName(fn), new Class[]{getter.getReturnType()});
                } catch (NoSuchMethodException e) {
                    throw new DeploymentDescException("Setter method not found for field-name " + fn + " in bean " + this.ejbName, e);
                }
            } catch (SecurityException e) {
                throw new DeploymentDescException("Cannot use java reflexion on " + this.ejbClass.getName());
            }
        }

        // isModifiedMethod deprecated for CMP 2.x
        if (jEnt.getIsModifiedMethodName() != null) {
            throw new DeploymentDescException("use of is-modified-method-name deprecated for CMP 2.x");
        }

        // EJB-QL query
        if (ent.getQueryList() != null) {
            for (Iterator i = ent.getQueryList().iterator(); i.hasNext();) {
                Query q = (Query) i.next();
                boolean foundMatch = false;
                for (Iterator j = getMethodDescIterator(); j.hasNext();) {
                    MethodDesc methd = (MethodDesc) j.next();
                    String methName = q.getQueryMethod().getMethodName();
                    if (methd.matchPattern(null, methName, q.getQueryMethod().getMethodParams())
                        != MethodDesc.APPLY_TO_NOTHING) {
                        foundMatch = true;
                        String query = q.getEjbQl();
                        if (!(methd instanceof MethodCmp2Desc)) {
                            throw new DeploymentDescException("ejbql query " + query + " can't apply to method "
                                                              + methName + " in bean " + ejbName);
                        }
                        try {
                            ((MethodCmp2Desc) methd).setQuery(query);
                        } catch (ParseException e) {
                            throw new DeploymentDescException("Invalid ejbql syntax for bean " + ejbName + ":\n"
                                                              + e.getMessage(query));
                        }
                        if (q.getResultTypeMapping() != null) {
                            ((MethodCmp2Desc) methd).setResultTypeMapping(q.getResultTypeMapping());
                        }
                    }
                }
                if (!foundMatch) {
                    throw new DeploymentDescException("invalid query-method definition for bean " + ejbName + "\nno such method as "
                                                      + MethodCmp2Desc.queryMethodElementToString(q.getQueryMethod()) + "\ncheck method name and method parameters");
                }
            }
        }
        // check that all finder/selecter methods but findByPrimaryKey have a non null query
        for (Iterator j = getMethodDescIterator(); j.hasNext();) {
            MethodDesc md = (MethodDesc) j.next();
            if ((md.isFinder() || md.isEjbSelect()) && !md.isFindByPrimaryKey()) {
                if (((MethodCmp2Desc) md).getQuery() == null) {
                    throw new DeploymentDescException("query not defined for method " + MethodDesc.toString(md.getMethod())
                                                      + " of bean" + ejbName);

                }
            }
        }
        if (isUndefinedPK()) {
           FieldDesc fd = this.newFieldDescInstance();
           fd.setName("JONASAUTOPKFIELD");
           fd.setPrimaryKey(true);
           fieldDesc.put("JONASAUTOPKFIELD", fd);
           ((FieldDesc) (fieldDesc.get("JONASAUTOPKFIELD"))).setFieldType(java.lang.Integer.class);
           ((FieldJdbcDesc) (fieldDesc.get("JONASAUTOPKFIELD"))).setJdbcFieldName(this.getJdbcAutomaticPkFieldName());
        }
    }

    public DeploymentDescEjb2 getDeploymentDescEjb2() {
        return dc2d;
    }

    /**
     * getter for field abstractSchemaName
     */
    public String getAbstractSchemaName() {
        return abstractSchemaName;
    }

    /**
     * Get the Jorm Class name in JOrm Meta Info
     * It is built from the Abstract Shema Name.
     * @return the Jorm Class Name
     */
    private String getJormCName() {
        if (jormClassName == null) {
            jormClassName = BeanNaming.firstToUpperCase(abstractSchemaName);
        }
        return jormClassName;
    }

    /**
     * Get the Jorm Fully Qualified Class name in JOrm Meta Info
     * It is built from the Abstract Shema Name.
     * @return the Jorm Class Name
     */
    public String getJormClassName() {
        if (jormFQClassName == null) {
            jormFQClassName = BeanNaming.getClassName(JORM_PACKAGE, getJormCName());
        }
        return jormFQClassName;
    }

    /**
     * Add meta-info of a relationship in which the bean is involved
     */
    public void addEjbRelationshipRoleDesc(EjbRelationshipRoleDesc ersrd) {
        ejbRelationshipRoleDesc.add(ersrd);
    }

    /**
     * Get Iterator on meta-info for all relationships for which the bean
     * is involved in.
     */
    public Iterator getEjbRelationshipRoleDescIterator() {
        return ejbRelationshipRoleDesc.iterator();
    }

    /**
     * Get the EjbRelationshipRoleDesc corresponding to the given cmr field name.
     * Return null if it doesn't exist.
     */
    public EjbRelationshipRoleDesc getEjbRelationshipRoleDesc(String cmr) {
        for (Iterator i = ejbRelationshipRoleDesc.iterator(); i.hasNext();) {
            EjbRelationshipRoleDesc rsr = (EjbRelationshipRoleDesc) i.next();
            if (rsr.hasCmrField() && cmr.equals(rsr.getCmrFieldName())) {
                return rsr;
            }
        }
        return null;
    }

    /**
     * Factory method for MethodDesc.
     * Only method with EJBQL queries are of type <code>MethodCmp2Desc</code>
     */
    protected MethodDesc newMethodDescInstance(Method meth, Class classDef, int index) {
        return new MethodCmp2Desc(this, meth, classDef, index);
    }

    /**
     * It retrieves the class name of the generated Jorm accessor interface.
     * Used in the templates
     * @return the fully qualified class name
     */
    public String getJormAccessorClassName() {
        if (jormAccessorClassName == null) {
            jormAccessorClassName = BeanNaming.getClassName(JORM_PACKAGE, getJormCName() + "Accessor");
        }
        return jormAccessorClassName;
    }

    /**
     * It retrieves the class name of the generated Jorm binding.
     * Used by the JormFactory
     * @return the fully qualified class name
     */
    public String getJormBindingClassName() {
        return BeanNaming.getClassName(JORM_PACKAGE, getJormCName() + "Binding");
    }

    /**
     * Return the class name of the generated Jorm state class.
     * @return the fully qualified class name
     */
    public String getJormStateClassName() {
        return BeanNaming.getClassName(JORM_PACKAGE, getJormCName() + "State");
    }

    /**
     * It retrieves the class name of the generated Jorm mapping.
     * Used in the JContainer to instanciate the JEntityFactory
     * @return the fully qualified class name
     */
    public String getFactoryClassName() {
        return BeanNaming.getClassName(JORM_PACKAGE, "rdb." + getJormCName() + "Mapping");
    }

    /**
     * Retrieves the Jorm PK Class, in case of composite PK
     * Used by Jorm to generate PNameGetter and Binder classes
     * @return the fully qualified class name
     */
    public String getJormPKClassName() {
        if (! hasPrimaryKeyField() && jormPKClassName == null) {
            jormPKClassName = BeanNaming.getClassName(JORM_PACKAGE,
                                                      BeanNaming.getBaseName(getPrimaryKeyClass().getName()));
        }
        return jormPKClassName;
    }

    /**
     * It retrieves the class name of the generated Jorm PNameGetter interface.
     * Used in the templates to generate CMP2 classes.
     * @return the fully qualified class name
     */
    public String getJormPNameGetterClassName() {
        if (! hasPrimaryKeyField() && jormPNameGetterClassName == null) {
            jormPNameGetterClassName = getJormPKClassName() + "PNG";
        }
        return jormPNameGetterClassName;
    }

    /**
     * It retrieves the class name of the generated Jorm Binder.
     * Used in the Jorm Factory
     * @return the fully qualified class name
     */
    public String getJormBinderClassName() {
        if (jormBinderClassName == null) {
            if (hasPrimaryKeyField()) {
                jormBinderClassName = "org.objectweb.jorm.facility.naming.basidir.BasidBinder";
            } else {
                jormBinderClassName = getJormPKClassName() + "Binder";
            }
        }
        return jormBinderClassName;
    }

    /**
     * @return true if at least one relation
     */
    public boolean needJormCoherenceHelper() {
        return ejbRelationshipRoleDesc.iterator().hasNext();
    }

    public String getJormCoherenceHelperItfName() {
        return "JOnAS" + ejbName + "CoherenceHelper";
    }

    public String getJormCoherenceHelperPackageName() {
        return BeanNaming.getPackageName(getFullDerivedBeanName());
    }

    public String getJormCoherenceHelperFQItfName() {
        String pn = getJormCoherenceHelperPackageName();
        return (pn != null && pn.length() > 0
                ? pn + "." + getJormCoherenceHelperItfName()
                : getJormCoherenceHelperItfName());
    }

    /**
     * String representation of the object for test and debug purpose
     * @return String representation of this object
     */
    public String toString() {
        StringBuffer ret = new StringBuffer();
        ret.append(super.toString());
        for (Iterator i = ejbRelationshipRoleDesc.iterator(); i.hasNext(); ) {
            ret.append("\nejbRelationshipRoleDesc[]=" + i.next());
        }
        ret.append("\ngetAbstractSchemaName()=" + getAbstractSchemaName());
        ret.append("\ngetJormAccessorClassName() = " + getJormAccessorClassName());
        ret.append("\nneedJormCoherenceHelper() = " + needJormCoherenceHelper());
        return ret.toString();
    }

}

