/**
 * 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: ServerAction.java 4106 2009-06-20 15:48:35Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.action;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.ow2.jasmine.jadort.api.entities.deployment.ApplicationBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean.Type;

/**
 * Action for a server (JOnAS or other)
 * 
 * @author Malek Chahine
 * @author Remy Bresson
 * @author S. Ali Tokmen
 */
public abstract class ServerAction extends AbstractJMXAction {

    private static final int FILE_READ_BUFFER_SIZE = 1024;

    public static final String STATE_PRESENT = "Present";

    public static final String STATE_DEPLOYED = "Deployed";

    private static Map<String, ServerAction> pool = new Hashtable<String, ServerAction>();

    public static ServerAction getServerAction(final ServerBean server) {
        synchronized (ServerAction.pool) {
            String key = ServerAction.getKey(server);
            ServerAction serverAction = ServerAction.pool.get(key);
            if (serverAction == null) {
                serverAction = ServerAction.newInstance(server);
                ServerAction.pool.put(key, serverAction);
            }
            return serverAction;
        }
    }

    private static String getKey(final ServerBean server) {
        StringBuilder sb = new StringBuilder();
        sb.append(server.getName());
        sb.append(server.getType());
        sb.append(server.getServerConnector().getConnectorUrl());
        if (server.getServerConnector().getUsername() != null && server.getServerConnector().getPassword() != null) {
            sb.append(server.getServerConnector().getUsername());
            sb.append(server.getServerConnector().getPassword());
        }
        return sb.toString();
    }

    protected static ServerAction newInstance(final ServerBean server) {
        try {
            Constructor<? extends ServerAction> constructor;

            if (server.getType().equals(Type.DUMMY)) {
                constructor = ServerAction.getConstructor(DummyServerAction.class);
            } else if (server.getType().equals(Type.JONAS)) {
                constructor = ServerAction.getConstructor(JonasServerAction.class);
            } else if (server.getType().equals(Type.JBOSS)) {
                constructor = ServerAction.getConstructor(JBossServerAction.class);
            } else if (server.getType().equals(Type.GLASSFISH)) {
                constructor = ServerAction.getConstructor(GlassFishServerAction.class);
            } else if (server.getType().equals(Type.OSGI)) {
                constructor = ServerAction.getConstructor(OSGiServerAction.class);
            } else if (server.getType().equals(Type.WEBLOGIC)) {
                constructor = ServerAction.getConstructor(WebLogicServerAction.class);
            } else if (server.getType().equals(Type.WEBSPHERE)) {
                constructor = ServerAction.getConstructor(WebSphereServerAction.class);
            } else {
                throw new IllegalArgumentException("Unknown server type :" + server.getType());
            }

            return constructor.newInstance(server);
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed creating the server action", e);
        }
    }

    @SuppressWarnings("unchecked")
    protected static Constructor<? extends ServerAction> getConstructor(final Class<? extends ServerAction> actionType)
        throws Exception {
        Constructor[] constructors = actionType.getDeclaredConstructors();
        for (Constructor<? extends ServerAction> constructor : constructors) {
            Class<?>[] params = constructor.getParameterTypes();
            if (params.length == 1 && params[0] == ServerBean.class) {
                return constructor;
            }
        }
        return null;
    }

    /**
     * @return List of applications currently present on this server,
     *         reformatted as ApplicationBean objects.
     */
    public abstract List<ApplicationBean> listOfApplications() throws Exception;

    /**
     * @return true if this ServerAction can deploy applications, false
     *         otherwise.
     */
    public abstract boolean canDeployApplications() throws Exception;

    /**
     * @return true if this ServerAction can start/stop the server, false
     *         otherwise.
     */
    public abstract boolean canStartStopServer() throws Exception;

    /**
     * @param application ApplicationBean to upload.
     * @return Name of the application as saved by the server, to use in the
     *         {@link ServerAction#deploy(String)} and
     *         {@link ServerAction#setDefault(String)},
     *         {@link ServerAction#undeploy(String)} and
     *         {@link ServerAction#erase(String)} methods.
     */
    public abstract String upload(final ApplicationBean application) throws Exception;

    /**
     * @param appName Name of the application, as returned by
     *        {@link ServerAction#upload(ApplicationBean)} or
     *        {@link ServerAction#setDefault(String)}, for which to obtain an
     *        {@link ApplicationBean}.
     * @return {@link ApplicationBean} corresponding to appName, null if nothing
     *         matches.
     */
    public abstract ApplicationBean getApplicationBean(final String appName) throws Exception;

    /**
     * @param appName Name of the application to deploy, as returned by
     *        {@link ServerAction#upload(ApplicationBean)} or
     *        {@link ServerAction#setDefault(String)}.
     */
    public abstract void deploy(final String appName) throws Exception;

    /**
     * @param appName Name of the application to set as default version
     *        (previous Default version will become Disabled), as returned by
     *        {@link ServerAction#upload(ApplicationBean)} or
     *        {@link ServerAction#setDefault(String)}.
     * @return Old application that was default.
     */
    public abstract String setDefault(final String appName) throws Exception;

    /**
     * @param appName Name of the application to undeploy, as returned by
     *        {@link ServerAction#upload(ApplicationBean)} or
     *        {@link ServerAction#setDefault(String)}.
     */
    public abstract void undeploy(final String appName) throws Exception;

    /**
     * @param appName Name of the application to erase, as returned by
     *        {@link ServerAction#upload(ApplicationBean)} or
     *        {@link ServerAction#setDefault(String)}.
     */
    public abstract void erase(final String appName) throws Exception;

    /**
     * @param appName Name of the application for which to get the number of
     *        active sessions, as returned by
     *        {@link ServerAction#upload(ApplicationBean)} or
     *        {@link ServerAction#setDefault(String)}.
     * @return Number of active sessions on application named appName.
     */
    public abstract int getActiveSessions(final String appName) throws Exception;

    /**
     * @return Number of active sessions on all applications of this server.
     */
    public abstract int getActiveSessions() throws Exception;

    /**
     * Start server
     */
    public abstract void start() throws Exception;

    /**
     * Stop server
     */
    public abstract void stop() throws Exception;

    /**
     * @return true if server started, false otherwise.
     */
    public abstract boolean isStarted();

    /**
     * Enable or disable or enable all applications in this server.
     * 
     * @param enable true to enable all applications, false to disable all
     *        applications.
     * @return true if disabling all applications has succeeded, false
     *         otherwise. An exception should only be thrown if a real error
     *         occurs.
     */
    public abstract boolean enableOrDisableApplications(boolean enable) throws Exception;

    /**
     * Reads bytes of a file
     * 
     * @param file File to read.
     * @return byte contents of the file.
     */
    protected static byte[] obtainByteData(final File file) throws IOException {
        InputStream inputStream = new FileInputStream(file);
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            try {
                int readBytes;
                byte[] bytes = new byte[ServerAction.FILE_READ_BUFFER_SIZE];

                // Read bytes from the input stream in bytes.length-sized chunks
                // and write them into the output stream
                while ((readBytes = inputStream.read(bytes)) > 0) {
                    outputStream.write(bytes, 0, readBytes);
                }

                // Convert the contents of the output stream into a byte array
                return outputStream.toByteArray();
            } finally {
                outputStream.close();
            }
        } finally {
            inputStream.close();
        }
    }
}
