/* Copyright (c) 2001 Oracle Corporation. All Rights Reserved. */
package oracle.portal.provider.tools.v1;

import java.io.*;
import java.util.*;
import java.net.URL;
import java.lang.reflect.*;

import javax.servlet.http.*;

import oracle.portal.provider.v1.*;
import oracle.portal.provider.v1.http.*;
import oracle.portal.utils.v1.*;
import oracle.portal.utils.xml.v1.*;

import org.w3c.dom.*;
import org.w3c.dom.Node;
import org.xml.sax.*;
import oracle.xml.parser.v2.XMLDocument;
import oracle.xml.parser.v2.XSLException;
import oracle.xml.parser.v2.XSLStylesheet;

/**
 * <code>FileToDatabase</code> is a command line utility used for migrating
 * cusomizations stored with {@link
 * oracle.portal.provider.v1.FilePersonalizationManager} or {@link
 * oracle.portal.provider.v1.http.DefaultPortletPersonalizationManager} to the
 * format used by {@link
 * oracle.portal.provider.v1.DBPersonalizationManager2}. The utility is
 * designed to run on JVM versions 1.1 and higher and is invoked in the
 * following manner:-<p>
 *
 * <code>java -DConnectionManager.config=/path/to/connectionManager.xml
 * oracle.portal.provider.tools.v1.FileToDatabase [mandatory arguments]
 * [optional arguments]</code><p>
 *
 * where the mandatory arguments are:-<p>
 *
 * <code>/path/to/provider.xml</code> - The location of the XML Provider
 * Definition for those providers whose portlet's customizations are to be
 * migrated.<br> <code>path/to/rootDir</code> - The root directory holding the
 * customization files. By default, PDK-Java saves these to the
 * <code>provider_root</code><br> <code>connectionName</code> - The name of the
 * database connection to be used. This is specified in the
 * <code>connectionManager.xml</code><p>
 *
 * and the optional arguments are:-<p>
 *
 * <code>-t tableName</code> - The name of the database table into which
 * customizations will be inserted. Default:
 * <code>jpdk_preference_store2</code><br> <code>-p providerID</code> - The id
 * of aspecific provider instance whose portlet's customizations are to be
 * migrated. Default: all directories below <code>rootDir</code> whose names
 * are parsable as <code>long</code>s.<br> <code>-o portletID</code> - The id
 * of a specific portlet whose customizations are to be migrated. Default: all
 * portlets that are declared in the XML Provider Definition to use {@link
 * oracle.portal.provider.v1.FilePersonalizationManager} or {@link
 * oracle.portal.provider.v1.http.DefaultPortletPersonalizationManager}. Note
 * that this optional argument <i>may</i> be supplied without a
 * <code>providerID</code> argument.<br> <code>-c transformClassName</code> -
 * The name of a loadable Java class that implements {@link
 * oracle.portal.utils.v1.Transform}. If this argument is supplied, an instance
 * of this class will be used to transform customization data objects before
 * they are written in to the database. This is accomplished by a call to the
 * {@link oracle.portal.utils.v1.Transform#transform(Object)} method. Note that
 * the user must supply the logic to transfer the data by providing the
 * implementation of this method. Default: No data object transform will
 * occur.<br> <code>-l logFile</code> - A file to which messages will be
 * logged. Default: the console window.<p>
 *
 * Run this utility with the same <code>classpath</code> used to start up the
 * JServ JVM on which the Provider exists. A database table equivalent to that
 * created by the
 * <code>/jpdk/providers/dbPersonalization2/jpdk_preference_store2.sql</code>
 * script must exist in the target database before this utility is invoked.
 */
public class FileToDatabase
{
    // Required statics for XML Parsing (code was copied from DefaultProvider
    // and these statics had private access there.)
    private static String PORTLET_TAG = "portlet";
    private static String PM_TAG = "personalizationManager";
    private static String ATTRIBUTE_FPM =
        "oracle.portal.provider.v1.FilePersonalizationManager";
    private static String ATTRIBUTE_DPPM =
        "oracle.portal.provider.v1.http.DefaultPortletPersonalizationManager";
    private static String DATACLASS_TAG = "dataClass";
    private static String ATTRIBUTE_CLASS = "class";
    private static String ADD_METHOD_PREFIX = "add";
    private static String SET_METHOD_PREFIX = "set";
    private static final String PROVIDER_XML_FORMAT_VERSION = "2.0";
    private static final int    PROVIDER_XML_FORMAT_MAJOR_VERSION =
        XMLUtil.getVersionMajor(PROVIDER_XML_FORMAT_VERSION);
    private static final String PROVIDER_UPGRADE_V1_STYLESHEET =
        "upgrade_v1_to_v2.xsl";
    private static final String PI_PROVIDER = "providerDefinition";
    private static final String DATA_VERSION_ATTRIBUTE = "version";
    private static final String ELEMENT_PROVIDER = "provider";

    private static String COMMAND =
        "Usage: java -DConnectionManager.config=/path/to/connectionManager.xml" +
        " oracle.portal.provider.tools.v1.FileToDataBase ";
    private static String ARGS = "/path/to/provider.xml " +
                                 "/root/directory " +
                                 "connectionName " +
                                 "-t tableName " +
                                 "-p providerID " +
                                 "-o portletID " +
                                 "-c transformClass " +
                                 "-l /path/to/logFile.txt ";

    private static int MANDATORY_ARGS_TOTAL = 3;
    private static String SEP = File.separator;

    private String mRegistry = null;
    private File mRootDir = null;
    private String mProviderIDToTransfer = null;
    private String mPortletIDToTransfer = null;
    private String mLogFile = null;

    private FileOutputStream mOutputStream = null;
    private XMLDocument mProviderDefinition = null;
    private Vector mProviders = new Vector();

    // Common DBPM instance used for writing to the database table.
    private static DBPersonalizationManager2 sDBPM = null;

    // Common instance of transform class used to transform data objects.
    private static Transform sTrans = null;

    private FileToDatabase()
    {
    }

    private FileToDatabase(String registryLocation, File rootDir,
                           String providerId, String portletId, String logFile)
    {
        mRegistry = registryLocation;
        mRootDir = rootDir;
        mProviderIDToTransfer = providerId;
        mPortletIDToTransfer = portletId;
        mLogFile = logFile;
    }

    /**
     * Entry point for the customization migration utility. Mandatory arguments
     * must be supplied and a combination of optional arguments may also be
     * supplied as described in the class level Javadoc.
     */
    public static void main(String[] args)
    {
        // process the command line arguments
        FileToDatabase inst = processArgs(args);

        // if a log file was specified, redirect System.out to that file.
        inst.setLogOutput();

        // read the xml provider definition.
        inst.readProviderDefinition();

        // prepare a collection of migration provider objects with provider ids
        // either from the filesystem or from the supplied optional program
        // argument.
        inst.prepareProviders();

        // populate the migration providers with migration portlets according
        // to the xml definition previously parsed. The node handler is fault
        // tolerant. It will only attach those portlets who use FilePM.  If no
        // portlet id was supplied as a program argument, all those portlets
        // that have a PM will be attached. Otherwise, only the portlet with
        // matching id is attached (provided it has a PM).
        inst.populateProviders();

        // now use the provider ids and portlet ids to process the correct
        // directories under the root dir and store reference strings for each
        // portlet.
        inst.processDirectories();

        // Loop through all of the PortletReferences. For each one, read in the
        // data object from the file system by invoking the FilePM. Then
        // provide a hook for data object transformation by invoking the
        // transformable interface. Finally, the objects are passed to a common
        // DBPM for writing into the database.
        inst.transferDataObjects();

        // call this at the end to close the output stream to the log file (if
        // one exists).
        inst.closeLogOutput();
    }

    private void transferDataObjects()
    {
        Enumeration e = mProviders.elements();
        while (e.hasMoreElements())
        {
            Provider p = (Provider)e.nextElement();
            Portlet[] portlets = null;
            try
            {
                portlets = p.getPortlets(0, 0, false, null);
            }
            catch (ProviderException pe)
            {
                //MigrationProvider doesn't throw this exception so just
                //ignore.
            }
            for (int i = 0; i < portlets.length; i++)
            {
                System.out.println("\nTransferring data objects for ProviderID: " +
                                   portlets[i].getProvider().getProviderId() + 
                                   " PortletID: " + portlets[i].getId());
                PortletReference[] refs =
                    ((MigrationPortlet)portlets[i]).getPortletReferences();
                MigrationFilePersonalizationManager fpm =
                    (MigrationFilePersonalizationManager)portlets[i].getPersonalizationManager();

                // get that data class so we can pass it on to DMPM2 later.
                String dataClass = fpm.getDataClass();
                
                if (fpm == null || refs == null)
                {
                    continue;
                }
                for (int j = 0; j < refs.length; j++)
                {
                    // Read data from file system.
                    Object dataFrom = null;
                    try
                    {
                        dataFrom = fpm.read(refs[j],
                                            refs[j].getUserCustomization());
                    }
                    catch (Exception re)
                    {
                        System.out.println("Unable to read data object: " +
                                           refs[j].toString() + ": " +
                                           re.getMessage());
                    }

                    // Hook for data transfer.
                    Object dataTo = dataFrom;
                    if (sTrans != null)
                    {
                        dataTo = sTrans.transform(dataFrom);
                        dataClass = dataTo.getClass().getName();
                        System.out.println("Successfully transformed data object: " +
                                           refs[j].toString());
                    }

                    // Pass on to the DBPersonalizationManager2
                    try
                    {
                        sDBPM.setDataClass(dataClass);
                        sDBPM.create(refs[j], dataTo, refs[j].getUserCustomization());
                        sDBPM.write(refs[j], dataTo, refs[j].getUserCustomization());
                        System.out.println("Successfully transferred data object: " +
                                           refs[j].toString());
                    }
                    catch (Exception pe)
                    {
                        System.out.println("Unable to write data object: " +
                                           refs[j].toString() + ": " + 
                                           "Error message: " + pe.getMessage());
                    }
                    
                }// end portlet reference loop

            }//end portlet loop

        }//end provider loop
    }

    private void processDirectories()
    {
        System.out.println("Building up PortletReferences from customizations " +
                           "found...\n");
        // Loop through each provider and each of its portlets, process the
        // matching directories beneath the root dir and attach the ref string
        // to the portlet.
        Enumeration e = mProviders.elements();
        while (e.hasMoreElements())
        {
            Provider p = (Provider)e.nextElement();
            Portlet[] portlets = null;
            try
            {
                portlets = p.getPortlets(0, 0, false, null);
            }
            catch (ProviderException pe)
            {
                // Our migration provider doesn't throw this exception, so just
                // ignore.
            }
            for (int i = 0; i < portlets.length; i++)
            {
                String startDir = mRootDir.getAbsolutePath() + SEP +
                    p.getProviderId() + SEP + portlets[i].getId();
                File f = new File(startDir);
                if (f.exists() && f.isDirectory())
                {
                    processDirectory(f, (MigrationPortlet)portlets[i]);
                }
                else
                {
                    System.out.println("Warning: Unable to find customization " +
                                       "files for ProviderID: " +
                                       p.getProviderId() + " PortletID: " +
                                       portlets[i].getId() + "\n");
                }
            }
        }
    }

    private void processDirectory(File start, MigrationPortlet p)
    {
        String[] children = start.list();
        for (int i = 0; i < children.length; i++)
        {
            StringBuffer name = new StringBuffer(start.getAbsolutePath());
            name.append(File.separator);
            name.append(children[i]);
            File f = new File(name.toString());
            if (f.isDirectory())
            {
                processDirectory(f, p);
            }
            else
            {
                String refString =
                    f.getAbsolutePath().substring(mRootDir.getAbsolutePath().length() + 1);
                System.out.print("Building PortletReference from: " +
                                 refString + " ...");
                p.addPortletReference(refString);
                System.out.println(".Done");
            }
        }
    }

    private void populateProviders()
    {
        // Instantiate the fault tolerant node handler.
        MigrationNodeHandler handler = new MigrationNodeHandler();

        // Process each of the migration providers we have using the parsed xml and
        // the fault tolerant node handler.
        Enumeration e = mProviders.elements();
        while (e.hasMoreElements())
        {
            Provider p = (Provider)e.nextElement();
            System.out.print("Building required object tree for provider ID: "
                             + p.getProviderId() + " ...");
            try
            {
                handler.processNode(mProviderDefinition.getDocumentElement(), p);
            }
            catch (NodeHandlerException nhe)
            {
                // **LOOK ME**. eat for now, may want to terminate the process?
                System.out.println(nhe.getMessage());
            }
            System.out.println(".Done.\n");
        }
    }

    private void prepareProviders()
    {
        String[] rootChildren = null;
        // First get the child directories of root dir whose names are
        // parseable as longs. (if root dir not supplied, program will have
        // terminated by now.
        if (mRootDir != null)
        {
            rootChildren = mRootDir.list(new ProviderIDFilter());
        }

        // If no such children found, terminate the process.
        if (rootChildren == null)
        {
            System.out.println("Unable to find child directories of the " +
                               "correct format for the supplied root.\n");
            closeLogOutput();
            System.exit(1);
        }

        // If we were supplied with a provider id, check for its existence,
        // and instantiate one migration provider only.
        if (mProviderIDToTransfer != null)
        {
            System.out.print("Checking for existence of supplied Provider ID...");
            boolean test = false;
            for (int i = 0; i < rootChildren.length; i++)
            {
                if (rootChildren[i].equals(mProviderIDToTransfer))
                {
                    test = true;
                    break;
                }
            }
            if (test != true)
            {
                System.out.println(".Failed. Process Exiting.\n");
                closeLogOutput();
                System.exit(1);
            }
            else
            {
                MigrationProvider temp = new MigrationProvider(mRootDir.getAbsolutePath());
                temp.setProviderId(mProviderIDToTransfer);
                mProviders.addElement(temp);
                System.out.println(".Done.\n");
                System.out.println("Successfully instantiated Provider object.\n");
            }
        }
        // Else loop through each matching directory and instantiate a dummy
        // provider with id equal to the name of that directory.
        else if (mProviderIDToTransfer == null)
        {
            System.out.println("Instantiating Provider objects with " +
                               "IDs found...\n");
            MigrationProvider temp = null;
            String name = null;
            for (int i = 0; i < rootChildren.length; i++)
            {
                temp = new MigrationProvider(mRootDir.getAbsolutePath());
                name = rootChildren[i];
                System.out.println("ID: " + name);
                temp.setProviderId(name);
                mProviders.addElement(temp);
            }
            System.out.println("\nSucessfully instantiated Provider objects.\n");
        }
    }

    private void readProviderDefinition()
    {
        // parse the registry.
        System.out.print("\nAttempting to parse XML Provider Definition...");
        try
        {
            mProviderDefinition = parseRegistry();
        }
        catch (ProviderException pe)
        {
            System.out.println(".Failed due to:\n");
            System.out.println("\t" + pe.getMessage() + "\n");
            closeLogOutput();
            System.exit(1);
        }
        System.out.println(".Successful.\n");
    }

    private void setLogOutput()
    {
        if (mLogFile != null)
        {
            try
            {
                File log = new File(mLogFile);
                mOutputStream = new FileOutputStream(log);
                PrintStream out = new PrintStream(mOutputStream);
                System.setOut(out);
                System.out.println("Sucessfully created log file.");
            }
            catch (IOException e)
            {
                // Let execution continue with System.out going to the console.
                System.out.println("Unable to redirect messages to log file.");
            }
        }
    }

    private void closeLogOutput()
    {
        if (mOutputStream != null)
        {
            try
            {
                mOutputStream.close();
            }
            catch (IOException ioe)
            {
                // do nothing.
            }
        }
    }

    private static FileToDatabase processArgs(String[] args)
    {
        if (args.length < MANDATORY_ARGS_TOTAL || args[0].equals(""))
        {
            System.out.println(COMMAND + " " + ARGS);
            System.exit(1);
        }
        // Instantiate the DBPM, terminating the process if an exception occurs.
        try
        {
            sDBPM = new DBPersonalizationManager2();
        }
        catch (ConnectionManagerException cme)
        {
            System.out.println("Failed to instantiate DBPersonalizationManager2" +
                               " due to:");
            System.out.println("\t" + cme.getMessage());
            System.exit(1);
        }

        // Validate first two arguments (order of first three is mandatory).
        File registry = new File(args[0]);
        if (!registry.exists())
        {
            System.out.println("Unable to access XML Provider Definition at: " +
                               args[0]);
            System.exit(1);
        }
        File root = new File(args[1]);
        if (!root.exists() || !root.isDirectory())
        {
            System.out.println("Invalid customization root directory: " +
                               args[1]);
            System.exit(1);
        }
        
        // Now build up the options list.
        String tableName = null;
        String providerId = null;
        String portletId = null;
        String transformClassName = null;
        String log = null;
        
        // Grab anything after index 2 to build up options list.
        for (int i = 3; i < args.length; )
        {
            if (args[i].startsWith("-") && args[i].length() == 2)
            {
                // args[i] is the option eg -t
                // args[i+1] is the argument
                // e.g. we have -t myTable
                if (args[i].equals("-t") && i != (args.length - 1))
                {
                    tableName = args[i+1];
                }
                if (args[i].equals("-p") && i != (args.length - 1))
                {
                    providerId = args[i+1];
                }
                if (args[i].equals("-o") && i != (args.length - 1))
                {
                    portletId = args[i+1];
                }
                if (args[i].equals("-c") && i != (args.length - 1))
                {
                    transformClassName = args[i+1];
                }
                if (args[i].equals("-l") && i != (args.length - 1))
                {
                    log = args[i+1];
                }
                i = i + 2;
                continue;
            }
            else if (args[i].startsWith("-") && args[i].length() !=2)
            {
                // args[i] is the option and the argument
                // e.g. we have -tmyTable
                String option = args[i].substring(0, 2);
                String argument = args[i].substring(2, args[i].length());
                if (option.equals("-t"))
                {
                    tableName = argument;
                }
                if (option.equals("-p"))
                {
                    providerId = argument;
                }
                if (option.equals("-o"))
                {
                    portletId = argument;
                }
                if (option.equals("-c"))
                {
                    transformClassName = argument;
                }
                if (option.equals("-l"))
                {
                    log = argument;
                }
                i++;
                continue;
            }
            else
            {
                // args[i] doesn't start with "-" so we've already processed
                // it.
                // e.g. myTable
                i++;
                continue;
            }

        }

        // Set up the DBPM, which if we've got this far is guaranteed to be
        // available!
        try
        {
            sDBPM.setConnection(args[2]);
        }
        catch (PersonalizationException pe)
        {
            System.out.println("Failed to initialize DBPersonalizationManager2" +
                               " due to:");
            System.out.println("\t" + pe.getMessage());
            System.exit(1);
        }
        if (tableName != null)
        {
            sDBPM.setTable(tableName);
        }

        if (transformClassName !=null)
        {
            try
            {
                Class transformClass = Class.forName(transformClassName);
                sTrans = (Transform)transformClass.newInstance();
            }
            catch (Exception tce)
            {
                System.out.println("Failed to instantiate Transform class" +
                                   " due to: ");
                System.out.println("\t" + tce.getMessage());
                System.exit(1);
            }
        }

        return new FileToDatabase(args[0], root, providerId, portletId, log);
    }

    // Copied from DefaultProvider.
    private XMLDocument parseRegistry() throws ProviderException
    {
        XMLDocument xmldoc = null;
        //Capture any XML / XSL parsing errors in the following block to this
        //output stream. A finally{} block at the end ensures it gets closed
        //afterwards
        ByteArrayOutputStream errOut = new ByteArrayOutputStream();
        try
        {
            // Make sure we release this InputStream when finished with it
            InputStream is = null;
            try
            {
                is = new FileInputStream(new File(mRegistry));
                // Obtain the document.
                try
                {
                    xmldoc = XMLUtil.parseDocument(is, errOut);
                }
                catch(SAXParseException e1)
                {
                    String errtext = "Error parsing " + mRegistry + " at line " +
                        e1.getLineNumber() + ", column " + e1.getColumnNumber();
                    throw new ProviderException(errtext);

                }
            }
            finally
            {
                if(is != null)
                {
                    try
                    {
                        is.close();
                    }
                    catch(Exception e1)
                    {
                    }
                    is = null;
                }
            }
            Enumeration xsldocs = XMLUtil.getXslDocs(xmldoc);
            // If the document explicitly names a list of stylesheets, then
            // apply them
            if(xsldocs.hasMoreElements())
            {
                //Apply the XSL transformations to the xml document.
                do
                {
                    xmldoc =
                        XMLUtil.applyXSL(xmldoc,
                                         (XSLStylesheet)xsldocs.nextElement(),
                                         errOut);
                }
                while(xsldocs.hasMoreElements());
            }
            // If there are no stylesheets explicitly named, check for a
            // version processing instruction
            else
            {
                String version = null;
                // Assume we have a version 1 format file, unless we can find a
                // providerDefinition processing instruction with a version
                // specified.
                int majorVersion = 1;
                Enumeration pdkpis = XMLUtil.getTargetPIs(xmldoc, PI_PROVIDER);
                if(pdkpis.hasMoreElements())
                {
                    ProcessingInstruction pi =
                        (ProcessingInstruction)pdkpis.nextElement();
                    Hashtable piAttributes = XMLUtil.parsePI(pi);
                    // Try to get a version number out of the processing
                    // instruction
                    version = (String)piAttributes.get(DATA_VERSION_ATTRIBUTE);
                    if(version != null)
                    {
                        try
                        {
                            majorVersion = XMLUtil.getVersionMajor(version);
                        }
                        catch (NumberFormatException nfe)
                        {
                            throw new ProviderException("XML processing error: Invalid version number specified in '" +
                                                        PI_PROVIDER + "' processing instruction.");
                        }
                    }
                }
                // Upgrade the xml, if it is below version 2
                if (majorVersion < PROVIDER_XML_FORMAT_MAJOR_VERSION)
                {
                    // Get the upgrade stylesheet from within the .jar file on
                    // the classpath
                    XMLDocument upgragedoc = null;
                    // Make sure we release the InputStream when finished with
                    // it
                    try
                    {
                        is = DefaultProvider.class.getResourceAsStream("resource/"
                                                                       + PROVIDER_UPGRADE_V1_STYLESHEET);
                        upgragedoc = XMLUtil.parseDocument(is, errOut);
                    }
                    finally
                    {
                        if(is != null)
                        {
                            try
                            {
                                is.close();
                            }
                            catch(Exception e1)
                            {
                            }
                        }
                        is = null;
                    }
                    XSLStylesheet upgradeXSL = new XSLStylesheet(upgragedoc, new URL("http://www.oracle.com/portal/"));
                    xmldoc = XMLUtil.applyXSL(xmldoc, upgradeXSL, errOut);
                }
                // Throw an exception if we have a data format we can't cope with
                else if(version != null && !version.equals(PROVIDER_XML_FORMAT_VERSION))
                {
                    throw new ProviderException("Error: this provider does not support XML registries with a " +
                                                PI_PROVIDER + " version above \"" + PROVIDER_XML_FORMAT_VERSION + "\"");
                }
            }
        } // End try
        catch (SAXException se)
        {
            String msg = se.getMessage() + Character.LINE_SEPARATOR + errOut.toString();
            throw new ProviderException(msg);
        }
        catch (XSLException xsle)
        {
            String msg = xsle.getMessage() + Character.LINE_SEPARATOR + errOut.toString();
            throw new ProviderException(msg);
        }
        catch (IOException ioe)
        {
            throw new ProviderException("IO Error parsing provider registry: " + ioe.getMessage());
        }
        finally
        {
            if(errOut!= null)
            {
                try
                {
                    errOut.close();
                }
                catch(Exception e1)
                {
                }
            }
            errOut = null;
        }
        // Make sure that the XML we end up with actually has <provider> at the root!
        Element docEl = xmldoc.getDocumentElement();
        if(docEl == null || !docEl.getTagName().equalsIgnoreCase(ELEMENT_PROVIDER))
        {
            throw new ProviderException("No provider element found in provider.xml");
        }
        return xmldoc;
    }

    private static class MigrationProvider implements Provider
    {
        private long mProviderID;
        private Vector mPortlets = new Vector(10);
        private String mProviderRepositoryPath = null;

        public MigrationProvider(String startDir)
        {
            mProviderRepositoryPath = startDir;
        }

        public void addPortlet(Object portlet) throws ProviderException
        {
            if (portlet instanceof Portlet)
                
            {
                mPortlets.addElement((Portlet)portlet);
            }
        }

        public void init(long providerId, ProviderLog logger)
            throws ProviderException
        {
        }

        public void setProviderId(String id)
        {
            try
            {
                mProviderID = Long.parseLong(id);
            }
            catch (NumberFormatException nfe)
            {
                // Set the provider id to -1 as we wont do anything with
                // providers whose id is less that 0.
                mProviderID = -1;
            }
        }

        public long getProviderId()
        {
            return mProviderID;
        }

        public String getProviderRepositoryPath()
        {
            return mProviderRepositoryPath;
        }

        public ProviderSubscriber getSubscriber()
        {
            return null;
        }

        public int getVersion()
        {
            return -1;
        }

        public ProviderSubscriber register(String subscriberId,
                                           String portalVersion)
            throws ProviderException
        {
            return null;
        }

        public void deregister() throws ProviderException
        {
        }

        public Object[] initSession(ProviderUser user,
                                    ExternalPrincipal externalApp) 
            throws ProviderException
        {
            return null;
        }

        public Object[] initSession(ProviderUser user) throws ProviderException
        {
            return null;
        }

        public Portlet[] getPortlets(int start, int count, boolean byUser,
                                     ProviderUser user)
        {
            Portlet[] retVal = new Portlet[mPortlets.size()];
            Enumeration e = mPortlets.elements();
            int i = 0;
            while (e.hasMoreElements())
            {
                retVal[i] = (Portlet)e.nextElement();
                i++;
            }
            return retVal;
        }

        public Portlet getPortlet(long id, ProviderUser user)
            throws ProviderException
        {
            return null;
        }

        public void log(String msg)
        {
        }

        public void log(String message, Throwable throwable)
        {
        }
    }

    private static class MigrationPortlet extends DefaultPortlet
    {
        private static String FM_EDIT_DEFAULTS = "edit_defaults";
        private static String FM_DEFAULT = "_default";

        private Vector mPortletReferences = new Vector();

        public void addPortletReference(String refString)
        {
            String instanceName = null;
            String userName = null;
            Locale locale = null;
            String fileName = null;

            PortletReference portletRef = null;

            // Tokenize the ref string to get at the instance name and file
            // name.
            StringTokenizer st = new StringTokenizer(refString, SEP);
            Vector temp = new Vector();
            while (st.hasMoreTokens())
            {
                temp.addElement(st.nextToken());
            }

            // If we have a 'hashing' directory, ignore
            // it. (providerId.portletId.instanceName.hashDir.fileName)
            if (temp.size() == 5)
            {
                temp.removeElementAt(3);
            }

            // Get the instance name and file name.
            instanceName = (String)temp.elementAt(2);
            fileName = (String)temp.elementAt(3);

            // Strip the .ser or .dat from the file name.
            fileName = fileName.substring(0, fileName.length() - 4);

            if (fileName.equals(FM_EDIT_DEFAULTS) || fileName.equals(FM_DEFAULT))
            {
                portletRef = new PortletReference(getProvider(), this,
                                                  instanceName, null, null,
                                                  null, false);
            }
            else if (fileName.startsWith(FM_EDIT_DEFAULTS))
            {
                // Strip off edit_defaults, process the rest as a locale
                // new ref with empty prov user and that locale.
                String localeTotal = fileName.substring(FM_EDIT_DEFAULTS.length() + 1);
                Locale l = processLocale(localeTotal);
                portletRef = new PortletReference(getProvider(), this,
                                                  instanceName, null, l,
                                                  null, false);
            }
            else if (fileName.startsWith(FM_DEFAULT))
            {
                // Strip off _default, process the rest as a locale
                // new ref with empty prov user and that locale.
                String localeTotal = fileName.substring(FM_DEFAULT.length() + 1);
                Locale l = processLocale(localeTotal);
                portletRef = new PortletReference(getProvider(), this,
                                                  instanceName, null, l,
                                                  null, false);
            }
            else
            {
                // We have no locale information so set the user name now. If
                // we do find locale information, this will be reassigned.
                userName = MultiByteDecoder.decodeUTF(fileName);

                // new port ref with prov user having user name, and locale
                // what ever locale is now.
                ProviderUser user = new FileToDatabaseUser(userName);
                
                portletRef = new PortletReference(getProvider(), this,
                                                  instanceName, user, locale,
                                                  null, false);
            }
            
            // add the portlet reference if it is non null.
            if (portletRef != null)
            {
                mPortletReferences.addElement(portletRef);
            }
        }

        private Locale processLocale(String total)
        {
            String lan = "";
            String con = "";
            int underScoreIndex = total.indexOf('_');
            if (underScoreIndex != -1)
            {
                lan = total.substring(0, underScoreIndex);
                con = total.substring(underScoreIndex + 1);
            }
            else
            {
                lan = total;
            }
            return new Locale(lan, con);
        }

        public PortletReference[] getPortletReferences()
        {
            if (mPortletReferences.isEmpty())
            {
                return null;
            }
            Enumeration e = mPortletReferences.elements();
            PortletReference[] refs =
                new PortletReference[mPortletReferences.size()];
            int i = 0;
            while (e.hasMoreElements())
            {
                refs[i] = (PortletReference)e.nextElement();
                i++;
            }
            return refs;
        }
    }

    private static class ProviderIDFilter implements FilenameFilter
    {
        public ProviderIDFilter()
        {
        }

        public boolean accept(File root, String name)
        {
            try
            {
                long id = Long.parseLong(name);
            }
            catch (NumberFormatException e)
            {
                return false;
            }
            return true;
        }
    }

    private class MigrationNodeHandler extends ProviderNodeHandler
    {
        public MigrationNodeHandler()
        {
            super();
        }

        public void processNode(Node n, Object o) throws NodeHandlerException
        {
            for (Node childNode = n.getFirstChild();
                 childNode != null;
                 childNode = childNode.getNextSibling())
            {
                if (childNode.getNodeType() != Node.ELEMENT_NODE)
                {
                    continue;
                }             
                Element e = (Element)childNode;

                if(isComplexElement(e))
                {
                    Object childObject = getApplicationObjectForElement(e);

                    // only proceed if we have a portlet or a personalization
                    // manager
                    if (childObject != null)
                    {
                        NodeHandler handler = getHandlerForElement(e);
                        if(handler == null)
                        {
                            handler = this;
                        }
                        
                        invokePreInitialize(childObject,o);
                        handler.processNode(childNode,childObject);
                        invokePostInitialize(childObject);
                        attachObject(o,childObject,e);
                    }
                }
                else
                {
                    handleSimpleElement(e, o);
                }
            }
        }

        public Object getApplicationObjectForElement(Element e)
        {
            String tagName = e.getTagName();

            // Only create a child object if the tag is for portlet or
            // personalizationManager.
            if (tagName != null && tagName.equals(PORTLET_TAG))
            {
                return new MigrationPortlet();
            }
            if ((tagName != null && tagName.equals(PM_TAG)) &&
                (e.getAttribute(FileToDatabase.ATTRIBUTE_CLASS).equals(ATTRIBUTE_FPM) ||
                 e.getAttribute(FileToDatabase.ATTRIBUTE_CLASS).equals(ATTRIBUTE_DPPM) ))
            {
                return new MigrationFilePersonalizationManager();
            }
            return null;
        }

        protected Method findMethod(Object o, String tagName, Class[] paramTypes)
        {
            String methodName;
            try
            {
                methodName = getMethodName(FileToDatabase.SET_METHOD_PREFIX, tagName);
                return o.getClass().getMethod(methodName, paramTypes);
            }
            catch (NoSuchMethodException nssme)
            {
                try
                {
                    methodName = getMethodName(FileToDatabase.ADD_METHOD_PREFIX, tagName);
                    return o.getClass().getMethod(methodName, paramTypes);
                }
                catch (NoSuchMethodException nsame)
                {
                    // dont't care.
                    return null;
                }
            }
        }

        protected void attachObject(Object o, Object childObject, Element e)
        {
            // Attach only those portlets we are interested in.
            boolean attach = true;
            if ((o instanceof Provider && childObject instanceof Portlet))
            {
                // We only allow creation of FilePMs so only attach portlet if
                // its personalization manager is non null.
                PortletPersonalizationManager pm =
                    ((Portlet)childObject).getPersonalizationManager();
                if (pm == null)
                {
                    attach = false;
                }

                // Next, if we were supplied a portlet id, only attach the
                // portlet if it matches.
                if (mPortletIDToTransfer != null)
                {
                    long testId = Long.parseLong(mPortletIDToTransfer);
                    if (!(testId == ((Portlet)childObject).getId()))
                    {
                        attach = false;
                    }
                }
            }
            if (attach == true)
            {
                String name = e.getTagName();
                Method m = findMethod(o, name, sObjectParamType);
                if (m == null)
                {
                    return;
                }
                Object[] params = {childObject};
                try
                {
                    m.invoke(o, params);
                }
                catch (Exception ex)
                {
                    // don't care.
                    return;
                }
            }
        }

        protected void setCustomValue(String name, String value, Object o)
        {
            // here, I'm checking that the data class can be located and
            // warning if not. An alternative to this would be to have yet
            // another static inner class which extends FilePM and overrides
            // the setDataClass method to do such a warning.
            if (name != null && name.equals(DATACLASS_TAG))
            {
                try
                {
                    Class temp = Class.forName(value);
                }
                catch (ClassNotFoundException cnf)
                {
                    System.out.println("The data class " + value +
                                       " cannot be located. Exiting...");
                    closeLogOutput();
                    System.exit(1);
                }
            }
            Method m = findMethod(o, name, sCustomParamType);
            if (m == null)
            {
                return;
            }
            Object[] params = {value};
            try
            {
                m.invoke(o, params);
            }
            catch (Exception ex)
            {
                // don't care.
                return;
            }
        }
    }

    private static class MigrationFilePersonalizationManager
        extends FilePersonalizationManager
    {
        private String mDataClass = null;

        public void setDataClass(String className)
            throws PersonalizationException
        {
            super.setDataClass(className);
            mDataClass = className;
        }

        public String getDataClass()
        {
            return mDataClass;
        }
    }

}
