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 * https://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 static final String ARGS = "args";
39 private static final String START_CLASS = "start";
40 private static final String START_METHOD = "start.method";
41 private static final String STOP_CLASS = "stop";
42 private static final String STOP_METHOD = "stop.method";
43 private static final 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 }