/**
 * JaDOrT: JASMINe Deployment Orchestration Tool
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Copyright (C) 2008-2009 France Telecom R&D
 * Contact: jasmine@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: JMSSender.java 5638 2009-11-24 17:24:39Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.implementation;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.ow2.jasmine.jadort.api.JadortServiceException;
import org.ow2.jasmine.jadort.api.IJadortService.OperationType;
import org.ow2.jasmine.jadort.api.entities.deployment.ApplicationBean;
import org.ow2.jasmine.jadort.api.entities.deployment.OperationStateBean;
import org.ow2.jasmine.jadort.api.entities.deployment.ServerProgressBean;
import org.ow2.jasmine.jadort.api.entities.deployment.WorkerProgressBean;
import org.ow2.jasmine.jadort.service.implementation.JMSReceiver.ActionType;
import org.ow2.jonas.lib.security.context.Marshalling;
import org.ow2.jonas.lib.security.context.SecurityContext;
import org.ow2.jonas.lib.security.context.SecurityCurrent;

/**
 * Sends asynchronous JMS messages.
 * 
 * @author Arda Aydin
 * @author Malek Chahine
 * @author S. Ali Tokmen
 */
public class JMSSender {

    // keep this public for tests
    public static QueueConnection qc;

    // keep this public for tests
    public static QueueSession session;

    protected static Queue queue;

    /**
     * Connect to the JMS queue and create a session.
     * 
     * @throws NamingException If queue not found.
     * @throws JMSException If creating a session fails.
     */
    public static void connect() throws NamingException, JMSException {
        InitialContext ctx = new InitialContext();
        JMSSender.queue = (Queue) ctx.lookup(JMSReceiver.QUEUE_NAME);
        QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("QCF");
        JMSSender.qc = factory.createQueueConnection();
        JMSSender.session = JMSSender.qc.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
    }

    /**
     * Disconnect from the JMS queue.
     * 
     * @throws JMSException If connection close fails.
     */
    public static void disconnect() throws JMSException {
        JMSSender.session.close();
        JMSSender.qc.close();
    }

    /**
     * Upload an application on a server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server on which the application will be uploaded.
     * @param application the application to be uploaded.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void upload(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final ApplicationBean application) throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MIGRATE, ActionType.UPLOAD);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setIntProperty(JMSReceiver.APPLICATION_ID, application.getId());

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Deploy an application on the server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server on which the application will be deployed.
     * @param applicationName The name (on the server side) of the application
     *        to be deployed.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void deploy(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final String applicationName) throws JMSException, JadortServiceException {
        if (applicationName == null) {
            throw new JadortServiceException("The application to deploy is null!", null);
        }
        Message message = JMSSender.createMessage(OperationType.MIGRATE, ActionType.DEPLOY);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.APPLICATION_NAME, applicationName);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Set a given application as the default version.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server on which the application will be set as
     *        default.
     * @param applicationName The name (on the server side) of the application
     *        to be set as default.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void setAsDefault(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final String applicationName) throws JMSException, JadortServiceException {
        if (applicationName == null) {
            throw new JadortServiceException("The application to set as default is null!", null);
        }
        Message message = JMSSender.createMessage(OperationType.MIGRATE, ActionType.SET_DEFAULT);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.APPLICATION_NAME, applicationName);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Undeploy an application on the server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server on which the application will be undeployed.
     * @param applicationName The name (on the server side) of the application
     *        to be undeployed.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void undeploy(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final String applicationName) throws JMSException, JadortServiceException {
        if (applicationName == null) {
            throw new JadortServiceException("The application to undeploy is null!", null);
        }
        Message message = JMSSender.createMessage(OperationType.MIGRATE, ActionType.UNDEPLOY);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.APPLICATION_NAME, applicationName);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Erases an application on the server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server on which the application will be erased.
     * @param applicationName The name (on the server side) of the application
     *        to be undeployed.
     * @throws JMSException If sending the JMS message fails.
     * @throws JadortServiceException If applicationName is null.
     */
    public static void erase(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final String applicationName) throws JMSException, JadortServiceException {
        if (applicationName == null) {
            throw new JadortServiceException("The application to erase is null!", null);
        }
        Message message = JMSSender.createMessage(OperationType.MIGRATE, ActionType.ERASE);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.APPLICATION_NAME, applicationName);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Stops a server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server that will be stopped.
     * @param useTarget Whether to use the target server (if available).
     * @throws JMSException If sending the JMS message fails.
     */
    public static void stopServer(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final boolean useTarget) throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.STOP_SERVER);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setBooleanProperty(JMSReceiver.USE_TARGET, useTarget);
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Starts a server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server that will be started.
     * @param useTarget Whether to use the target server (if available).
     * @throws JMSException If sending the JMS message fails.
     */
    public static void startServer(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final boolean useTarget) throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.START_SERVER);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setBooleanProperty(JMSReceiver.USE_TARGET, useTarget);
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Disables all applications a server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server on which all applications will be disabled.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void disableApplications(final OperationStateBean operation, final ServerProgressBean serverProgress)
        throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.DISABLE_APPLICATIONS);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Enables all applications a server.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server on which all applications will be enabled.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void enableApplications(final OperationStateBean operation, final ServerProgressBean serverProgress)
        throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.ENABLE_APPLICATIONS);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Starts a worker.
     * 
     * @param operation Operation to work on.
     * @param workerProgress Worker that will be started.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void startWorker(final OperationStateBean operation, final WorkerProgressBean workerProgress)
        throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.START_WORKER);

        message.setIntProperty(JMSReceiver.WORKER_PROGRESS_ID, workerProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedWorkerProgressState().toString());

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Stop a worker.
     * 
     * @param operation Operation to work on.
     * @param workerProgress Worker that will be stop.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void stopWorker(final OperationStateBean operation, final WorkerProgressBean workerProgress)
        throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.STOP_WORKER);

        message.setIntProperty(JMSReceiver.WORKER_PROGRESS_ID, workerProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedWorkerProgressState().toString());

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Starts a VM.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server of the VM to start.
     * @param useTarget Whether to use the target server (if available).
     * @param vmName Name of the VM to start.
     * @throws JMSException If sending the JMS message fails.
     * @throws JadortServiceException If the vmName is null.
     */
    public static void startVM(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final boolean useTarget, final String vmName) throws JMSException, JadortServiceException {
        if (vmName == null) {
            throw new JadortServiceException("The name of the VM to start is null!", null);
        }
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.START_VM);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setBooleanProperty(JMSReceiver.USE_TARGET, useTarget);
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.VM_NAME, vmName);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Stop a VM.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server of the VM to stop.
     * @param useTarget Whether to use the target server (if available).
     * @param vmName Name of the VM to stop.
     * @throws JMSException If sending the JMS message fails.
     * @throws JadortServiceException If the vmName is null.
     */
    public static void stopVM(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final boolean useTarget, final String vmName) throws JMSException, JadortServiceException {
        if (vmName == null) {
            throw new JadortServiceException("The name of the VM to stop is null!", null);
        }
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.STOP_VM);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setBooleanProperty(JMSReceiver.USE_TARGET, useTarget);
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.VM_NAME, vmName);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Creates a VM.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server of the VM to copy VM properties from
     *        (memory, etc.).
     * @param vmName Name of the VM to create.
     * @throws JMSException If sending the JMS message fails.
     */
    public static void createVM(final OperationStateBean operation, final ServerProgressBean serverProgress, final String vmName)
        throws JMSException {
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.CREATE_VM);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.VM_IMAGE_UUID, serverProgress.getVmImage().getUuid());
        message.setStringProperty(JMSReceiver.VM_NAME, vmName);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    /**
     * Destroys a VM.
     * 
     * @param operation Operation to work on.
     * @param serverProgress Server of the VM to destroy.
     * @param vmName Name of the VM to destroy.
     * @throws JMSException If sending the JMS message fails.
     * @throws JadortServiceException If the vmName is null.
     */
    public static void destroyVM(final OperationStateBean operation, final ServerProgressBean serverProgress,
        final String vmName, final boolean checkIfHostHalted) throws JMSException, JadortServiceException {
        if (vmName == null) {
            throw new JadortServiceException("The name of the VM to destroy is null!", null);
        }
        Message message = JMSSender.createMessage(OperationType.MAINTAIN, ActionType.DESTROY_VM);

        message.setIntProperty(JMSReceiver.SERVER_PROGRESS_ID, serverProgress.getId());
        message.setIntProperty(JMSReceiver.AIMED_PROGRESS_PERCENT, operation.getAimedProgressPercent());
        message.setStringProperty(JMSReceiver.AIMED_STATE, operation.getAimedServerProgressState().toString());
        message.setStringProperty(JMSReceiver.VM_NAME, vmName);
        message.setBooleanProperty(JMSReceiver.CHECK_IF_HOST_HALTED, checkIfHostHalted);

        QueueSender sender = JMSSender.session.createSender(JMSSender.queue);
        sender.send(message);
    }

    private static Message createMessage(final OperationType operationType, final ActionType actionType) throws JMSException {
        Message message = JMSSender.session.createMessage();

        try {
            SecurityCurrent securityCurrent = SecurityCurrent.getCurrent();
            if (securityCurrent != null) {
                SecurityContext securityContext = securityCurrent.getSecurityContext();
                if (securityContext != null) {
                    byte[] signature = securityContext.getSignature();
                    byte[] context = Marshalling.marshallSecurityContext(securityContext);
                    if (context != null) {
                        message.setStringProperty(JMSReceiver.SECURITY_CONTEXT, new String(context));
                    }
                    if (signature != null) {
                        message.setStringProperty(JMSReceiver.SECURITY_SIGNATURE, new String(signature));
                    }
                }
            }
        } catch (Throwable ignored) {
            // We're not on an OW2 security manager, ignore
        }

        message.setStringProperty(JMSReceiver.OPERATION_TYPE, operationType.toString());
        message.setStringProperty(JMSReceiver.ACTION_TYPE, actionType.toString());

        return message;
    }
}
