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