/*
 * Copyright (C) 2005-2007 Alfresco Software Limited.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program 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 General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

 * As a special exception to the terms and conditions of version 2.0 of 
 * the GPL, you may redistribute this Program in connection with Free/Libre 
 * and Open Source Software ("FLOSS") applications as described in Alfresco's 
 * FLOSS exception.  You should have recieved a copy of the text describing 
 * the FLOSS exception, and it is also available here: 
 * http://www.alfresco.com/legal/licensing"
 */
package org.alfresco.filesys.server;

import java.net.InetAddress;

import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.server.auth.AuthContext;
import org.alfresco.filesys.server.auth.ClientInfo;
import org.alfresco.filesys.server.core.SharedDevice;
import org.alfresco.filesys.server.core.SharedDeviceList;
import org.alfresco.filesys.server.filesys.FilesysTransaction;
import org.alfresco.service.transaction.TransactionService;

/**
 * Server Session Base Class
 * <p>
 * Base class for server session implementations for different protocols.
 */
public abstract class SrvSession
{

    // Network server this session is associated with

    private NetworkServer m_server;

    // Session id/slot number

    private int m_sessId;

    // Unique session id string

    private String m_uniqueId;

    // Process id

    private int m_processId = -1;

    // Session/user is logged on/validated

    private boolean m_loggedOn;

    // Client details

    private ClientInfo m_clientInfo;

    // Debug flags for this session

    private int m_debug;

    // Session shutdown flag

    private boolean m_shutdown;

    // Protocol type

    private String m_protocol;

    // Remote client/host name

    private String m_remoteName;

    // Authentication token, used during logon
    
    private Object m_authToken;

    //  Authentication context, used during the initial session setup phase
    
    private AuthContext m_authContext;
    
    //  List of dynamic/temporary shares created for this session
    
    private SharedDeviceList m_dynamicShares;
    
    // Active transaction and read/write flag
    
    private ThreadLocal<FilesysTransaction> m_tx = new ThreadLocal<FilesysTransaction>();
    
//    UserTransaction m_transaction;
//    private boolean m_readOnlyTrans;
    
    // Request and transaction counts
    
    protected int m_reqCount;
    protected int m_transCount;
    protected int m_transConvCount;
    
    /**
     * Class constructor
     * 
     * @param sessId int
     * @param srv NetworkServer
     * @param proto String
     * @param remName String
     */
    public SrvSession(int sessId, NetworkServer srv, String proto, String remName)
    {
        m_sessId = sessId;
        m_server = srv;

        setProtocolName(proto);
        setRemoteName(remName);
    }

    /**
     * Add a dynamic share to the list of shares created for this session
     * 
     * @param shrDev SharedDevice
     */
    public final void addDynamicShare(SharedDevice shrDev) {
        
        //  Check if the dynamic share list must be allocated
        
        if ( m_dynamicShares == null)
            m_dynamicShares = new SharedDeviceList();
            
        //  Add the new share to the list
        
        m_dynamicShares.addShare(shrDev);
    }
    
    /**
     * Return the authentication token
     * 
     * @return Object
     */
    public final Object getAuthenticationToken()
    {
        return m_authToken;
    }
    
    /**
     * Determine if the authentication token is set
     * 
     * @return boolean
     */
    public final boolean hasAuthenticationToken()
    {
        return m_authToken != null ? true : false;
    }
    
    /**
     * Return the process id
     * 
     * @return int
     */
    public final int getProcessId()
    {
        return m_processId;
    }

    /**
     * Return the remote client network address
     * 
     * @return InetAddress
     */
    public abstract InetAddress getRemoteAddress();

    /**
     * Return the session id for this session.
     * 
     * @return int
     */
    public final int getSessionId()
    {
        return m_sessId;
    }

    /**
     * Return the server this session is associated with
     * 
     * @return NetworkServer
     */
    public final NetworkServer getServer()
    {
        return m_server;
    }

    /**
     * Check if the session has valid client information
     * 
     * @return boolean
     */
    public final boolean hasClientInformation()
    {
        return m_clientInfo != null ? true : false;
    }

    /**
     * Return the client information
     * 
     * @return ClientInfo
     */
    public final ClientInfo getClientInformation()
    {
        return m_clientInfo;
    }

    /**
     * Check if the session has an authentication context
     * 
     * @return boolean
     */
    public final boolean hasAuthenticationContext()
    {
        return m_authContext != null ? true : false;
    }
    
    /**
     * Return the authentication context for this sesion
     * 
     * @return AuthContext
     */
    public final AuthContext getAuthenticationContext()
    {
        return m_authContext;
    }
    
    /**
     * Determine if the session has any dynamic shares
     * 
     * @return boolean
     */
    public final boolean hasDynamicShares() {
        return m_dynamicShares != null ? true : false;
    }

    /**
     * Return the list of dynamic shares created for this session
     * 
     * @return SharedDeviceList
     */
    public final SharedDeviceList getDynamicShareList() {
        return m_dynamicShares;
    }

    /**
     * Determine if the protocol type has been set
     * 
     * @return boolean
     */
    public final boolean hasProtocolName()
    {
        return m_protocol != null ? true : false;
    }

    /**
     * Return the protocol name
     * 
     * @return String
     */
    public final String getProtocolName()
    {
        return m_protocol;
    }

    /**
     * Determine if the remote client name has been set
     * 
     * @return boolean
     */
    public final boolean hasRemoteName()
    {
        return m_remoteName != null ? true : false;
    }

    /**
     * Return the remote client name
     * 
     * @return String
     */
    public final String getRemoteName()
    {
        return m_remoteName;
    }

    /**
     * Determine if the session is logged on/validated
     * 
     * @return boolean
     */
    public final boolean isLoggedOn()
    {
        return m_loggedOn;
    }

    /**
     * Determine if the session has been shut down
     * 
     * @return boolean
     */
    public final boolean isShutdown()
    {
        return m_shutdown;
    }

    /**
     * Return the unique session id
     * 
     * @return String
     */
    public final String getUniqueId()
    {
        return m_uniqueId;
    }

    /**
     * Determine if the specified debug flag is enabled.
     * 
     * @return boolean
     * @param dbg int
     */
    public final boolean hasDebug(int dbgFlag)
    {
        if ((m_debug & dbgFlag) != 0)
            return true;
        return false;
    }

    /**
     * Set the authentication token
     * 
     * @param authToken Object
     */
    public final void setAuthenticationToken(Object authToken)
    {
        m_authToken = authToken;
    }
    
    /**
     * Set the authentication context, used during the initial session setup phase
     * 
     * @param ctx AuthContext
     */
    public final void setAuthenticationContext( AuthContext ctx)
    {
        m_authContext = ctx;
    }
    
    /**
     * Set the client information
     * 
     * @param client ClientInfo
     */
    public final void setClientInformation(ClientInfo client)
    {
        m_clientInfo = client;
    }

    /**
     * Set the debug output interface.
     * 
     * @param flgs int
     */
    public final void setDebug(int flgs)
    {
        m_debug = flgs;
    }

    /**
     * Set the logged on/validated status for the session
     * 
     * @param loggedOn boolean
     */
    public final void setLoggedOn(boolean loggedOn)
    {
        m_loggedOn = loggedOn;
    }

    /**
     * Set the process id
     * 
     * @param id int
     */
    public final void setProcessId(int id)
    {
        m_processId = id;
    }

    /**
     * Set the protocol name
     * 
     * @param name String
     */
    public final void setProtocolName(String name)
    {
        m_protocol = name;
    }

    /**
     * Set the remote client name
     * 
     * @param name String
     */
    public final void setRemoteName(String name)
    {
        m_remoteName = name;
    }

    /**
     * Set the session id for this session.
     * 
     * @param id int
     */
    public final void setSessionId(int id)
    {
        m_sessId = id;
    }

    /**
     * Set the unique session id
     * 
     * @param unid String
     */
    public final void setUniqueId(String unid)
    {
        m_uniqueId = unid;
    }

    /**
     * Set the shutdown flag
     * 
     * @param flg boolean
     */
    protected final void setShutdown(boolean flg)
    {
        m_shutdown = flg;
    }

    /**
     * Close the network session
     */
    public void closeSession()
    {
        //  Release any dynamic shares owned by this session
        
        if ( hasDynamicShares()) {
            
            //  Close the dynamic shares
            
            getServer().getShareMapper().deleteShares(this);
        }
    }
    
    /**
     * Create a read transaction, if not already active
     * 
     * @param transService TransactionService
     * @return boolean
     * @exception AlfrescoRuntimeException
     */
    public final boolean beginReadTransaction( TransactionService transService)
    	throws AlfrescoRuntimeException
    {
    	return beginTransaction(transService, true);
    }
    
    /**
     * Create a write transaction, if not already active
     * 
     * @param transService TransactionService
     * @return boolean
     * @exception AlfrescoRuntimeException
     */
    public final boolean beginWriteTransaction( TransactionService transService)
    	throws AlfrescoRuntimeException
    {
    	return beginTransaction(transService, false);
    }
    
    /**
     * Create and start a transaction, if not already active
     * 
     * @param transService TransactionService
     * @param readOnly boolean
     * @return boolean
     * @exception AlfrescoRuntimeException
     */
    private final boolean beginTransaction(TransactionService transService, boolean readOnly)
        throws AlfrescoRuntimeException
    {
        boolean created = false;
        
        // Get the filesystem transaction
        
        FilesysTransaction filesysTx = m_tx.get();
        if ( filesysTx == null)
        {
        	filesysTx = new FilesysTransaction();
        	m_tx.set( filesysTx);
        }
        
        // If there is an active transaction check that it is the required type

        if ( filesysTx.hasTransaction())
        {
        	// Get the active transaction
        	
            UserTransaction tx = filesysTx.getTransaction();
            
            // Check if the current transaction is marked for rollback
            
            try
            {
                if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK ||
                     tx.getStatus() == Status.STATUS_ROLLEDBACK ||
                     tx.getStatus() == Status.STATUS_ROLLING_BACK)
                {
                    //  Rollback the current transaction
                    
                    tx.rollback();
                }
            }
            catch ( Exception ex)
            {
            }
            
            // Check if the transaction is a write transaction, if write has been requested
            
            if ( readOnly == false && filesysTx.isReadOnly() == true)
            {
                // Commit the read-only transaction
                
                try
                {
                    tx.commit();
                    m_transConvCount++;
                }
                catch ( Exception ex)
                {
                    throw new AlfrescoRuntimeException("Failed to commit read-only transaction, " + ex.getMessage());
                }
                finally
                {
                    // Clear the active transaction

                    filesysTx.clearTransaction();
                }
            }
        }
        
        // Create the transaction
        
        if ( filesysTx.hasTransaction() == false)
        {
            try
            {
                UserTransaction userTrans = transService.getUserTransaction(readOnly);
                userTrans.begin();
                
                created = true;

                // Store the transaction
                
                filesysTx.setTransaction( userTrans, readOnly);
                m_transCount++;
            }
            catch (Exception ex)
            {
                throw new AlfrescoRuntimeException("Failed to create transaction, " + ex.getMessage());
            }
        }
        
        return created;
    }
    
    /**
     * End a transaction by either committing or rolling back
     * 
     * @exception AlfrescoRuntimeException
     */
    public final void endTransaction()
        throws AlfrescoRuntimeException
    {
        // Get the filesystem transaction
        
        FilesysTransaction filesysTx = m_tx.get();

        // Check if there is an active transaction
        
        if ( filesysTx != null && filesysTx.hasTransaction())
        {
        	// Get the active transaction
        	
            UserTransaction tx = filesysTx.getTransaction();
            
            try
            {
                // Commit or rollback the transaction
                
                if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
                {
                    // Transaction is marked for rollback
                    
                    tx.rollback();
                }
                else
                {
                    // Commit the transaction
                    
                    tx.commit();
                }
            }
            catch ( Exception ex)
            {
                throw new AlfrescoRuntimeException("Failed to end transaction", ex);
            }
            finally
            {
                // Clear the current transaction
                
                filesysTx.clearTransaction();
        }
    }
    
    }
    /**
     * Determine if the session has an active transaction
     * 
     * @return boolean
     */
    public final boolean hasUserTransaction()
    {
        // Get the filesystem transaction
        
        FilesysTransaction filesysTx = m_tx.get();
        if ( filesysTx != null)
        	return filesysTx.hasTransaction();
        return false;
    }
    
    /**
     * Get the active transaction and clear the stored transaction
     * 
     *  @return UserTransaction
     */
    public final UserTransaction getUserTransaction()
    {
        // Get the filesystem transaction

    	UserTransaction userTrans = null;
        FilesysTransaction filesysTx = m_tx.get();
        
        if ( filesysTx != null)
        {
	        userTrans = filesysTx.getTransaction();
	        filesysTx.clearTransaction();
        }
        
        return userTrans;
    }
}
