1 | package dmg.cells.nucleus; |
2 | import dmg.cells.network.*; |
3 | import dmg.util.*; |
4 | import dmg.util.logback.FilterShell; |
5 | import dmg.util.logback.FilterThresholds; |
6 | import dmg.util.logback.RootFilterThresholds; |
7 | import java.util.*; |
8 | import java.io.*; |
9 | import java.lang.reflect.*; |
10 | |
11 | import org.slf4j.MDC; |
12 | import org.slf4j.Logger; |
13 | import org.slf4j.LoggerFactory; |
14 | |
15 | /** |
16 | * |
17 | * |
18 | * |
19 | * The CellAdapter builds the basic implementation of a Cell. |
20 | * The CellAdapter implements all required methods of the |
21 | * Cell interface and performs some basic actions as long |
22 | * as those methods are not overloaded by a subclass of |
23 | * CellAdapter. CellAdapter introduces a new set of methods |
24 | * which provide a similiar functionallity as the original |
25 | * Cell callbacks, but are much easier to use. |
26 | * Additionally CellAdapter offers a subset of the CellNucleus |
27 | * methods to be called directly without storing the |
28 | * corresponding handle to the CellNucleus. |
29 | * CellAdapter has a buildin command interpreter |
30 | * <code>see CommandInterpreter</code> which is automatically |
31 | * invokes for each packet containing a plain String object. |
32 | * The result is returned to the sender. |
33 | * CellAdapter forwards and answers ping requests without |
34 | * informing the subclass of CellAdapter as long as this |
35 | * capabitity is not switched of explicitly. |
36 | * |
37 | * @author Patrick Fuhrmann |
38 | * @version 0.2.11, 10/22/1998 |
39 | */ |
40 | |
41 | public class CellAdapter |
42 | extends CommandInterpreter |
43 | implements Cell, CellEventListener, CellEndpoint |
44 | { |
45 | private final static Logger _log = |
46 | LoggerFactory.getLogger(CellAdapter.class); |
47 | |
48 | /** |
49 | * Retry period for cell communication failures. |
50 | */ |
51 | private final static long RETRY_PERIOD = 30000; // 30 seconds |
52 | |
53 | private final CellNucleus _nucleus; |
54 | private final Gate _readyGate = new Gate(false); |
55 | private final Gate _startGate = new Gate(false); |
56 | private final Args _args; |
57 | private boolean _useInterpreter = true; |
58 | private boolean _returnCommandException = false; |
59 | private boolean _answerPing = true; |
60 | private CellMessage _currentMessage = null; |
61 | private String _autoSetup = null; |
62 | private String _definedSetup = null; |
63 | |
64 | /** |
65 | * Creates a Cell and the corresponding CellNucleus with the |
66 | * specified name. An extra boolean argument 'startNow' |
67 | * allows to delay the arrival of messages until the |
68 | * CellAdapter.start() method is called. |
69 | * |
70 | * @param cellName is the name of the newly created cell. The name |
71 | * has to be unique within the context of this CellDomain. |
72 | * @param args an arbitrary argument string with can be obtained |
73 | * by getArgs later on. |
74 | * @param startNow the arrival of messages is enabled. |
75 | * @exception IllegalArgumentException is thrown if the name is |
76 | * not unique within this CellDomain. |
77 | */ |
78 | public CellAdapter(String cellName, |
79 | String args, |
80 | boolean startNow) { |
81 | |
82 | this(cellName, new Args(args == null ? "" : args), startNow); |
83 | |
84 | } |
85 | public CellAdapter(String cellName, |
86 | String cellType, |
87 | String args, |
88 | boolean startNow) { |
89 | |
90 | this(cellName, cellType, new Args(args == null ? "" : args), startNow); |
91 | |
92 | } |
93 | public CellAdapter(String cellName, |
94 | Args args, |
95 | boolean startNow) { |
96 | this(cellName, "Generic", args, startNow); |
97 | } |
98 | public CellAdapter(String cellName, |
99 | String cellType, |
100 | Args args, |
101 | boolean startNow) { |
102 | |
103 | _args = args; |
104 | _nucleus = new CellNucleus(this, cellName, cellType); |
105 | _autoSetup = cellName + "Setup"; |
106 | |
107 | createPinboard(0); |
108 | |
109 | if ((_args.argc() > 0) && |
110 | ((_definedSetup = _args.argv(0)).length() > 1) && |
111 | (_definedSetup.startsWith("!"))) { |
112 | |
113 | _definedSetup = _definedSetup.substring(1); |
114 | _args.shift(); |
115 | |
116 | } else { |
117 | _definedSetup = null; |
118 | } |
119 | |
120 | if (_args.getOpt("export") != null)export(); |
121 | |
122 | String async = _args.getOpt("callback"); |
123 | if (async == null)async = (String)_nucleus.getDomainContext("callback"); |
124 | if (async != null) { |
125 | if (async.equals("async")) { |
126 | setAsyncCallback(true); |
127 | _log.info("Callback set to async"); |
128 | } else if (async.equals("sync")) { |
129 | setAsyncCallback(false); |
130 | _log.info("Callback set to sync"); |
131 | } else _log.warn("Illegal value for 'callback' option : "+async); |
132 | } |
133 | if (_args.getOpt("replyObject") != null)setCommandExceptionEnabled(true); |
134 | |
135 | /* Instantiate management component for log filtering. |
136 | */ |
137 | CellNucleus parentNucleus = |
138 | CellNucleus.getLogTargetForCell(MDC.get(CDC.MDC_CELL)); |
139 | FilterThresholds parentThresholds = |
140 | (parentNucleus.isSystemNucleus() || parentNucleus == _nucleus) |
141 | ? RootFilterThresholds.getInstance() |
142 | : parentNucleus.getLoggingThresholds(); |
143 | |
144 | FilterThresholds thresholds = new FilterThresholds(parentThresholds); |
145 | _nucleus.setLoggingThresholds(thresholds); |
146 | addCommandListener(new FilterShell(thresholds)); |
147 | |
148 | if (startNow)start(); |
149 | } |
150 | |
151 | /** |
152 | * starts the delivery of messages to this cell and |
153 | * executes the auto and defined Setup context. |
154 | * (<cellName>Setup and "!<setupContextName>) |
155 | * This method has to be called if the |
156 | * contructor has been used with the startNow |
157 | * argument set to 'false'. |
158 | * |
159 | */ |
160 | public void start() { |
161 | executeSetupContext(); |
162 | _startGate.open(); |
163 | } |
164 | /** |
165 | * Executes the ContextVariable : |
166 | * <cellName>Setup and "!<setupContextName>" |
167 | * |
168 | */ |
169 | public void executeSetupContext() |
170 | { |
171 | if (_autoSetup != null) { |
172 | executeDomainContext(_autoSetup); |
173 | } |
174 | _autoSetup = null; |
175 | if (_definedSetup != null) { |
176 | executeDomainContext(_definedSetup); |
177 | } |
178 | _definedSetup = null; |
179 | } |
180 | |
181 | protected void executeDomainContext(String name) |
182 | { |
183 | if (name != null) { |
184 | try { |
185 | Reader in = _nucleus.getDomainContextReader(name); |
186 | try { |
187 | CellShell shell = new CellShell(this); |
188 | shell.execute("context:" + name, in, new Args("")); |
189 | } finally { |
190 | in.close(); |
191 | } |
192 | } catch (FileNotFoundException e) { |
193 | // Ignored: Context variable is not defined |
194 | } catch (CommandExitException e) { |
195 | _log.warn(e.getMessage()); |
196 | } catch (IOException e) { |
197 | _log.warn(e.getMessage()); |
198 | } |
199 | } |
200 | } |
201 | |
202 | public void setAsyncCallback(boolean async) { |
203 | _nucleus.setAsyncCallback(async); |
204 | } |
205 | |
206 | public final static String hh_exec_context = "<var> [<arg> ...]"; |
207 | public final static String fh_exec_context = |
208 | "Executes the batch script in the context variable."; |
209 | public String ac_exec_context_$_1_99(Args args) |
210 | throws IOException, CommandExitException |
211 | { |
212 | StringWriter out = new StringWriter(); |
213 | String var = args.argv(0); |
214 | Reader in = _nucleus.getDomainContextReader(var); |
215 | try { |
216 | args.shift(); |
217 | CellShell shell = new CellShell(this); |
218 | shell.execute("context:" + var, in, out, out, args); |
219 | } finally { |
220 | in.close(); |
221 | } |
222 | return out.toString(); |
223 | } |
224 | |
225 | /** |
226 | * Creates a Cell and the corresponding CellNucleus with the |
227 | * specified name. |
228 | * |
229 | * @param cellName is the name of the newly created cell. The name |
230 | * has to be unique within the context of this CellDomain. |
231 | * @exception IllegalArgumentException is thrown if the name is |
232 | * not unique within this CellDomain. |
233 | */ |
234 | public CellAdapter(String cellName) { |
235 | this(cellName, "", true); |
236 | } |
237 | /** |
238 | * Creates a Cell and the corresponding CellNucleus with the |
239 | * specified name and a set of arguments. |
240 | * |
241 | * @param cellName is the name of the newly created cell. The name |
242 | * has to be unique within the context of this CellDomain. |
243 | * @exception IllegalArgumentException is thrown if the name is |
244 | * not unique within this CellDomain. |
245 | */ |
246 | public CellAdapter(String cellName, String args) { |
247 | this(cellName, args, true); |
248 | } |
249 | // |
250 | // adapter to the nucleus |
251 | // |
252 | |
253 | /** |
254 | * Adds a CellEventListener to the current CellNucleus. |
255 | * @param cel has to be an object which implements CellEventListener. |
256 | * @see CellEventListener |
257 | */ |
258 | public void addCellEventListener(CellEventListener cel) { |
259 | _nucleus.addCellEventListener(cel); |
260 | } |
261 | /** |
262 | * Declares this Cell to be a CellEventListener. |
263 | * All methods are implemented by the CellAdapter but |
264 | * don't perform any actions. The subclass has to |
265 | * overwrite all those methods, it is interested in. |
266 | * |
267 | * @see CellEventListener |
268 | */ |
269 | public void addCellEventListener() { |
270 | _nucleus.addCellEventListener(this); |
271 | } |
272 | /** |
273 | * returns an Args object created from the second |
274 | * argument of the constructor : this(String name, String args). |
275 | * |
276 | * @return a handle to an dmg.util.Args object. |
277 | * |
278 | * @see dmg.util.Args |
279 | */ |
280 | public Args getArgs() { return _args; } |
281 | /** |
282 | * enables or disables the return type of the buildin command interpreter. |
283 | * |
284 | * @param use enables the return of CommandExceptions. |
285 | */ |
286 | public void setCommandExceptionEnabled(boolean use) { |
287 | _returnCommandException = use; |
288 | } |
289 | /** |
290 | * enables or disables the buildin command interpreter. |
291 | * The default behaviour is to use the interpreter. |
292 | * @param use enables the interpreter if set to 'true' otherwise |
293 | * the interpreter is disabled. |
294 | */ |
295 | public void useInterpreter(boolean use) { _useInterpreter = use; } |
296 | /** |
297 | * enables or disables the ability to answer or to forward |
298 | * a ping request without calling 'messageArrived' or |
299 | * 'messageToForward'. |
300 | * The default behaviour is to answer or to forward a ping. |
301 | * @param ping instructs the CellAdapter to answer or forward ping requests. |
302 | */ |
303 | public void setAnswerPing(boolean ping) { _answerPing = ping; } |
304 | /** |
305 | * returns the CellNucleus assigned to this cell. This handle |
306 | * might be usefull to have access to the full nucleus functionallity. |
307 | * |
308 | * @return a handle to the CellNucleus connected to this cell. |
309 | */ |
310 | public CellNucleus getNucleus() { return _nucleus; } |
311 | |
312 | /** |
313 | * Setup the logging context of the calling thread. Threads |
314 | * created from the calling thread automatically inherit this |
315 | * information. |
316 | */ |
317 | public void initLoggingContext() { CDC.setCellsContext(_nucleus); } |
318 | |
319 | /** |
320 | * informs the CellCore to remove this cell. |
321 | * The cell kernel will start the kill sequence as soon as |
322 | * possible. |
323 | */ |
324 | protected void kill() { _nucleus.kill(); } |
325 | /** |
326 | * returns the name of this cell. |
327 | * @return the name of this cell. |
328 | */ |
329 | public String getCellName() { return _nucleus.getCellName(); } |
330 | /** |
331 | * returns the name of the domain this cell resides in. |
332 | * @return the name of this domain. |
333 | */ |
334 | public String getCellDomainName() { return _nucleus.getCellDomainName(); } |
335 | /** |
336 | * marks this cell to be exportable. This call triggers an |
337 | * CellExported event to be delivered to all CellEventListeners. |
338 | * The call should only be used for cells with a |
339 | * wellknown name because this name is distributed to |
340 | * all relevent domains as soon as a |
341 | * RoutingManager is |
342 | * running. |
343 | * |
344 | * @see dmg.cells.services.RoutingManager |
345 | */ |
346 | public void export() { _nucleus.export(); } |
347 | /** |
348 | * Defines a pinboard for this CellAdapter. |
349 | * |
350 | * @param size maximum number of lines kept by the pinboard. |
351 | * |
352 | */ |
353 | public void createPinboard(int size) |
354 | { |
355 | _nucleus.setPinboard(new Pinboard(size <= 0 ? 200 : size)); |
356 | } |
357 | |
358 | /** |
359 | * sets the printout level to the specified value. The printoutlevel |
360 | * determines whether the calls to say and esay produce output or are |
361 | * ignored. The value is '1' for stderr and '2' for stdout. A value of zero |
362 | * suppresses all printout. |
363 | * |
364 | * @param level |
365 | * the printout level. |
366 | * |
367 | */ |
368 | public void setPrintoutLevel(int level) { _nucleus.setPrintoutLevel(level); } |
369 | /** |
370 | * |
371 | * |
372 | * @param className Name of the cellClass which should be created |
373 | * @param cellName Name of the cell instance |
374 | * @param args An array of Objects which are passed to the |
375 | * constructor of the specified cellClass. |
376 | * |
377 | */ |
378 | public Object createNewCell(String className, |
379 | String cellName, |
380 | String [] argsClassNames, |
381 | Object [] args) |
382 | throws ClassNotFoundException, |
383 | NoSuchMethodException, |
384 | SecurityException, |
385 | InstantiationException, |
386 | InvocationTargetException, |
387 | IllegalAccessException, |
388 | ClassCastException { |
389 | |
390 | return _nucleus.createNewCell(className, cellName, |
391 | argsClassNames, args); |
392 | |
393 | } |
394 | |
395 | public Map<String,Object> getDomainContext() |
396 | { |
397 | return _nucleus.getDomainContext(); |
398 | } |
399 | |
400 | /** |
401 | * |
402 | * Returns a reader of the specified context Object. |
403 | * The method allows to read throw a 'context object' |
404 | * as if it was a file. |
405 | * |
406 | * @param contextName Name of the context Object. |
407 | * |
408 | */ |
409 | public Reader getDomainContextReader(String contextName) |
410 | throws FileNotFoundException { |
411 | return _nucleus.getDomainContextReader(contextName); |
412 | } |
413 | |
414 | /** |
415 | * sends a <code>CellMessage</code> along the specified path. |
416 | * |
417 | * @param msg the message to be sent. |
418 | * @exception SerializationException if the payload object of this |
419 | * message is not Serializable. |
420 | * @exception NoRouteToCellException if the destination <code>CellPath</code> |
421 | * couldn't be reached. |
422 | * |
423 | */ |
424 | public void sendMessage(CellMessage msg) |
425 | throws SerializationException, |
426 | NoRouteToCellException { |
427 | _nucleus.sendMessage(msg); |
428 | } |
429 | /** |
430 | * sends a <code>CellMessage</code> along the specified path. |
431 | * Two additional boolean arguments allow to specify whether |
432 | * the message should only be delivered locally, remotely or |
433 | * both. The callback arguments (which has to be non-null |
434 | * allows to specify a Class which is informed as soon as |
435 | * an answer arrived or if the timeout has expired. |
436 | * |
437 | * @param msg the message to be sent. |
438 | * @param locally if set to 'false' the message is not delivered |
439 | * locally. |
440 | * @param remotely if set to 'false' the message is not delivered |
441 | * remotely. |
442 | * @param callback specifies a class which will be informed as |
443 | * soon as the message arrives. |
444 | * @param timeout is the timeout interval in msec. |
445 | * |
446 | * @exception SerializationException if the payload object of this |
447 | * message is not Serializable. |
448 | * |
449 | */ |
450 | public void sendMessage(CellMessage msg, |
451 | boolean locally, |
452 | boolean remotely, |
453 | CellMessageAnswerable callback, |
454 | long timeout) |
455 | throws SerializationException { |
456 | _nucleus.sendMessage(msg, locally, remotely, callback, timeout); |
457 | } |
458 | public void sendMessage(CellMessage msg, |
459 | CellMessageAnswerable callback, |
460 | long timeout) |
461 | throws SerializationException { |
462 | _nucleus.sendMessage(msg, true, true, callback, timeout); |
463 | } |
464 | /** |
465 | * sends a <code>CellMessage</code> along the specified path. |
466 | * Two additional boolean arguments allow to specify whether |
467 | * the message should only be delivered locally, remotely or |
468 | * both. |
469 | * |
470 | * @param msg the message to be sent. |
471 | * @param locally if set to 'false' the message is not delivered |
472 | * locally. |
473 | * @param remotely if set to 'false' the message is not delivered |
474 | * remotely. |
475 | * @exception SerializationException if the payload object of this |
476 | * message is not Serializable. |
477 | * @exception NoRouteToCellException if the destination <code>CellPath</code> |
478 | * couldn't be reached. |
479 | * |
480 | */ |
481 | public void sendMessage(CellMessage msg, boolean locally, |
482 | boolean remotely) |
483 | throws SerializationException, |
484 | NoRouteToCellException { |
485 | _nucleus.sendMessage(msg, locally, remotely); |
486 | } |
487 | /** |
488 | * sends a <code>CellMessage</code> along the specified path, |
489 | * and waits <code>millisecs</code> for an answer to arrive. |
490 | * The answer will bypass the ordinary queuing mechanism and |
491 | * will be delivered before any other asynchronous message. |
492 | * The answer need to have the getLastUOID set to the |
493 | * UOID of the message send with sendAndWait. If the answer |
494 | * doesn't arrive withing the specified time intervall, |
495 | * the method returns 'null' and the answer will be handled |
496 | * as if it was an ordinary asynchronous message. |
497 | * |
498 | * @param msg the message to be sent. |
499 | * @param local if 'false' the destination is not looked up locally. |
500 | * @param remote if 'false' the destination is not looked up remotely. |
501 | * @param millisecs milliseconds to wait for an answer. |
502 | * @return the answer CellMessage or 'null' if intervall timed out. |
503 | * @exception SerializationException if the payload object of this |
504 | * message is not Serializable. |
505 | * @exception NoRouteToCellException if the destination <code>CellPath</code> |
506 | * couldn't be reached. |
507 | * |
508 | */ |
509 | public CellMessage sendAndWait(CellMessage msg, |
510 | boolean local, |
511 | boolean remote, |
512 | long millisecs) |
513 | throws SerializationException, |
514 | NoRouteToCellException, |
515 | InterruptedException { |
516 | return _nucleus.sendAndWait(msg, local, remote, millisecs); |
517 | } |
518 | /** |
519 | * |
520 | * convenience method : identical to <br> |
521 | * sendAndWait(msg, millisecs, true, true); |
522 | * |
523 | * @param msg the message to be sent. |
524 | * @param millisecs milliseconds to wait. |
525 | * @return the answer CellMessage or 'null' if intervall timed out. |
526 | * @exception SerializationException if the payload object of this |
527 | * message is not Serializable. |
528 | * @exception NoRouteToCellException if the destination <code>CellPath</code> |
529 | * couldn't be reached. |
530 | * |
531 | * @see dmg.cells.nucleus.CellNucleus#sendAndWait(CellMessage,long,boolean,boolean) |
532 | */ |
533 | public CellMessage sendAndWait(CellMessage msg, |
534 | long millisecs) |
535 | throws SerializationException, |
536 | NoRouteToCellException, |
537 | InterruptedException { |
538 | return _nucleus.sendAndWait(msg, true, true, millisecs); |
539 | } |
540 | |
541 | private long timeUntil(long time) |
542 | { |
543 | return time - System.currentTimeMillis(); |
544 | } |
545 | |
546 | /** |
547 | * @see CellEndpoint.sendAndWaitToPermanent |
548 | */ |
549 | public CellMessage sendAndWaitToPermanent(CellMessage envelope, |
550 | long timeout) |
551 | throws SerializationException, |
552 | InterruptedException |
553 | { |
554 | long deadline = System.currentTimeMillis() + timeout; |
555 | while (true) { |
556 | try { |
557 | return sendAndWait(envelope, timeUntil(deadline)); |
558 | } catch (NoRouteToCellException e) { |
559 | _log.warn(e.toString(), e); |
560 | Thread.sleep(Math.min(timeUntil(deadline), RETRY_PERIOD)); |
561 | } |
562 | } |
563 | } |
564 | |
565 | /** |
566 | * sends a <code>CellMessage</code> along the specified path. |
567 | * <strong>resendMessage does not resolve the local cell |
568 | * Namespace, only the routes are inspected.</strong> |
569 | * |
570 | * |
571 | * @param msg the message to be sent. |
572 | * @exception SerializationException if the payload object of this |
573 | * message is not Serializable. |
574 | * @exception NoRouteToCellException if the destination <code>CellPath</code> |
575 | * couldn't be reached. |
576 | * |
577 | */ |
578 | public void resendMessage(CellMessage msg) |
579 | throws SerializationException, |
580 | NoRouteToCellException { |
581 | _nucleus.resendMessage(msg); |
582 | } |
583 | /** |
584 | * Returns the message object which caused a |
585 | * Command Interpreter client method to trigger. |
586 | * The result object is only 'non-zero' inside |
587 | * a ac_xxx method. |
588 | */ |
589 | public CellMessage getThisMessage() { |
590 | return _currentMessage; |
591 | } |
592 | // |
593 | // methods which may be overwriten |
594 | // |
595 | |
596 | /** |
597 | * should be overwrite to provide a more specific |
598 | * one line information about this cell. |
599 | * |
600 | * @return a one line information String. |
601 | */ |
602 | public String toString() { return _nucleus.getCellName(); } |
603 | /** |
604 | * should be overwrite to provide more specific |
605 | * information about this cell. |
606 | * |
607 | * @param printWrite the printWrite which has to be used to |
608 | * write the information to. |
609 | * |
610 | */ |
611 | public void getInfo(PrintWriter printWriter) { |
612 | printWriter.println(" CellName : "+_nucleus.getCellName()); |
613 | printWriter.println(" CellClass : "+this.getClass().getName()); |
614 | printWriter.println(" Arguments : "+_args); |
615 | } |
616 | public CellVersion getCellVersion() |
617 | { |
618 | String pv = null; |
619 | Package p = Package.getPackage("dmg.cells.nucleus"); |
620 | if (p != null) { |
621 | pv = p.getSpecificationVersion(); |
622 | } |
623 | return new CellVersion((pv == null) ? "cells" : pv, |
624 | "CA-$Revision: 1.28 $"); |
625 | } |
626 | public CellInfo getCellInfo() { return _nucleus.getCellInfo(); } |
627 | /** |
628 | * has to be overwritten to receive arriving messages. |
629 | * The LastMessageEvent is filtered out and starts the |
630 | * kill sequence which calls 'cleanUp' at the end of the |
631 | * sequence. If the CommandInterpreter facility is enabled, |
632 | * all string messages are send to the command interpreter |
633 | * and answered without intervention of the callback. |
634 | * If the command could not be found by the CommandInterpreter, |
635 | * <link>dmg.cells.nucleus.CellAdapter#commandArrived(CellMessage)</link> |
636 | * is called if it is overwritten |
637 | * by one of the CellAdapters subclasses. |
638 | * This callback is only used to inform about messages of which |
639 | * the current cell is the final destination. |
640 | * Other messages are delivered throu <code>messageToForward</code>. |
641 | * |
642 | * @param msg the reference to message arrived. |
643 | * @see dmg.cells.nucleus.CellAdapter#commandArrived(CellMessage) |
644 | * |
645 | */ |
646 | public void messageArrived(CellMessage msg) { |
647 | _log.info(" CellMessage From : "+msg.getSourceAddress()); |
648 | _log.info(" CellMessage To : "+msg.getDestinationAddress()); |
649 | _log.info(" CellMessage Object : "+msg.getMessageObject()); |
650 | |
651 | } |
652 | /** |
653 | * has to be overwritten to receive arriving messages which |
654 | * are not directly addressed to this cell. The default behaviour |
655 | * is to select the next destination and to resend the message. |
656 | * |
657 | * @param msg the reference to message arrived. |
658 | * |
659 | */ |
660 | public void messageToForward(CellMessage msg) { |
661 | msg.nextDestination(); |
662 | try { |
663 | _nucleus.sendMessage(msg); |
664 | } catch (NoRouteToCellException nrtc) { |
665 | _log.warn("CellAdapter : NoRouteToCell in messageToForward : "+nrtc); |
666 | } catch (Exception eee) { |
667 | _log.warn("CellAdapter : Exception in messageToForward : "+eee); |
668 | } |
669 | } |
670 | public Class loadClass(String className) throws ClassNotFoundException { |
671 | return _nucleus.loadClass(className); |
672 | } |
673 | |
674 | /** |
675 | * |
676 | * If overwritten this method delivers commands which |
677 | * produced a syntax error which intereted by the |
678 | * CommandInterpreter. The original message string |
679 | * is provides together with a help text offered |
680 | * by the interpreter. |
681 | * If not overwritten this helptext is send back to the |
682 | * caller. |
683 | * |
684 | * @param str is the orginal command string. |
685 | * @param cse is the syntax error exception thrown by the |
686 | * command interpreter. cse.getHelpText offers |
687 | * the possible help text. |
688 | * @return the object which is send back to the caller. |
689 | * If <code>null</code> nothing is send back. |
690 | */ |
691 | public Object commandArrived(String str, CommandSyntaxException cse) { |
692 | StringBuffer sb = new StringBuffer(); |
693 | sb.append("Syntax Error : "+cse.getMessage()+"\n"); |
694 | String help = cse.getHelpText(); |
695 | if (help != null) { |
696 | sb.append("Help : \n"); |
697 | sb.append(help); |
698 | } |
699 | return sb.toString(); |
700 | } |
701 | /** |
702 | * has to be overwritten to perform any actions before this |
703 | * cell is destroyed. 'cleanUp' is called after the last |
704 | * message has arrived. The default behaviour is to do nothing. |
705 | * |
706 | */ |
707 | public void cleanUp() { } |
708 | // |
709 | // methods from the cellEventListener Interface |
710 | // |
711 | /** |
712 | * belongs to the CellEventListener Interface |
713 | */ |
714 | public void cellCreated(CellEvent ce) {} |
715 | /** |
716 | * belongs to the CellEventListener Interface |
717 | */ |
718 | public void cellDied(CellEvent ce) {} |
719 | /** |
720 | * belongs to the CellEventListener Interface |
721 | */ |
722 | public void cellExported(CellEvent ce) {} |
723 | /** |
724 | * belongs to the CellEventListener Interface |
725 | */ |
726 | public void routeAdded(CellEvent ce) {} |
727 | /** |
728 | * belongs to the CellEventListener Interface |
729 | */ |
730 | public void routeDeleted(CellEvent ce) {} |
731 | // |
732 | // methods which are automatically scanned by |
733 | // the CommandInterpreterFacility |
734 | // |
735 | public String fh_set_printout = |
736 | "Syntax: set printout <level>\n\n"+ |
737 | "Obsolete: Replaced by the log4j command set, see help in the\n" + |
738 | " System cell. The printout level now only controls the\n" + |
739 | " log level at which messages generated through the old\n" + |
740 | " logging system are logged to log4j.\n\n" + |
741 | " <level> Bitmask of the following fields:\n" + |
742 | " 1 -> log cell messages at WARN when set\n"+ |
743 | " 2 -> log cell errors at ERROR when set\n"+ |
744 | " 4 -> log nucleus messages at WARN when set\n"+ |
745 | " 8 -> log nucleus error at ERROR when set\n"+ |
746 | " If a field is not set, then the corresponding messages\n"+ |
747 | " are logged at INFO level.\n"; |
748 | public String ac_set_printout_$_1(Args args) { |
749 | int printout = Integer.parseInt(args.argv(0)); |
750 | _nucleus.setPrintoutLevel(printout); |
751 | return "Obsolete, see help for details"; |
752 | } |
753 | |
754 | public String ac_say_$_1(Args args) { |
755 | _log.info(args.argv(0)); |
756 | return ""; |
757 | } |
758 | public Object ac_xgetcellinfo(Args args) { |
759 | return getCellInfo(); |
760 | } |
761 | public String hh_info = "[-l|-a]"; |
762 | public String ac_info(Args args) throws Exception { |
763 | boolean full = args.getOpt("a") != null; |
764 | boolean lng = full || (args.getOpt("l") != null); |
765 | if (lng) { |
766 | StringBuffer sb = new StringBuffer(); |
767 | sb.append(getInfo()).append("\n"); |
768 | Map<UOID,CellLock > map = _nucleus.getWaitQueue(); |
769 | if (! map.isEmpty())sb.append("\nWe are waiting for the following messages\n"); |
770 | for (Map.Entry<UOID,CellLock > entry : map.entrySet()) { |
771 | Object key = entry.getKey(); |
772 | CellLock lock = entry.getValue(); |
773 | sb.append(key.toString()).append(" r="); |
774 | long res = lock.getTimeout() - System.currentTimeMillis(); |
775 | sb.append(res/1000).append(" sec;"); |
776 | CellMessage msg = lock.getMessage(); |
777 | if (msg == null) { |
778 | sb.append("msg=none"); |
779 | } else { |
780 | Object obj = msg.getMessageObject(); |
781 | if (obj != null) { |
782 | sb.append("msg=").append(obj.getClass().getName()); |
783 | if (full) |
784 | sb.append("/").append(obj.toString()); |
785 | } |
786 | } |
787 | sb.append("\n"); |
788 | } |
789 | return sb.toString(); |
790 | } else { |
791 | return getInfo(); |
792 | } |
793 | } |
794 | public String hh_show_pinboard = |
795 | "[<lines>] # dumps the last <lines> to the terminal"; |
796 | public String ac_show_pinboard_$_0_1(Args args) |
797 | { |
798 | Pinboard pinboard = _nucleus.getPinboard(); |
799 | if (pinboard == null) return "No Pinboard defined"; |
800 | StringBuffer sb = new StringBuffer(); |
801 | if (args.argc() > 0) { |
802 | pinboard.dump(sb, Integer.parseInt(args.argv(0))); |
803 | } else { |
804 | pinboard.dump(sb, 20); |
805 | } |
806 | |
807 | return sb.toString(); |
808 | } |
809 | |
810 | public String hh_dump_pinboard = |
811 | "<filename> # dumps the full pinboard to <filename>"; |
812 | public String ac_dump_pinboard_$_1(Args args) |
813 | { |
814 | Pinboard pinboard = _nucleus.getPinboard(); |
815 | if (pinboard == null) return "No Pinboard defined"; |
816 | |
817 | try { |
818 | pinboard.dump(new File(args.argv(0))); |
819 | } catch (Exception e) { |
820 | return "Dump Failed : "+e; |
821 | } |
822 | return "Pinboard dumped to "+args.argv(0); |
823 | } |
824 | |
825 | /** |
826 | * belongs to the Cell Interface. |
827 | * If this method is overwritten, the 'cleanUp' |
828 | * method won't becalled. |
829 | */ |
830 | public void prepareRemoval(KillEvent ce) { |
831 | _log.info("CellAdapter : prepareRemoval : waiting for gate to open"); |
832 | _readyGate.check(); |
833 | try { |
834 | cleanUp(); |
835 | } catch (Throwable t) { |
836 | _log.warn("CellAdapter : prepareRemoval : got "+t, t); |
837 | } |
838 | dumpPinboard(); |
839 | _log.info("CellAdapter : prepareRemoval : done"); |
840 | } |
841 | // |
842 | // package private (we need it in CellShell) |
843 | // |
844 | void dumpPinboard() |
845 | { |
846 | Pinboard pinboard = _nucleus.getPinboard(); |
847 | try { |
848 | Map<String,Object> context = getDomainContext(); |
849 | String dumpDir = (String)context.get("dumpDirectory"); |
850 | if (dumpDir == null) { |
851 | _log.info("Pinboard not dumped (dumpDirectory not sp.)"); |
852 | return; |
853 | } |
854 | File dir = new File(dumpDir); |
855 | if (! dir.isDirectory()) { |
856 | _log.info( |
857 | "Pinboard not dumped (dumpDirectory[="+dumpDir+"] not found)"); |
858 | return; |
859 | } |
860 | if (pinboard == null) { |
861 | _log.info("Pinboard not dumped (no pinboard defined)"); |
862 | return; |
863 | } |
864 | |
865 | File dump = new File(dir, |
866 | getCellDomainName()+"-"+ |
867 | getCellName()+"-"+ |
868 | Long.toHexString(System.currentTimeMillis())); |
869 | pinboard.dump(dump); |
870 | } catch (Throwable t) { |
871 | _log.warn("Dumping pinboard failed : "+t); |
872 | } |
873 | } |
874 | /** |
875 | * belongs to the Cell Interface. |
876 | * Is never called. |
877 | */ |
878 | public void exceptionArrived(ExceptionEvent ce) { |
879 | _log.info(" exceptionArrived "+ce); |
880 | } |
881 | /** |
882 | * belongs to the Cell Interface. |
883 | * If this method is overwritten, the getInfo(PrintWriter pw) |
884 | * is never called. |
885 | */ |
886 | public String getInfo() { |
887 | StringWriter stringWriter = new StringWriter(); |
888 | PrintWriter printWriter = new PrintWriter(stringWriter); |
889 | |
890 | getInfo(printWriter); |
891 | printWriter.flush(); |
892 | return stringWriter.getBuffer().toString(); |
893 | } |
894 | /** |
895 | * belongs to the Cell Interface. |
896 | * If this method is overwritten, the messageArrived(CellMessage cm) |
897 | * and the messageToForward(CellMessage) methods |
898 | * are never called. |
899 | */ |
900 | public void messageArrived(MessageEvent me) { |
901 | _startGate.check(); |
902 | if (me instanceof LastMessageEvent) { |
903 | _log.info("messageArrived : LastMessageEvent (opening gate)"); |
904 | _readyGate.open(); |
905 | } else { |
906 | CellMessage msg = me.getMessage(); |
907 | Object obj = msg.getMessageObject(); |
908 | // _log.info("messageArrived Object : [final="+ |
909 | // msg.isFinalDestination()+";i="+_useInterpreter+"] "+obj.getClass()); |
910 | if (msg.isFinalDestination()) { |
911 | if (_useInterpreter && (! msg.isReply()) && |
912 | ((obj instanceof String) || |
913 | (obj instanceof AuthorizedString) || |
914 | (obj instanceof CommandRequestable))) { |
915 | |
916 | Object o; |
917 | UOID uoid = msg.getUOID(); |
918 | EventLogger.deliverBegin(msg); |
919 | try { |
920 | _currentMessage = msg; |
921 | o = executeLocalCommand(obj); |
922 | if (o == null) |
923 | return; |
924 | } catch (CommandException ce) { |
925 | o = ce; |
926 | } catch (Throwable te) { |
927 | o = te; |
928 | } finally { |
929 | EventLogger.deliverEnd(msg.getSession(), uoid); |
930 | _currentMessage = null; |
931 | } |
932 | |
933 | try { |
934 | msg.revertDirection(); |
935 | if (o instanceof Reply) { |
936 | Reply reply = (Reply)o; |
937 | reply.deliver(this, msg); |
938 | } else { |
939 | msg.setMessageObject(o); |
940 | _nucleus.sendMessage(msg); |
941 | } |
942 | } catch (Exception e) { |
943 | _log.warn("PANIC : Problem returning answer : " + e); |
944 | } |
945 | } else if ((obj instanceof PingMessage) && _answerPing) { |
946 | PingMessage ping = (PingMessage)obj; |
947 | if (ping.isWayBack()) { |
948 | messageArrived(msg); |
949 | return; |
950 | } |
951 | ping.setWayBack(); |
952 | msg.revertDirection(); |
953 | try { |
954 | _nucleus.sendMessage(msg); |
955 | } catch (Exception ee) { |
956 | _log.warn("Couldn't revert PingMessage : "+ee); |
957 | } |
958 | } else { |
959 | UOID uoid = msg.getUOID(); |
960 | EventLogger.deliverBegin(msg); |
961 | try { |
962 | messageArrived(msg); |
963 | } finally { |
964 | EventLogger.deliverEnd(msg.getSession(), uoid); |
965 | } |
966 | } |
967 | } else { |
968 | // |
969 | /* |
970 | if (((obj instanceof PingMessage) && _answerPing) || |
971 | (_useInterpreter && |
972 | ((obj instanceof String) || |
973 | (obj instanceof CommandRequestable)))) { |
974 | msg.nextDestination(); |
975 | try { |
976 | _nucleus.sendMessage(msg); |
977 | } catch (Exception ee) { |
978 | _log.warn("Couldn't forward PingMessage : "+ee); |
979 | } |
980 | } else { |
981 | messageToForward(msg); |
982 | } |
983 | */ |
984 | if (obj instanceof PingMessage) { |
985 | msg.nextDestination(); |
986 | try { |
987 | _nucleus.sendMessage(msg); |
988 | } catch (Exception ee) { |
989 | _log.warn("Couldn't forward PingMessage : "+ee); |
990 | } |
991 | } else { |
992 | UOID uoid = msg.getUOID(); |
993 | EventLogger.deliverBegin(msg); |
994 | try { |
995 | messageToForward(msg); |
996 | } finally { |
997 | EventLogger.deliverEnd(msg.getSession(), uoid); |
998 | } |
999 | } |
1000 | } |
1001 | } |
1002 | |
1003 | } |
1004 | private Object executeLocalCommand(Object command) |
1005 | throws CommandException { |
1006 | // _log.info("executeLocalCommand() : "+command.getClass().getName()+" [_returnCommandException="+_returnCommandException); |
1007 | if (command instanceof Authorizable) { |
1008 | |
1009 | if (_returnCommandException) { |
1010 | try { |
1011 | return command(new AuthorizedArgs((Authorizable)command)); |
1012 | } catch (CommandException ce) { |
1013 | throw ce; |
1014 | } catch (Throwable xe) { |
1015 | throw new CommandException(1, "Unknown : "+xe.toString()); |
1016 | } |
1017 | } else { |
1018 | return autoCommand(command); |
1019 | } |
1020 | |
1021 | } else if (command instanceof String) { |
1022 | |
1023 | if (_returnCommandException) { |
1024 | try { |
1025 | return command(new Args((String)command)); |
1026 | } catch (CommandException ce) { |
1027 | throw ce; |
1028 | } catch (Throwable xe) { |
1029 | throw new CommandException(1, "Unknown : "+xe.toString()); |
1030 | } |
1031 | } else { |
1032 | return autoCommand((String)command); |
1033 | } |
1034 | |
1035 | } else if (command instanceof CommandRequestable) { |
1036 | try { |
1037 | return command((CommandRequestable)command); |
1038 | } catch (CommandException ce) { |
1039 | throw ce; |
1040 | } catch (Throwable xe) { |
1041 | throw new CommandException(1,"Unknown : "+xe.toString()); |
1042 | } |
1043 | } else |
1044 | throw new |
1045 | CommandPanicException("Illegal CommandClass detected", |
1046 | new Exception("PANIC")); |
1047 | |
1048 | |
1049 | } |
1050 | private Object autoCommand(Object command) { |
1051 | |
1052 | try { |
1053 | if (command instanceof String) |
1054 | return command(new Args((String)command)); |
1055 | else if (command instanceof AuthorizedString) |
1056 | return command(new AuthorizedArgs((AuthorizedString)command)); |
1057 | else |
1058 | return "Panic : internal server error 14345"; |
1059 | } catch (CommandSyntaxException cse) { |
1060 | return commandArrived(command.toString(), cse); |
1061 | } catch (CommandExitException cee) { |
1062 | return "Sorry, can't exit"; |
1063 | } catch (CommandThrowableException cte) { |
1064 | StringBuffer sb = new StringBuffer(); |
1065 | sb.append(cte.getMessage()+"\n"); |
1066 | Throwable t = cte.getTargetException(); |
1067 | sb.append(t.getClass().getName()+" : "+t.getMessage()+"\n"); |
1068 | return sb.toString(); |
1069 | } catch (CommandPanicException cpe) { |
1070 | StringBuffer sb = new StringBuffer(); |
1071 | sb.append("Panic : "+cpe.getMessage()+"\n"); |
1072 | Throwable t = cpe.getTargetException(); |
1073 | sb.append(t.getClass().getName()+" : "+t.getMessage()+"\n"); |
1074 | return sb.toString(); |
1075 | } catch (Exception e) { |
1076 | return "??? : "+e.toString(); |
1077 | } |
1078 | } |
1079 | private CellPath _aclPath = new CellPath("acm"); |
1080 | private long _aclTimeout = 10000L; |
1081 | protected void checkAclPermission(Authorizable auth, Object command, String [] acls) throws CommandException { |
1082 | |
1083 | String user = auth.getAuthorizedPrincipal(); |
1084 | |
1085 | if (user.equals("admin") || (acls == null) || (acls.length == 0))return; |
1086 | |
1087 | CommandException recentException = null; |
1088 | |
1089 | for (int i = 0; i < acls.length; i++) { |
1090 | try { |
1091 | checkAclPermission(user, command, acls[i]); |
1092 | return; |
1093 | } catch (CommandAclException ce) { |
1094 | recentException = ce; |
1095 | } |
1096 | } |
1097 | throw recentException; |
1098 | } |
1099 | protected void checkAclPermission(String user, Object command, String acl) throws CommandException { |
1100 | |
1101 | Object [] request = new Object[5]; |
1102 | |
1103 | request[0] = "request"; |
1104 | request[1] = "<nobody>"; |
1105 | request[2] = "check-permission"; |
1106 | request[3] = user; |
1107 | request[4] = acl; |
1108 | |
1109 | CellMessage reply = null; |
1110 | |
1111 | try { |
1112 | reply = _nucleus.sendAndWait( |
1113 | new CellMessage(_aclPath, request), |
1114 | _aclTimeout); |
1115 | |
1116 | if (reply == null) |
1117 | throw new |
1118 | Exception("Acl Request timed out ("+_aclPath+")"); |
1119 | |
1120 | } catch (Exception ee) { |
1121 | throw new |
1122 | CommandException("Error in acl handling : "+ee.getMessage()); |
1123 | } |
1124 | Object r = reply.getMessageObject(); |
1125 | if ((r == null) || |
1126 | (! (r instanceof Object [])) || |
1127 | (((Object [])r).length < 6) || |
1128 | (! (((Object [])r)[5] instanceof Boolean))) |
1129 | throw new |
1130 | CommandException("Error in acl handling : illegal reply arrived"); |
1131 | |
1132 | if (! (((Boolean)((Object [])r)[5]).booleanValue())) |
1133 | throw new |
1134 | CommandAclException(user, acl); |
1135 | |
1136 | return; |
1137 | |
1138 | } |
1139 | } |