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