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