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