001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017
018/* @version $Id: DaemonWrapper.java 1453245 2013-03-06 09:54:38Z mturk $ */
019
020package org.apache.commons.daemon.support;
021
022import java.lang.reflect.Method;
023import java.lang.reflect.Modifier;
024import java.util.ArrayList;
025import java.util.Arrays;
026import org.apache.commons.daemon.Daemon;
027import org.apache.commons.daemon.DaemonContext;
028
029/**
030 * Implementation of the Daemon that allows running
031 * standard applications as daemons.
032 * The applications must have the mechanism to manage
033 * the application lifecycle.
034 *
035 * @version $Id: DaemonWrapper.java 1453245 2013-03-06 09:54:38Z mturk $
036 * @author Mladen Turk
037 */
038public class DaemonWrapper implements Daemon
039{
040
041    private final static String ARGS            = "args";
042    private final static String START_CLASS     = "start";
043    private final static String START_METHOD    = "start.method";
044    private final static String STOP_CLASS      = "stop";
045    private final static String STOP_METHOD     = "stop.method";
046    private final static String STOP_ARGS       = "stop.args";
047    private String              configFileName  = null;
048    private final DaemonConfiguration config;
049
050    private final Invoker             startup;
051    private final Invoker             shutdown;
052
053    public DaemonWrapper()
054    {
055        super();
056        config   = new DaemonConfiguration();
057        startup  = new Invoker();
058        shutdown = new Invoker();
059    }
060
061    /**
062     * Called from DaemonLoader on init stage.
063     * <p>
064     * Accepts the following configuration arguments:
065     * <ul>
066     * <li>-daemon-properties: - load DaemonConfiguration properties from the specified file to act as defaults</li>
067     * <li>-start: set start class name</li>
068     * <li>-start-method: set start method name</li>
069     * <li>-stop: set stop class name</li>
070     * <li>-stop-method: set stop method name</li>
071     * <li>-stop-argument: set optional argument to stop method</li>
072     * <li>Anything else is treated as a startup argument</li>
073     * </ul>
074     * <p>
075     * The following "-daemon-properties" are recognised:
076     * <ul>
077     * <li>args (startup argument)</li>
078     * <li>start</li>
079     * <li>start.method</li>
080     * <li>stop</li>
081     * <li>stop.method</li>
082     * <li>stop.args</li>
083     * </ul>
084     * These are used to set the corresponding item if it has not already been
085     * set by the command arguments. <b>However, note that args and stop.args are
086     * appended to any existing values.</b>
087     */
088    public void init(DaemonContext context)
089        throws Exception
090    {
091        String[] args = context.getArguments();
092
093        if (args != null) {
094            int i;
095            // Parse our arguments and remove them
096            // from the final argument array we are
097            // passing to our child.
098            for (i = 0; i < args.length; i++) {
099                if (args[i].equals("--")) {
100                    // Done with argument processing
101                    break;
102                }
103                else if (args[i].equals("-daemon-properties")) {
104                    if (++i == args.length)
105                        throw new IllegalArgumentException(args[i - 1]);
106                    configFileName = args[i];
107                }
108                else if (args[i].equals("-start")) {
109                    if (++i == args.length)
110                        throw new IllegalArgumentException(args[i - 1]);
111                    startup.setClassName(args[i]);
112                }
113                else if (args[i].equals("-start-method")) {
114                    if (++i == args.length)
115                        throw new IllegalArgumentException(args[i - 1]);
116                    startup.setMethodName(args[i]);
117                }
118                else if (args[i].equals("-stop")) {
119                    if (++i == args.length)
120                        throw new IllegalArgumentException(args[i - 1]);
121                    shutdown.setClassName(args[i]);
122                }
123                else if (args[i].equals("-stop-method")) {
124                    if (++i == args.length)
125                        throw new IllegalArgumentException(args[i - 1]);
126                    shutdown.setMethodName(args[i]);
127                }
128                else if (args[i].equals("-stop-argument")) {
129                    if (++i == args.length)
130                        throw new IllegalArgumentException(args[i - 1]);
131                    String[] aa = new String[1];
132                    aa[0] = args[i];
133                    shutdown.addArguments(aa);
134                }
135                else {
136                    // This is not our option.
137                    // Everything else will be forwarded to the main
138                    break;
139                }
140            }
141            if (args.length > i) {
142                String[] copy = new String[args.length - i];
143                System.arraycopy(args, i, copy, 0, copy.length);
144                startup.addArguments(copy);
145            }
146        }
147        if (config.load(configFileName)) {
148            // Setup params if not set via cmdline.
149            startup.setClassName(config.getProperty(START_CLASS));
150            startup.setMethodName(config.getProperty(START_METHOD));
151            // Merge the config with command line arguments
152            startup.addArguments(config.getPropertyArray(ARGS));
153
154            shutdown.setClassName(config.getProperty(STOP_CLASS));
155            shutdown.setMethodName(config.getProperty(STOP_METHOD));
156            shutdown.addArguments(config.getPropertyArray(STOP_ARGS));
157        }
158        startup.validate();
159        shutdown.validate();
160    }
161
162    /**
163     */
164    public void start()
165        throws Exception
166    {
167        startup.invoke();
168    }
169
170    /**
171     */
172    public void stop()
173        throws Exception
174    {
175        shutdown.invoke();
176    }
177
178    /**
179     */
180    public void destroy()
181    {
182        // Nothing for the moment
183        System.err.println("DaemonWrapper: instance " + this.hashCode() + " destroy");
184    }
185
186    // Internal class for wrapping the start/stop methods
187    class Invoker
188    {
189        private String      name = null;
190        private String      call = null;
191        private String[]    args = null;
192        private Method      inst = null;
193        private Class       main = null;
194
195        protected Invoker()
196        {
197        }
198
199        protected void setClassName(String name)
200        {
201            if (this.name == null)
202                this.name = name;
203        }
204        protected void setMethodName(String name)
205        {
206            if (this.call == null)
207                this.call = name;
208        }
209        protected void addArguments(String[] args)
210        {
211            if (args != null) {
212                ArrayList aa = new ArrayList();
213                if (this.args != null)
214                    aa.addAll(Arrays.asList(this.args));
215                aa.addAll(Arrays.asList(args));
216                this.args = (String[])aa.toArray(new String[aa.size()]);
217            }
218        }
219
220        protected void invoke()
221            throws Exception
222        {
223            if (name.equals("System") && call.equals("exit")) {
224                // Just call a System.exit()
225                // The start method was probably installed
226                // a shutdown hook.
227                System.exit(0);
228            }
229            else {
230                Object obj   = null;
231                if ((inst.getModifiers() & Modifier.STATIC) == 0) {
232                    // We only need object instance for non-static methods.
233                    obj = main.newInstance();
234                }
235                Object arg[] = new Object[1];
236
237                arg[0] = args;
238                inst.invoke(obj, arg);
239            }
240        }
241        // Load the class using reflection
242        protected void validate()
243            throws Exception
244        {
245            /* Check the class name */
246            if (name == null) {
247                name = "System";
248                call = "exit";
249                return;
250            }
251            if (args == null)
252                args = new String[0];
253            if (call == null)
254                call = "main";
255
256            // Get the ClassLoader loading this class
257            ClassLoader cl = DaemonWrapper.class.getClassLoader();
258            if (cl == null)
259                throw new NullPointerException("Cannot retrieve ClassLoader instance");
260            Class[] ca = new Class[1];
261            ca[0]      = args.getClass();
262            // Find the required class
263            main = cl.loadClass(name);
264            if (main == null)
265                throw new ClassNotFoundException(name);
266            // Find the required method.
267            // NoSuchMethodException will be thrown if matching method
268            // is not found.
269            inst = main.getMethod(call, ca);
270        }
271    }
272}