/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/* @version $Id: DaemonWrapper.java 1453245 2013-03-06 09:54:38Z mturk $ */

package org.apache.commons.daemon.support;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;

/**
 * Implementation of the Daemon that allows running
 * standard applications as daemons.
 * The applications must have the mechanism to manage
 * the application lifecycle.
 *
 * @version $Id: DaemonWrapper.java 1453245 2013-03-06 09:54:38Z mturk $
 * @author Mladen Turk
 */
public class DaemonWrapper implements Daemon
{

    private final static String ARGS            = "args";
    private final static String START_CLASS     = "start";
    private final static String START_METHOD    = "start.method";
    private final static String STOP_CLASS      = "stop";
    private final static String STOP_METHOD     = "stop.method";
    private final static String STOP_ARGS       = "stop.args";
    private String              configFileName  = null;
    private final DaemonConfiguration config;

    private final Invoker             startup;
    private final Invoker             shutdown;

    public DaemonWrapper()
    {
        super();
        config   = new DaemonConfiguration();
        startup  = new Invoker();
        shutdown = new Invoker();
    }

    /**
     * Called from DaemonLoader on init stage.
     * <p>
     * Accepts the following configuration arguments:
     * <ul>
     * <li>-daemon-properties: - load DaemonConfiguration properties from the specified file to act as defaults</li>
     * <li>-start: set start class name</li>
     * <li>-start-method: set start method name</li>
     * <li>-stop: set stop class name</li>
     * <li>-stop-method: set stop method name</li>
     * <li>-stop-argument: set optional argument to stop method</li>
     * <li>Anything else is treated as a startup argument</li>
     * </ul>
     * <p>
     * The following "-daemon-properties" are recognised:
     * <ul>
     * <li>args (startup argument)</li>
     * <li>start</li>
     * <li>start.method</li>
     * <li>stop</li>
     * <li>stop.method</li>
     * <li>stop.args</li>
     * </ul>
     * These are used to set the corresponding item if it has not already been
     * set by the command arguments. <b>However, note that args and stop.args are
     * appended to any existing values.</b>
     */
    public void init(DaemonContext context)
        throws Exception
    {
        String[] args = context.getArguments();

        if (args != null) {
            int i;
            // Parse our arguments and remove them
            // from the final argument array we are
            // passing to our child.
            for (i = 0; i < args.length; i++) {
                if (args[i].equals("--")) {
                    // Done with argument processing
                    break;
                }
                else if (args[i].equals("-daemon-properties")) {
                    if (++i == args.length)
                        throw new IllegalArgumentException(args[i - 1]);
                    configFileName = args[i];
                }
                else if (args[i].equals("-start")) {
                    if (++i == args.length)
                        throw new IllegalArgumentException(args[i - 1]);
                    startup.setClassName(args[i]);
                }
                else if (args[i].equals("-start-method")) {
                    if (++i == args.length)
                        throw new IllegalArgumentException(args[i - 1]);
                    startup.setMethodName(args[i]);
                }
                else if (args[i].equals("-stop")) {
                    if (++i == args.length)
                        throw new IllegalArgumentException(args[i - 1]);
                    shutdown.setClassName(args[i]);
                }
                else if (args[i].equals("-stop-method")) {
                    if (++i == args.length)
                        throw new IllegalArgumentException(args[i - 1]);
                    shutdown.setMethodName(args[i]);
                }
                else if (args[i].equals("-stop-argument")) {
                    if (++i == args.length)
                        throw new IllegalArgumentException(args[i - 1]);
                    String[] aa = new String[1];
                    aa[0] = args[i];
                    shutdown.addArguments(aa);
                }
                else {
                    // This is not our option.
                    // Everything else will be forwarded to the main
                    break;
                }
            }
            if (args.length > i) {
                String[] copy = new String[args.length - i];
                System.arraycopy(args, i, copy, 0, copy.length);
                startup.addArguments(copy);
            }
        }
        if (config.load(configFileName)) {
            // Setup params if not set via cmdline.
            startup.setClassName(config.getProperty(START_CLASS));
            startup.setMethodName(config.getProperty(START_METHOD));
            // Merge the config with command line arguments
            startup.addArguments(config.getPropertyArray(ARGS));

            shutdown.setClassName(config.getProperty(STOP_CLASS));
            shutdown.setMethodName(config.getProperty(STOP_METHOD));
            shutdown.addArguments(config.getPropertyArray(STOP_ARGS));
        }
        startup.validate();
        shutdown.validate();
    }

    /**
     */
    public void start()
        throws Exception
    {
        startup.invoke();
    }

    /**
     */
    public void stop()
        throws Exception
    {
        shutdown.invoke();
    }

    /**
     */
    public void destroy()
    {
        // Nothing for the moment
        System.err.println("DaemonWrapper: instance " + this.hashCode() + " destroy");
    }

    // Internal class for wrapping the start/stop methods
    class Invoker
    {
        private String      name = null;
        private String      call = null;
        private String[]    args = null;
        private Method      inst = null;
        private Class       main = null;

        protected Invoker()
        {
        }

        protected void setClassName(String name)
        {
            if (this.name == null)
                this.name = name;
        }
        protected void setMethodName(String name)
        {
            if (this.call == null)
                this.call = name;
        }
        protected void addArguments(String[] args)
        {
            if (args != null) {
                ArrayList aa = new ArrayList();
                if (this.args != null)
                    aa.addAll(Arrays.asList(this.args));
                aa.addAll(Arrays.asList(args));
                this.args = (String[])aa.toArray(new String[aa.size()]);
            }
        }

        protected void invoke()
            throws Exception
        {
            if (name.equals("System") && call.equals("exit")) {
                // Just call a System.exit()
                // The start method was probably installed
                // a shutdown hook.
                System.exit(0);
            }
            else {
                Object obj   = null;
                if ((inst.getModifiers() & Modifier.STATIC) == 0) {
                    // We only need object instance for non-static methods.
                    obj = main.newInstance();
                }
                Object arg[] = new Object[1];

                arg[0] = args;
                inst.invoke(obj, arg);
            }
        }
        // Load the class using reflection
        protected void validate()
            throws Exception
        {
            /* Check the class name */
            if (name == null) {
                name = "System";
                call = "exit";
                return;
            }
            if (args == null)
                args = new String[0];
            if (call == null)
                call = "main";

            // Get the ClassLoader loading this class
            ClassLoader cl = DaemonWrapper.class.getClassLoader();
            if (cl == null)
                throw new NullPointerException("Cannot retrieve ClassLoader instance");
            Class[] ca = new Class[1];
            ca[0]      = args.getClass();
            // Find the required class
            main = cl.loadClass(name);
            if (main == null)
                throw new ClassNotFoundException(name);
            // Find the required method.
            // NoSuchMethodException will be thrown if matching method
            // is not found.
            inst = main.getMethod(call, ca);
        }
    }
}
