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