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 java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Objects; 025 026import org.apache.commons.daemon.Daemon; 027import org.apache.commons.daemon.DaemonContext; 028 029/** 030 * Implementation of the Daemon that allows running 031 * standard applications as daemons. 032 * The applications must have the mechanism to manage 033 * the application lifecycle. 034 */ 035public class DaemonWrapper implements Daemon 036{ 037 038 private final static String ARGS = "args"; 039 private final static String START_CLASS = "start"; 040 private final static String START_METHOD = "start.method"; 041 private final static String STOP_CLASS = "stop"; 042 private final static String STOP_METHOD = "stop.method"; 043 private final static String STOP_ARGS = "stop.args"; 044 private String configFileName; 045 private final DaemonConfiguration config; 046 047 private final Invoker startup; 048 private final Invoker shutdown; 049 050 /** 051 * Constructs a new initialized instance. 052 */ 053 public DaemonWrapper() 054 { 055 config = new DaemonConfiguration(); 056 startup = new Invoker(); 057 shutdown = new Invoker(); 058 } 059 060 /** 061 * Called from DaemonLoader on init stage. 062 * <p> 063 * Accepts the following configuration arguments: 064 * <ul> 065 * <li>-daemon-properties: - load DaemonConfiguration properties from the specified file to act as defaults</li> 066 * <li>-start: set start class name</li> 067 * <li>-start-method: set start method name</li> 068 * <li>-stop: set stop class name</li> 069 * <li>-stop-method: set stop method name</li> 070 * <li>-stop-argument: set optional argument to stop method</li> 071 * <li>Anything else is treated as a startup argument</li> 072 * </ul> 073 * <p> 074 * The following "-daemon-properties" are recognized: 075 * <ul> 076 * <li>args (startup argument)</li> 077 * <li>start</li> 078 * <li>start.method</li> 079 * <li>stop</li> 080 * <li>stop.method</li> 081 * <li>stop.args</li> 082 * </ul> 083 * These are used to set the corresponding item if it has not already been 084 * set by the command arguments. <b>However, note that args and stop.args are 085 * appended to any existing values.</b> 086 */ 087 @Override 088 public void init(final DaemonContext context) 089 throws Exception 090 { 091 final String[] args = context.getArguments(); 092 093 if (args != null) { 094 int i; 095 // Parse our arguments and remove them 096 // from the final argument array we are 097 // passing to our child. 098 arguments: 099 for (i = 0; i < args.length; i++) { 100 if (args[i].equals("--")) { 101 // Done with argument processing 102 break; 103 } 104 switch (args[i]) { 105 case "-daemon-properties": 106 if (++i == args.length) { 107 throw new IllegalArgumentException(args[i - 1]); 108 } 109 configFileName = args[i]; 110 break; 111 case "-start": 112 if (++i == args.length) { 113 throw new IllegalArgumentException(args[i - 1]); 114 } 115 startup.setClassName(args[i]); 116 break; 117 case "-start-method": 118 if (++i == args.length) { 119 throw new IllegalArgumentException(args[i - 1]); 120 } 121 startup.setMethodName(args[i]); 122 break; 123 case "-stop": 124 if (++i == args.length) { 125 throw new IllegalArgumentException(args[i - 1]); 126 } 127 shutdown.setClassName(args[i]); 128 break; 129 case "-stop-method": 130 if (++i == args.length) { 131 throw new IllegalArgumentException(args[i - 1]); 132 } 133 shutdown.setMethodName(args[i]); 134 break; 135 case "-stop-argument": 136 if (++i == args.length) { 137 throw new IllegalArgumentException(args[i - 1]); 138 } 139 final String[] aa = new String[1]; 140 aa[0] = args[i]; 141 shutdown.addArguments(aa); 142 break; 143 default: 144 // This is not our option. 145 // Everything else will be forwarded to the main 146 break arguments; 147 } 148 } 149 if (args.length > i) { 150 final String[] copy = new String[args.length - i]; 151 System.arraycopy(args, i, copy, 0, copy.length); 152 startup.addArguments(copy); 153 } 154 } 155 if (config.load(configFileName)) { 156 // Setup params if not set via cmdline. 157 startup.setClassName(config.getProperty(START_CLASS)); 158 startup.setMethodName(config.getProperty(START_METHOD)); 159 // Merge the config with command line arguments 160 startup.addArguments(config.getPropertyArray(ARGS)); 161 162 shutdown.setClassName(config.getProperty(STOP_CLASS)); 163 shutdown.setMethodName(config.getProperty(STOP_METHOD)); 164 shutdown.addArguments(config.getPropertyArray(STOP_ARGS)); 165 } 166 startup.validate(); 167 shutdown.validate(); 168 } 169 170 /** 171 */ 172 @Override 173 public void start() 174 throws Exception 175 { 176 startup.invoke(); 177 } 178 179 /** 180 */ 181 @Override 182 public void stop() 183 throws Exception 184 { 185 shutdown.invoke(); 186 } 187 188 /** 189 */ 190 @Override 191 public void destroy() 192 { 193 // Nothing for the moment 194 System.err.println("DaemonWrapper: instance " + hashCode() + " destroy"); 195 } 196 197 // Internal class for wrapping the start/stop methods 198 static class Invoker 199 { 200 private String name; 201 private String call; 202 private String[] args; 203 private Method inst; 204 private Class<?> main; 205 206 protected Invoker() 207 { 208 } 209 210 protected void setClassName(final String name) 211 { 212 if (this.name == null) { 213 this.name = name; 214 } 215 } 216 protected void setMethodName(final String name) 217 { 218 if (this.call == null) { 219 this.call = name; 220 } 221 } 222 protected void addArguments(final String[] args) 223 { 224 if (args != null) { 225 final ArrayList<String> aa = new ArrayList<>(); 226 if (this.args != null) { 227 aa.addAll(Arrays.asList(this.args)); 228 } 229 aa.addAll(Arrays.asList(args)); 230 this.args = aa.toArray(DaemonConfiguration.EMPTY_STRING_ARRAY); 231 } 232 } 233 234 protected void invoke() 235 throws Exception 236 { 237 if (name.equals("System") && call.equals("exit")) { 238 // Just call a System.exit() 239 // The start method was probably installed 240 // a shutdown hook. 241 System.exit(0); 242 } 243 else { 244 Object obj = null; 245 if ((inst.getModifiers() & Modifier.STATIC) == 0) { 246 // We only need object instance for non-static methods. 247 obj = main.getConstructor().newInstance(); 248 } 249 final Object[] arg = new Object[1]; 250 251 arg[0] = args; 252 inst.invoke(obj, arg); 253 } 254 } 255 // Load the class using reflection 256 protected void validate() 257 throws Exception 258 { 259 /* Check the class name */ 260 if (name == null) { 261 name = "System"; 262 call = "exit"; 263 return; 264 } 265 if (args == null) { 266 args = new String[0]; 267 } 268 if (call == null) { 269 call = "main"; 270 } 271 272 // Get the ClassLoader loading this class 273 final ClassLoader classLoader = DaemonWrapper.class.getClassLoader(); 274 Objects.requireNonNull(classLoader, "classLoader"); 275 final Class<?>[] ca = new Class[1]; 276 ca[0] = args.getClass(); 277 // Find the required class 278 main = classLoader.loadClass(name); 279 if (main == null) { 280 throw new ClassNotFoundException(name); 281 } 282 // Find the required method. 283 // NoSuchMethodException will be thrown if matching method 284 // is not found. 285 inst = main.getMethod(call, ca); 286 } 287 } 288}