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    *      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 }