/**
 * 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.ee.jdbc;

import java.lang.reflect.Method;
import java.io.ByteArrayInputStream;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;

import javax.resource.ResourceException;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.SecurityException;
import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * <p>This class contains the support methods for the Resource Adapter.
 * @author Eric Hardesty
 */
public class Utility {

    static synchronized Object getDataSource(ManagedConnectionFactoryImpl mcf, PasswordCredential pc, Logger trace)
            throws ResourceException {

        MCFData prop = mcf.mcfData;

        if (prop.getMCFData(MCFData.DSCLASS) == null) {
            ResourceException re = new ResourceException("A DataSource (dsClass) value must be specified");
            trace.log(BasicLevel.INFO, re.getMessage());
        }
        String clsName = prop.getMCFData(MCFData.DSCLASS);
        Class dsClass = null;
        Object dsObj = null;
        try {
            dsClass = Class.forName(clsName, true, Thread.currentThread().getContextClassLoader());
            dsObj = dsClass.newInstance();
            if (trace.isLoggable(BasicLevel.DEBUG)) {
                trace.log(BasicLevel.DEBUG, "dsClass(" + clsName + ") is " + dsObj);
            }
        } catch (ClassNotFoundException cnfe) {
            throw new ResourceException("Class Name not found:" + clsName, cnfe);
        } catch (Exception ex) {
            throw new ResourceException("Error in class: " + clsName + " " + ex.getMessage(), ex);
        }

        Object[] param = new Object[1];
        String methodName = null;
        String paramVal = null;
        Method meth = null;
        for (Enumeration e = prop.getProperties(); e.hasMoreElements();) {
            int offset = Integer.parseInt((String) e.nextElement());
            methodName = MCFData.dsMethodNames[offset];
            if (!(methodName.equals("setDSClass") || methodName.equals("setDbSpecificMethods") || offset > MCFData.JONASOFFSET)) {
                try {
                    /* Determine if a String method exist */
                    paramVal = prop.getProperty("" + offset);
                    if (trace.isLoggable(BasicLevel.DEBUG)) {
                        trace.log(BasicLevel.DEBUG, "calling method " + methodName + " with String " + paramVal);
                    }
                    meth = dsClass.getMethod(methodName, new Class[] {String.class});
                    param[0] = paramVal;
                    meth.invoke(dsObj, param);
                } catch (NoSuchMethodException ns) {
                    try {
                        /* Valid case, determine if an Integer method exist */
                        if (trace.isLoggable(BasicLevel.DEBUG)) {
                            trace.log(BasicLevel.DEBUG, "calling method " + methodName + " with int " + paramVal);
                        }
                        meth = dsClass.getMethod(methodName, new Class[] {int.class});
                        param[0] = new Integer(paramVal);
                        meth.invoke(dsObj, param);
                    } catch (NoSuchMethodException nsme) {
                        // Valid case no String or Integer method continue thru
                        // remainder of properties
                        ;
                    } catch (NumberFormatException nfm) {
                        // Valid case cannot convert to an Integer
                        ;
                    } catch (Exception ex0) {
                        ex0.printStackTrace();
                        throw new ResourceException("Error on method: " + methodName + " " + ex0.getMessage(), ex0);
                    }
                } catch (IllegalArgumentException iae) {
                    ;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    throw new ResourceException("Error on method: " + methodName + " " + ex.getMessage(), ex);
                }
            }
            if (methodName.equals("setDbSpecificMethods")) {
                Vector meths = new Vector();
                Vector methParams = new Vector();
                Vector methTypes = new Vector();
                try {
                    Utility.parseValues(prop.getProperty("" + offset), meths, methParams, methTypes, trace);
                } catch (Exception ex1) {
                    throw new ResourceException("Error parsing dbSpecificMethods: " + ex1.getMessage());
                }
                if (meths != null && meths.size() > 0) {
                    for (int i = 0; i < meths.size(); i++) {
                        try {
                            /* Determine if a String method exist */
                            methodName = (String) meths.elementAt(i);
                            Class toPass = null;
                            String curMethType = (String) methTypes.elementAt(i);
                            if (curMethType.equalsIgnoreCase("String")) {
                                toPass = String.class;
                                param[0] = (String) methParams.elementAt(i);
                            } else if (curMethType.equalsIgnoreCase("Integer")) {
                                toPass = Integer.class;
                                param[0] = Integer.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equalsIgnoreCase("int")) {
                                toPass = int.class;
                                param[0] = Integer.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("Float")) {
                                toPass = Float.class;
                                param[0] = Float.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("float")) {
                                toPass = float.class;
                                param[0] = Float.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("Boolean")) {
                                toPass = Boolean.class;
                                param[0] = Boolean.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("boolean")) {
                                toPass = boolean.class;
                                param[0] = Boolean.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equalsIgnoreCase("Character")) {
                                toPass = Character.class;
                                param[0] = new Character(((String) methParams.elementAt(i)).charAt(0));
                            } else if (curMethType.equalsIgnoreCase("char")) {
                                toPass = char.class;
                                param[0] = new Character(((String) methParams.elementAt(i)).charAt(0));
                            } else if (curMethType.equals("Double")) {
                                toPass = Double.class;
                                param[0] = Double.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("double")) {
                                toPass = double.class;
                                param[0] = Double.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("Byte")) {
                                toPass = Byte.class;
                                param[0] = Byte.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("byte")) {
                                toPass = byte.class;
                                param[0] = Byte.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("Short")) {
                                toPass = Short.class;
                                param[0] = Short.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("short")) {
                                toPass = short.class;
                                param[0] = Short.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("Long")) {
                                toPass = Long.class;
                                param[0] = Long.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("long")) {
                                toPass = long.class;
                                param[0] = Long.valueOf((String) methParams.elementAt(i));
                            } else if (curMethType.equals("Properties") || curMethType.equals("java.lang.Properties")) {
                                toPass = Properties.class;
                                param[0] = Utility.buildProperties((String) methParams.elementAt(i), trace);
                            }
                            if (trace.isLoggable(BasicLevel.DEBUG)) {
                                trace.log(BasicLevel.DEBUG, "calling method " + methodName + " with " + param[0]);
                            }
                            meth = dsClass.getMethod(methodName, new Class[] {toPass});
                            meth.invoke(dsObj, param);
                        } catch (NoSuchMethodException ns) {
                            throw new ResourceException("No such method: " + methodName + " " + ns.getMessage(), ns);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                            throw new ResourceException("Error on method: " + methodName + " " + ex.getMessage(), ex);
                        }
                    }
                }
            }
        }
        if (pc != null) {
            try {
                param[0] = pc.getUserName();
                meth = dsClass.getMethod("setUserName", new Class[] {Class.forName("String")});
                meth.invoke(dsObj, param);

                param[0] = new String(pc.getPassword());
                meth = dsClass.getMethod("setPassword", new Class[] {Class.forName("String")});
                meth.invoke(dsObj, param);
            } catch (Exception ex) {
                throw new ResourceException("Error on method: " + methodName + " " + ex.getMessage(), ex);
            }
        }

        if (trace.isLoggable(BasicLevel.DEBUG)) {
            try {
                meth = dsClass.getMethod("getURL", (Class[]) null);
                trace.log(BasicLevel.DEBUG, "URL is " + meth.invoke(dsObj, (Object[]) null));
            } catch (Exception e) {
            }
        }
        return dsObj;

    }
    private static Properties buildProperties(String val, Logger trace)
            throws Exception {
        if (trace.isLoggable(BasicLevel.DEBUG)) {
            trace.log(BasicLevel.DEBUG, "" + val);
        }
        Properties ret = new Properties();

        if (val.length() == 0) {
            return ret;
        }
        String pairs = val.substring(1, val.length()-1);
        String parseVal = pairs.replace(',', '\n');
        try {
            ByteArrayInputStream valStream = new ByteArrayInputStream(parseVal.getBytes());
            ret.load(valStream);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new Exception("Invalid value specified for Properties parameter in dbSpecificMethods");
        }

        return ret;
    }

    private static void parseValues(String val, Vector vMeth, Vector vValues, Vector vTypes, Logger trace)
            throws Exception {
        if (trace.isLoggable(BasicLevel.DEBUG)) {
            trace.log(BasicLevel.DEBUG, "");
        }
        char delim = ':';
        boolean done = false;
        String methName = null;
        int offset = 0;
        boolean parsed = false;
        String parseVal = val.trim();
        String typeVal = "";
        String valVal = "";

        if (parseVal.length() == 0) {
            return;
        }
        if (parseVal.startsWith(":")) {
            delim = parseVal.charAt(1);
            parseVal = parseVal.substring(2);
        }
        while (!parsed) {
            offset = parseVal.indexOf('=');
            if (offset < 0) {
                throw new Exception("Invalid value specified for dbSpecificMethods");
            }
            methName = parseVal.substring(0, offset);
            vMeth.add(methName);
            parseVal = parseVal.substring(offset + 1);
            if (parseVal.charAt(0) == delim) {
                valVal = "";
            } else {
                offset = parseVal.indexOf(delim);
                if (offset < 0) {
                    valVal = parseVal;
                    offset = valVal.length() - 1;
                } else {
                    valVal = parseVal.substring(0, offset);
                }
            }
            vValues.add(valVal);
            if (offset < 0) {
                parsed = true;
            } else {
                parseVal = parseVal.substring(offset + 1);
                if (parseVal.length() == 0) {
                    parsed = true;
                }
            }
            if (parseVal.startsWith("" + delim)) {
                parseVal = parseVal.substring(1);
                offset = parseVal.indexOf(delim);
                if (offset < 0) {
                    typeVal = parseVal;
                } else {
                    typeVal = parseVal.substring(0, offset);
                }
                vTypes.add(typeVal);
                if (offset < 0) {
                    parsed = true;
                } else {
                    parseVal = parseVal.substring(offset + 1);
                    if (parseVal.length() == 0) {
                        parsed = true;
                    }
                }
            } else {
                vTypes.add("String");
            }
            if (trace.isLoggable(BasicLevel.DEBUG)) {
                trace.log(BasicLevel.DEBUG, "Parsed: method(" + methName + ") value(" + valVal + ") type(" + typeVal + ")");
            }
        }
    }

    /**
     * Returns the Password credential
     * @param mcf ManagedConnectionFactory currently being used
     * @param subject Subject associated with this call
     * @param info ConnectionRequestInfo
     * @return PasswordCredential for this user
     */
    static synchronized PasswordCredential getPasswordCredential(ManagedConnectionFactory mcf, Subject subject,
            ConnectionRequestInfo info, java.io.PrintWriter out) throws ResourceException {

        if (subject == null) {
            if (info == null) {
                return null;
            }
            ConnectionRequestInfoImpl crii = (ConnectionRequestInfoImpl) info;
            PasswordCredential pc = new PasswordCredential(crii.user, crii.password.toCharArray());
            pc.setManagedConnectionFactory(mcf);
            return pc;
        }
        Set cred = subject.getPrivateCredentials(PasswordCredential.class);
        PasswordCredential pc = null;
        for (Iterator iter = cred.iterator(); iter.hasNext();) {
            PasswordCredential tmpPc = (PasswordCredential) iter.next();
            if (tmpPc.getManagedConnectionFactory().equals(mcf)) {
                pc = tmpPc;
                break;
            }
        }
        if (pc == null) {
            SecurityException se = new SecurityException("No PasswordCredential found");
            out.println("" + se);
            throw se;
        }
        return pc;
    }

}