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    
020    package org.apache.commons.daemon.support;
021    
022    import java.lang.reflect.Method;
023    import java.lang.reflect.Modifier;
024    import java.util.ArrayList;
025    import java.util.Arrays;
026    import org.apache.commons.daemon.Daemon;
027    import 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     */
038    public 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    }