/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.enterprise.management.deploy;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.Iterator;
import java.util.Collections;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;

import javax.management.ObjectName;
import javax.management.Notification;


import com.sun.appserv.management.base.Utility;
import com.sun.appserv.management.base.Singleton;
import com.sun.appserv.management.base.XTypes;
import com.sun.appserv.management.deploy.DeploymentMgr;

import com.sun.enterprise.management.support.UniqueIDGenerator;

import com.sun.appserv.management.deploy.DeploymentSupport;
import com.sun.appserv.management.deploy.DeploymentSource;
import com.sun.appserv.management.deploy.DeploymentStatus;
import com.sun.appserv.management.deploy.DeploymentProgress;
import com.sun.appserv.management.base.UploadDownloadMgr;

import com.sun.appserv.management.util.misc.GSetUtil;
import com.sun.appserv.management.util.jmx.NotificationBuilder;

import com.sun.enterprise.management.support.AMXNonConfigImplBase;


/**
	Implementation note--the design of this class is unnecessarily
	complicated due to the inclusion of certain polling-style methods
	such as takeNotifications() and getFinalDeploymentStatus().  If this
	aspect of the API can be eliminated, the implemention of this class
	as well as DeployThread can be simplified.  This limitation was
	driven by the issue of not having Notification support in the http
	connector used by the deployment team.
 */
public final class DeploymentMgrImpl extends AMXNonConfigImplBase
	implements Utility, Singleton, DeploymentMgr
{
	/**
		A Map keyed by deployID to values of DeployThread
	 */
	private final Map<Object,DeployThread>	mDeployThreads;
	
		public void
	remove( final String name )
	{
		throw new RuntimeException( "not applicable" );
	}
	
	private final UniqueIDGenerator	mDeployIDs;
	
	private long	mDeploymentCompletedNotificationSequenceNumber;
	
		public
	DeploymentMgrImpl( )
	{
		mDeployThreads	= Collections.synchronizedMap( new HashMap<Object,DeployThread>() );
		mDeployIDs		= new UniqueIDGenerator( "deploy:" );
		
		mDeploymentCompletedNotificationSequenceNumber	= 0;
	}
	
	private final Set<String>	NOTIF_TYPES	= GSetUtil.newUnmodifiableStringSet(
    		DEPLOYMENT_STARTED_NOTIFICATION_TYPE,
    		DEPLOYMENT_ABORTED_NOTIFICATION_TYPE,
    		DEPLOYMENT_PROGRESS_NOTIFICATION_TYPE,
    		DEPLOYMENT_COMPLETED_NOTIFICATION_TYPE );
	
		protected Set<String>
	getNotificationTypes( Set<String> existing )
	{
	    existing.addAll( NOTIF_TYPES );
	    return existing;
	}


	private static final long 	SECOND_MILLIS	= 60 * 1000;
	private static final long 	MINUTE_MILLIS	= 60 * SECOND_MILLIS;
	private static final long	DEPLOY_KEEP_ALIVE_MILLIS	= 15 * MINUTE_MILLIS;
	
	
		private DeployThread
	removeDeployThread( final Object	deployID)
	{
		trace( "\n###Removing deploy thread: " + deployID );
		return( (DeployThread)mDeployThreads.remove( deployID ) );
	}
	
	/**
		Cleanup any threads that have been done for a proscribed
		amount of time given by UPLOAD_KEEP_ALIVE_MILLIS.  We don't want to clean them
		up immediately because the client should have a reasonable chance to 
		get the status after completion.
	 */
		private final void
	staleDeployCheck()
	{
		final Set<Object> keySet		= mDeployThreads.keySet();
		
		synchronized( mDeployThreads )
		{
			final String[]	deployIDs	= GSetUtil.toStringArray( keySet );
		
			for( final String deployID : deployIDs )
			{
				final DeployThread	deployThread	= (DeployThread)mDeployThreads.get( deployID );
				
				if ( deployThread.getMillisSinceDone() > DEPLOY_KEEP_ALIVE_MILLIS )
				{
					removeDeployThread( deployID );
				}
			}
		}
	}
	
		private DeployThread
	addDeployThread(
		final Object				deployID )
	{
		final DeploymentCallback	callback	=
			new InternalDeploymentCallback( deployID );
			
		final DeployThread	deployThread	=
			new DeployThread( deployID, callback, null );
		mDeployThreads.put( deployThread.getID(), deployThread );
		
		return( deployThread );
	}

		public Object
	initDeploy()
	{
		final Object	deployID	= mDeployIDs.createID();
		
		final DeployThread	deployThread	= addDeployThread( deployID );
		
		return( deployID );
	}
	
		public void
	startDeploy(
		final Object	deployID,
		final Object	uploadID,
		final Object	planUploadID,
		final Map<String,String>		options)
	{
		staleDeployCheck();
		
		final UploadDownloadMgr	mgr	= getUploadDownloadMgr();
		
		final File	deployFile	= mgr.takeUpload( uploadID );
		final File	planFile	= planUploadID == null ? null : mgr.takeUpload( planUploadID );
		
		final DeployThreadParams	params	=
			new DeployThreadParams(
				getQueryMgr(),
				options,
				deployFile,
				planFile );
		
		startDeploy( deployID, params );
	}
	
    public <T1 extends Serializable,T2 extends Serializable> void
    startDeploy(
        Object deployID,
        Map<String,T1> sourceData,
        Map<String,T2> planData,
        Map<String,String> options)
	{
		final DeploymentSource	source	=
			DeploymentSupport.mapToDeploymentSource( sourceData );
			
		final DeploymentSource	plan	= planData == null ?
			null : DeploymentSupport.mapToDeploymentSource( planData );
			
		final DeployThreadParams	params	=
			new DeployThreadParams( getQueryMgr(), options, source, plan );
		
		startDeploy( deployID, params );
	}
	
		private void
	startDeploy(
		final Object				deployID,
		final DeployThreadParams	params )
	{
		final DeployThread	deployThread	= getDeployThread( deployID );
		if ( deployThread == null )
		{
			throw new IllegalArgumentException( deployID.toString() );
		}
		deployThread.setParams( params );
		
		/**
			Issue a DEPLOYMENT_STARTED_NOTIFICATION_TYPE *before*
			starting the thread, because a client could receive
			other Notifications (even "done") before the "started" one.
		 */
		issueDeploymentStartedNotification( deployID );
		
		deployThread.start();
	}

	   	private DeployThread
	getDeployThread( Object deployID )
	{
		final DeployThread	deployThread	= (DeployThread)mDeployThreads.get( deployID );
		if ( deployThread == null )
		{
			final IllegalArgumentException	e	= new IllegalArgumentException( "" + deployID );
			
			e.printStackTrace();
			throw e;
		}
		return( deployThread );
	}
	
		private boolean
	notifsAreDone( final Notification[] notifs )
	{
		boolean	done	= false;
		
		for( int i = notifs.length -1; i >= 0; --i )
		{
			final String	notifType	= notifs[ notifs.length - 1].getType();
		
			if ( notifType.equals( DEPLOYMENT_COMPLETED_NOTIFICATION_TYPE ) ||
						notifType.equals( DEPLOYMENT_ABORTED_NOTIFICATION_TYPE ) )
			{
				done	= true;
				break;
			}
		}
		
		return( done );
	}
    
   		public Notification[]
    takeNotifications( final Object	deployID)
    {
		final DeployThread	deployThread	= getDeployThread( deployID );
		
		final Notification[]	notifs	= deployThread.takeNotifications();
		
		return( notifs );
    }
	
		public boolean
	abortDeploy(final Object deployID)
	{
		final DeployThread	deployThread	= getDeployThread( deployID );
		
		final boolean	abortedSuccessfully	= deployThread.quit();
		
		issueDeploymentAbortedNotification( deployID );
		
		return( abortedSuccessfully );
	}
	
	
		private void
	issueNotification(
		final Object		deployID,
		final Notification	notif )
	{
		// send it, for normal callers
		sendNotification( notif );
		
		trace( "\nDeploymentMgrImpl.issueNotification: sent notification for " +
			deployID + " = " + notif.toString() );
		
		// queue it, for pollers
		final DeployThread	deployThread	= getDeployThread( deployID );
		deployThread.queueNotification( notif );
	}
	
	/** 
	 */
		protected void
	issueDeploymentStartedNotification( final Object deployID )
	{
		final NotificationBuilder	builder	=
			getNotificationBuilder( DEPLOYMENT_STARTED_NOTIFICATION_TYPE );
		
		final Notification	notif	= builder.buildNew( );
		builder.putMapData( notif, NOTIF_DEPLOYMENT_ID_KEY, (Serializable)deployID );
		
		issueNotification( deployID, notif );
	}
	
	/** 
	 */
		protected void
	issueDeploymentDoneNotification(
		final Object			deployID,
		final DeploymentStatus	deploymentStatus )
	{
		final NotificationBuilder	builder	=
			getNotificationBuilder( DEPLOYMENT_COMPLETED_NOTIFICATION_TYPE );
		
		final Notification	notif	= builder.buildNew( );
		builder.putMapData( notif, NOTIF_DEPLOYMENT_ID_KEY, (Serializable)deployID );
		builder.putMapData( notif, NOTIF_DEPLOYMENT_COMPLETED_STATUS_KEY, (Serializable)deploymentStatus.asMap() );
		
		issueNotification( deployID, notif );
	}
	
	/** 
	 */
		protected void
	issueDeploymentProgressNotification(
		final Object				deployID,
		final DeploymentProgress	progress)
	{
		final NotificationBuilder	builder	=
			getNotificationBuilder( DEPLOYMENT_PROGRESS_NOTIFICATION_TYPE );
		
		final Notification	notif	= builder.buildNew( );
		builder.putMapData( notif, NOTIF_DEPLOYMENT_ID_KEY, (Serializable)deployID );
		builder.putMapData( notif, NOTIF_DEPLOYMENT_PROGRESS_KEY, (Serializable)progress.asMap() );
		
		issueNotification( deployID, notif );
	}
	
	/** 
	 */
		protected void
	issueDeploymentAbortedNotification( final Object deployID )
	{
		final NotificationBuilder	builder	=
			getNotificationBuilder( DEPLOYMENT_ABORTED_NOTIFICATION_TYPE );
		
		final Notification	notif	= builder.buildNew( );
		builder.putMapData( notif, NOTIF_DEPLOYMENT_ID_KEY, (Serializable)deployID );
		
		issueNotification( deployID, notif );
	}

	private final class InternalDeploymentCallback implements DeploymentCallback
	{
		final Object	mDeployID;
	
			public
		InternalDeploymentCallback( Object deployID )
		{
			mDeployID	= deployID;
		}
		
			public void
		deploymentDone(
			final DeploymentStatus	status )
		{
			issueDeploymentDoneNotification( mDeployID, status );
		}
		
			public void
		deploymentProgress(
			final DeploymentProgress	progress )
		{
			issueDeploymentProgressNotification( mDeployID, progress );
		}
	}

		public Map<String,Serializable>
	undeploy(
		final String	moduleID,
		final Map<String,String>		optionalParams)
	{
		final Undeployer		undeployer	= new Undeployer( moduleID, optionalParams );
		final DeploymentStatus	undeploymentStatus	= undeployer.undeploy();
		
		return( undeploymentStatus.asMap() );
	}

		public Map<String,Serializable>
	getFinalDeploymentStatus (Object deployID)
	{
		final DeployThread	deployThread	= removeDeployThread( deployID );
		
		if ( deployThread == null )
		{
			throw new IllegalArgumentException( deployID.toString() );
		}
		
		return( deployThread.getDeploymentStatus().asMap() );
	} 

		private UploadDownloadMgr
	getUploadDownloadMgr()
	{
		return( getDomainRoot().getUploadDownloadMgr() );
	}


		public Object
	initiateFileUpload( long totalSize )
		throws IOException
	{
		return initiateFileUpload( null, totalSize );
	}
	
		public Object
	initiateFileUpload( final String name, final long totalSize )
		throws IOException
	{
	    debug( "initiateFileUpload(" + name + ", " + totalSize + ")" );
		return( getUploadDownloadMgr().initiateUpload( name, totalSize ) );
	}

		public boolean
	uploadBytes(
		final Object	uploadID,
		final byte[]	bytes)
		throws IOException
	{
		return( getUploadDownloadMgr().uploadBytes( uploadID, bytes ) );
	}
	
	
		public Object
	initiateFileDownload(
		final String	moduleID,
		final String	filename)
		throws IOException
	{
		final DownloadFileSource	source	= new DownloadFileSource( moduleID, filename );
		final File			theFile	= source.getDownloadFile( );
		final boolean		deleteWhenDone	= source.isTempFile();
		
		return( getUploadDownloadMgr().initiateDownload( theFile, deleteWhenDone ) );
	}

    	
    /**
    	Get the total length the download will be, in bytes.
    	
     	@param downloadID the file download operation id, from initiateFileDownload()
     */
    	public long
    getDownloadLength( final Object downloadID )
    {
		return( getUploadDownloadMgr().getDownloadLength( downloadID ) );
    }
    
    
    	public byte[]
    downloadBytes(
    	final Object	downloadID,
    	final int		requestSize )
    	throws IOException
    {
		return( getUploadDownloadMgr().downloadBytes( downloadID, requestSize ) );
    }

}







