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