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