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      public DaemonWrapper()
51      {
52          config   = new DaemonConfiguration();
53          startup  = new Invoker();
54          shutdown = new Invoker();
55      }
56  
57      /**
58       * Called from DaemonLoader on init stage.
59       * <p>
60       * Accepts the following configuration arguments:
61       * <ul>
62       * <li>-daemon-properties: - load DaemonConfiguration properties from the specified file to act as defaults</li>
63       * <li>-start: set start class name</li>
64       * <li>-start-method: set start method name</li>
65       * <li>-stop: set stop class name</li>
66       * <li>-stop-method: set stop method name</li>
67       * <li>-stop-argument: set optional argument to stop method</li>
68       * <li>Anything else is treated as a startup argument</li>
69       * </ul>
70       * <p>
71       * The following "-daemon-properties" are recognized:
72       * <ul>
73       * <li>args (startup argument)</li>
74       * <li>start</li>
75       * <li>start.method</li>
76       * <li>stop</li>
77       * <li>stop.method</li>
78       * <li>stop.args</li>
79       * </ul>
80       * These are used to set the corresponding item if it has not already been
81       * set by the command arguments. <b>However, note that args and stop.args are
82       * appended to any existing values.</b>
83       */
84      @Override
85      public void init(final DaemonContext context)
86          throws Exception
87      {
88          final String[] args = context.getArguments();
89  
90          if (args != null) {
91              int i;
92              // Parse our arguments and remove them
93              // from the final argument array we are
94              // passing to our child.
95              arguments:
96              for (i = 0; i < args.length; i++) {
97                  if (args[i].equals("--")) {
98                      // Done with argument processing
99                      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 }