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 *      https://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 java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.util.Objects;
023
024import org.apache.commons.daemon.DaemonContext;
025import org.apache.commons.daemon.DaemonController;
026import org.apache.commons.daemon.DaemonInitException;
027
028/**
029 * Used by jsvc for Daemon management.
030 */
031public final class DaemonLoader
032{
033
034    // 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     * Constructs a new instance.
046     */
047    public DaemonLoader() {
048        // empty
049    }
050    
051    /**
052     * Prints version information to {@link System#err}.
053     */
054    public static void version()
055    {
056        System.err.println("java version \"" +
057                           System.getProperty("java.version") + "\"");
058        System.err.println(System.getProperty("java.runtime.name") +
059                           " (build " +
060                           System.getProperty("java.runtime.version") + ")");
061        System.err.println(System.getProperty("java.vm.name") +
062                           " (build " +
063                           System.getProperty("java.vm.version") +
064                           ", " + System.getProperty("java.vm.info") + ")");
065        System.err.println("commons daemon version \"" +
066                System.getProperty("commons.daemon.version") + "\"");
067        System.err.println("commons daemon process (id: " +
068                           System.getProperty("commons.daemon.process.id") +
069                           ", parent: " +
070                           System.getProperty("commons.daemon.process.parent") + ")");
071    }
072
073    /**
074     * Checks whether the given class name can be instantiated with a zero-argument constructor.
075     *
076     * @param className The class name.
077     * @return true if the given class name can be instantiated, false otherwise.
078     */
079    public static boolean check(final String className)
080    {
081        try {
082            /* Check the class name */
083            Objects.requireNonNull(className, "className");
084            /* Gets the ClassLoader loading this class */
085            final ClassLoader cl = DaemonLoader.class.getClassLoader();
086            if (cl == null) {
087                System.err.println("Cannot retrieve ClassLoader instance");
088                return false;
089            }
090
091            /* Find the required class */
092            final Class<?> c = cl.loadClass(className);
093
094            /* This should _never_ happen, but double-checking doesn't harm */
095            if (c == null) {
096                throw new ClassNotFoundException(className);
097            }
098
099            /* 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}