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            /* Gets 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            /* Gets 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            t.printStackTrace(System.err);
224            return false;
225        }
226        // The class was loaded and instantiated correctly, we can return
227        return true;
228    }
229
230    public static boolean start()
231    {
232        try {
233            // Attempt to start the daemon
234            start.invoke(daemon);
235
236            // Set the availability flag in the controller
237            if (controller != null) {
238                controller.setAvailable(true);
239            }
240
241        } catch (final Throwable t) {
242            // In case we encounter ANY error, we dump the stack trace and
243            // return false (load, start and stop won't be called).
244            t.printStackTrace(System.err);
245            return false;
246        }
247        return true;
248    }
249
250    public static boolean stop()
251    {
252        try {
253            // Set the availability flag in the controller
254            if (controller != null) {
255                controller.setAvailable(false);
256            }
257
258            /* Attempt to stop the daemon */
259            stop.invoke(daemon);
260        }
261        catch (final Throwable t) {
262            // In case we encounter ANY error, we dump the stack trace and
263            // return false (load, start and stop won't be called).
264            t.printStackTrace(System.err);
265            return false;
266        }
267        return true;
268    }
269
270    public static boolean destroy()
271    {
272        try {
273            /* Attempt to stop the daemon */
274            destroy.invoke(daemon);
275
276            daemon = null;
277            controller = null;
278        } catch (final Throwable t) {
279            // In case we encounter ANY error, we dump the stack trace and
280            // return false (load, start and stop won't be called).
281            t.printStackTrace(System.err);
282            return false;
283        }
284        return true;
285    }
286
287    private static native void shutdown(boolean reload);
288    private static native void failed(String message);
289
290    public static class Controller
291        implements DaemonController
292    {
293
294        private boolean available;
295
296        private Controller()
297        {
298            this.setAvailable(false);
299        }
300
301        private boolean isAvailable()
302        {
303            synchronized (this) {
304                return this.available;
305            }
306        }
307
308        private void setAvailable(final boolean available)
309        {
310            synchronized (this) {
311                this.available = available;
312            }
313        }
314
315        @Override
316        public void shutdown()
317            throws IllegalStateException
318        {
319            synchronized (this) {
320                if (!this.isAvailable()) {
321                    throw new IllegalStateException();
322                }
323                this.setAvailable(false);
324                DaemonLoader.shutdown(false);
325            }
326        }
327
328        @Override
329        public void reload()
330            throws IllegalStateException
331        {
332            synchronized (this) {
333                if (!this.isAvailable()) {
334                    throw new IllegalStateException();
335                }
336                this.setAvailable(false);
337                DaemonLoader.shutdown(true);
338            }
339        }
340
341        @Override
342        public void fail()
343        {
344            fail(null, null);
345        }
346
347        @Override
348        public void fail(final String message)
349        {
350            fail(message, null);
351        }
352
353        @Override
354        public void fail(final Exception exception)
355        {
356            fail(null, exception);
357        }
358
359        @Override
360        public void fail(final String message, final Exception exception)
361        {
362            synchronized (this) {
363                this.setAvailable(false);
364                String msg = message;
365                if (exception != null) {
366                    if (msg != null) {
367                        msg = msg + ": " + exception.toString();
368                    }
369                    else {
370                        msg = exception.toString();
371                    }
372                }
373                failed(msg);
374            }
375        }
376
377    }
378
379    public static class Context
380        implements DaemonContext
381    {
382
383        private DaemonController daemonController;
384
385        private String[] args;
386
387        @Override
388        public DaemonController getController()
389        {
390            return daemonController;
391        }
392
393        public void setController(final DaemonController controller)
394        {
395            this.daemonController = controller;
396        }
397
398        @Override
399        public String[] getArguments()
400        {
401            return args;
402        }
403
404        public void setArguments(final String[]args)
405        {
406            this.args = args;
407        }
408
409    }
410}