/**
 * 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: JadortServiceStatefulBean.java 5850 2010-01-05 14:50:56Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.implementation;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Stateful;
import javax.xml.bind.UnmarshalException;

import org.ow2.jasmine.jadort.api.IJadortService;
import org.ow2.jasmine.jadort.api.JadortServiceException;
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.ServerProgressState;
import org.ow2.jasmine.jadort.api.entities.deployment.VMImageBean;
import org.ow2.jasmine.jadort.api.entities.deployment.WorkerProgressBean;
import org.ow2.jasmine.jadort.api.entities.deployment.WorkerProgressState;
import org.ow2.jasmine.jadort.api.entities.deployment.OperationStateBean.ActionState;
import org.ow2.jasmine.jadort.api.entities.deployment.OperationStateBean.Step;
import org.ow2.jasmine.jadort.api.entities.topology.GroupBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean;
import org.ow2.jasmine.jadort.api.entities.topology.TopologyBean;
import org.ow2.jasmine.jadort.api.entities.topology.VMBean;
import org.ow2.jasmine.jadort.api.entities.topology.WorkerBean;
import org.ow2.jasmine.jadort.api.topology.TopologyReader;
import org.ow2.jasmine.jadort.service.action.ServerAction;
import org.ow2.jasmine.jadort.service.action.VMMAction;
import org.ow2.jasmine.jadort.service.action.WorkerAction;

/**
 * Stateful session bean that provides the JaDOrT service.
 * 
 * @author Malek Chahine
 * @author Remy Bresson
 * @author S. Ali Tokmen
 */
@Stateful(mappedName = IJadortService.EJB_JNDI_NAME)
public class JadortServiceStatefulBean extends StepManager implements IJadortService {

    // Constants used internally by JadortServiceStatefulBean
    private final static int BUFFER_SIZE = 16384;

    /**
     * Logger.
     */
    private Logger logger = Logger.getLogger(this.getClass().getName());

    @PostConstruct
    protected void initialize() {
        try {
            JMSSender.connect();
        } catch (Throwable t) {
            final String message = "Failed initializing a JadortService stateful bean: " + t.getMessage();
            IllegalStateException exception = new IllegalStateException(message, t);

            this.logger.log(Level.SEVERE, message, t);
            throw exception;
        }
    }

    @PreDestroy
    protected void terminate() {
        try {
            JMSSender.disconnect();
        } catch (Throwable t) {
            final String message = "Failed terminating a JadortService stateful bean: " + t.getMessage();
            IllegalStateException exception = new IllegalStateException(message, t);

            this.logger.log(Level.SEVERE, message, t);
            throw exception;
        }
    }

    /**
     * {@inheritDoc}<br/>
     * <br/>
     * <b>IMPORTANT</b>: Avoid calling this method from inside other methods of
     * the JadortServiceStatefulBean class (it will be costly)
     */
    public Step getCurrentStep() {
        synchronized (this) {
            this.refreshOperation();

            if (this.operation == null) {
                return Step.SELECT_OPERATION;
            } else {
                return this.operation.getCurrentStep();
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if the getOperationsList() method can not
     *         be called in the current step. (this method can be called only in
     *         the {@link Step#SELECT_OPERATION} step)
     */
    @SuppressWarnings("unchecked")
    public List<OperationStateBean> getOperationsList() throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_OPERATION);

            List<OperationStateBean> result = new ArrayList<OperationStateBean>();

            // Create a dummy list which contains only the needed fields
            for (OperationStateBean operation : (List<OperationStateBean>) this.em.createQuery(
                "select o from JaDOrT_OperationStateBean o order by o.date asc").getResultList()) {
                OperationStateBean temp = new OperationStateBean();
                temp.setAimedServerProgressState(operation.getAimedServerProgressState());
                temp.setAimedWorkerProgressState(operation.getAimedWorkerProgressState());
                temp.setCurrentStep(operation.getCurrentStep());
                temp.setDate(operation.getDate());
                temp.setId(operation.getId());
                temp.setName(operation.getName());
                temp.setType(operation.getType());
                result.add(temp);
            }
            return result;
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the selectOperation can not be called in the current step.
     *         (this method can be called only in the
     *         {@link Step#SELECT_OPERATION} step)
     *         <li>if the operation to delete is still in progress in a critical
     *         phase and therefore cannot be deleted
     *         </ul>
     */
    public void deleteOperation(final OperationStateBean selectedOperation) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_OPERATION);

            OperationStateBean toRemove = this.em.find(OperationStateBean.class, selectedOperation.getId());

            if (toRemove.getIsDeletable()) {
                this.em.remove(toRemove);
                this.em.flush();

                if (this.operation != null && selectedOperation.getId() == this.operation.getId()) {
                    this.operation = null;
                }
            } else {
                throw new JadortServiceException("Operation could not be deleted because it currently is in the "
                    + toRemove.getCurrentStep() + " step.", null);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if the selectOperation can not be called
     *         in the current step. (this method can be called only in the
     *         {@link Step#SELECT_OPERATION} step)
     */
    public void selectOperation(final OperationStateBean selectedOperation) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_OPERATION);

            this.operation = this.em.find(OperationStateBean.class, selectedOperation.getId());
            if (this.operation == null) {
                throw new JadortServiceException("OperationStateBean \"" + selectedOperation + "\" not found", null);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the selectOperation can not be called in the current step.
     *         (this method can be called only in the
     *         {@link Step#SELECT_OPERATION} step)
     *         </ul>
     */
    public void createNewOperation(final String newOperationName) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_OPERATION);

            String filteredOperationName = newOperationName.trim();
            if (filteredOperationName.length() < 1) {
                throw new JadortServiceException("Operation name cannot be empty!", null);
            }

            this.operation = new OperationStateBean();
            this.operation.setName(filteredOperationName);
            this.operation.setDate(new Date());
            this.em.persist(this.operation);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException If fetching fails.
     */
    public OperationStateBean getCurrentOperation() throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();

            if (this.operation == null) {
                return null;
            } else {
                // Return a copy since operation has been initialized lazily
                OperationStateBean bean = new OperationStateBean();
                bean.setAimedServerProgressState(this.operation.getAimedServerProgressState());
                bean.setAimedWorkerProgressState(this.operation.getAimedWorkerProgressState());
                bean.setAimedProgressPercent(this.operation.getAimedProgressPercent());
                bean.setApplication(this.operation.getApplication());
                bean.setCurrentStep(this.operation.getCurrentStep());
                bean.setDate(this.operation.getDate());
                bean.setId(this.operation.getId());
                bean.setName(this.operation.getName());
                bean.setType(this.operation.getType());
                bean.setVmImage(this.operation.getVmImage());
                bean.setCanGoToNextStep(this.canGoToNextStep());
                bean.setCanGoToPreviousStep(this.canGoToPreviousStep());

                // Copy arrays since they've been initialized lazily
                if (this.operation.getServerProgressList() != null) {
                    bean.setServerProgressList(new ArrayList<ServerProgressBean>(this.operation.getServerProgressList()));
                }
                if (this.operation.getWorkerProgressList() != null) {
                    bean.setWorkerProgressList(new ArrayList<WorkerProgressBean>(this.operation.getWorkerProgressList()));
                }
                if (this.operation.getSelectedGroup() != null) {
                    GroupBean groupBean = new GroupBean();
                    groupBean.setId(this.operation.getSelectedGroup().getId());
                    groupBean.setWorkers(new ArrayList<WorkerBean>(this.operation.getSelectedGroup().getWorkers()));
                    groupBean.setName(this.operation.getSelectedGroup().getName());
                    groupBean.setServers(new ArrayList<ServerBean>(this.operation.getSelectedGroup().getServers()));
                    groupBean.setClustered(this.operation.getSelectedGroup().getClustered());
                    groupBean.setConnected(this.operation.getSelectedGroup().getConnected());
                    bean.setSelectedGroup(groupBean);
                }
                if (this.operation.getCurrentStep().equals(Step.INITIALIZE_TOPOLOGY)) {
                    if (this.operation.getTopology() != null) {
                        TopologyBean topology = new TopologyBean();
                        topology.setGroups(this.fetchGroups(false));
                        topology.setId(this.operation.getTopology().getId());
                        bean.setTopology(topology);
                    }
                }

                // These calls will contact the servers,
                // call only if absolutely necessary
                if (this.operation.getCurrentStep().equals(Step.SELECT_SERVERS)
                    || this.operation.getCurrentStep().equals(Step.EXECUTING_MAINTENANCE_NO_CLUSTER)
                    || this.operation.getCurrentStep().equals(Step.UNDEPLOY_ERASE_OLD_VERSION)) {
                    this.refreshActiveSessions();
                }
                if (this.operation.getCurrentStep().equals(Step.SELECT_GROUP)) {
                    if (this.operation.getTopology() != null) {
                        TopologyBean topology = new TopologyBean();
                        topology.setGroups(this.fetchGroups(true));
                        topology.setId(this.operation.getTopology().getId());
                        bean.setTopology(topology);
                    }
                }
                if (bean.getSelectedGroup() != null) {
                    if (this.operation.getCurrentStep().equals(Step.SELECT_APPLICATION)) {
                        this.fillApplications(bean.getSelectedGroup());
                    }
                    if (this.operation.getCurrentStep().equals(Step.SELECT_VM_IMAGE)
                        || this.operation.getCurrentStep().equals(Step.SELECT_VM_IMAGE_FOR_SERVER)) {
                        this.fillVMImages(bean.getSelectedGroup());
                    }
                }

                return bean;
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if going to the next step is not allowed.
     */
    public void next() throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            if (!this.canGoToNextStep()) {
                throw new JadortServiceException("You are not allowed to go to the next step!", null);
            }
            this.executeNextStep();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if going to the previous step is not
     *         allowed.
     */
    public void previous() throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            if (!this.canGoToPreviousStep()) {
                throw new JadortServiceException("You are not allowed to go to the previous step!", null);
            }
            this.executePreviousStep();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the loadTopology can not be called in the current step.
     *         (this method can be called only in the
     *         {@link Step#INITIALIZE_TOPOLOGY} step)
     *         <li>if an error is occurred when parsing the .xml file
     *         </ul>
     */
    public void loadTopology(final File xmlTopoFile) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.INITIALIZE_TOPOLOGY);

            TopologyBean topology = null;
            Exception jadortFormatException = null;
            Exception deployMEFormatException = null;
            try {
                topology = TopologyReader.loadJadortSpecificTopology(xmlTopoFile);
            } catch (Exception e1) {
                jadortFormatException = e1;
                try {
                    topology = TopologyReader.loadDeployMETopology(xmlTopoFile);
                } catch (Exception e2) {
                    deployMEFormatException = e2;
                }
            }

            if (topology == null) {
                StringBuilder sb = new StringBuilder();

                if (jadortFormatException != null && jadortFormatException instanceof UnmarshalException
                    && deployMEFormatException != null && deployMEFormatException instanceof UnmarshalException) {
                    // XML doesn't match any XSD
                    UnmarshalException e1 = (UnmarshalException) jadortFormatException;
                    if (e1.getLinkedException() != null) {
                        e1 = new UnmarshalException(e1.getLinkedException().getMessage(), e1);
                    }
                    UnmarshalException e2 = (UnmarshalException) deployMEFormatException;
                    if (e2.getLinkedException() != null) {
                        e2 = new UnmarshalException(e2.getLinkedException().getMessage(), e2);
                    }

                    sb.append(xmlTopoFile.getName());
                    sb.append(" is not a valid JASMINe Deploy ME or JaDOrT topology file.");
                    sb.append("\n\nHere are some potentially helpful error messages:");
                    sb.append("\n\t(For the JaDOrT-specific format: ");
                    sb.append(e1.getMessage());
                    sb.append("\n\t(For the JASMINe DeployME format: ");
                    sb.append(e2.getMessage());

                    throw new JadortServiceException(sb.toString(), null);
                }

                if (deployMEFormatException != null && jadortFormatException != null
                    && jadortFormatException instanceof UnmarshalException) {
                    // XML matches Deploy ME XSD but has another problem
                    sb.append(xmlTopoFile.getName());
                    sb.append(" is not a valid JASMINe Deploy ME topology file.\n\n");
                    sb.append(deployMEFormatException.getMessage());

                    throw new JadortServiceException(sb.toString(), deployMEFormatException);
                }

                if (jadortFormatException != null && deployMEFormatException != null
                    && deployMEFormatException instanceof UnmarshalException) {
                    // XML matches JaDOrT XSD but has another problem
                    sb.append(xmlTopoFile.getName());
                    sb.append(" is not a valid JaDOrT topology file.\n\n");
                    sb.append(jadortFormatException.getMessage());

                    throw new JadortServiceException(sb.toString(), jadortFormatException);
                }

                sb.append("Cannot load topology file ");
                sb.append(xmlTopoFile);

                if (jadortFormatException != null) {
                    throw new JadortServiceException(sb.toString(), jadortFormatException);
                }
                if (deployMEFormatException != null) {
                    throw new JadortServiceException(sb.toString(), deployMEFormatException);
                }

                throw new JadortServiceException(sb.toString(), null);
            }

            this.operation.setTopology(topology);
            this.mergeOperation();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if the selectedGroup method can not be
     *         called in the current step. (this method can be called only in
     *         the {@link Step#SELECT_GROUP} step)
     */
    public void selectGroup(final GroupBean selectedGroup) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_GROUP);

            for (GroupBean group : this.operation.getTopology().getGroups()) {
                if (group.getId().equals(selectedGroup.getId())) {
                    // fillApplications and fillVMImages will check connectivity
                    this.fillApplications(group);
                    this.fillVMImages(group);

                    // check connectivity with workers
                    if (group.getWorkers() != null) {
                        for (WorkerBean worker : group.getWorkers()) {
                            WorkerAction workerAction = WorkerAction.getWorkerAction(worker);
                            try {
                                workerAction.getState();
                            } catch (Exception e) {
                                String message = e.getClass().getName() + ": " + e.getMessage();
                                throw new JadortServiceException("Failed getting the state of worker " + worker.getName()
                                    + ": " + message, e);
                            }
                        }
                    }

                    this.operation.setSelectedGroup(group);
                    this.em.flush();
                    return;
                }
            }

            throw new JadortServiceException("GroupBean \"" + selectedGroup + "\" not found", null);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if the selectedGroup method can not be
     *         called in the current step. (this method can be called only in
     *         the {@link Step#SELECT_OPERATION_TYPE} step)
     */
    public void selectOperationType(final OperationType operationType) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_OPERATION_TYPE);

            if (OperationType.MAINTAIN.equals(operationType)) {
                for (ServerBean server : this.operation.getSelectedGroup().getServers()) {
                    ServerAction action = ServerAction.getServerAction(server);
                    boolean canStartStopServer;
                    try {
                        canStartStopServer = action.canStartStopServer();
                    } catch (Exception e) {
                        throw new JadortServiceException("Cannot check if server '" + server
                            + "' can be started/stopped by JaDOrT: " + e.getMessage(), e);
                    }
                    if (!canStartStopServer) {
                        throw new JadortServiceException("This version of JaDOrT cannot start/stop the server '"
                            + server.getName() + "'. Please use the \"Migration\" functions instead.", null);
                    }
                }
            } else if (OperationType.MIGRATE.equals(operationType)) {
                for (ServerBean server : this.operation.getSelectedGroup().getServers()) {
                    ServerAction action = ServerAction.getServerAction(server);
                    boolean canDeployApplications;
                    try {
                        canDeployApplications = action.canDeployApplications();
                    } catch (Exception e) {
                        throw new JadortServiceException("Cannot check if server '" + server + "' can deploy applications: "
                            + e.getMessage(), e);
                    }
                    if (!canDeployApplications) {
                        throw new JadortServiceException(
                            "This version of JaDOrT doesn't support the deployment of applications on the server '"
                                + server.getName() + "'. Please use the \"Maintenance\" functions instead.", null);
                    }
                }
            } else {
                throw new IllegalStateException("Unknown operation type: " + operationType);
            }

            this.operation.setType(operationType);

            Set<ServerProgressBean> oldServerProgressList = new HashSet<ServerProgressBean>();
            Set<WorkerProgressBean> oldWorkerProgressList = new HashSet<WorkerProgressBean>();
            if (this.operation.getServerProgressList() != null) {
                oldServerProgressList.addAll(this.operation.getServerProgressList());
            }
            if (this.operation.getAllServerProgressList() != null) {
                oldServerProgressList.addAll(this.operation.getAllServerProgressList());
            }
            if (this.operation.getWorkerProgressList() != null) {
                oldWorkerProgressList.addAll(this.operation.getWorkerProgressList());
            }
            if (this.operation.getAllWorkerProgressList() != null) {
                oldWorkerProgressList.addAll(this.operation.getAllWorkerProgressList());
            }

            this.operation.setServerProgressList(null);
            this.operation.setWorkerProgressList(null);
            this.operation.setAllServerProgressList(null);
            this.operation.setAllWorkerProgressList(null);

            this.mergeOperation();

            for (ServerProgressBean serverProgressBean : oldServerProgressList) {
                this.em.remove(serverProgressBean);
            }
            for (WorkerProgressBean workerProgressBean : oldWorkerProgressList) {
                this.em.remove(workerProgressBean);
            }
            this.em.flush();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the selectApplication can not be called in the current
     *         step. (this method can be called only in the
     *         {@link Step#SELECT_APPLICATION} step)
     *         </ul>
     */
    public void selectApplication(final URL url) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_APPLICATION);

            ApplicationBean applicationBean;
            File file;
            if (url.getProtocol().equals("file")) {
                try {
                    file = new File(url.toURI());
                } catch (URISyntaxException e) {
                    throw new JadortServiceException("Cannot create file for URL '" + url + "': " + e.getMessage(), e);
                }
            } else {
                try {
                    URLConnection connection = url.openConnection();
                    InputStream reader = connection.getInputStream();
                    String name = url.getPath();
                    name = name.substring(name.lastIndexOf('/'));
                    file = new File(System.getProperty("java.io.tmpdir") + File.separatorChar + "jadort.temp."
                        + System.currentTimeMillis());
                    file.mkdirs();
                    file = new File(file, name);
                    FileOutputStream writer = new FileOutputStream(file);
                    int read;
                    byte[] buffer = new byte[JadortServiceStatefulBean.BUFFER_SIZE];
                    while ((read = reader.read(buffer)) > 0) {
                        writer.write(buffer, 0, read);
                    }
                    writer.close();
                    reader.close();
                } catch (IOException e) {
                    throw new JadortServiceException("Cannot read from URL: '" + url + "'", null);
                }
            }
            try {
                applicationBean = new ApplicationBean(file);
            } catch (Exception e) {
                throw new JadortServiceException("Cannot create application for URL '" + url + "': " + e.getMessage(), e);
            }

            ApplicationBean oldApplicationBean = this.operation.getApplication();
            this.operation.setApplication(applicationBean);

            this.mergeOperation();

            if (oldApplicationBean != null) {
                this.em.remove(oldApplicationBean);
                this.em.flush();
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException<ul>
     *         <li>if the selectVMImage can not be called in the current step.
     *         (this method can be called only in the
     *         {@link Step#SELECT_VM_IMAGE_FOR_SERVER} step)
     *         <li>or if selectedVMImage is not known
     *         <li>or if fetching fails
     *         </ul>
     */
    public void selectVMImage(final VMImageBean selectedVMImage) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_VM_IMAGE);

            this.fillVMImages(this.operation.getSelectedGroup());
            for (VMImageBean vMImage : this.operation.getSelectedGroup().getVmImages()) {
                if (vMImage.equals(selectedVMImage)) {
                    VMImageBean oldVMImage = this.operation.getVmImage();
                    this.operation.setVmImage(vMImage);

                    this.mergeOperation();

                    if (oldVMImage != null) {
                        this.em.remove(oldVMImage);
                        this.em.flush();
                    }

                    return;
                }
            }

            throw new JadortServiceException("VMImageBean \"" + selectedVMImage + "\" not found", null);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException<ul>
     *         <li>if the selectVMImage can not be called in the current step.
     *         (this method can be called only in the
     *         {@link Step#SELECT_VM_IMAGE} step)
     *         <li>or if selectedVMImage is not known
     *         <li>or if server is not known
     *         <li>or if fetching fails
     *         </ul>
     */
    public void selectVMImage(final VMImageBean selectedVMImage, final ServerBean server) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_VM_IMAGE_FOR_SERVER);

            this.fillVMImages(this.operation.getSelectedGroup());
            ServerProgressBean serverProgress = this.getServerProgress(server);
            for (VMImageBean vMImage : this.operation.getSelectedGroup().getVmImages()) {
                if (vMImage.equals(selectedVMImage)) {
                    VMImageBean oldVMImage = serverProgress.getVmImage();
                    serverProgress.setVmImage(vMImage);

                    this.mergeOperation();

                    if (oldVMImage != null) {
                        this.em.remove(oldVMImage);
                        this.em.flush();
                    }

                    return;
                }
            }

            throw new JadortServiceException("VMImageBean \"" + selectedVMImage + "\" not found", null);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if the selectServers method can not be
     *         called in the current step (this method can be called only in the
     *         {@link Step#SELECT_SERVERS} step)
     */
    public void selectServers(final List<ServerBean> selectedServers) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.SELECT_SERVERS);

            List<ServerBean> selectedServersList = new ArrayList<ServerBean>();
            List<ServerProgressBean> serverProgressList = new ArrayList<ServerProgressBean>();

            for (ServerBean selectedServer : selectedServers) {
                for (ServerBean server : this.operation.getSelectedGroup().getServers()) {
                    if (server.equals(selectedServer)) {
                        if (!selectedServersList.contains(server)) {
                            selectedServersList.add(server);
                            boolean hasProgress = false;
                            if (this.operation.getAllServerProgressList() != null) {
                                for (ServerProgressBean oldServerProgress : this.operation.getAllServerProgressList()) {
                                    if (oldServerProgress.getServer().equals(server)) {
                                        hasProgress = true;
                                        serverProgressList.add(oldServerProgress);
                                        break;
                                    }
                                }
                            }
                            if (!hasProgress) {
                                serverProgressList.add(new ServerProgressBean(server));
                            }
                        }
                        break;
                    }
                }
            }
            if (selectedServersList.size() == 0 || serverProgressList.size() == 0) {
                throw new JadortServiceException("You must select at least one server!", null);
            }
            for (ServerProgressBean serverProgress : serverProgressList) {
                if (serverProgress.getServer().getProcessed()) {
                    throw new JadortServiceException("The server '" + serverProgress.getServer().getName()
                        + "' has already been processed!", null);
                }
            }
            List<VMImageBean> oldVMImages = new ArrayList<VMImageBean>();
            for (ServerProgressBean serverProgress : serverProgressList) {
                if (serverProgress.getOldDeploymentItem() != null) {
                    throw new JadortServiceException("The VM for server '" + serverProgress.getServer().getName()
                        + "' has already been processed!", null);
                }

                if (serverProgress.getVmImage() != null) {
                    oldVMImages.add(serverProgress.getVmImage());
                }

                if (this.operation.getVmImage() != null) {
                    VMImageBean image = new VMImageBean();
                    image.setName(this.operation.getVmImage().getName());
                    image.setUuid(this.operation.getVmImage().getUuid());
                    serverProgress.setVmImage(image);
                } else {
                    serverProgress.setVmImage(null);
                }
                VMBean vm = serverProgress.getServer().getVm();
                if (vm != null) {
                    VMMAction vmmAction = VMMAction.getVMMAction(vm);
                    String oldVMFullName;
                    try {
                        oldVMFullName = vmmAction.getFullVMName(vm.getName());
                    } catch (Exception e) {
                        throw new JadortServiceException("Failed getting the full VM name for VM '" + vm.getName() + "': "
                            + e.getMessage(), e);
                    }
                    serverProgress.setOldDeploymentItem(oldVMFullName);
                }
            }
            this.operation.setServerProgressList(serverProgressList);
            this.operation.addAllServerProgressList(serverProgressList);

            if (this.operation.getType().equals(OperationType.MAINTAIN)) {
                this.fillWorkerProgressListBasedOnServerProgressList();
            }

            this.mergeOperation();

            for (VMImageBean oldVMImage : oldVMImages) {
                this.em.remove(oldVMImage);
                this.em.flush();
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException If the abortServer can not be called in
     *         the current step. (this method can be called only in the
     *         {@link Step#EXECUTING_MIGRATION},
     *         {@link Step#UNDEPLOY_ERASE_OLD_VERSION},
     *         {@link Step#EXECUTING_MIGRATION_OSGI},
     *         {@link Step#ERASE_OLD_VERSIONS},
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     */
    public void abortServer(final ServerBean server) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MIGRATION, Step.UNDEPLOY_ERASE_OLD_VERSION, Step.EXECUTING_MIGRATION_OSGI,
                Step.ERASE_OLD_VERSIONS, Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER);

            ServerProgressBean serverProgress = this.getServerProgress(server);
            if (!serverProgress.getActionState().equals(ActionState.RUNNING)) {
                return;
            }

            serverProgress.appendToLog("Action has been aborted by the user");

            serverProgress.setProgress(this.operation.getAimedProgressPercent());
            serverProgress.setActionState(ActionState.FINISHED_ERROR);

            this.mergeOperation();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the restartServermethod can not be called in the current
     *         step. (this method can be called only in the
     *         {@link Step#EXECUTING_MIGRATION},
     *         {@link Step#UNDEPLOY_ERASE_OLD_VERSION},
     *         {@link Step#EXECUTING_MIGRATION_OSGI},
     *         {@link Step#ERASE_OLD_VERSIONS},
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     *         <li>if the server state is not in error state.
     *         </ul>
     */
    public void restartServer(final ServerBean server) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MIGRATION, Step.UNDEPLOY_ERASE_OLD_VERSION, Step.EXECUTING_MIGRATION_OSGI,
                Step.ERASE_OLD_VERSIONS, Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER,
                Step.DESTROY_OLD_VM_HOSTS);

            ServerProgressBean serverProgress = this.getServerProgress(server);
            if (!serverProgress.getActionState().equals(ActionState.FINISHED_ERROR)) {
                throw new JadortServiceException("Server state for server '" + server.getName() + "' is "
                    + serverProgress.getActionState() + ", it should be " + ActionState.FINISHED_ERROR, null);
            }

            serverProgress.setActionState(ActionState.RUNNING);

            this.mergeOperation();

            this.reachAimedServerProgressState(this.getServerProgress(server));
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if the checkServer can not be called in
     *         the current step. (this method can be called only in the
     *         {@link Step#EXECUTING_MIGRATION},
     *         {@link Step#UNDEPLOY_ERASE_OLD_VERSION},
     *         {@link Step#EXECUTING_MIGRATION_OSGI},
     *         {@link Step#ERASE_OLD_VERSIONS},
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     */
    public boolean checkServer(final ServerBean server) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MIGRATION, Step.UNDEPLOY_ERASE_OLD_VERSION, Step.EXECUTING_MIGRATION_OSGI,
                Step.ERASE_OLD_VERSIONS, Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER,
                Step.DESTROY_OLD_VM_HOSTS);

            try {
                ServerProgressBean serverProgress = this.getServerProgress(server);
                ServerAction serverAction = ServerAction.getServerAction(serverProgress.getServer());
                List<ApplicationBean> applications = serverAction.listOfApplications();

                ServerProgressState serverProgressState = serverProgress.getProgressState();
                if (serverProgressState.equals(ServerProgressState.INITIAL)) {
                    OperationType type = this.operation.getType();
                    if (type.equals(OperationType.MAINTAIN)) {
                        return serverAction.isStarted();
                    } else if (type.equals(OperationType.MIGRATE)) {
                        for (ApplicationBean application : applications) {
                            if (this.isSameApplication(this.operation.getApplication(), application)) {
                                if (!this.checkApplication(serverProgress, application, null, null)) {
                                    return false;
                                }
                            }
                            if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getNewDeploymentItem()),
                                application)) {
                                if (!this.checkApplication(serverProgress, application, null, null)) {
                                    return false;
                                }
                            }
                            if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getOldDeploymentItem()),
                                application)) {
                                if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                    return false;
                                }
                            }
                        }

                        // If we came here all applications are OK
                        return true;
                    }

                    throw new IllegalStateException("Unknown OperationType: " + type);
                } else if (serverProgressState.equals(ServerProgressState.UPLOAD_OK)) {
                    for (ApplicationBean application : applications) {
                        if (this.isSameApplication(this.operation.getApplication(), application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_PRESENT, null)) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getNewDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_PRESENT, null)) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getOldDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                    }

                    // If we came here all applications are OK
                    return true;
                } else if (serverProgressState.equals(ServerProgressState.DEPLOY_OK)) {
                    for (ApplicationBean application : applications) {
                        if (this.isSameApplication(this.operation.getApplication(), application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Reserved")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getNewDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Reserved")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getOldDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                    }

                    // If we came here all applications are OK
                    return true;
                } else if (serverProgressState.equals(ServerProgressState.SET_DEFAULT_OK)) {
                    for (ApplicationBean application : applications) {
                        if (this.isSameApplication(this.operation.getApplication(), application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getNewDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getOldDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Disabled")) {
                                return false;
                            }
                        }
                    }

                    // If we came here all applications are OK
                    return true;
                } else if (serverProgressState.equals(ServerProgressState.UNDEPLOY_OK)) {
                    for (ApplicationBean application : applications) {
                        if (this.isSameApplication(this.operation.getApplication(), application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getNewDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getOldDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_PRESENT, null)) {
                                return false;
                            }
                        }
                    }

                    // If we came here all applications are OK
                    return true;
                } else if (serverProgressState.equals(ServerProgressState.ERASE_OK)) {
                    for (ApplicationBean application : applications) {
                        if (this.isSameApplication(this.operation.getApplication(), application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getNewDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, ServerAction.STATE_DEPLOYED, "Default")) {
                                return false;
                            }
                        }
                        if (this.isSameApplication(serverAction.getApplicationBean(serverProgress.getOldDeploymentItem()),
                            application)) {
                            if (!this.checkApplication(serverProgress, application, null, null)) {
                                return false;
                            }
                        }
                    }

                    // If we came here all applications are OK
                    return true;
                } else if (serverProgressState.equals(ServerProgressState.DISABLE_APPLICATIONS_OK)
                    || serverProgressState.equals(ServerProgressState.START_OK)) {
                    return serverAction.isStarted();
                } else if (serverProgressState.equals(ServerProgressState.STOP_OK)
                    || serverProgressState.equals(ServerProgressState.MAINTAIN_OK)) {
                    return !serverAction.isStarted();
                } else if (serverProgressState.equals(ServerProgressState.DESTROY_VM_HOSTS_OK)) {
                    // We don't know how to check this
                    return false;
                }

                throw new IllegalStateException("Unknown ServerProgressState: " + serverProgressState);
            } catch (Exception e) {
                throw new JadortServiceException("Cannot check server '" + server + "': " + e.getMessage(), e);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the ignoreServer can not be called in the current step.
     *         (this method can be called only in the
     *         {@link Step#EXECUTING_MIGRATION},
     *         {@link Step#UNDEPLOY_ERASE_OLD_VERSION},
     *         {@link Step#EXECUTING_MIGRATION_OSGI},
     *         {@link Step#ERASE_OLD_VERSIONS},
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     *         <li>if the server state is not in error state.
     *         </ul>
     */
    public void ignoreServer(final ServerBean server) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MIGRATION, Step.UNDEPLOY_ERASE_OLD_VERSION, Step.EXECUTING_MIGRATION_OSGI,
                Step.ERASE_OLD_VERSIONS, Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER);

            ServerProgressBean serverProgress = this.getServerProgress(server);

            if (ServerProgressState.INITIAL.equals(serverProgress.getProgressState())
                && ServerProgressState.UPLOAD_OK.equals(this.operation.getAimedServerProgressState())) {
                throw new JadortServiceException(
                    "The application upload step is mandatory for JaDOrT to function properly. It cannot be ignored.", null);
            }

            if (ServerProgressState.STOP_OK.equals(serverProgress.getProgressState())
                && ServerProgressState.MAINTAIN_OK.equals(this.operation.getAimedServerProgressState())) {
                throw new JadortServiceException(
                    "The server maintain step is mandatory for JaDOrT to function properly. It cannot be ignored.", null);
            }

            if (!serverProgress.getActionState().equals(ActionState.FINISHED_ERROR)) {
                throw new JadortServiceException("Server state for server '" + server.getName() + "' is "
                    + serverProgress.getActionState() + ", it should be " + ActionState.FINISHED_ERROR, null);
            }

            serverProgress.appendToLog("Error on the server has been ignored by the user");

            serverProgress.setProgress(this.operation.getAimedProgressPercent());
            serverProgress.setActionState(ActionState.WAITING);
            serverProgress.setProgressState(this.operation.getAimedServerProgressState());

            this.mergeOperation();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException If the abortWorker can not be called in
     *         the current step. (this method can be called only in the
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     */
    public void abortWorker(final WorkerBean worker) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER);

            WorkerProgressBean workerProgress = this.getWorkerProgress(worker);
            if (!workerProgress.getActionState().equals(ActionState.RUNNING)) {
                return;
            }

            workerProgress.appendToLog("Action has been aborted by the user");

            workerProgress.setProgress(this.operation.getAimedProgressPercent());
            workerProgress.setActionState(ActionState.FINISHED_ERROR);

            this.mergeOperation();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the restartWorkermethod can not be called in the current
     *         step. (this method can be called only in the
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     *         <li>if the worker state is not in error state.
     *         </ul>
     */
    public void restartWorker(final WorkerBean worker) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER);

            WorkerProgressBean workerProgress = this.getWorkerProgress(worker);
            if (!workerProgress.getActionState().equals(ActionState.FINISHED_ERROR)) {
                throw new JadortServiceException("Worker state for worker '" + worker.getName() + "' is "
                    + workerProgress.getActionState() + ", it should be " + ActionState.FINISHED_ERROR, null);
            }

            workerProgress.setActionState(ActionState.RUNNING);

            this.mergeOperation();

            this.reachAimedWorkerProgressState(this.getWorkerProgress(worker));
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException if the checkWorker can not be called in
     *         the current step. (this method can be called only in the
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     */
    public boolean checkWorker(final WorkerBean worker) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER);

            try {
                WorkerProgressBean workerProgress = this.getWorkerProgress(worker);
                WorkerAction workerAction = WorkerAction.getWorkerAction(workerProgress.getWorker());
                WorkerAction.State state = workerAction.getState();

                WorkerProgressState workerProgressState = workerProgress.getProgressState();
                if (workerProgressState.equals(WorkerProgressState.INITIAL)
                    || workerProgressState.equals(WorkerProgressState.START_OK)) {
                    return (state == WorkerAction.State.ACTIVE);
                } else if (workerProgressState.equals(WorkerProgressState.STOP_OK)) {
                    return (state == WorkerAction.State.STOPPED);
                }

                throw new IllegalStateException("Unknown WorkerProgressState: " + workerProgressState);
            } catch (Exception e) {
                throw new JadortServiceException("Cannot check worker '" + worker + "': " + e.getMessage(), e);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @throws JadortServiceException <ul>
     *         <li>if the ignoreWorker can not be called in the current step.
     *         (this method can be called only in the
     *         {@link Step#EXECUTING_MAINTENANCE_CLUSTER} and
     *         {@link Step#EXECUTING_MAINTENANCE_NO_CLUSTER} steps)
     *         <li>if the worker state is not in error state.
     *         </ul>
     */
    public void ignoreWorker(final WorkerBean worker) throws JadortServiceException {
        synchronized (this) {
            this.refreshOperation();
            this.checkStep(Step.EXECUTING_MAINTENANCE_CLUSTER, Step.EXECUTING_MAINTENANCE_NO_CLUSTER);

            WorkerProgressBean workerProgress = this.getWorkerProgress(worker);
            if (!workerProgress.getActionState().equals(ActionState.FINISHED_ERROR)) {
                throw new JadortServiceException("Worker state for worker '" + worker.getName() + "' is "
                    + workerProgress.getActionState() + ", it should be " + ActionState.FINISHED_ERROR, null);
            }

            workerProgress.appendToLog("Error on the worker has been ignored by the user");

            workerProgress.setProgress(this.operation.getAimedProgressPercent());
            workerProgress.setActionState(ActionState.WAITING);
            workerProgress.setProgressState(this.operation.getAimedWorkerProgressState());

            this.mergeOperation();
        }
    }
}
