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;
026import java.util.Objects;
027
028/**
029 * Used by jsvc for Daemon management.
030 */
031public final class DaemonLoader
032{
033
034    // N.B. These static mutable variables need to be accessed using synch.
035    private static Controller controller; //@GuardedBy("this")
036    private static Object daemon; //@GuardedBy("this")
037    /* Methods to call */
038    private static Method init; //@GuardedBy("this")
039    private static Method start; //@GuardedBy("this")
040    private static Method stop; //@GuardedBy("this")
041    private static Method destroy; //@GuardedBy("this")
042    private static Method signal; //@GuardedBy("this")
043
044    /**
045     * Prints version information to {@link System#err}.
046     */
047    public static void version()
048    {
049        System.err.println("java version \"" +
050                           System.getProperty("java.version") + "\"");
051        System.err.println(System.getProperty("java.runtime.name") +
052                           " (build " +
053                           System.getProperty("java.runtime.version") + ")");
054        System.err.println(System.getProperty("java.vm.name") +
055                           " (build " +
056                           System.getProperty("java.vm.version") +
057                           ", " + System.getProperty("java.vm.info") + ")");
058        System.err.println("commons daemon version \"" +
059                System.getProperty("commons.daemon.version") + "\"");
060        System.err.println("commons daemon process (id: " +
061                           System.getProperty("commons.daemon.process.id") +
062                           ", parent: " +
063                           System.getProperty("commons.daemon.process.parent") + ")");
064    }
065
066    /**
067     * Checks whether the given class name can be instantiated with a zero-argument constructor.
068     *
069     * @param className The class name.
070     * @return true if the given class name can be instantiated, false otherwise.
071     */
072    public static boolean check(final String className)
073    {
074        try {
075            /* Check the class name */
076            Objects.requireNonNull(className, "className");
077            /* Gets the ClassLoader loading this class */
078            final ClassLoader cl = DaemonLoader.class.getClassLoader();
079            if (cl == null) {
080                System.err.println("Cannot retrieve ClassLoader instance");
081                return false;
082            }
083
084            /* Find the required class */
085            final Class<?> c = cl.loadClass(className);
086
087            /* This should _never_ happen, but double-checking doesn't harm */
088            if (c == null) {
089                throw new ClassNotFoundException(className);
090            }
091
092            /* Create a new instance of the daemon */
093            c.getConstructor().newInstance();
094
095        } catch (final Throwable t) {
096            /* In case we encounter ANY error, we dump the stack trace and
097             * return false (load, start and stop won't be called).
098             */
099            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}