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