EMMA Coverage Report (generated Mon Aug 23 17:21:34 CEST 2010)
[all classes][org.dcache.cells]

COVERAGE SUMMARY FOR SOURCE FILE [AbstractCell.java]

nameclass, %method, %block, %line, %
AbstractCell.java100% (3/3)41%  (17/41)20%  (269/1353)21%  (62.9/302)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AbstractCell100% (1/1)38%  (14/37)19%  (249/1312)21%  (60.9/295)
cleanUp (): void 0%   (0/1)0%   (0/10)0%   (0/4)
debug (String): void 0%   (0/1)0%   (0/6)0%   (0/2)
debug (Throwable): void 0%   (0/1)0%   (0/40)0%   (0/6)
error (String): void 0%   (0/1)0%   (0/6)0%   (0/2)
error (Throwable): void 0%   (0/1)0%   (0/40)0%   (0/6)
esay (String): void 0%   (0/1)0%   (0/4)0%   (0/2)
esay (Throwable): void 0%   (0/1)0%   (0/4)0%   (0/2)
executeInit (): void 0%   (0/1)0%   (0/7)0%   (0/4)
fatal (String): void 0%   (0/1)0%   (0/6)0%   (0/2)
fatal (Throwable): void 0%   (0/1)0%   (0/40)0%   (0/6)
getCellVersion (): CellVersion 0%   (0/1)0%   (0/2)0%   (0/1)
getFriendlyName (): String 0%   (0/1)0%   (0/3)0%   (0/1)
getOption (Option): String 0%   (0/1)0%   (0/52)0%   (0/9)
getStaticCellVersion (): CellVersion 0%   (0/1)0%   (0/6)0%   (0/1)
isReply (CellMessage): boolean 0%   (0/1)0%   (0/14)0%   (0/2)
messageArrived (CellMessage): void 0%   (0/1)0%   (0/44)0%   (0/9)
messageToForward (CellMessage): void 0%   (0/1)0%   (0/63)0%   (0/15)
removeMessageListener (Object): void 0%   (0/1)0%   (0/9)0%   (0/3)
say (String): void 0%   (0/1)0%   (0/4)0%   (0/2)
sendReply (CellEndpoint, CellMessage, Object): void 0%   (0/1)0%   (0/85)0%   (0/26)
toType (Object, Class): Object 0%   (0/1)0%   (0/366)0%   (0/81)
warn (String): void 0%   (0/1)0%   (0/6)0%   (0/2)
writeOptions (PrintWriter): void 0%   (0/1)0%   (0/103)0%   (0/18)
parseOptions (): void 100% (1/1)22%  (35/157)22%  (6/27)
doInit (): void 100% (1/1)80%  (44/55)71%  (12/17)
startTimeoutTask (): void 100% (1/1)81%  (21/26)83%  (5/6)
getCellType (Args): String 100% (1/1)90%  (9/10)95%  (1.9/2)
AbstractCell (String, String, Args): void 100% (1/1)94%  (61/65)94%  (15/16)
<static initializer> 100% (1/1)100% (6/6)100% (1/1)
AbstractCell (String, Args): void 100% (1/1)100% (7/7)100% (2/2)
AbstractCell (String, String): void 100% (1/1)100% (8/8)100% (2/2)
addMessageListener (Object): void 100% (1/1)100% (9/9)100% (3/3)
executeDefinedSetup (): void 100% (1/1)100% (8/8)100% (3/3)
getDefinedSetup (Args): String 100% (1/1)100% (17/17)100% (3/3)
info (String): void 100% (1/1)100% (6/6)100% (2/2)
init (): void 100% (1/1)100% (1/1)100% (1/1)
stripDefinedSetup (Args): Args 100% (1/1)100% (17/17)100% (4/4)
     
class AbstractCell$2100% (1/1)50%  (1/2)30%  (9/30)94%  (5.6/6)
run (): void 0%   (0/1)0%   (0/21)0%   (0/5)
AbstractCell$2 (AbstractCell, CDC): void 100% (1/1)100% (9/9)100% (1/1)
     
class AbstractCell$1100% (1/1)100% (2/2)100% (11/11)100% (3/3)
AbstractCell$1 (AbstractCell): void 100% (1/1)100% (6/6)100% (1/1)
call (): Object 100% (1/1)100% (5/5)100% (2/2)

1package org.dcache.cells;
2 
3import org.slf4j.Logger;
4import org.slf4j.LoggerFactory;
5import java.math.BigInteger;
6import java.io.PrintWriter;
7import java.io.StringWriter;
8import java.io.IOException;
9 
10import java.lang.reflect.Constructor;
11import java.lang.reflect.Field;
12import java.lang.reflect.InvocationTargetException;
13import java.lang.reflect.Method;
14import java.util.concurrent.FutureTask;
15import java.util.concurrent.Callable;
16import java.util.concurrent.ExecutionException;
17import java.util.Timer;
18import java.util.TimerTask;
19 
20import dmg.util.Args;
21import dmg.cells.nucleus.CellAdapter;
22import dmg.cells.nucleus.CellPath;
23import dmg.cells.nucleus.CellMessage;
24import dmg.cells.nucleus.CellVersion;
25import dmg.cells.nucleus.UOID;
26import dmg.cells.nucleus.NoRouteToCellException;
27import dmg.cells.nucleus.Reply;
28import dmg.cells.nucleus.CDC;
29import dmg.cells.nucleus.CellEndpoint;
30 
31import diskCacheV111.vehicles.Message;
32import diskCacheV111.util.CacheException;
33import 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 */
131public 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}

[all classes][org.dcache.cells]
EMMA 2.0.5312 (C) Vladimir Roubtsov