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 | } |