| 1 | package org.dcache.cells; |
| 2 | |
| 3 | import org.slf4j.Logger; |
| 4 | import org.slf4j.LoggerFactory; |
| 5 | import java.math.BigInteger; |
| 6 | import java.io.PrintWriter; |
| 7 | import java.io.StringWriter; |
| 8 | import java.io.IOException; |
| 9 | |
| 10 | import java.lang.reflect.Constructor; |
| 11 | import java.lang.reflect.Field; |
| 12 | import java.lang.reflect.InvocationTargetException; |
| 13 | import java.lang.reflect.Method; |
| 14 | import java.util.concurrent.FutureTask; |
| 15 | import java.util.concurrent.Callable; |
| 16 | import java.util.concurrent.ExecutionException; |
| 17 | import java.util.Timer; |
| 18 | import java.util.TimerTask; |
| 19 | |
| 20 | import dmg.util.Args; |
| 21 | import dmg.cells.nucleus.CellAdapter; |
| 22 | import dmg.cells.nucleus.CellPath; |
| 23 | import dmg.cells.nucleus.CellMessage; |
| 24 | import dmg.cells.nucleus.CellVersion; |
| 25 | import dmg.cells.nucleus.UOID; |
| 26 | import dmg.cells.nucleus.NoRouteToCellException; |
| 27 | import dmg.cells.nucleus.Reply; |
| 28 | import dmg.cells.nucleus.CDC; |
| 29 | import dmg.cells.nucleus.CellEndpoint; |
| 30 | |
| 31 | import diskCacheV111.vehicles.Message; |
| 32 | import diskCacheV111.util.CacheException; |
| 33 | import diskCacheV111.util.PnfsId; |
| 34 | |
| 35 | /** |
| 36 | * Abstract cell implementation providing features needed by many |
| 37 | * dCache cells. |
| 38 | * |
| 39 | * <h2>Automatic dispatch of dCache messages to message handler</h2> |
| 40 | * |
| 41 | * See org.dcache.util.CellMessageDispatcher for details. |
| 42 | * |
| 43 | * <h2>Logging</h2> |
| 44 | * |
| 45 | * A logger exposed via the protected field _logger. The logger is |
| 46 | * given the name of the instantiated class (i.e. if you name the |
| 47 | * subclass org.dcache.A, then the logger is named org.dcache.A). |
| 48 | * |
| 49 | * The cells say and esay methods are redirected to the logger using |
| 50 | * info and error levels, respectively. |
| 51 | * |
| 52 | * <h2>Initialisation</h2> |
| 53 | * |
| 54 | * AbstractCell provides the <code>init</code> method for performing |
| 55 | * cell initilisation. This method is executed in a thread allocated |
| 56 | * from the cells thread, and thus the thread group and log4j context |
| 57 | * are automatically inherited for any threads created during |
| 58 | * initialisation. Any log messages generated from within the |
| 59 | * <code>init</code> method are correctly attributed to the |
| 60 | * cell. Subclasses should override <code>init</code> rather than |
| 61 | * performing initilisation steps in the constructor. |
| 62 | * |
| 63 | * The <code>init</code> method is called by <code>doInit</code>, |
| 64 | * which makes sure <code>init</code> is executed in the correct |
| 65 | * thread. <code>doInit</code> also enables cells message delivery by |
| 66 | * calling <code>CellAdapter.start</code>. Should <code>init</code> |
| 67 | * throw an exception, then <code>doInit</code> immediately kills the |
| 68 | * cell and logs an error message. |
| 69 | * |
| 70 | * Subclasses must call doInit (preferably from their constructor) for |
| 71 | * any of this to work. |
| 72 | * |
| 73 | * <h2>Option parsing</h2> |
| 74 | * |
| 75 | * AbstractCell supports automatic option parsing based on annotations |
| 76 | * of fields. A field is annotated with the Option annotation. The |
| 77 | * annotation supports the following attributes: |
| 78 | * |
| 79 | * <dl> |
| 80 | * <dt>name</dt> |
| 81 | * <dd>The name of the option.</dd> |
| 82 | * |
| 83 | * <dt>description</dt> |
| 84 | * <dd>A one line description of the option.</dd> |
| 85 | * |
| 86 | * <dt>defaultValue</dt> |
| 87 | * <dd>The default value if the option is not specified, |
| 88 | * specified as a string.</dd> |
| 89 | * |
| 90 | * <dt>unit</dt> |
| 91 | * <dd>The unit of the value, if any, e.g. seconds.</dd> |
| 92 | * |
| 93 | * <dt>required</dt> |
| 94 | * <dd>Whether this is a mandatory option. Defaults to false.</dd> |
| 95 | * |
| 96 | * <dt>log</dt> |
| 97 | * <dd>Whether to log the value of the option during startup. |
| 98 | * Defaults to true, but should be disabled for sensitive |
| 99 | * information.</dd> |
| 100 | * </dl> |
| 101 | * |
| 102 | * Options are automatically converted to the type of the field. In |
| 103 | * case of non-POD fields, the class must have a one-argument |
| 104 | * constructor taking a String. The File class is an example of such a |
| 105 | * class. |
| 106 | * |
| 107 | * By defaults options are logged at the info level. The description |
| 108 | * and unit should be formulated in such a way that the a message can |
| 109 | * be formed as "<description> set to <value> <unit>". |
| 110 | * |
| 111 | * In case a required option is missing, an IllegalArgumentException |
| 112 | * is thrown during option parsing. |
| 113 | * |
| 114 | * It is important that fields used for storing options do not have an |
| 115 | * initializer. An initializer would overwrite the value retrieved |
| 116 | * from the option. Empty Strings will become null. |
| 117 | * |
| 118 | * Example code: |
| 119 | * |
| 120 | * <code> |
| 121 | * @Option( |
| 122 | * name = "maxPinDuration", |
| 123 | * description = "Max. lifetime of a pin", |
| 124 | * defaultValue = "86400000", // one day |
| 125 | * unit = "ms" |
| 126 | * ) |
| 127 | * protected long _maxPinDuration; |
| 128 | * |
| 129 | * @see org.dcache.util.CellMessageDispatcher |
| 130 | */ |
| 131 | public class AbstractCell extends CellAdapter |
| 132 | { |
| 133 | private final static String MSG_UOID_MISMATCH = |
| 134 | "A reply [%s] was generated by a message listener, but the " + |
| 135 | "message UOID indicates that another message listener has " + |
| 136 | "already replied to the message."; |
| 137 | |
| 138 | /** |
| 139 | * Timer for periodic low-priority maintenance tasks. Shared among |
| 140 | * all AbstractCell instances. Since a Timer is single-threaded, |
| 141 | * it is important that the timer is not used for long-running or |
| 142 | * blocking tasks, nor for time critical tasks. |
| 143 | */ |
| 144 | protected final static Timer _timer = new Timer(true); |
| 145 | |
| 146 | /** |
| 147 | * Task for calling the Cell nucleus message timeout mechanism. |
| 148 | */ |
| 149 | private TimerTask _timeoutTask; |
| 150 | |
| 151 | /** |
| 152 | * Logger for the package of the instantiated class. Notice that |
| 153 | * this is not a static field, as the logger instance to use |
| 154 | * depends on the particular instance of this class. |
| 155 | */ |
| 156 | protected Logger _logger; |
| 157 | |
| 158 | /** |
| 159 | * Helper object used to dispatch messages to message listeners. |
| 160 | */ |
| 161 | protected final CellMessageDispatcher _messageDispatcher = |
| 162 | new CellMessageDispatcher("messageArrived"); |
| 163 | |
| 164 | /** |
| 165 | * Helper object used to dispatch messages to forward to message |
| 166 | * listeners. |
| 167 | */ |
| 168 | protected final CellMessageDispatcher _forwardDispatcher = |
| 169 | new CellMessageDispatcher("messageToForward"); |
| 170 | |
| 171 | /** |
| 172 | * Name of context variable to execute during setup, or null. |
| 173 | */ |
| 174 | protected String _definedSetup; |
| 175 | |
| 176 | protected MessageProcessingMonitor _monitor; |
| 177 | |
| 178 | /** |
| 179 | * Strips the first argument if it starts with an exclamation |
| 180 | * mark. |
| 181 | */ |
| 182 | private static Args stripDefinedSetup(Args args) |
| 183 | { |
| 184 | args = (Args) args.clone(); |
| 185 | if ((args.argc() > 0) && args.argv(0).startsWith("!")) { |
| 186 | args.shift(); |
| 187 | } |
| 188 | return args; |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Returns the defined setup declaration, or null if there is no |
| 193 | * defined setup. |
| 194 | * |
| 195 | * The defined setup is declared as the first argument and starts |
| 196 | * with an exclamation mark. |
| 197 | */ |
| 198 | private static String getDefinedSetup(Args args) |
| 199 | { |
| 200 | if ((args.argc() > 0) && args.argv(0).startsWith("!")) { |
| 201 | return args.argv(0).substring(1); |
| 202 | } else { |
| 203 | return null; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Returns the cell type specified as option 'cellType', or |
| 209 | * "Generic" if the option was not given. |
| 210 | */ |
| 211 | static private String getCellType(Args args) |
| 212 | { |
| 213 | String type = args.getOpt("cellType"); |
| 214 | return (type == null) ? "Generic" : type; |
| 215 | } |
| 216 | |
| 217 | public AbstractCell(String cellName, String arguments) |
| 218 | throws InterruptedException, ExecutionException |
| 219 | { |
| 220 | this(cellName, new Args(arguments)); |
| 221 | } |
| 222 | |
| 223 | public AbstractCell(String cellName, Args arguments) |
| 224 | { |
| 225 | this(cellName, getCellType(arguments), arguments); |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * Constructs an AbstractCell. |
| 230 | * |
| 231 | * @param cellName the name of the cell |
| 232 | * @param cellType the type of the cell |
| 233 | * @param arguments the cell arguments |
| 234 | */ |
| 235 | public AbstractCell(String cellName, String cellType, Args arguments) |
| 236 | { |
| 237 | super(cellName, cellType, stripDefinedSetup(arguments), false); |
| 238 | |
| 239 | _monitor = new MessageProcessingMonitor(); |
| 240 | _monitor.setCellEndpoint(this); |
| 241 | if (arguments.getOpt("monitor") != null) { |
| 242 | _monitor.setEnabled(true); |
| 243 | } |
| 244 | |
| 245 | String cellClass = arguments.getOpt("cellClass"); |
| 246 | if (cellClass != null) { |
| 247 | getNucleus().setCellClass(cellClass); |
| 248 | } |
| 249 | |
| 250 | _logger = LoggerFactory.getLogger(getClass()); |
| 251 | _definedSetup = getDefinedSetup(arguments); |
| 252 | |
| 253 | parseOptions(); |
| 254 | addMessageListener(this); |
| 255 | addCommandListener(_monitor); |
| 256 | } |
| 257 | |
| 258 | public void cleanUp() |
| 259 | { |
| 260 | super.cleanUp(); |
| 261 | |
| 262 | if (_timeoutTask != null) { |
| 263 | _timeoutTask.cancel(); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | |
| 268 | /** |
| 269 | * Performs cell initialisation and starts cell message delivery. |
| 270 | * |
| 271 | * Initialisation is delegated to the <code>init</code> method, |
| 272 | * and subclasses should perform initilisation by overriding |
| 273 | * <code>init</code>. If the <code>init</code> method throws an |
| 274 | * exception, then the cell is immediately killed. |
| 275 | * |
| 276 | * @throws InterruptedException if the thread was interrupted |
| 277 | * @throws ExecutionException if init threw an exception |
| 278 | */ |
| 279 | final protected void doInit() |
| 280 | throws InterruptedException, ExecutionException |
| 281 | { |
| 282 | /* Execute initialisation in a different thread allocated from |
| 283 | * the correct thread group. |
| 284 | */ |
| 285 | try { |
| 286 | FutureTask task = new FutureTask(new Callable() { |
| 287 | public Object call() throws Exception { |
| 288 | AbstractCell.this.executeInit(); |
| 289 | return null; |
| 290 | } |
| 291 | }); |
| 292 | getNucleus().newThread(task, "init").start(); |
| 293 | task.get(); |
| 294 | |
| 295 | start(); |
| 296 | } catch (InterruptedException e) { |
| 297 | _logger.info("Cell initialisation was interrupted."); |
| 298 | start(); |
| 299 | kill(); |
| 300 | throw e; |
| 301 | } catch (ExecutionException e) { |
| 302 | Throwable t = e.getCause(); |
| 303 | _logger.error("Failed to initialise cell: " + t.getMessage(), t); |
| 304 | start(); |
| 305 | kill(); |
| 306 | throw e; |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | /** |
| 311 | * Called from the initialization thread. By default the method |
| 312 | * first calls the <code>executeDefinedSetup</code> method, |
| 313 | * followed by the <code>init</code> method and the |
| 314 | * <code>startTimeoutTask</code> method. Subclasses may override |
| 315 | * this behaviour if they wish to modify when the defined setup is |
| 316 | * executed. |
| 317 | */ |
| 318 | protected void executeInit() |
| 319 | throws Exception |
| 320 | { |
| 321 | executeDefinedSetup(); |
| 322 | init(); |
| 323 | startTimeoutTask(); |
| 324 | } |
| 325 | |
| 326 | |
| 327 | /** |
| 328 | * Start the timeout task. |
| 329 | * |
| 330 | * Cells relies on periodic calls to updateWaitQueue to implement |
| 331 | * message timeouts. This method starts a task which calls |
| 332 | * updateWaitQueue every 30 seconds. |
| 333 | */ |
| 334 | protected void startTimeoutTask() |
| 335 | { |
| 336 | if (_timeoutTask != null) |
| 337 | throw new IllegalStateException("Timeout task is already running"); |
| 338 | |
| 339 | final CDC cdc = new CDC(); |
| 340 | _timeoutTask = new TimerTask() { |
| 341 | public void run() |
| 342 | { |
| 343 | cdc.apply(); |
| 344 | try { |
| 345 | getNucleus().updateWaitQueue(); |
| 346 | } finally { |
| 347 | cdc.clear(); |
| 348 | } |
| 349 | } |
| 350 | }; |
| 351 | _timer.schedule(_timeoutTask, 30000, 30000); |
| 352 | } |
| 353 | |
| 354 | /** |
| 355 | * Executes the defined setup (specified with !variable in the |
| 356 | * argument string). |
| 357 | * |
| 358 | * By default, this method is called from |
| 359 | * <code>executeInit</code>. |
| 360 | */ |
| 361 | protected void executeDefinedSetup() |
| 362 | { |
| 363 | if (_definedSetup != null) { |
| 364 | executeDomainContext(_definedSetup); |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | /** |
| 369 | * Initialize cell. This method should be overridden in subclasses |
| 370 | * to perform cell initialization. |
| 371 | * |
| 372 | * The method is called from the <code>executeInit</code> method, |
| 373 | * but using a thread belonging to the thread group of the |
| 374 | * associated cell nucleus. This ensure correct logging and |
| 375 | * correct thread group inheritance. |
| 376 | * |
| 377 | * It is valid for the method to call |
| 378 | * <code>CellAdapter.start</code> if early start of message |
| 379 | * delivery is needed. |
| 380 | */ |
| 381 | protected void init() throws Exception {} |
| 382 | |
| 383 | /** |
| 384 | * Returns the friendly cell name used for logging. It defaults to |
| 385 | * the cell name. |
| 386 | */ |
| 387 | protected String getFriendlyName() |
| 388 | { |
| 389 | return getCellName(); |
| 390 | } |
| 391 | |
| 392 | public void debug(String str) |
| 393 | { |
| 394 | _logger.debug(str.toString()); |
| 395 | } |
| 396 | |
| 397 | public void debug(Throwable t) |
| 398 | { |
| 399 | _logger.debug(t.getMessage()); |
| 400 | StringWriter sw = new StringWriter(); |
| 401 | t.printStackTrace(new PrintWriter(sw)); |
| 402 | for (String s : sw.toString().split("\n")) { |
| 403 | _logger.debug(s.toString()); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | public void info(String str) |
| 408 | { |
| 409 | _logger.info(str.toString()); |
| 410 | } |
| 411 | |
| 412 | public void warn(String str) |
| 413 | { |
| 414 | _logger.warn(str.toString()); |
| 415 | } |
| 416 | |
| 417 | public void error(String str) |
| 418 | { |
| 419 | _logger.error(str.toString()); |
| 420 | } |
| 421 | |
| 422 | public void error(Throwable t) |
| 423 | { |
| 424 | _logger.error(t.getMessage()); |
| 425 | StringWriter sw = new StringWriter(); |
| 426 | t.printStackTrace(new PrintWriter(sw)); |
| 427 | for (String s : sw.toString().split("\n")) { |
| 428 | _logger.error(s.toString()); |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | public void fatal(String str) |
| 433 | { |
| 434 | _logger.error(str.toString()); |
| 435 | } |
| 436 | |
| 437 | public void fatal(Throwable t) |
| 438 | { |
| 439 | _logger.error(t.getMessage()); |
| 440 | StringWriter sw = new StringWriter(); |
| 441 | t.printStackTrace(new PrintWriter(sw)); |
| 442 | for (String s : sw.toString().split("\n")) { |
| 443 | _logger.error(s.toString()); |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | /** @deprecated */ |
| 448 | public void say(String s) |
| 449 | { |
| 450 | info(s); |
| 451 | } |
| 452 | |
| 453 | /** @deprecated */ |
| 454 | public void esay(String s) |
| 455 | { |
| 456 | error(s); |
| 457 | } |
| 458 | |
| 459 | /** @deprecated */ |
| 460 | public void esay(Throwable t) |
| 461 | { |
| 462 | error(t); |
| 463 | } |
| 464 | |
| 465 | /** |
| 466 | * Convert an instance to a specific type (kind of intelligent |
| 467 | * casting). Note: you can set primitive types as input |
| 468 | * <i>type</i> but the return type will be the corresponding |
| 469 | * wrapper type (e.g. Integer.TYPE will result in Integer.class) |
| 470 | * with the difference that instead of a result 'null' a numeric 0 |
| 471 | * (or boolean false) will be returned because primitive types |
| 472 | * can't be null. |
| 473 | * |
| 474 | * <p> |
| 475 | * Supported simple destination types are: |
| 476 | * <ul> |
| 477 | * <li>java.lang.Boolean, Boolean.TYPE (= boolean.class) |
| 478 | * <li>java.lang.Byte, Byte.TYPE (= byte.class) |
| 479 | * <li>java.lang.Character, Character.TYPE (= char.class) |
| 480 | * <li>java.lang.Double, Double.TYPE (= double.class) |
| 481 | * <li>java.lang.Float, Float.TYPE (= float.class) |
| 482 | * <li>java.lang.Integer, Integer.TYPE (= int.class) |
| 483 | * <li>java.lang.Long, Long.TYPE (= long.class) |
| 484 | * <li>java.lang.Short, Short.TYPE (= short.class) |
| 485 | * <li>java.lang.String |
| 486 | * <li>java.math.BigDecimal |
| 487 | * <li>java.math.BigInteger |
| 488 | * </ul> |
| 489 | * |
| 490 | * @param object Instance to convert. |
| 491 | * @param type Destination type (e.g. Boolean.class). |
| 492 | * @return Converted instance/datatype/collection or null if |
| 493 | * input object is null. |
| 494 | * @throws ClassCastException if <i>object</i> can't be converted to |
| 495 | * <i>type</i>. |
| 496 | * @author MartinHilpert at SUN's Java Forum |
| 497 | */ |
| 498 | @SuppressWarnings("unchecked") |
| 499 | static public <T> T toType(final Object object, final Class<T> type) |
| 500 | { |
| 501 | T result = null; |
| 502 | |
| 503 | if (object == null) { |
| 504 | //initalize primitive types: |
| 505 | if (type == Boolean.TYPE) { |
| 506 | result = ((Class<T>) Boolean.class).cast(false); |
| 507 | } else if (type == Byte.TYPE) { |
| 508 | result = ((Class<T>) Byte.class).cast(0); |
| 509 | } else if (type == Character.TYPE) { |
| 510 | result = ((Class<T>) Character.class).cast(0); |
| 511 | } else if (type == Double.TYPE) { |
| 512 | result = ((Class<T>) Double.class).cast(0.0); |
| 513 | } else if (type == Float.TYPE) { |
| 514 | result = ((Class<T>) Float.class).cast(0.0); |
| 515 | } else if (type == Integer.TYPE) { |
| 516 | result = ((Class<T>) Integer.class).cast(0); |
| 517 | } else if (type == Long.TYPE) { |
| 518 | result = ((Class<T>) Long.class).cast(0); |
| 519 | } else if (type == Short.TYPE) { |
| 520 | result = ((Class<T>) Short.class).cast(0); |
| 521 | } |
| 522 | } else { |
| 523 | final String so = object.toString(); |
| 524 | |
| 525 | //custom type conversions: |
| 526 | if (type == BigInteger.class) { |
| 527 | result = type.cast(new BigInteger(so)); |
| 528 | } else if (type == Boolean.class || type == Boolean.TYPE) { |
| 529 | Boolean r = null; |
| 530 | if ("1".equals(so) || "true".equalsIgnoreCase(so) || "yes".equalsIgnoreCase(so) || "on".equalsIgnoreCase(so) || "enabled".equalsIgnoreCase(so)) { |
| 531 | r = Boolean.TRUE; |
| 532 | } else if ("0".equals(object) || "false".equalsIgnoreCase(so) || "no".equalsIgnoreCase(so) || "off".equalsIgnoreCase(so) || "disabled".equalsIgnoreCase(so)) { |
| 533 | r = Boolean.FALSE; |
| 534 | } else { |
| 535 | r = Boolean.valueOf(so); |
| 536 | } |
| 537 | |
| 538 | if (type == Boolean.TYPE) { |
| 539 | result = ((Class<T>) Boolean.class).cast(r); //avoid ClassCastException through autoboxing |
| 540 | } else { |
| 541 | result = type.cast(r); |
| 542 | } |
| 543 | } else if (type == Byte.class || type == Byte.TYPE) { |
| 544 | Byte i = Byte.valueOf(so); |
| 545 | if (type == Byte.TYPE) { |
| 546 | result = ((Class<T>) Byte.class).cast(i); //avoid ClassCastException through autoboxing |
| 547 | } else { |
| 548 | result = type.cast(i); |
| 549 | } |
| 550 | } else if (type == Character.class || type == Character.TYPE) { |
| 551 | Character i = Character.valueOf(so.charAt(0)); |
| 552 | if (type == Character.TYPE) { |
| 553 | result = ((Class<T>) Character.class).cast(i); //avoid ClassCastException through autoboxing |
| 554 | } else { |
| 555 | result = type.cast(i); |
| 556 | } |
| 557 | } else if (type == Double.class || type == Double.TYPE) { |
| 558 | Double i = Double.valueOf(so); |
| 559 | if (type == Double.TYPE) { |
| 560 | result = ((Class<T>) Double.class).cast(i); //avoid ClassCastException through autoboxing |
| 561 | } else { |
| 562 | result = type.cast(i); |
| 563 | } |
| 564 | } else if (type == Float.class || type == Float.TYPE) { |
| 565 | Float i = Float.valueOf(so); |
| 566 | if (type == Float.TYPE) { |
| 567 | result = ((Class<T>) Float.class).cast(i); //avoid ClassCastException through autoboxing |
| 568 | } else { |
| 569 | result = type.cast(i); |
| 570 | } |
| 571 | } else if (type == Integer.class || type == Integer.TYPE) { |
| 572 | Integer i = Integer.valueOf(so); |
| 573 | if (type == Integer.TYPE) { |
| 574 | result = ((Class<T>) Integer.class).cast(i); //avoid ClassCastException through autoboxing |
| 575 | } else { |
| 576 | result = type.cast(i); |
| 577 | } |
| 578 | } else if (type == Long.class || type == Long.TYPE) { |
| 579 | Long i = Long.valueOf(so); |
| 580 | if (type == Long.TYPE) { |
| 581 | result = ((Class<T>) Long.class).cast(i); //avoid ClassCastException through autoboxing |
| 582 | } else { |
| 583 | result = type.cast(i); |
| 584 | } |
| 585 | } else if (type == Short.class || type == Short.TYPE) { |
| 586 | Short i = Short.valueOf(so); |
| 587 | if (type == Short.TYPE) { |
| 588 | result = ((Class<T>) Short.class).cast(i); //avoid ClassCastException through autoboxing |
| 589 | } else { |
| 590 | result = type.cast(i); |
| 591 | } |
| 592 | } else { |
| 593 | try { |
| 594 | Constructor<T> constructor = |
| 595 | type.getConstructor(String.class); |
| 596 | result = constructor.newInstance(object); |
| 597 | } catch (NoSuchMethodException e) { |
| 598 | //hard cast: |
| 599 | result = type.cast(object); |
| 600 | } catch (SecurityException e) { |
| 601 | //hard cast: |
| 602 | result = type.cast(object); |
| 603 | } catch (InstantiationException e) { |
| 604 | //hard cast: |
| 605 | result = type.cast(object); |
| 606 | } catch (IllegalAccessException e) { |
| 607 | //hard cast: |
| 608 | result = type.cast(object); |
| 609 | } catch (InvocationTargetException e) { |
| 610 | //hard cast: |
| 611 | result = type.cast(object); |
| 612 | } |
| 613 | } |
| 614 | } |
| 615 | |
| 616 | return result; |
| 617 | } |
| 618 | |
| 619 | /** |
| 620 | * Returns the value of an option. If the option is found as a |
| 621 | * cell argument, the value is taken from there. Otherwise it is |
| 622 | * taken from the domain context, if found. |
| 623 | * |
| 624 | * @param name the name of the option |
| 625 | * @param required if true, an exception is thrown if the option |
| 626 | * is not defined |
| 627 | * @return the value of the option, or null if the option is |
| 628 | * not defined |
| 629 | * @throws IllegalArgumentException if <code>required</code> is true |
| 630 | * and the option is not defined. |
| 631 | */ |
| 632 | protected String getOption(Option option) |
| 633 | { |
| 634 | String s; |
| 635 | |
| 636 | s = getArgs().getOpt(option.name()); |
| 637 | if (s != null && (s.length() > 0 || !option.required())) |
| 638 | return s; |
| 639 | |
| 640 | s = (String)getDomainContext().get(option.name()); |
| 641 | if (s != null && (s.length() > 0 || !option.required())) |
| 642 | return s; |
| 643 | |
| 644 | if (option.required()) |
| 645 | throw new IllegalArgumentException(option.name() |
| 646 | + " is a required argument"); |
| 647 | |
| 648 | return option.defaultValue(); |
| 649 | } |
| 650 | |
| 651 | /** |
| 652 | * Parses options for this cell. |
| 653 | * |
| 654 | * Option parsing is based on <code>Option</code> annotation of |
| 655 | * fields. This fields must not be class private. |
| 656 | * |
| 657 | * Values are logger at the INFO level. |
| 658 | */ |
| 659 | protected void parseOptions() |
| 660 | { |
| 661 | for (Class c = getClass(); c != null; c = c.getSuperclass()) { |
| 662 | for (Field field : c.getDeclaredFields()) { |
| 663 | Option option = field.getAnnotation(Option.class); |
| 664 | try { |
| 665 | if (option != null) { |
| 666 | field.setAccessible(true); |
| 667 | |
| 668 | String s = getOption(option); |
| 669 | Object value; |
| 670 | // this filters empty strings with the result that they |
| 671 | // become null |
| 672 | if (s != null && s.length() > 0) { |
| 673 | try { |
| 674 | value = toType(s, field.getType()); |
| 675 | field.set(this, value); |
| 676 | } catch (ClassCastException e) { |
| 677 | throw new IllegalArgumentException("Cannot convert '" + s + "' to " + field.getType(), e); |
| 678 | } |
| 679 | } else { |
| 680 | value = field.get(this); |
| 681 | } |
| 682 | |
| 683 | if (option.log()) { |
| 684 | String description = option.description(); |
| 685 | String unit = option.unit(); |
| 686 | if (description.length() == 0) |
| 687 | description = option.name(); |
| 688 | if (unit.length() > 0) { |
| 689 | info(description + " set to " + value + " " + unit); |
| 690 | } else { |
| 691 | info(description + " set to " + value); |
| 692 | } |
| 693 | } |
| 694 | } |
| 695 | } catch (SecurityException e) { |
| 696 | throw new RuntimeException("Bug detected while processing option " + option.name(), e); |
| 697 | } catch (IllegalAccessException e) { |
| 698 | throw new RuntimeException("Bug detected while processing option " + option.name(), e); |
| 699 | } |
| 700 | } |
| 701 | } |
| 702 | } |
| 703 | |
| 704 | /** |
| 705 | * Writes information about all options (Option annotated fields) |
| 706 | * to a writer. |
| 707 | */ |
| 708 | protected void writeOptions(PrintWriter out) |
| 709 | { |
| 710 | for (Class c = getClass(); c != null; c = c.getSuperclass()) { |
| 711 | for (Field field : c.getDeclaredFields()) { |
| 712 | Option option = field.getAnnotation(Option.class); |
| 713 | try { |
| 714 | if (option != null) { |
| 715 | if (option.log()) { |
| 716 | field.setAccessible(true); |
| 717 | Object value = field.get(this); |
| 718 | String description = option.description(); |
| 719 | String unit = option.unit(); |
| 720 | if (description.length() == 0) |
| 721 | description = option.name(); |
| 722 | out.println(description + " is " + value + " " + unit); |
| 723 | } |
| 724 | } |
| 725 | } catch (SecurityException e) { |
| 726 | throw new RuntimeException("Bug detected while processing option " + option.name(), e); |
| 727 | } catch (IllegalAccessException e) { |
| 728 | throw new RuntimeException("Bug detected while processing option " + option.name(), e); |
| 729 | } |
| 730 | } |
| 731 | } |
| 732 | } |
| 733 | |
| 734 | /** |
| 735 | * Adds a listener for dCache messages. |
| 736 | * |
| 737 | * @see CellMessageDispatcher.addMessageListener |
| 738 | */ |
| 739 | public void addMessageListener(Object o) |
| 740 | { |
| 741 | _messageDispatcher.addMessageListener(o); |
| 742 | _forwardDispatcher.addMessageListener(o); |
| 743 | } |
| 744 | |
| 745 | /** |
| 746 | * Removes a listener previously added with addMessageListener. |
| 747 | */ |
| 748 | public void removeMessageListener(Object o) |
| 749 | { |
| 750 | _messageDispatcher.removeMessageListener(o); |
| 751 | _forwardDispatcher.removeMessageListener(o); |
| 752 | } |
| 753 | |
| 754 | /** |
| 755 | * Sends a reply back to the sender of <code>envelope</code>. |
| 756 | */ |
| 757 | private void sendReply(CellEndpoint endpoint, CellMessage envelope, Object result) |
| 758 | { |
| 759 | Object o = envelope.getMessageObject(); |
| 760 | if (o instanceof Message) { |
| 761 | Message msg = (Message)o; |
| 762 | |
| 763 | /* Don't send reply if not requested. Some vehicles |
| 764 | * contain a bug in which the message is marked as not |
| 765 | * requiring a reply, while what was intended was |
| 766 | * asynchronous processing on the server side. Therefore |
| 767 | * we have a special test for Reply results. |
| 768 | */ |
| 769 | if (!msg.getReplyRequired() && !(result instanceof Reply)) |
| 770 | return; |
| 771 | |
| 772 | /* dCache vehicles can transport errors back to the |
| 773 | * requestor, so detect if this is an error reply. |
| 774 | */ |
| 775 | if (result instanceof CacheException) { |
| 776 | CacheException e = (CacheException)result; |
| 777 | msg.setFailed(e.getRc(), e.getMessage()); |
| 778 | result = msg; |
| 779 | } else if (result instanceof IllegalArgumentException) { |
| 780 | msg.setFailed(CacheException.INVALID_ARGS, |
| 781 | result.toString()); |
| 782 | result = msg; |
| 783 | } else if (result instanceof Exception) { |
| 784 | msg.setFailed(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, |
| 785 | result); |
| 786 | result = msg; |
| 787 | } |
| 788 | } |
| 789 | |
| 790 | try { |
| 791 | envelope.revertDirection(); |
| 792 | if (result instanceof Reply) { |
| 793 | Reply reply = (Reply)result; |
| 794 | reply.deliver(endpoint, envelope); |
| 795 | } else { |
| 796 | envelope.setMessageObject(result); |
| 797 | endpoint.sendMessage(envelope); |
| 798 | } |
| 799 | } catch (NoRouteToCellException e) { |
| 800 | _logger.error("Cannot deliver reply: No route to " + envelope.getDestinationAddress()); |
| 801 | } |
| 802 | } |
| 803 | |
| 804 | /** |
| 805 | * Delivers message to registered forward listeners. |
| 806 | * |
| 807 | * A reply is delivered back to the client if any message |
| 808 | * listener: |
| 809 | * |
| 810 | * - Returns a value |
| 811 | * |
| 812 | * - Throws a checked exception, IllegalStateException or |
| 813 | * IllegalArgumentException. |
| 814 | * |
| 815 | * dCache vehicles (subclasses of Message) are recognized, and |
| 816 | * a reply is only sent if requested by the client. |
| 817 | * |
| 818 | * For dCache vehicles, errors are reported by sending back the |
| 819 | * vehicle with an error code. CacheException and |
| 820 | * IllegalArgumentException are recognised and an appropriate |
| 821 | * error code is used. |
| 822 | * |
| 823 | * Return values implementing Reply are recognized and the reply |
| 824 | * is delivered by calling the deliver method on the Reply object. |
| 825 | * |
| 826 | * If no listener returns a value or throws Throws a checked |
| 827 | * exception, IllegalStateException or IllegalArgumentException, |
| 828 | * and the UOID of the envelope is unaltered, then the message is |
| 829 | * forwarded to the next destination. |
| 830 | */ |
| 831 | @Override |
| 832 | public void messageToForward(CellMessage envelope) |
| 833 | { |
| 834 | CellEndpoint endpoint = _monitor.getReplyCellEndpoint(envelope); |
| 835 | UOID uoid = envelope.getUOID(); |
| 836 | boolean isReply = isReply(envelope); |
| 837 | Object result = _forwardDispatcher.call(envelope); |
| 838 | |
| 839 | if (result != null && !isReply) { |
| 840 | if (!uoid.equals(envelope.getUOID())) { |
| 841 | throw new RuntimeException(String.format(MSG_UOID_MISMATCH, result)); |
| 842 | } |
| 843 | |
| 844 | sendReply(endpoint, envelope, result); |
| 845 | } else if (uoid.equals(envelope.getUOID())) { |
| 846 | envelope.nextDestination(); |
| 847 | try { |
| 848 | endpoint.sendMessage(envelope); |
| 849 | } catch (NoRouteToCellException e) { |
| 850 | sendReply(this, envelope, e); |
| 851 | } |
| 852 | } |
| 853 | } |
| 854 | |
| 855 | private boolean isReply(CellMessage envelope) |
| 856 | { |
| 857 | Object message = envelope.getMessageObject(); |
| 858 | return (message instanceof Message) && ((Message) message).isReply(); |
| 859 | } |
| 860 | |
| 861 | /** |
| 862 | * Delivers messages to registered message listeners. |
| 863 | * |
| 864 | * A reply is delivered back to the client if any message |
| 865 | * listener: |
| 866 | * |
| 867 | * - Returns a value |
| 868 | * |
| 869 | * - Throws a checked exception, IllegalStateException or |
| 870 | * IllegalArgumentException. |
| 871 | * |
| 872 | * dCache vehicles (subclasses of Message) are recognized, and |
| 873 | * a reply is only sent if requested by the client. |
| 874 | * |
| 875 | * For dCache vehicles, errors are reported by sending back the |
| 876 | * vehicle with an error code. CacheException and |
| 877 | * IllegalArgumentException are recognised and an appropriate |
| 878 | * error code is used. |
| 879 | * |
| 880 | * Return values implementing Reply are recognized and the reply |
| 881 | * is delivered by calling the deliver method on the Reply object. |
| 882 | */ |
| 883 | public void messageArrived(CellMessage envelope) |
| 884 | { |
| 885 | CellEndpoint endpoint = _monitor.getReplyCellEndpoint(envelope); |
| 886 | UOID uoid = envelope.getUOID(); |
| 887 | boolean isReply = isReply(envelope); |
| 888 | Object result = _messageDispatcher.call(envelope); |
| 889 | |
| 890 | if (result != null && !isReply) { |
| 891 | if (!uoid.equals(envelope.getUOID())) { |
| 892 | throw new RuntimeException(String.format(MSG_UOID_MISMATCH, result)); |
| 893 | } |
| 894 | sendReply(endpoint, envelope, result); |
| 895 | } |
| 896 | } |
| 897 | |
| 898 | /** |
| 899 | * A static version of the getCellVersion method. This method is called |
| 900 | * by reflection in |
| 901 | * {@link dmg.cells.services.login.LoginManager#getCellVersion} |
| 902 | * @return |
| 903 | */ |
| 904 | public static CellVersion getStaticCellVersion(){ |
| 905 | return new CellVersion(diskCacheV111.util.Version.getVersion(),"$Revision: 14038 $" ); |
| 906 | } |
| 907 | |
| 908 | @Override |
| 909 | public CellVersion getCellVersion(){ |
| 910 | return getStaticCellVersion() ; |
| 911 | } |
| 912 | } |