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.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.util.Objects;
23
24 import org.apache.commons.daemon.DaemonContext;
25 import org.apache.commons.daemon.DaemonController;
26 import org.apache.commons.daemon.DaemonInitException;
27
28 /**
29 * Used by jsvc for Daemon management.
30 */
31 public final class DaemonLoader
32 {
33
34 // These static mutable variables need to be accessed using synch.
35 private static Controller controller; //@GuardedBy("this")
36 private static Object daemon; //@GuardedBy("this")
37 /* Methods to call */
38 private static Method init; //@GuardedBy("this")
39 private static Method start; //@GuardedBy("this")
40 private static Method stop; //@GuardedBy("this")
41 private static Method destroy; //@GuardedBy("this")
42 private static Method signal; //@GuardedBy("this")
43
44 /**
45 * Constructs a new instance.
46 */
47 public DaemonLoader() {
48 // empty
49 }
50
51 /**
52 * Prints version information to {@link System#err}.
53 */
54 public static void version()
55 {
56 System.err.println("java version \"" +
57 System.getProperty("java.version") + "\"");
58 System.err.println(System.getProperty("java.runtime.name") +
59 " (build " +
60 System.getProperty("java.runtime.version") + ")");
61 System.err.println(System.getProperty("java.vm.name") +
62 " (build " +
63 System.getProperty("java.vm.version") +
64 ", " + System.getProperty("java.vm.info") + ")");
65 System.err.println("commons daemon version \"" +
66 System.getProperty("commons.daemon.version") + "\"");
67 System.err.println("commons daemon process (id: " +
68 System.getProperty("commons.daemon.process.id") +
69 ", parent: " +
70 System.getProperty("commons.daemon.process.parent") + ")");
71 }
72
73 /**
74 * Checks whether the given class name can be instantiated with a zero-argument constructor.
75 *
76 * @param className The class name.
77 * @return true if the given class name can be instantiated, false otherwise.
78 */
79 public static boolean check(final String className)
80 {
81 try {
82 /* Check the class name */
83 Objects.requireNonNull(className, "className");
84 /* Gets the ClassLoader loading this class */
85 final ClassLoader cl = DaemonLoader.class.getClassLoader();
86 if (cl == null) {
87 System.err.println("Cannot retrieve ClassLoader instance");
88 return false;
89 }
90
91 /* Find the required class */
92 final Class<?> c = cl.loadClass(className);
93
94 /* This should _never_ happen, but double-checking doesn't harm */
95 if (c == null) {
96 throw new ClassNotFoundException(className);
97 }
98
99 /* Create a new instance of the daemon */
100 c.getConstructor().newInstance();
101
102 } catch (final Throwable t) {
103 /* In case we encounter ANY error, we dump the stack trace and
104 * return false (load, start and stop won't be called).
105 */
106 t.printStackTrace(System.err);
107 return false;
108 }
109 /* The class was loaded and instantiated correctly, we can return
110 */
111 return true;
112 }
113
114 /**
115 * Invokes the wrapped {@code signal} method.
116 *
117 * @return whether the call succeeded.
118 */
119 public static boolean signal()
120 {
121 try {
122 if (signal != null) {
123 signal.invoke(daemon);
124 return true;
125 }
126 System.out.println("Daemon doesn't support signaling");
127 } catch (final Throwable ex) {
128 System.err.println("Cannot send signal: " + ex);
129 ex.printStackTrace(System.err);
130 }
131 return false;
132 }
133
134 /**
135 * Loads the given class by name, initializing wrapper methods.
136 *
137 * @param className The class name to load.
138 * @param args arguments for the context.
139 * @return whether the operation succeeded.
140 */
141 public static boolean load(final String className, String[] args)
142 {
143 try {
144 /* Check if the underlying library supplied a valid list of
145 arguments */
146 if (args == null) {
147 args = new String[0];
148 }
149
150 /* Check the class name */
151 Objects.requireNonNull(className, "className");
152
153 /* Gets the ClassLoader loading this class */
154 final ClassLoader cl = DaemonLoader.class.getClassLoader();
155 if (cl == null) {
156 System.err.println("Cannot retrieve ClassLoader instance");
157 return false;
158 }
159 final Class<?> c;
160 if (className.charAt(0) == '@') {
161 /* Wrap the class with DaemonWrapper
162 * and modify arguments to include the real class name.
163 */
164 c = DaemonWrapper.class;
165 final String[] a = new String[args.length + 2];
166 a[0] = "-start";
167 a[1] = className.substring(1);
168 System.arraycopy(args, 0, a, 2, args.length);
169 args = a;
170 }
171 else {
172 c = cl.loadClass(className);
173 }
174 /* This should _never_ happen, but double-checking doesn't harm */
175 if (c == null) {
176 throw new ClassNotFoundException(className);
177 }
178 /* Check interfaces */
179 boolean isdaemon = false;
180
181 try {
182 final Class<?> dclass = cl.loadClass("org.apache.commons.daemon.Daemon");
183 isdaemon = dclass.isAssignableFrom(c);
184 }
185 catch (final Exception ignored) {
186 // Swallow if Daemon not found.
187 }
188
189 /* Check methods */
190 final Class<?>[] myclass = new Class[1];
191 if (isdaemon) {
192 myclass[0] = DaemonContext.class;
193 }
194 else {
195 myclass[0] = args.getClass();
196 }
197
198 init = c.getMethod("init", myclass);
199 start = c.getMethod("start");
200 stop = c.getMethod("stop");
201 destroy = c.getMethod("destroy");
202
203 try {
204 signal = c.getMethod("signal");
205 } catch (final NoSuchMethodException ignored) {
206 // Signaling will be disabled.
207 }
208
209 /* Create a new instance of the daemon */
210 daemon = c.getConstructor().newInstance();
211
212 if (isdaemon) {
213 // Create a new controller instance
214 controller = new Controller();
215
216 // Set the availability flag in the controller
217 controller.setAvailable(false);
218
219 /* Create context */
220 final Context context = new Context();
221 context.setArguments(args);
222 context.setController(controller);
223
224 // Now we want to call the init method in the class
225 final Object[] arg = new Object[1];
226 arg[0] = context;
227 init.invoke(daemon, arg);
228 }
229 else {
230 final Object[] arg = new Object[1];
231 arg[0] = args;
232 init.invoke(daemon, arg);
233 }
234
235 }
236 catch (final InvocationTargetException e) {
237 final Throwable thrown = e.getTargetException();
238 // DaemonInitExceptions can fail with a nicer message
239 if (thrown instanceof DaemonInitException) {
240 failed(((DaemonInitException) thrown).getMessageWithCause());
241 }
242 else {
243 thrown.printStackTrace(System.err);
244 }
245 return false;
246 }
247 catch (final Throwable t) {
248 // In case we encounter ANY error, we dump the stack trace and
249 // return false (load, start and stop won't be called).
250 t.printStackTrace(System.err);
251 return false;
252 }
253 // The class was loaded and instantiated correctly, we can return
254 return true;
255 }
256
257 /**
258 * Invokes the wrapped {@code start} method.
259 *
260 * @return whether the call succeeded.
261 */
262 public static boolean start()
263 {
264 try {
265 // Attempt to start the daemon
266 start.invoke(daemon);
267 // Set the availability flag in the controller
268 if (controller != null) {
269 controller.setAvailable(true);
270 }
271 } catch (final Throwable t) {
272 // In case we encounter ANY error, we dump the stack trace and
273 // return false (load, start and stop won't be called).
274 t.printStackTrace(System.err);
275 return false;
276 }
277 return true;
278 }
279
280 /**
281 * Invokes the wrapped {@code stop} method.
282 *
283 * @return whether the call succeeded.
284 */
285 public static boolean stop()
286 {
287 try {
288 // Set the availability flag in the controller
289 if (controller != null) {
290 controller.setAvailable(false);
291 }
292 /* Attempt to stop the daemon */
293 stop.invoke(daemon);
294 }
295 catch (final Throwable t) {
296 // In case we encounter ANY error, we dump the stack trace and
297 // return false (load, start and stop won't be called).
298 t.printStackTrace(System.err);
299 return false;
300 }
301 return true;
302 }
303
304 /**
305 * Invokes the wrapped {@code destroy} method.
306 *
307 * @return whether the call succeeded.
308 */
309 public static boolean destroy()
310 {
311 try {
312 /* Attempt to stop the daemon */
313 destroy.invoke(daemon);
314 daemon = null;
315 controller = null;
316 } catch (final Throwable t) {
317 // In case we encounter ANY error, we dump the stack trace and
318 // return false (load, start and stop won't be called).
319 t.printStackTrace(System.err);
320 return false;
321 }
322 return true;
323 }
324
325 private static native void shutdown(boolean reload);
326 private static native void failed(String message);
327
328 /**
329 * A DaemonController that acts on the the global {@link DaemonLoader} state.
330 */
331 public static class Controller
332 implements DaemonController
333 {
334
335 private boolean available;
336
337 private Controller()
338 {
339 setAvailable(false);
340 }
341
342 private boolean isAvailable()
343 {
344 synchronized (this) {
345 return this.available;
346 }
347 }
348
349 private void setAvailable(final boolean available)
350 {
351 synchronized (this) {
352 this.available = available;
353 }
354 }
355
356 @Override
357 public void shutdown()
358 throws IllegalStateException
359 {
360 synchronized (this) {
361 if (!isAvailable()) {
362 throw new IllegalStateException();
363 }
364 setAvailable(false);
365 DaemonLoader.shutdown(false);
366 }
367 }
368
369 @Override
370 public void reload()
371 throws IllegalStateException
372 {
373 synchronized (this) {
374 if (!isAvailable()) {
375 throw new IllegalStateException();
376 }
377 setAvailable(false);
378 DaemonLoader.shutdown(true);
379 }
380 }
381
382 @Override
383 public void fail()
384 {
385 fail(null, null);
386 }
387
388 @Override
389 public void fail(final String message)
390 {
391 fail(message, null);
392 }
393
394 @Override
395 public void fail(final Exception exception)
396 {
397 fail(null, exception);
398 }
399
400 @Override
401 public void fail(final String message, final Exception exception)
402 {
403 synchronized (this) {
404 setAvailable(false);
405 String msg = message;
406 if (exception != null) {
407 if (msg != null) {
408 msg = msg + ": " + exception.toString();
409 }
410 else {
411 msg = exception.toString();
412 }
413 }
414 failed(msg);
415 }
416 }
417
418 }
419
420 /**
421 * A concrete {@link DaemonContext} that acts as a simple value container.
422 */
423 public static class Context implements DaemonContext
424 {
425
426 private DaemonController daemonController;
427
428 private String[] args;
429
430 /**
431 * Constructs a new instance.
432 */
433 public Context() {
434 // empty
435 }
436
437 @Override
438 public DaemonController getController()
439 {
440 return daemonController;
441 }
442
443 /**
444 * Sets the daemon controller.
445 *
446 * @param controller the daemon controller.
447 */
448 public void setController(final DaemonController controller)
449 {
450 this.daemonController = controller;
451 }
452
453 @Override
454 public String[] getArguments()
455 {
456 return args;
457 }
458
459 /**
460 * Sets arguments. Note that this implementation doesn't currently make a defensive copy.
461 *
462 * @param args arguments.
463 */
464 public void setArguments(final String[] args)
465 {
466 this.args = args;
467 }
468
469 }
470 }