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