/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2007 Bull S.A.S.
 * 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.ejb2.internal.mbean;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

import javax.mail.Session;
import javax.mail.internet.MimePartDataSource;
import javax.management.BadAttributeValueExpException;
import javax.management.BadBinaryOpValueExpException;
import javax.management.BadStringOperationException;
import javax.management.InvalidApplicationException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import javax.management.QueryEval;
import javax.management.QueryExp;

import org.ow2.jonas.deployment.api.IResourceEnvRefDesc;
import org.ow2.jonas.deployment.api.IResourceRefDesc;
import org.ow2.jonas.deployment.ejb.BeanDesc;
import org.ow2.jonas.deployment.ejb.EntityJdbcCmp1Desc;
import org.ow2.jonas.deployment.ejb.EntityJdbcCmp2Desc;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.ejb21.JFactory;
import org.ow2.jonas.lib.management.javaee.J2EEManagedObject;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;



/**
 * This class implements the EJB type specified in JSR77.
 * @author Adriana Danes
 */
public class EJB extends J2EEManagedObject {

    /**
     * The Bean to manage.
     */
    protected JFactory ejbToManage = null;

    /**
     * Bean Deployment Descriptor.
     */
    protected BeanDesc desc = null;

    /**
     * Bean ejbjar filename.
     */
    protected String fileName = null;

    /**
     * mbeanServer where mbeans are registered
     */
    MBeanServer mbeanServer = null;

    /**
     * Constructs an EJB MBean object.
     * @param objectName EJB {@link ObjectName}
     * @param ejbToManage This bean's {@link JFactory}
     */
    public EJB(String objectName, JFactory ejbToManage, JmxService jmx) {
        super(objectName);
        this.ejbToManage = ejbToManage;
        this.fileName = ejbToManage.getContainer().getExternalFileName();
        this.desc = ejbToManage.getDeploymentDescriptor();
        mbeanServer = jmx.getJmxServer();
    }


    /**
     * @return String The Name of this JFactory
     */
    public String getName() {
        return desc.getEjbName();
    }

    /**
     * @return Ejb File Name
     */
    public String getFileName() {
        return fileName;
    }

    /**
     * Construct Hashtable containing for each ResourceRef jndi name
     * its corresponding the ResourceAdapter MBean OBJECT_NAME.
     * @return Hashtable table
     */
    public Hashtable getAllJdbcResourceAdapterName() {
        Hashtable result = new Hashtable();
        ArrayList jdbcResourceRefJndiNames = getAllJdbcResourceRefJndiNames();
        if (jdbcResourceRefJndiNames.isEmpty()) {
            // return empty result
            return result;
        }

        for (int i = 0; i < jdbcResourceRefJndiNames.size(); i++) {
            String jndiName = (String) jdbcResourceRefJndiNames.get(i);
            QueryExp match = Query.match(Query.attr("jndiName"), Query.value(jndiName));
            QueryExp query = Query.and(match, new DataSourcePropertiesQueryExp());
            Set objectNames = mbeanServer.queryNames(J2eeObjectName.getResourceAdapters(J2eeObjectName.ALL, null), query);

            // If the set is not empty, return the first value
            if (!objectNames.isEmpty()) {
                result.put(jndiName, objectNames.iterator().next());
            }

        }
        return result;
    }
    /**
     * Construct Hashtable containing for each ResourceRef jndi name
     * its corresponding the datasource name or empty String if no
     * corresponding datasource was found.
     * @return Hashtable table
     */
    public Hashtable getAllDataSourceName() {
        Hashtable result = new Hashtable();
        ArrayList jdbcResourceRefJndiNames = getAllJdbcResourceRefJndiNames();
        if (jdbcResourceRefJndiNames.isEmpty()) {
            // return empty result
            return result;
        }

        for (Iterator it = jdbcResourceRefJndiNames.iterator(); it.hasNext();) {
            String jndiName = (String) it.next();
            QueryExp match = Query.match(Query.attr("jndiName"),
                                         Query.value(jndiName));
            Set objectNames = mbeanServer.queryNames(J2eeObjectName.getJDBCDataSources(J2eeObjectName.ALL, null),
                                                     match);

            // If the set is not empty, return the first value
            if (!objectNames.isEmpty()) {
                ObjectName on = (ObjectName) objectNames.iterator().next();
                result.put(jndiName, on.getKeyProperty("name"));
            }
        }
        return result;
    }

    /**
     * @return Returns an {@link ArrayList} of JDBC resource-ref JNDI Names.
     */
    private ArrayList getAllJdbcResourceRefJndiNames() {
        ArrayList jndiNames = new ArrayList();
        String jndiName = null;
        // find the resource refs
        IResourceRefDesc[] rrDesc = desc.getResourceRefDesc();
        for (int i = 0; i < rrDesc.length; i++) {
            if (rrDesc[i].isJdbc()) {
                // get the DD provided JNDI name
                jndiName = rrDesc[i].getJndiName();
                jndiNames.add(jndiName);
            }
        }

        // if the EJB is an entity with CMP, we have to find jndi name in the jdbc_mapping
        if (desc instanceof EntityJdbcCmp1Desc) {
            // in case of CMP1 :
            EntityJdbcCmp1Desc jdbcCmp1Desc = (EntityJdbcCmp1Desc) desc;
            jndiName = jdbcCmp1Desc.getDatasourceJndiName();
            jndiNames.add(jndiName);
        } else if (desc instanceof EntityJdbcCmp2Desc) {
            // in case of CMP2 :
            EntityJdbcCmp2Desc jdbcCmp2Desc = (EntityJdbcCmp2Desc) desc;
            jndiName = jdbcCmp2Desc.getDatasourceJndiName();
            jndiNames.add(jndiName);
        }
        return jndiNames;
    }

    /**
     * @return Set The Name set of the Connection Factories
     */
    public Set getAllJMSConnectionFactoryName(){
        Set result= new HashSet();
        IResourceRefDesc[] rrDesc = desc.getResourceRefDesc();
        for (int i = 0; i < rrDesc.length; i ++) {
            if ("javax.jms.TopicConnectionFactory".equals(rrDesc[i].getTypeName())
                    || "javax.jms.QueueConnectionFactory".equals(rrDesc[i].getTypeName())
                    || "javax.jms.ConnectionFactory".equals(rrDesc[i].getTypeName())) {
                result.add(rrDesc[i].getJndiName());
            }
        }
        return result;
    }

    /**
     * @return Hashtable which maps the JNDI names provided by the DD to the Session Mail factyory resources
     * known by the Mail Service
     */
    public Hashtable getAllMailFactorySName() {
        return getAllMailFactoryName(Session.class.getName());
    }

    /**
     * @return Hashtable which maps the JNDI names provided by the DD to the MimePartDataSource Mail factory resources
     * known by the Mail Service
     */
    public Hashtable getAllMailFactoryMName() {
        return getAllMailFactoryName(MimePartDataSource.class.getName());
    }

    /**
     * @param type the resource type (fully qualified class name)?
     * @return Returns a {@link Hashtable} (jndiName to factoryName) for all
     *         mail resource-refs of the given type.
     */
    private Hashtable getAllMailFactoryName(final String type){
        Hashtable result = new Hashtable();

        IResourceRefDesc[] rrDesc = desc.getResourceRefDesc();
        for (int i = 0; i < rrDesc.length; i ++) {
            // Only handle the resource of the given factory Type
            if (type.equals(rrDesc[i].getTypeName())) {

                // get the DD provided JNDI name
                String jndiName = rrDesc[i].getJndiName();

                // Create the query matching the name attribute which is the factory JNDI name
                QueryExp match = Query.match(Query.attr("Name"),
                                             Query.value(jndiName));
                Set objectNames = mbeanServer.queryNames(J2eeObjectName.JavaMailResources(J2eeObjectName.ALL, null, type),
                                                         match);

                if (!objectNames.isEmpty()) {
                    // found an mbean
                    ObjectName on = (ObjectName) objectNames.iterator().next();
                    String factoryName = null;
                    try {
                        factoryName = (String) mbeanServer.getAttribute(on, "FactoryName");
                    } catch (Exception e) {
                        factoryName = "";
                    }
                    result.put(jndiName, factoryName);
                } else {
                    // no mbean found
                    result.put(jndiName, "");
                }
            }
        }
        return result;
    }

    /**
     * @return Set The Name set of the JMS Destinations
     */
    public Set getAllJMSDestinationName(){
        Set result= new HashSet();
        IResourceEnvRefDesc[] rrDesc = desc.getResourceEnvRefDesc();
        for (int i = 0; i < rrDesc.length; i ++) {
            if ((rrDesc[i].getType() == javax.jms.Topic.class )
                    ||(rrDesc[i].getType() == javax.jms.Queue.class )) {
                result.add(rrDesc[i].getJndiName());
            }
        }
        return result;
    }

    /**
     * @return Set The URL resources used by the bean
     */
    public Set getAllURLs() {
        Set result= new HashSet();
        IResourceRefDesc[] rrDesc = desc.getResourceRefDesc();
        for (int i = 0; i < rrDesc.length; i ++) {
            if ("java.net.URL".equals(rrDesc[i].getTypeName())) {
                result.add(rrDesc[i].getJndiName());
            }
        }
        return result;
    }

    /**
     * @return The current instance pool size
     */
    public  int getPoolSize(){
        return ejbToManage.getPoolSize();
    }

    /**
     * @return min-pool-size value
     */
    public int getMinPoolSize() {
        return ejbToManage.getMinPoolSize();
    }

    /**
     * @return max-cache-size value
     */
    public int getMaxCacheSize() {
        return ejbToManage.getMaxCacheSize();
    }

    /**
     * @return  nb of instances (current cache size)
     */
    public int getCacheSize() {
        return ejbToManage.getCacheSize();
    }
    /**
     * Reduce number of instances
     */
    public void reduceCacheSize() {
        ejbToManage.reduceCache();
    }
    /**
     * @return String the  JFactory Class
     */
    public String getEjbClass(){
        return desc.getEjbClass().getName();
    }

    /**
     * @return String the displayName of the bean, or bean name if not defined.
     */
    public String getDisplayName() {
        if (desc.getDisplayName() != null) {
            return desc.getDisplayName();
        } else {
            return desc.getEjbName();
        }
    }

    /**
     * @return String the JNDI Name of the bean.
     */
    public String getJndiName() {
        return desc.getJndiName();
    }

    /**
     * @return String the HomeClass of the bean.
     */
    public String getHomeClass() {
        if (desc.getHomeClass() != null) {
            return desc.getHomeClass().getName();
        } else {
            return null;
        }
    }

    /**
     * @return String the RemoteClass of the bean.
     */
    public String getRemoteClass() {
        if (desc.getRemoteClass() != null) {
            return desc.getRemoteClass().getName();
        } else {
            return null;
        }
    }

    /**
     * @return String the LocalHomeClass of the bean.
     */
    public String getLocalHomeClass() {
        if (desc.getLocalHomeClass() != null) {
            return desc.getLocalHomeClass().getName();
        } else {
            return null;
        }
    }

    /**
     * @return String the LocalClass of the bean.
     */
    public String getLocalClass() {
        if (desc.getLocalClass() != null) {
            return desc.getLocalClass().getName();
        } else {
            return null;
        }
    }


    /**
     * Define a JMX {@link QueryExp} that browse the ResourceAdapter ObjectInstance
     * <code>properties</code> attribute and find if it's a valid DataSource
     * resource by checking the opresence of "dsClass" and "URL" properties.
     *
     * @author Guillaume Sauthier
     */
    private class DataSourcePropertiesQueryExp extends QueryEval implements QueryExp {

        /**
         * serialVersionUID.
         */
        private static final long serialVersionUID = 4843312566830083622L;

        public boolean apply(ObjectName name) throws BadStringOperationException, BadBinaryOpValueExpException,
                BadAttributeValueExpException, InvalidApplicationException {

            Properties properties = null;
            try {
                properties = (Properties) getMBeanServer().getAttribute(name, "properties");
            } catch (Exception e) {
                // if something goes wrong, the matching cannot be performed
                return false;
            }

            // Check that the associated properties are DataSource properties
            String dsClassValue = properties.getProperty("dsClass");
            String urlValue = properties.getProperty("URL");
            if (dsClassValue != null
                && dsClassValue.length() != 0
                && urlValue != null
                && urlValue.length() != 0) {
                // This is a well configured JDBC ResourceAdapter
                return true;
            }

            return false;
        }

    }
}
