View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.daemon.support;
19  
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Objects;
25  
26  import org.apache.commons.daemon.Daemon;
27  import org.apache.commons.daemon.DaemonContext;
28  
29  /**
30   * Implementation of the Daemon that allows running
31   * standard applications as daemons.
32   * The applications must have the mechanism to manage
33   * the application lifecycle.
34   */
35  public class DaemonWrapper implements Daemon
36  {
37  
38      private final static String ARGS            = "args";
39      private final static String START_CLASS     = "start";
40      private final static String START_METHOD    = "start.method";
41      private final static String STOP_CLASS      = "stop";
42      private final static String STOP_METHOD     = "stop.method";
43      private final static String STOP_ARGS       = "stop.args";
44      private String              configFileName;
45      private final DaemonConfiguration config;
46  
47      private final Invoker             startup;
48      private final Invoker             shutdown;
49  
50      /**
51       * Constructs a new initialized instance.
52       */
53      public DaemonWrapper()
54      {
55          config   = new DaemonConfiguration();
56          startup  = new Invoker();
57          shutdown = new Invoker();
58      }
59  
60      /**
61       * Called from DaemonLoader on init stage.
62       * <p>
63       * Accepts the following configuration arguments:
64       * <ul>
65       * <li>-daemon-properties: - load DaemonConfiguration properties from the specified file to act as defaults</li>
66       * <li>-start: set start class name</li>
67       * <li>-start-method: set start method name</li>
68       * <li>-stop: set stop class name</li>
69       * <li>-stop-method: set stop method name</li>
70       * <li>-stop-argument: set optional argument to stop method</li>
71       * <li>Anything else is treated as a startup argument</li>
72       * </ul>
73       * <p>
74       * The following "-daemon-properties" are recognized:
75       * <ul>
76       * <li>args (startup argument)</li>
77       * <li>start</li>
78       * <li>start.method</li>
79       * <li>stop</li>
80       * <li>stop.method</li>
81       * <li>stop.args</li>
82       * </ul>
83       * These are used to set the corresponding item if it has not already been
84       * set by the command arguments. <b>However, note that args and stop.args are
85       * appended to any existing values.</b>
86       */
87      @Override
88      public void init(final DaemonContext context)
89          throws Exception
90      {
91          final String[] args = context.getArguments();
92  
93          if (args != null) {
94              int i;
95              // Parse our arguments and remove them
96              // from the final argument array we are
97              // passing to our child.
98              arguments:
99              for (i = 0; i < args.length; i++) {
100                 if (args[i].equals("--")) {
101                     // Done with argument processing
102                     break;
103                 }
104                 switch (args[i]) {
105                     case "-daemon-properties":
106                         if (++i == args.length) {
107                             throw new IllegalArgumentException(args[i - 1]);
108                         }
109                         configFileName = args[i];
110                         break;
111                     case "-start":
112                         if (++i == args.length) {
113                             throw new IllegalArgumentException(args[i - 1]);
114                         }
115                         startup.setClassName(args[i]);
116                         break;
117                     case "-start-method":
118                         if (++i == args.length) {
119                             throw new IllegalArgumentException(args[i - 1]);
120                         }
121                         startup.setMethodName(args[i]);
122                         break;
123                     case "-stop":
124                         if (++i == args.length) {
125                             throw new IllegalArgumentException(args[i - 1]);
126                         }
127                         shutdown.setClassName(args[i]);
128                         break;
129                     case "-stop-method":
130                         if (++i == args.length) {
131                             throw new IllegalArgumentException(args[i - 1]);
132                         }
133                         shutdown.setMethodName(args[i]);
134                         break;
135                     case "-stop-argument":
136                         if (++i == args.length) {
137                             throw new IllegalArgumentException(args[i - 1]);
138                         }
139                         final String[] aa = new String[1];
140                         aa[0] = args[i];
141                         shutdown.addArguments(aa);
142                         break;
143                     default:
144                         // This is not our option.
145                         // Everything else will be forwarded to the main
146                         break arguments;
147                 }
148             }
149             if (args.length > i) {
150                 final String[] copy = new String[args.length - i];
151                 System.arraycopy(args, i, copy, 0, copy.length);
152                 startup.addArguments(copy);
153             }
154         }
155         if (config.load(configFileName)) {
156             // Setup params if not set via cmdline.
157             startup.setClassName(config.getProperty(START_CLASS));
158             startup.setMethodName(config.getProperty(START_METHOD));
159             // Merge the config with command line arguments
160             startup.addArguments(config.getPropertyArray(ARGS));
161 
162             shutdown.setClassName(config.getProperty(STOP_CLASS));
163             shutdown.setMethodName(config.getProperty(STOP_METHOD));
164             shutdown.addArguments(config.getPropertyArray(STOP_ARGS));
165         }
166         startup.validate();
167         shutdown.validate();
168     }
169 
170     /**
171      */
172     @Override
173     public void start()
174         throws Exception
175     {
176         startup.invoke();
177     }
178 
179     /**
180      */
181     @Override
182     public void stop()
183         throws Exception
184     {
185         shutdown.invoke();
186     }
187 
188     /**
189      */
190     @Override
191     public void destroy()
192     {
193         // Nothing for the moment
194         System.err.println("DaemonWrapper: instance " + hashCode() + " destroy");
195     }
196 
197     // Internal class for wrapping the start/stop methods
198     static class Invoker
199     {
200         private String      name;
201         private String      call;
202         private String[]    args;
203         private Method      inst;
204         private Class<?>    main;
205 
206         protected Invoker()
207         {
208         }
209 
210         protected void setClassName(final String name)
211         {
212             if (this.name == null) {
213                 this.name = name;
214             }
215         }
216         protected void setMethodName(final String name)
217         {
218             if (this.call == null) {
219                 this.call = name;
220             }
221         }
222         protected void addArguments(final String[] args)
223         {
224             if (args != null) {
225                 final ArrayList<String> aa = new ArrayList<>();
226                 if (this.args != null) {
227                     aa.addAll(Arrays.asList(this.args));
228                 }
229                 aa.addAll(Arrays.asList(args));
230                 this.args = aa.toArray(DaemonConfiguration.EMPTY_STRING_ARRAY);
231             }
232         }
233 
234         protected void invoke()
235             throws Exception
236         {
237             if (name.equals("System") && call.equals("exit")) {
238                 // Just call a System.exit()
239                 // The start method was probably installed
240                 // a shutdown hook.
241                 System.exit(0);
242             }
243             else {
244                 Object obj   = null;
245                 if ((inst.getModifiers() & Modifier.STATIC) == 0) {
246                     // We only need object instance for non-static methods.
247                     obj = main.getConstructor().newInstance();
248                 }
249                 final Object[] arg = new Object[1];
250 
251                 arg[0] = args;
252                 inst.invoke(obj, arg);
253             }
254         }
255         // Load the class using reflection
256         protected void validate()
257             throws Exception
258         {
259             /* Check the class name */
260             if (name == null) {
261                 name = "System";
262                 call = "exit";
263                 return;
264             }
265             if (args == null) {
266                 args = new String[0];
267             }
268             if (call == null) {
269                 call = "main";
270             }
271 
272             // Get the ClassLoader loading this class
273             final ClassLoader classLoader = DaemonWrapper.class.getClassLoader();
274             Objects.requireNonNull(classLoader, "classLoader");
275             final Class<?>[] ca = new Class[1];
276             ca[0] = args.getClass();
277             // Find the required class
278             main = classLoader.loadClass(name);
279             if (main == null) {
280                 throw new ClassNotFoundException(name);
281             }
282             // Find the required method.
283             // NoSuchMethodException will be thrown if matching method
284             // is not found.
285             inst = main.getMethod(call, ca);
286         }
287     }
288 }