001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017
018package org.apache.commons.daemon.support;
019
020import org.apache.commons.daemon.DaemonContext;
021import org.apache.commons.daemon.DaemonController;
022import org.apache.commons.daemon.DaemonInitException;
023
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Method;
026
027/**
028 * Used by jsvc for Daemon management.
029 *
030 * @version $Id: DaemonLoader.java 1204006 2011-11-19 16:09:15Z ggregory $
031 */
032public final class DaemonLoader
033{
034
035    // N.B. These static mutable variables need to be accessed using synch.
036    private static Controller controller = null; //@GuardedBy("this")
037    private static Object daemon    = null; //@GuardedBy("this")
038    /* Methods to call */
039    private static Method init      = null; //@GuardedBy("this")
040    private static Method start     = null; //@GuardedBy("this")
041    private static Method stop      = null; //@GuardedBy("this")
042    private static Method destroy   = null; //@GuardedBy("this")
043    private static Method signal    = null; //@GuardedBy("this")
044
045    public static void version()
046    {
047        System.err.println("java version \"" +
048                           System.getProperty("java.version") + "\"");
049        System.err.println(System.getProperty("java.runtime.name") +
050                           " (build " +
051                           System.getProperty("java.runtime.version") + ")");
052        System.err.println(System.getProperty("java.vm.name") +
053                           " (build " +
054                           System.getProperty("java.vm.version") +
055                           ", " + System.getProperty("java.vm.info") + ")");
056        System.err.println("commons daemon version \"" +
057                System.getProperty("commons.daemon.version") + "\"");
058        System.err.println("commons daemon process (id: " +
059                           System.getProperty("commons.daemon.process.id") +
060                           ", parent: " +
061                           System.getProperty("commons.daemon.process.parent") + ")");
062    }
063
064    public static boolean check(String cn)
065    {
066        try {
067            /* Check the class name */
068            if (cn == null)
069                throw new NullPointerException("Null class name specified");
070
071            /* Get the ClassLoader loading this class */
072            ClassLoader cl = DaemonLoader.class.getClassLoader();
073            if (cl == null) {
074                System.err.println("Cannot retrieve ClassLoader instance");
075                return false;
076            }
077
078            /* Find the required class */
079            Class c = cl.loadClass(cn);
080
081            /* This should _never_ happen, but doublechecking doesn't harm */
082            if (c == null)
083                throw new ClassNotFoundException(cn);
084
085            /* Create a new instance of the daemon */
086            c.newInstance();
087
088        } catch (Throwable t) {
089            /* In case we encounter ANY error, we dump the stack trace and
090             * return false (load, start and stop won't be called).
091             */
092            t.printStackTrace(System.err);
093            return false;
094        }
095        /* The class was loaded and instantiated correctly, we can return
096         */
097        return true;
098    }
099
100    public static boolean signal()
101    {
102        try {
103            if (signal != null) {
104                signal.invoke(daemon, new Object[0]);
105                return true;
106            }
107            else {
108                System.out.println("Daemon doesn't support signaling");
109            }
110        } catch (Throwable ex) {
111            System.err.println("Cannot send signal: " + ex);
112            ex.printStackTrace(System.err);
113        }
114        return false;
115    }
116
117    public static boolean load(String className, String args[])
118    {
119        try {
120            /* Make sure any previous instance is garbage collected */
121            System.gc();
122
123            /* Check if the underlying libray supplied a valid list of
124               arguments */
125            if (args == null)
126                args = new String[0];
127
128            /* Check the class name */
129            if (className == null)
130                throw new NullPointerException("Null class name specified");
131
132            /* Get the ClassLoader loading this class */
133            ClassLoader cl = DaemonLoader.class.getClassLoader();
134            if (cl == null) {
135                System.err.println("Cannot retrieve ClassLoader instance");
136                return false;
137            }
138            Class c;
139            if (className.charAt(0) == '@') {
140                /* Wrapp the class with DaemonWrapper
141                 * and modify arguments to include the real class name.
142                 */
143                c = DaemonWrapper.class;
144                String[] a = new String[args.length + 2];
145                a[0] = "-start";
146                a[1] = className.substring(1);
147                System.arraycopy(args, 0, a, 2, args.length);
148                args = a;
149            }
150            else
151                c = cl.loadClass(className);
152            /* This should _never_ happen, but doublechecking doesn't harm */
153            if (c == null)
154                throw new ClassNotFoundException(className);
155            /* Check interfaces */
156            boolean isdaemon = false;
157
158            try {
159                Class dclass =
160                    cl.loadClass("org.apache.commons.daemon.Daemon");
161                isdaemon = dclass.isAssignableFrom(c);
162            }
163            catch (Exception cnfex) {
164                // Swallow if Daemon not found.
165            }
166
167            /* Check methods */
168            Class[] myclass = new Class[1];
169            if (isdaemon) {
170                myclass[0] = DaemonContext.class;
171            }
172            else {
173                myclass[0] = args.getClass();
174            }
175
176            init    = c.getMethod("init", myclass);
177
178            myclass = null;
179            start   = c.getMethod("start", myclass);
180            stop    = c.getMethod("stop", myclass);
181            destroy = c.getMethod("destroy", myclass);
182
183            try {
184                signal = c.getMethod("signal", myclass);
185            } catch (NoSuchMethodException e) {
186                // Signaling will be disabled.
187            }
188
189            /* Create a new instance of the daemon */
190            daemon = c.newInstance();
191
192            if (isdaemon) {
193                /* Create a new controller instance */
194                controller = new Controller();
195
196                /* Set the availability flag in the controller */
197                controller.setAvailable(false);
198
199                /* Create context */
200                Context context = new Context();
201                context.setArguments(args);
202                context.setController(controller);
203
204                /* Now we want to call the init method in the class */
205                Object arg[] = new Object[1];
206                arg[0] = context;
207                init.invoke(daemon, arg);
208            }
209            else {
210                Object arg[] = new Object[1];
211                arg[0] = args;
212                init.invoke(daemon, arg);
213            }
214
215        }
216        catch (InvocationTargetException e) {
217            Throwable thrown = e.getTargetException();
218            /* DaemonInitExceptions can fail with a nicer message */
219            if (thrown instanceof DaemonInitException) {
220                failed(((DaemonInitException) thrown).getMessageWithCause());
221            }
222            else {
223                thrown.printStackTrace(System.err);
224            }
225            return false;
226        }
227        catch (Throwable t) {
228            /* In case we encounter ANY error, we dump the stack trace and
229             * return false (load, start and stop won't be called).
230             */
231            t.printStackTrace(System.err);
232            return false;
233        }
234        /* The class was loaded and instantiated correctly, we can return */
235        return true;
236    }
237
238    public static boolean start()
239    {
240        try {
241            /* Attempt to start the daemon */
242            Object arg[] = null;
243            start.invoke(daemon, arg);
244
245            /* Set the availability flag in the controller */
246            if (controller != null)
247                controller.setAvailable(true);
248
249        } catch (Throwable t) {
250            /* In case we encounter ANY error, we dump the stack trace and
251             * return false (load, start and stop won't be called).
252             */
253            t.printStackTrace(System.err);
254            return false;
255        }
256        return true;
257    }
258
259    public static boolean stop()
260    {
261        try {
262            /* Set the availability flag in the controller */
263            if (controller != null)
264                controller.setAvailable(false);
265
266            /* Attempt to stop the daemon */
267            Object arg[] = null;
268            stop.invoke(daemon, arg);
269
270            /* Run garbage collector */
271            System.gc();
272
273        }
274        catch (Throwable t) {
275            /* In case we encounter ANY error, we dump the stack trace and
276             * return false (load, start and stop won't be called).
277             */
278            t.printStackTrace(System.err);
279            return false;
280        }
281        return true;
282    }
283
284    public static boolean destroy()
285    {
286        try {
287            /* Attempt to stop the daemon */
288            Object arg[] = null;
289            destroy.invoke(daemon, arg);
290
291            /* Run garbage collector */
292            daemon = null;
293            controller = null;
294            System.gc();
295
296        } catch (Throwable t) {
297            /* In case we encounter ANY error, we dump the stack trace and
298             * return false (load, start and stop won't be called).
299             */
300            t.printStackTrace(System.err);
301            return false;
302        }
303        return true;
304    }
305
306    private static native void shutdown(boolean reload);
307    private static native void failed(String message);
308
309    public static class Controller
310        implements DaemonController
311    {
312
313        private boolean available = false;
314
315        private Controller()
316        {
317            super();
318            this.setAvailable(false);
319        }
320
321        private boolean isAvailable()
322        {
323            synchronized (this) {
324                return this.available;
325            }
326        }
327
328        private void setAvailable(boolean available)
329        {
330            synchronized (this) {
331                this.available = available;
332            }
333        }
334
335        public void shutdown()
336            throws IllegalStateException
337        {
338            synchronized (this) {
339                if (!this.isAvailable()) {
340                    throw new IllegalStateException();
341                }
342                else {
343                    this.setAvailable(false);
344                    DaemonLoader.shutdown(false);
345                }
346            }
347        }
348
349        public void reload()
350            throws IllegalStateException
351        {
352            synchronized (this) {
353                if (!this.isAvailable()) {
354                    throw new IllegalStateException();
355                }
356                else {
357                    this.setAvailable(false);
358                    DaemonLoader.shutdown(true);
359                }
360            }
361        }
362
363        public void fail()
364        {
365            fail(null, null);
366        }
367
368        public void fail(String message)
369        {
370            fail(message, null);
371        }
372
373        public void fail(Exception exception)
374        {
375            fail(null, exception);
376        }
377
378        public void fail(String message, Exception exception)
379        {
380            synchronized (this) {
381                this.setAvailable(false);
382                String msg = message;
383                if (exception != null) {
384                    if (msg != null)
385                        msg = msg + ": " + exception.toString();
386                    else
387                        msg = exception.toString();
388                }
389                DaemonLoader.failed(msg);
390            }
391        }
392
393    }
394
395    public static class Context
396        implements DaemonContext
397    {
398
399        private DaemonController daemonController = null;
400
401        private String[] args = null;
402
403        public DaemonController getController()
404        {
405            return daemonController;
406        }
407
408        public void setController(DaemonController controller)
409        {
410            this.daemonController = controller;
411        }
412
413        public String[] getArguments()
414        {
415            return args;
416        }
417
418        public void setArguments(String[]args)
419        {
420            this.args = args;
421        }
422
423    }
424}
425