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