DaemonWrapper.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */

  17. package org.apache.commons.daemon.support;

  18. import java.lang.reflect.Method;
  19. import java.lang.reflect.Modifier;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Objects;

  23. import org.apache.commons.daemon.Daemon;
  24. import org.apache.commons.daemon.DaemonContext;

  25. /**
  26.  * Implementation of the Daemon that allows running
  27.  * standard applications as daemons.
  28.  * The applications must have the mechanism to manage
  29.  * the application lifecycle.
  30.  */
  31. public class DaemonWrapper implements Daemon
  32. {

  33.     private final static String ARGS            = "args";
  34.     private final static String START_CLASS     = "start";
  35.     private final static String START_METHOD    = "start.method";
  36.     private final static String STOP_CLASS      = "stop";
  37.     private final static String STOP_METHOD     = "stop.method";
  38.     private final static String STOP_ARGS       = "stop.args";
  39.     private String              configFileName;
  40.     private final DaemonConfiguration config;

  41.     private final Invoker             startup;
  42.     private final Invoker             shutdown;

  43.     /**
  44.      * Constructs a new initialized instance.
  45.      */
  46.     public DaemonWrapper()
  47.     {
  48.         config   = new DaemonConfiguration();
  49.         startup  = new Invoker();
  50.         shutdown = new Invoker();
  51.     }

  52.     /**
  53.      * Called from DaemonLoader on init stage.
  54.      * <p>
  55.      * Accepts the following configuration arguments:
  56.      * <ul>
  57.      * <li>-daemon-properties: - load DaemonConfiguration properties from the specified file to act as defaults</li>
  58.      * <li>-start: set start class name</li>
  59.      * <li>-start-method: set start method name</li>
  60.      * <li>-stop: set stop class name</li>
  61.      * <li>-stop-method: set stop method name</li>
  62.      * <li>-stop-argument: set optional argument to stop method</li>
  63.      * <li>Anything else is treated as a startup argument</li>
  64.      * </ul>
  65.      * <p>
  66.      * The following "-daemon-properties" are recognized:
  67.      * <ul>
  68.      * <li>args (startup argument)</li>
  69.      * <li>start</li>
  70.      * <li>start.method</li>
  71.      * <li>stop</li>
  72.      * <li>stop.method</li>
  73.      * <li>stop.args</li>
  74.      * </ul>
  75.      * These are used to set the corresponding item if it has not already been
  76.      * set by the command arguments. <b>However, note that args and stop.args are
  77.      * appended to any existing values.</b>
  78.      */
  79.     @Override
  80.     public void init(final DaemonContext context)
  81.         throws Exception
  82.     {
  83.         final String[] args = context.getArguments();

  84.         if (args != null) {
  85.             int i;
  86.             // Parse our arguments and remove them
  87.             // from the final argument array we are
  88.             // passing to our child.
  89.             arguments:
  90.             for (i = 0; i < args.length; i++) {
  91.                 if (args[i].equals("--")) {
  92.                     // Done with argument processing
  93.                     break;
  94.                 }
  95.                 switch (args[i]) {
  96.                     case "-daemon-properties":
  97.                         if (++i == args.length) {
  98.                             throw new IllegalArgumentException(args[i - 1]);
  99.                         }
  100.                         configFileName = args[i];
  101.                         break;
  102.                     case "-start":
  103.                         if (++i == args.length) {
  104.                             throw new IllegalArgumentException(args[i - 1]);
  105.                         }
  106.                         startup.setClassName(args[i]);
  107.                         break;
  108.                     case "-start-method":
  109.                         if (++i == args.length) {
  110.                             throw new IllegalArgumentException(args[i - 1]);
  111.                         }
  112.                         startup.setMethodName(args[i]);
  113.                         break;
  114.                     case "-stop":
  115.                         if (++i == args.length) {
  116.                             throw new IllegalArgumentException(args[i - 1]);
  117.                         }
  118.                         shutdown.setClassName(args[i]);
  119.                         break;
  120.                     case "-stop-method":
  121.                         if (++i == args.length) {
  122.                             throw new IllegalArgumentException(args[i - 1]);
  123.                         }
  124.                         shutdown.setMethodName(args[i]);
  125.                         break;
  126.                     case "-stop-argument":
  127.                         if (++i == args.length) {
  128.                             throw new IllegalArgumentException(args[i - 1]);
  129.                         }
  130.                         final String[] aa = new String[1];
  131.                         aa[0] = args[i];
  132.                         shutdown.addArguments(aa);
  133.                         break;
  134.                     default:
  135.                         // This is not our option.
  136.                         // Everything else will be forwarded to the main
  137.                         break arguments;
  138.                 }
  139.             }
  140.             if (args.length > i) {
  141.                 final String[] copy = new String[args.length - i];
  142.                 System.arraycopy(args, i, copy, 0, copy.length);
  143.                 startup.addArguments(copy);
  144.             }
  145.         }
  146.         if (config.load(configFileName)) {
  147.             // Setup params if not set via cmdline.
  148.             startup.setClassName(config.getProperty(START_CLASS));
  149.             startup.setMethodName(config.getProperty(START_METHOD));
  150.             // Merge the config with command line arguments
  151.             startup.addArguments(config.getPropertyArray(ARGS));

  152.             shutdown.setClassName(config.getProperty(STOP_CLASS));
  153.             shutdown.setMethodName(config.getProperty(STOP_METHOD));
  154.             shutdown.addArguments(config.getPropertyArray(STOP_ARGS));
  155.         }
  156.         startup.validate();
  157.         shutdown.validate();
  158.     }

  159.     /**
  160.      */
  161.     @Override
  162.     public void start()
  163.         throws Exception
  164.     {
  165.         startup.invoke();
  166.     }

  167.     /**
  168.      */
  169.     @Override
  170.     public void stop()
  171.         throws Exception
  172.     {
  173.         shutdown.invoke();
  174.     }

  175.     /**
  176.      */
  177.     @Override
  178.     public void destroy()
  179.     {
  180.         // Nothing for the moment
  181.         System.err.println("DaemonWrapper: instance " + hashCode() + " destroy");
  182.     }

  183.     // Internal class for wrapping the start/stop methods
  184.     static class Invoker
  185.     {
  186.         private String      name;
  187.         private String      call;
  188.         private String[]    args;
  189.         private Method      inst;
  190.         private Class<?>    main;

  191.         protected Invoker()
  192.         {
  193.         }

  194.         protected void setClassName(final String name)
  195.         {
  196.             if (this.name == null) {
  197.                 this.name = name;
  198.             }
  199.         }
  200.         protected void setMethodName(final String name)
  201.         {
  202.             if (this.call == null) {
  203.                 this.call = name;
  204.             }
  205.         }
  206.         protected void addArguments(final String[] args)
  207.         {
  208.             if (args != null) {
  209.                 final ArrayList<String> aa = new ArrayList<>();
  210.                 if (this.args != null) {
  211.                     aa.addAll(Arrays.asList(this.args));
  212.                 }
  213.                 aa.addAll(Arrays.asList(args));
  214.                 this.args = aa.toArray(DaemonConfiguration.EMPTY_STRING_ARRAY);
  215.             }
  216.         }

  217.         protected void invoke()
  218.             throws Exception
  219.         {
  220.             if (name.equals("System") && call.equals("exit")) {
  221.                 // Just call a System.exit()
  222.                 // The start method was probably installed
  223.                 // a shutdown hook.
  224.                 System.exit(0);
  225.             }
  226.             else {
  227.                 Object obj   = null;
  228.                 if ((inst.getModifiers() & Modifier.STATIC) == 0) {
  229.                     // We only need object instance for non-static methods.
  230.                     obj = main.getConstructor().newInstance();
  231.                 }
  232.                 final Object[] arg = new Object[1];

  233.                 arg[0] = args;
  234.                 inst.invoke(obj, arg);
  235.             }
  236.         }
  237.         // Load the class using reflection
  238.         protected void validate()
  239.             throws Exception
  240.         {
  241.             /* Check the class name */
  242.             if (name == null) {
  243.                 name = "System";
  244.                 call = "exit";
  245.                 return;
  246.             }
  247.             if (args == null) {
  248.                 args = new String[0];
  249.             }
  250.             if (call == null) {
  251.                 call = "main";
  252.             }

  253.             // Get the ClassLoader loading this class
  254.             final ClassLoader classLoader = DaemonWrapper.class.getClassLoader();
  255.             Objects.requireNonNull(classLoader, "classLoader");
  256.             final Class<?>[] ca = new Class[1];
  257.             ca[0] = args.getClass();
  258.             // Find the required class
  259.             main = classLoader.loadClass(name);
  260.             if (main == null) {
  261.                 throw new ClassNotFoundException(name);
  262.             }
  263.             // Find the required method.
  264.             // NoSuchMethodException will be thrown if matching method
  265.             // is not found.
  266.             inst = main.getMethod(call, ca);
  267.         }
  268.     }
  269. }