/**
 * Copyright (C) 2000-2010 Atomikos <info@atomikos.com>
 *
 * This code ("Atomikos TransactionsEssentials"), by itself,
 * is being distributed under the
 * Apache License, Version 2.0 ("License"), a copy of which may be found at
 * http://www.atomikos.com/licenses/apache-license-2.0.txt .
 * You may not use this file except in compliance with the License.
 *
 * While the License grants certain patent license rights,
 * those patent license rights only extend to the use of
 * Atomikos TransactionsEssentials by itself.
 *
 * This code (Atomikos TransactionsEssentials) contains certain interfaces
 * in package (namespace) com.atomikos.icatch
 * (including com.atomikos.icatch.Participant) which, if implemented, may
 * infringe one or more patents held by Atomikos.
 * It should be appreciated that you may NOT implement such interfaces;
 * licensing to implement these interfaces must be obtained separately from Atomikos.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

package com.atomikos.icatch.config.imp;

import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;

import com.atomikos.datasource.RecoverableResource;
import com.atomikos.datasource.ResourceException;
import com.atomikos.icatch.CompositeTransactionManager;
import com.atomikos.icatch.ExportingTransactionManager;
import com.atomikos.icatch.ImportingTransactionManager;
import com.atomikos.icatch.SysException;
import com.atomikos.icatch.TSListener;
import com.atomikos.icatch.admin.LogAdministrator;
import com.atomikos.icatch.config.TSInitInfo;
import com.atomikos.icatch.config.UserTransactionService;
import com.atomikos.icatch.system.Configuration;
import com.atomikos.util.IOHelper;

/**
 * 
 * 
 * The user's (client program) view of the transaction manager's configuration,
 * a compact facade with all the information the client program needs. This base
 * class should be overridden for each module (CORBA, trmi,...).
 * 
 */

public abstract class AbstractUserTransactionService implements
        UserTransactionService
{

	 
	private static void echoProperties ( Properties properties )
    {
		if ( Configuration.isInfoLoggingEnabled() ) Configuration.logInfo ( "USING core version: " + Configuration.getVersion() );
        if ( properties != null ) {
            Enumeration names = properties.propertyNames ();
            while ( names.hasMoreElements () ) {
                String name = (String) names.nextElement ();
                String value = properties.getProperty ( name );
                //log as INFO - see case 24689
                if ( Configuration.isInfoLoggingEnabled() ) Configuration.logInfo ( "USING " + name + " = " + value );
            }
        }
    }

    /**
     * Utility method to get and trim properties.
     * 
     * @param name
     *            The name of the property to get.
     * @param p
     *            The properties to look in.
     * @return String The property without leading or trailing spaces, or null
     *         if not found.
     */
    public static String getTrimmedProperty ( String name , Properties p )
    {
        String ret = null;
        ret = p.getProperty ( name );
        if ( ret != null )
            ret = ret.trim ();
        // System.err.println ( "getTrimmedProperty ( " + name + " ) returning:
        // " + ret );
        return ret;
    }

    /**
     * Utility method to get a default TM name.
     * 
     * @return A default TM name based on the local IP.
     */
    protected static String getDefaultName ()
    {

        String ret = "tm";
        try {
            ret = java.net.InetAddress.getLocalHost ().getHostAddress ()
                    + ".tm";
        } catch ( UnknownHostException e ) {
            // ignore: use short default
        }

        return ret;
    }

    /**
     * Utility method to get the local host address.
     * 
     * @return
     */
    protected static String getHostAddress ()
    {
        String ret = "localhost";
        try {
            ret = java.net.InetAddress.getLocalHost ().getHostAddress ();
        } catch ( UnknownHostException e ) {
            // ignore: use short default
        }

        return ret;
    }

    /**
     * Utility method to find or create a given folder
     * 
     * @param path
     *            The folder path.
     * @return String The resulting file path, or a default if the given path is
     *         not valid as a folder.
     */

    protected static String findOrCreateFolder ( String path )
    {
        File ret = new File ( "." );
        // current dir is default to return
        // System.out.println ( "Creating path: " + path );
        if ( path != null ) {
            File tmp = new File ( path );
            if ( tmp.exists () ) {
                if ( tmp.isDirectory () ) {
                    // System.err.println ( "Using existing directory: " + path
                    // );
                    ret = tmp;
                } else {
                    // if exists but not a directory: use default
                    String msg = path + " is not a directory - using default";
                    System.err.println ( msg );
                }
            } else {
                // file does not exist; attempt to create
                String msg = path + " could not be created - using default";
                try {
                    if ( IOHelper.createPathTo ( tmp, true ) ) {
                        ret = tmp;
                    } else {
                        System.err.println ( msg );
                    }
                } catch ( IOException e ) {
                    System.err.println ( msg );
                }
            }
        }

        String result = ret.getAbsolutePath ();
        if ( !result.endsWith ( File.separator ) )
            result = result + File.separator;
        // System.out.println ( "Returning path: " + result );
        return result;
    }

    private TSInitInfo info_;

    // the info object, needed at shutdown to remove resources
    // so that re-init calls work fine.

    /**
     * @see UserTransactionService
     */

    public void shutdown ( boolean force ) throws IllegalStateException
    {
        boolean errors = false;
        
        //remove shutdown hooks to avoid case 21519
        Configuration.removeShutdownHooks();
        
        // Ask the Configuration for all current resources, since
        // newly added ones are not known at init.
        Enumeration resources = Configuration.getResources ();
        while ( resources.hasMoreElements () ) {
            RecoverableResource res = (RecoverableResource) resources
                    .nextElement ();

            Configuration.removeResource ( res.getName () );
            try {
                res.close ();
            } catch ( ResourceException re ) {
            	   
         	   //Issue 10038:
         	   //Ignore errors in force mode: force is most likely
         	   //during VM exit; in that case interleaving of shutdown hooks
         	   //means that resource connectors may have closed already
         	   //by the time the TM hook runs. We don't want useless 
         	   //reports in that case.
         	   //NOTE: any invalid states will be detected during the next
         	   //(re)init so they can be ignored here (if force mode)
            	
                if ( !force ) {
                	   //log to System.err because console file 
             	   //is closed already!!!
             	   String msg = "WARNING: error closing resource: " + 
             	   			re.getMessage ();
                	   System.err.println ( msg );
                	   re.printStackTrace();
                }
            	  
                errors = true;
            }

        }
        Configuration.removeConsoles ();
        Enumeration logAdmins = Configuration.getLogAdministrators ();
        while ( logAdmins.hasMoreElements () ) {
            LogAdministrator admin = (LogAdministrator) logAdmins
                    .nextElement ();
            Configuration.removeLogAdministrator ( admin );
        }

        // remove TM handles to enable restarts
        Configuration.installCompositeTransactionManager ( null );
        Configuration.installExportingTransactionManager ( null );
        Configuration.installImportingTransactionManager ( null );
        Configuration.installRecoveryService ( null );
        Configuration.installTransactionService ( null );
        Configuration.installLogControl ( null );

        if ( errors && ! force ) {
        	   //Issue 10038:
        	   //Ignore errors in force mode: force is most likely
        	   //during VM exit; in that case interleaving of shutdown hooks
        	   //means that resource connectors may have closed already
        	   //by the time the TM hook runs. We don't want useless 
        	   //exceptions in that case.
        	   //NOTE: any invalid states will be detected during the next
        	   //(re)init so they can be ignored here
            throw new RuntimeException ( "Error(s) during shutdown." );
        }
    }

    /**
     * @see UserTransactionService
     */

    public void init ( TSInitInfo info ) throws SysException
    {
        // inspector_ = info.getTmAdminTool();
        info_ = info;

        // NOTE: adding resources is no longer done here,
        // since they are directly added to the Configuration.
        // Also, in 2.0 useWeakCompare has been deprecated,
        // so we don't have to check for duplicate vendor
        // additions like before.

        // ADDED IN 2.0: if one or more resources were added before init,
        // then assume that dynamic registration is NOT being used.
        // In that case, also add an AcceptAllXATransactionalResource
        // to avoid that users have to use weakCompare modes (which
        // are restricted to one instance per vendor class).
        // NOTE: to avoid lack of recovery, this should only
        // be done if automatic resource registration is NOT set
        // Also note: this is done ONLY if resources are
        // added explicitly, to ensure that the acceptAll is LAST
        // otherwise custom XidFactory settings would be impossible.

        echoProperties ( info.getProperties () );


        String hookAsString = getTrimmedProperty ( AbstractUserTransactionServiceFactory.REGISTER_SHUTDOWN_HOOK_PROPERTY_NAME , info.getProperties() );
        boolean register = false;
        if ( hookAsString != null ) register = "true".equals ( hookAsString.toLowerCase() );
 
        // ADDED IN 2.0 for automatic initialization: shutdown hook needed
        if ( register ) Configuration.addShutdownHook ( new ShutdownHook ( this ) );

    }

    /**
     * @see UserTransactionService
     */

    public CompositeTransactionManager getCompositeTransactionManager ()
    {
        return Configuration.getCompositeTransactionManager ();
    }



    /**
     * @see UserTransactionService
     */

    public ImportingTransactionManager getImportingTransactionManager ()
    {
        return Configuration.getImportingTransactionManager ();
    }

    /**
     * @see UserTransactionService
     */

    public ExportingTransactionManager getExportingTransactionManager ()
    {
        return Configuration.getExportingTransactionManager ();
    }

    public void registerResource ( RecoverableResource res )
    {
        Configuration.addResource ( res );
    }

    public void registerLogAdministrator ( LogAdministrator admin )
    {
        Configuration.addLogAdministrator ( admin );
    }

    public Enumeration getResources ()
    {
        return Configuration.getResources ();
    }

    public Enumeration getLogAdministrators ()
    {
        return Configuration.getLogAdministrators ();
    }
    
	public void removeResource ( RecoverableResource res ) 
	{
		if ( res == null ) throw new IllegalArgumentException ( "Null not allowed" );
		Configuration.removeResource ( res.getName() );
		
	}

	public void removeLogAdministrator ( LogAdministrator admin ) 
	{
		if ( admin == null ) throw new IllegalArgumentException ( "Null not allowed" );
		Configuration.removeLogAdministrator ( admin );
	}

	public void registerTSListener ( TSListener listener ) 
	{
		if ( listener == null ) throw new IllegalArgumentException ( "Null not allowed");
		Configuration.addTSListener ( listener );
	}

	public void removeTSListener ( TSListener listener ) 
	{
		if ( listener == null ) throw new IllegalArgumentException ( "Null not allowed");
		Configuration.removeTSListener ( listener );
	}


	public void init ( Properties properties ) throws SysException {
		TSInitInfo info = createTSInitInfo();
		info.setProperties(properties);
		init ( info );
	}

    //
    //
    // NESTED SHUTDOWNHOOK CLASS
    //
    //

    private static class ShutdownHook extends Thread
    {
        private UserTransactionService uts_;

        private ShutdownHook ( UserTransactionService uts )
        {
            super ();
            uts_ = uts;
        }

        public void run ()
        {
            // this code executes during VM shutdown
            // so force the uts to shutdown too
            uts_.shutdown ( true );

        }
    }


}
