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 org.apache.commons.daemon.DaemonContext; 021import org.apache.commons.daemon.DaemonController; 022import org.apache.commons.daemon.DaemonInitException; 023 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Method; 026import java.util.Objects; 027 028/** 029 * Used by jsvc for Daemon management. 030 */ 031public final class DaemonLoader 032{ 033 034 // N.B. These static mutable variables need to be accessed using synch. 035 private static Controller controller; //@GuardedBy("this") 036 private static Object daemon; //@GuardedBy("this") 037 /* Methods to call */ 038 private static Method init; //@GuardedBy("this") 039 private static Method start; //@GuardedBy("this") 040 private static Method stop; //@GuardedBy("this") 041 private static Method destroy; //@GuardedBy("this") 042 private static Method signal; //@GuardedBy("this") 043 044 /** 045 * Prints version information to {@link System#err}. 046 */ 047 public static void version() 048 { 049 System.err.println("java version \"" + 050 System.getProperty("java.version") + "\""); 051 System.err.println(System.getProperty("java.runtime.name") + 052 " (build " + 053 System.getProperty("java.runtime.version") + ")"); 054 System.err.println(System.getProperty("java.vm.name") + 055 " (build " + 056 System.getProperty("java.vm.version") + 057 ", " + System.getProperty("java.vm.info") + ")"); 058 System.err.println("commons daemon version \"" + 059 System.getProperty("commons.daemon.version") + "\""); 060 System.err.println("commons daemon process (id: " + 061 System.getProperty("commons.daemon.process.id") + 062 ", parent: " + 063 System.getProperty("commons.daemon.process.parent") + ")"); 064 } 065 066 /** 067 * Checks whether the given class name can be instantiated with a zero-argument constructor. 068 * 069 * @param className The class name. 070 * @return true if the given class name can be instantiated, false otherwise. 071 */ 072 public static boolean check(final String className) 073 { 074 try { 075 /* Check the class name */ 076 Objects.requireNonNull(className, "className"); 077 /* Gets the ClassLoader loading this class */ 078 final ClassLoader cl = DaemonLoader.class.getClassLoader(); 079 if (cl == null) { 080 System.err.println("Cannot retrieve ClassLoader instance"); 081 return false; 082 } 083 084 /* Find the required class */ 085 final Class<?> c = cl.loadClass(className); 086 087 /* This should _never_ happen, but double-checking doesn't harm */ 088 if (c == null) { 089 throw new ClassNotFoundException(className); 090 } 091 092 /* Create a new instance of the daemon */ 093 c.getConstructor().newInstance(); 094 095 } catch (final Throwable t) { 096 /* In case we encounter ANY error, we dump the stack trace and 097 * return false (load, start and stop won't be called). 098 */ 099 t.printStackTrace(System.err); 100 return false; 101 } 102 /* The class was loaded and instantiated correctly, we can return 103 */ 104 return true; 105 } 106 107 /** 108 * Invokes the wrapped {@code signal} method. 109 * 110 * @return whether the call succeeded. 111 */ 112 public static boolean signal() 113 { 114 try { 115 if (signal != null) { 116 signal.invoke(daemon); 117 return true; 118 } 119 System.out.println("Daemon doesn't support signaling"); 120 } catch (final Throwable ex) { 121 System.err.println("Cannot send signal: " + ex); 122 ex.printStackTrace(System.err); 123 } 124 return false; 125 } 126 127 /** 128 * Loads the given class by name, initializing wrapper methods. 129 * 130 * @param className The class name to load. 131 * @param args arguments for the context. 132 * @return whether the operation succeeded. 133 */ 134 public static boolean load(final String className, String[] args) 135 { 136 try { 137 /* Check if the underlying library supplied a valid list of 138 arguments */ 139 if (args == null) { 140 args = new String[0]; 141 } 142 143 /* Check the class name */ 144 Objects.requireNonNull(className, "className"); 145 146 /* Gets the ClassLoader loading this class */ 147 final ClassLoader cl = DaemonLoader.class.getClassLoader(); 148 if (cl == null) { 149 System.err.println("Cannot retrieve ClassLoader instance"); 150 return false; 151 } 152 final Class<?> c; 153 if (className.charAt(0) == '@') { 154 /* Wrap the class with DaemonWrapper 155 * and modify arguments to include the real class name. 156 */ 157 c = DaemonWrapper.class; 158 final String[] a = new String[args.length + 2]; 159 a[0] = "-start"; 160 a[1] = className.substring(1); 161 System.arraycopy(args, 0, a, 2, args.length); 162 args = a; 163 } 164 else { 165 c = cl.loadClass(className); 166 } 167 /* This should _never_ happen, but double-checking doesn't harm */ 168 if (c == null) { 169 throw new ClassNotFoundException(className); 170 } 171 /* Check interfaces */ 172 boolean isdaemon = false; 173 174 try { 175 final Class<?> dclass = cl.loadClass("org.apache.commons.daemon.Daemon"); 176 isdaemon = dclass.isAssignableFrom(c); 177 } 178 catch (final Exception ignored) { 179 // Swallow if Daemon not found. 180 } 181 182 /* Check methods */ 183 final Class<?>[] myclass = new Class[1]; 184 if (isdaemon) { 185 myclass[0] = DaemonContext.class; 186 } 187 else { 188 myclass[0] = args.getClass(); 189 } 190 191 init = c.getMethod("init", myclass); 192 start = c.getMethod("start"); 193 stop = c.getMethod("stop"); 194 destroy = c.getMethod("destroy"); 195 196 try { 197 signal = c.getMethod("signal"); 198 } catch (final NoSuchMethodException ignored) { 199 // Signaling will be disabled. 200 } 201 202 /* Create a new instance of the daemon */ 203 daemon = c.getConstructor().newInstance(); 204 205 if (isdaemon) { 206 // Create a new controller instance 207 controller = new Controller(); 208 209 // Set the availability flag in the controller 210 controller.setAvailable(false); 211 212 /* Create context */ 213 final Context context = new Context(); 214 context.setArguments(args); 215 context.setController(controller); 216 217 // Now we want to call the init method in the class 218 final Object[] arg = new Object[1]; 219 arg[0] = context; 220 init.invoke(daemon, arg); 221 } 222 else { 223 final Object[] arg = new Object[1]; 224 arg[0] = args; 225 init.invoke(daemon, arg); 226 } 227 228 } 229 catch (final InvocationTargetException e) { 230 final Throwable thrown = e.getTargetException(); 231 // DaemonInitExceptions can fail with a nicer message 232 if (thrown instanceof DaemonInitException) { 233 failed(((DaemonInitException) thrown).getMessageWithCause()); 234 } 235 else { 236 thrown.printStackTrace(System.err); 237 } 238 return false; 239 } 240 catch (final Throwable t) { 241 // In case we encounter ANY error, we dump the stack trace and 242 // return false (load, start and stop won't be called). 243 t.printStackTrace(System.err); 244 return false; 245 } 246 // The class was loaded and instantiated correctly, we can return 247 return true; 248 } 249 250 /** 251 * Invokes the wrapped {@code start} method. 252 * 253 * @return whether the call succeeded. 254 */ 255 public static boolean start() 256 { 257 try { 258 // Attempt to start the daemon 259 start.invoke(daemon); 260 // Set the availability flag in the controller 261 if (controller != null) { 262 controller.setAvailable(true); 263 } 264 } catch (final Throwable t) { 265 // In case we encounter ANY error, we dump the stack trace and 266 // return false (load, start and stop won't be called). 267 t.printStackTrace(System.err); 268 return false; 269 } 270 return true; 271 } 272 273 /** 274 * Invokes the wrapped {@code stop} method. 275 * 276 * @return whether the call succeeded. 277 */ 278 public static boolean stop() 279 { 280 try { 281 // Set the availability flag in the controller 282 if (controller != null) { 283 controller.setAvailable(false); 284 } 285 /* Attempt to stop the daemon */ 286 stop.invoke(daemon); 287 } 288 catch (final Throwable t) { 289 // In case we encounter ANY error, we dump the stack trace and 290 // return false (load, start and stop won't be called). 291 t.printStackTrace(System.err); 292 return false; 293 } 294 return true; 295 } 296 297 /** 298 * Invokes the wrapped {@code destroy} method. 299 * 300 * @return whether the call succeeded. 301 */ 302 public static boolean destroy() 303 { 304 try { 305 /* Attempt to stop the daemon */ 306 destroy.invoke(daemon); 307 daemon = null; 308 controller = null; 309 } catch (final Throwable t) { 310 // In case we encounter ANY error, we dump the stack trace and 311 // return false (load, start and stop won't be called). 312 t.printStackTrace(System.err); 313 return false; 314 } 315 return true; 316 } 317 318 private static native void shutdown(boolean reload); 319 private static native void failed(String message); 320 321 /** 322 * A DaemonController that acts on the the global {@link DaemonLoader} state. 323 */ 324 public static class Controller 325 implements DaemonController 326 { 327 328 private boolean available; 329 330 private Controller() 331 { 332 setAvailable(false); 333 } 334 335 private boolean isAvailable() 336 { 337 synchronized (this) { 338 return this.available; 339 } 340 } 341 342 private void setAvailable(final boolean available) 343 { 344 synchronized (this) { 345 this.available = available; 346 } 347 } 348 349 @Override 350 public void shutdown() 351 throws IllegalStateException 352 { 353 synchronized (this) { 354 if (!isAvailable()) { 355 throw new IllegalStateException(); 356 } 357 setAvailable(false); 358 DaemonLoader.shutdown(false); 359 } 360 } 361 362 @Override 363 public void reload() 364 throws IllegalStateException 365 { 366 synchronized (this) { 367 if (!isAvailable()) { 368 throw new IllegalStateException(); 369 } 370 setAvailable(false); 371 DaemonLoader.shutdown(true); 372 } 373 } 374 375 @Override 376 public void fail() 377 { 378 fail(null, null); 379 } 380 381 @Override 382 public void fail(final String message) 383 { 384 fail(message, null); 385 } 386 387 @Override 388 public void fail(final Exception exception) 389 { 390 fail(null, exception); 391 } 392 393 @Override 394 public void fail(final String message, final Exception exception) 395 { 396 synchronized (this) { 397 setAvailable(false); 398 String msg = message; 399 if (exception != null) { 400 if (msg != null) { 401 msg = msg + ": " + exception.toString(); 402 } 403 else { 404 msg = exception.toString(); 405 } 406 } 407 failed(msg); 408 } 409 } 410 411 } 412 413 /** 414 * A concrete {@link DaemonContext} that acts as a simple value container. 415 */ 416 public static class Context 417 implements DaemonContext 418 { 419 420 private DaemonController daemonController; 421 422 private String[] args; 423 424 @Override 425 public DaemonController getController() 426 { 427 return daemonController; 428 } 429 430 /** 431 * Sets the daemon controller. 432 * 433 * @param controller the daemon controller. 434 */ 435 public void setController(final DaemonController controller) 436 { 437 this.daemonController = controller; 438 } 439 440 @Override 441 public String[] getArguments() 442 { 443 return args; 444 } 445 446 /** 447 * Sets arguments. Note that this implementation doesn't currently make a defensive copy. 448 * 449 * @param args arguments. 450 */ 451 public void setArguments(final String[] args) 452 { 453 this.args = args; 454 } 455 456 } 457}