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 */
030public final class DaemonLoader
031{
032
033    // N.B. These static mutable variables need to be accessed using synch.
034    private static Controller controller; //@GuardedBy("this")
035    private static Object daemon; //@GuardedBy("this")
036    /* Methods to call */
037    private static Method init; //@GuardedBy("this")
038    private static Method start; //@GuardedBy("this")
039    private static Method stop; //@GuardedBy("this")
040    private static Method destroy; //@GuardedBy("this")
041    private static Method signal; //@GuardedBy("this")
042
043    public static void version()
044    {
045        System.err.println("java version \"" +
046                           System.getProperty("java.version") + "\"");
047        System.err.println(System.getProperty("java.runtime.name") +
048                           " (build " +
049                           System.getProperty("java.runtime.version") + ")");
050        System.err.println(System.getProperty("java.vm.name") +
051                           " (build " +
052                           System.getProperty("java.vm.version") +
053                           ", " + System.getProperty("java.vm.info") + ")");
054        System.err.println("commons daemon version \"" +
055                System.getProperty("commons.daemon.version") + "\"");
056        System.err.println("commons daemon process (id: " +
057                           System.getProperty("commons.daemon.process.id") +
058                           ", parent: " +
059                           System.getProperty("commons.daemon.process.parent") + ")");
060    }
061
062    public static boolean check(final String cn)
063    {
064        try {
065            /* Check the class name */
066            if (cn == null) {
067                throw new NullPointerException("Null class name specified");
068            }
069
070            /* Get the ClassLoader loading this class */
071            final ClassLoader cl = DaemonLoader.class.getClassLoader();
072            if (cl == null) {
073                System.err.println("Cannot retrieve ClassLoader instance");
074                return false;
075            }
076
077            /* Find the required class */
078            final Class<?> c = cl.loadClass(cn);
079
080            /* This should _never_ happen, but double-checking doesn't harm */
081            if (c == null) {
082                throw new ClassNotFoundException(cn);
083            }
084
085            /* Create a new instance of the daemon */
086            c.getConstructor().newInstance();
087
088        } catch (final 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);
105                return true;
106            }
107            System.out.println("Daemon doesn't support signaling");
108        } catch (final Throwable ex) {
109            System.err.println("Cannot send signal: " + ex);
110            ex.printStackTrace(System.err);
111        }
112        return false;
113    }
114
115    public static boolean load(final String className, String[] args)
116    {
117        try {
118            /* Check if the underlying library supplied a valid list of
119               arguments */
120            if (args == null) {
121                args = new String[0];
122            }
123
124            /* Check the class name */
125            if (className == null) {
126                throw new NullPointerException("Null class name specified");
127            }
128
129            /* Get the ClassLoader loading this class */
130            final ClassLoader cl = DaemonLoader.class.getClassLoader();
131            if (cl == null) {
132                System.err.println("Cannot retrieve ClassLoader instance");
133                return false;
134            }
135            final Class<?> c;
136            if (className.charAt(0) == '@') {
137                /* Wrap the class with DaemonWrapper
138                 * and modify arguments to include the real class name.
139                 */
140                c = DaemonWrapper.class;
141                final String[] a = new String[args.length + 2];
142                a[0] = "-start";
143                a[1] = className.substring(1);
144                System.arraycopy(args, 0, a, 2, args.length);
145                args = a;
146            }
147            else {
148                c = cl.loadClass(className);
149            }
150            /* This should _never_ happen, but double-checking doesn't harm */
151            if (c == null) {
152                throw new ClassNotFoundException(className);
153            }
154            /* Check interfaces */
155            boolean isdaemon = false;
156
157            try {
158                final Class<?> dclass = cl.loadClass("org.apache.commons.daemon.Daemon");
159                isdaemon = dclass.isAssignableFrom(c);
160            }
161            catch (final Exception cnfex) {
162                // Swallow if Daemon not found.
163            }
164
165            /* Check methods */
166            final Class<?>[] myclass = new Class[1];
167            if (isdaemon) {
168                myclass[0] = DaemonContext.class;
169            }
170            else {
171                myclass[0] = args.getClass();
172            }
173
174            init    = c.getMethod("init", myclass);
175
176            start   = c.getMethod("start");
177            stop    = c.getMethod("stop");
178            destroy = c.getMethod("destroy");
179
180            try {
181                signal = c.getMethod("signal");
182            } catch (final NoSuchMethodException e) {
183                // Signalling will be disabled.
184            }
185
186            /* Create a new instance of the daemon */
187            daemon = c.getConstructor().newInstance();
188
189            if (isdaemon) {
190                /* Create a new controller instance */
191                controller = new Controller();
192
193                /* Set the availability flag in the controller */
194                controller.setAvailable(false);
195
196                /* Create context */
197                final Context context = new Context();
198                context.setArguments(args);
199                context.setController(controller);
200
201                /* Now we want to call the init method in the class */
202                final Object[] arg = new Object[1];
203                arg[0] = context;
204                init.invoke(daemon, arg);
205            }
206            else {
207                final Object[] arg = new Object[1];
208                arg[0] = args;
209                init.invoke(daemon, arg);
210            }
211
212        }
213        catch (final InvocationTargetException e) {
214            final Throwable thrown = e.getTargetException();
215            /* DaemonInitExceptions can fail with a nicer message */
216            if (thrown instanceof DaemonInitException) {
217                failed(((DaemonInitException) thrown).getMessageWithCause());
218            }
219            else {
220                thrown.printStackTrace(System.err);
221            }
222            return false;
223        }
224        catch (final Throwable t) {
225            /* In case we encounter ANY error, we dump the stack trace and
226             * return false (load, start and stop won't be called).
227             */
228            t.printStackTrace(System.err);
229            return false;
230        }
231        /* The class was loaded and instantiated correctly, we can return */
232        return true;
233    }
234
235    public static boolean start()
236    {
237        try {
238            /* Attempt to start the daemon */
239            start.invoke(daemon);
240
241            /* Set the availability flag in the controller */
242            if (controller != null) {
243                controller.setAvailable(true);
244            }
245
246        } catch (final Throwable t) {
247            /* In case we encounter ANY error, we dump the stack trace and
248             * return false (load, start and stop won't be called).
249             */
250            t.printStackTrace(System.err);
251            return false;
252        }
253        return true;
254    }
255
256    public static boolean stop()
257    {
258        try {
259            /* Set the availability flag in the controller */
260            if (controller != null) {
261                controller.setAvailable(false);
262            }
263
264            /* Attempt to stop the daemon */
265            stop.invoke(daemon);
266        }
267        catch (final Throwable t) {
268            /* In case we encounter ANY error, we dump the stack trace and
269             * return false (load, start and stop won't be called).
270             */
271            t.printStackTrace(System.err);
272            return false;
273        }
274        return true;
275    }
276
277    public static boolean destroy()
278    {
279        try {
280            /* Attempt to stop the daemon */
281            destroy.invoke(daemon);
282
283            daemon = null;
284            controller = null;
285        } catch (final Throwable t) {
286            /* In case we encounter ANY error, we dump the stack trace and
287             * return false (load, start and stop won't be called).
288             */
289            t.printStackTrace(System.err);
290            return false;
291        }
292        return true;
293    }
294
295    private static native void shutdown(boolean reload);
296    private static native void failed(String message);
297
298    public static class Controller
299        implements DaemonController
300    {
301
302        private boolean available;
303
304        private Controller()
305        {
306            this.setAvailable(false);
307        }
308
309        private boolean isAvailable()
310        {
311            synchronized (this) {
312                return this.available;
313            }
314        }
315
316        private void setAvailable(final boolean available)
317        {
318            synchronized (this) {
319                this.available = available;
320            }
321        }
322
323        @Override
324        public void shutdown()
325            throws IllegalStateException
326        {
327            synchronized (this) {
328                if (!this.isAvailable()) {
329                    throw new IllegalStateException();
330                }
331                this.setAvailable(false);
332                DaemonLoader.shutdown(false);
333            }
334        }
335
336        @Override
337        public void reload()
338            throws IllegalStateException
339        {
340            synchronized (this) {
341                if (!this.isAvailable()) {
342                    throw new IllegalStateException();
343                }
344                this.setAvailable(false);
345                DaemonLoader.shutdown(true);
346            }
347        }
348
349        @Override
350        public void fail()
351        {
352            fail(null, null);
353        }
354
355        @Override
356        public void fail(final String message)
357        {
358            fail(message, null);
359        }
360
361        @Override
362        public void fail(final Exception exception)
363        {
364            fail(null, exception);
365        }
366
367        @Override
368        public void fail(final String message, final Exception exception)
369        {
370            synchronized (this) {
371                this.setAvailable(false);
372                String msg = message;
373                if (exception != null) {
374                    if (msg != null) {
375                        msg = msg + ": " + exception.toString();
376                    }
377                    else {
378                        msg = exception.toString();
379                    }
380                }
381                DaemonLoader.failed(msg);
382            }
383        }
384
385    }
386
387    public static class Context
388        implements DaemonContext
389    {
390
391        private DaemonController daemonController;
392
393        private String[] args;
394
395        @Override
396        public DaemonController getController()
397        {
398            return daemonController;
399        }
400
401        public void setController(final DaemonController controller)
402        {
403            this.daemonController = controller;
404        }
405
406        @Override
407        public String[] getArguments()
408        {
409            return args;
410        }
411
412        public void setArguments(final String[]args)
413        {
414            this.args = args;
415        }
416
417    }
418}