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