View Javadoc

1   /*
2   * The contents of this file are subject to the BT "ZEUS" Open Source 
3   * Licence (L77741), Version 1.0 (the "Licence"); you may not use this file 
4   * except in compliance with the Licence. You may obtain a copy of the Licence
5   * from $ZEUS_INSTALL/licence.html or alternatively from
6   * http://www.labs.bt.com/projects/agents/zeus/licence.htm
7   * 
8   * Except as stated in Clause 7 of the Licence, software distributed under the
9   * Licence is distributed WITHOUT WARRANTY OF ANY KIND, either express or 
10  * implied. See the Licence for the specific language governing rights and 
11  * limitations under the Licence.
12  * 
13  * The Original Code is within the package zeus.*.
14  * The Initial Developer of the Original Code is British Telecommunications
15  * public limited company, whose registered office is at 81 Newgate Street, 
16  * London, EC1A 7AJ, England. Portions created by British Telecommunications 
17  * public limited company are Copyright 1996-9. All Rights Reserved.
18  * 
19  * THIS NOTICE MUST BE INCLUDED ON ANY COPY OF THIS FILE
20  */
21  
22  
23  
24  /*
25   * @(#)MailBox.java 1.03b
26   */
27  
28  package zeus.actors;
29  
30  import java.net.*;
31  import java.io.*;
32  import java.util.*;
33  import zeus.util.*;
34  import zeus.concepts.*;
35  import zeus.actors.event.*;
36  import zeus.actors.intrays.*;
37  
38  /***
39   * Each agent has a Mailbox component that implements its communication
40   * mechanism. The sub-components of the MailBox are together responsible for 
41   * creating and reading the TCP/IP sockets that send and receive messages. <p>
42   *
43   * The MailBox maintains two independent threads of activity, one is a reader 
44   * thread, which continually listens for incoming socket connections, whereupon
45   * a new transient thread is created to read the message and deliver it to the 
46   * {@link MsgHandler}, which processes it. This approach delegates
47   * responsibility for reading messages to the new connection thread, leaving the
48   * main {@link Server} thread free to continue listening for incoming messages, (thus
49   * enabling several messages to be received simultaneously).  When the incoming
50   * message is read, the connection thread terminates. <p>
51   *
52   * The other Mailbox thread is a {@link PostMan} object, which creates transient
53   * threads that open sockets to the message recipients. If the connection is made
54   * the message is then streamed down the socket, this allows the agent to
55   * dispatch more than one message at a time. <p>
56   *
57   * More details on the workings of the communication mechanism are provided
58   * in the Zeus Technical Manual.
59   */
60  public class MailBox 
61  {
62      /***
63          eventMonitor used to be private, but I needed to alter this so 
64          that I could build an effective sub-class
65              */
66    protected HSet[]  eventMonitor = new HSet[4];
67  
68    public static final int RECEIVE      = 0;
69    public static final int QUEUE        = 1;
70    public static final int DISPATCH     = 2;
71    public static final int NOT_DISPATCH = 3;
72  
73    /*** A data structure holding the agent's incoming mail messages */
74    protected Queue       inMail = new Queue("Zeus inMail");
75  
76    /*** A data structure holding the agent's outgoing mail messages */
77    protected Queue       outMail = new Queue("Zeus outMail");
78  
79    /*** Holds mail messages that need to be CC'ed to Visualiser agents */
80    protected Queue       ccMail = new Queue("Zeus ccMail");
81  
82    protected Hashtable   asTable = new Hashtable();
83    protected Hashtable   visualisers = new Hashtable();
84  
85    /*** The sub-component responsible for reading incoming mail */
86    protected Server       server;
87  
88    /*** The sub-component responsible for dispatching outgoing mail */
89    protected PostMan[]    postman;
90  
91    protected Address      myAddress;
92    protected AgentContext context;
93  
94    public MailBox () {;} 
95  
96    public MailBox(AgentContext context) {
97      Assert.notNull(context);
98      this.context = context;
99      context.set(this);
100 
101     Address addr;
102     Performative msg;
103 
104     // setup event-monitor db
105     for(int i = 0; i < eventMonitor.length; i++ )
106        eventMonitor[i] = new HSet();
107 
108     context.set(new AddressBook());
109     server = new Server(context,this,inMail);
110     myAddress = server.getAddress();
111 
112     postman = new PostMan[2];
113     postman[0] = new PostMan(this,outMail,ccMail,myAddress);
114     postman[1] = new PostMan(this,ccMail,myAddress);
115 
116     // Register with Name Servers
117     String key = context.newId();
118     String[] pattern = { "type", "inform", "in-reply-to", key };
119 
120     context.MsgHandler().addRule(new MessageRuleImpl(context.newId("Rule"),
121        pattern,MessageActionImpl.EXECUTE_ONCE,this,"register")
122     );
123 
124     for(int i = 0; i < context.nameservers().size(); i++ ) {
125        addr = (Address)context.nameservers().elementAt(i);
126        context.AddressBook().add(addr);
127 
128        msg = new Performative("request");
129        msg.setReceiver(addr.getName());
130        msg.setReplyWith(key);
131        msg.setContent("register");
132        sendMsg(msg);
133     }
134   }
135 
136   public void register(Performative msg) {
137     String content = msg.getContent();
138     if ( context.Clock() == null && content != null ) {
139        StringTokenizer st = new StringTokenizer(content);
140        long prev, incr, now0, now1;
141        long deltaX, deltaT = 160;
142 
143        prev = Long.parseLong(st.nextToken());
144        incr = Long.parseLong(st.nextToken());
145        now0 = Long.parseLong(st.nextToken());
146        now1 = System.currentTimeMillis();
147        deltaX = now1 - (now0+deltaT);
148        context.set(new Clock(prev+deltaX,incr));
149     }
150   }
151 
152   public AgentContext getAgentContext() { return  context; }
153 
154   public void del( Address addr ) {
155     Assert.notNull(addr);
156     context.AddressBook().del( addr );
157 
158 
159     // LL 040500 1.03B
160     // after deregister, remove all records of address query
161     // and explicitly extract msg from waitQueue and put to outMail (which
162     // will then fail when do a mbox.lookup).
163     KeyValue data = (KeyValue) asTable.remove(addr.getName()); // LL 040500 1.03b
164     if ( data != null) 
165         for(int i = 0; i < postman.length; i++ )
166             postman[i].addressReceived(data.key);
167     // LL 040500 1.03bE
168 
169 /* LL this would only solve the type cast problem. However, the logic doesn't
170       make sense. As an agent deregisters itself, all the pending out messages
171       to it should not be left in the waitQueue. 
172     Time t = context.currentTime();
173     KeyValue data = (KeyValue) asTable.get(addr.getName());
174 
175     if ( t != null  ) 
176         if ( data != null )
177             asTable.put(addr.getName(), new KeyValue(data.key,t));
178         else {
179             String key = context.newId(); 
180             asTable.put(agent,new KeyValue(key,now));
181         }
182 */
183 
184   }
185 
186   public void del( Vector v ) {
187     Assert.notNull(v);
188     for(int i = 0; i < v.size(); i++ )
189       context.AddressBook().del( (Address)v.elementAt(i) );
190   }
191 
192   public void add( Address addr ) {
193     Assert.notNull(addr);
194     context.AddressBook().add( addr );
195   }
196 
197   public void add( Vector v ) {
198     Assert.notNull(v);
199     for(int i = 0; i < v.size(); i++ )
200        context.AddressBook().add( (Address)v.elementAt(i) );
201   }
202 
203   public Address lookup( String name ) {
204     Assert.notNull(name);
205     return context.AddressBook().lookup( name );
206   }
207 
208   public void stopDispatching() {
209     for( int i = 0; i < postman.length; i++ )
210       postman[i].stopDispatching();
211   }
212 
213   public void stopProcessing() {
214     server.stopProcessing();
215   }
216 
217   public void lowerStatus() {
218     for( int i = 0; i < postman.length; i++ )
219       postman[i].lowerStatus();
220     server.lowerStatus();
221   }
222 
223     /***
224     changed to public so that subclasses of the classes in this package can have access
225     (Simon) 
226     */
227   public String addressSought(String agent) {
228     // First we should clear 'asTable' of entries older than a predefined
229     // age so that our agent should query known nameservers for the
230     // receiver's address. This way, a receiver that went off-line and
231     // later comes online would be found.
232 
233     String name;
234     KeyValue data;
235     double now = context.now();
236     Enumeration enum = asTable.keys();
237     while( enum.hasMoreElements() ) {
238       name = (String) enum.nextElement();
239       data = (KeyValue)asTable.get(name);
240       if ( now-data.value >= context.getAddressBookRefresh() ) { // LL 040500 1.03b
241         asTable.remove(name);
242          context.MsgHandler().removeRule(data.key);
243       }
244     }
245     data = (KeyValue)asTable.get(agent);
246     if (data == null) {
247       // try contacting known nameservers to find agent's address
248       Performative query;
249       Address addr;
250       String key = context.newId();
251       String[] pattern = { "type", "inform", "in-reply-to", key };
252       context.MsgHandler().addRule(new MessageRuleImpl(key,pattern,
253          MessageActionImpl.EXECUTE_ONCE,this,"addressReceived")
254       );
255       for(int i = 0; i < context.nameservers().size(); i++ ) {
256          addr = (Address)context.nameservers().elementAt(i);
257          query = new Performative("query-ref");
258          query.setReceiver(addr.getName());
259          query.setReplyWith(key);
260          query.setContent("address_of " + agent);
261          sendMsg(query);
262       }
263 
264       // add receiver to list of agents whose addresses
265       // are being looked for
266       now = context.now();
267       if ( !context.nameservers().isEmpty() ) {
268          now += context.getAddressTimeout();
269          asTable.put(agent,new KeyValue(key,now));
270          return key;
271       }
272       else {
273          return null;
274       }
275     }
276 
277     else if ( data.value > context.now()){
278       return data.key;}
279     else{
280       return null;}
281   }
282 
283   public void addressReceived(Performative msg) {
284     String key = msg.getInReplyTo();
285     Address  address = ZeusParser.address(msg.getContent());
286     add(address);
287     asTable.remove(address.getName());
288     for(int i = 0; i < postman.length; i++ )
289        postman[i].addressReceived(key);
290   }
291 
292   public void logMessages(String agent, String tag) {
293     Assert.notNull(agent);
294     Assert.notNull(tag);
295     visualisers.put(agent,tag);
296   }
297 
298 
299   public void stopLoggingMessages(String agent) {
300     Assert.notNull(agent);
301     visualisers.remove(agent);
302   }
303 
304 
305   public void informVisualisers(Performative msg) {
306     Enumeration keys = visualisers.keys();
307     String agent, replyTag;
308     Performative inform;
309     while( keys.hasMoreElements() ) {
310       agent = (String) keys.nextElement();
311       replyTag = (String)visualisers.get(agent);
312       inform = new Performative("inform");
313       inform.setReceiver(agent);
314       inform.setInReplyTo(replyTag);
315       inform.setContent((msg.toString()).trim());
316       ccMail.enqueue(inform);
317       notifyMonitors(inform,QUEUE);
318     }
319   }
320 
321 
322   public Address getAddress() {
323     return myAddress;
324   }
325 
326 
327   public void shutdown() {
328     Address addr;
329     Performative msg;
330     // Deregister from Name Servers
331     for(int i = 0; i < context.nameservers().size(); i++ ) {
332       addr = (Address)context.nameservers().elementAt(i);
333       msg = new Performative("request");
334       msg.setContent("deregister");
335       msg.setReceiver(addr.getName());
336       sendMsg(msg);
337     }
338   }
339 
340 
341   public void postErrorMsg(Performative msg, String content) {
342     notifyMonitors(msg,NOT_DISPATCH);
343     String reply_with;
344     Time time;
345     Performative errorMsg = new Performative("failure");
346     errorMsg.setSender(myAddress.getName());
347     errorMsg.setReceiver(myAddress.getName());
348     errorMsg.setAddress(myAddress);
349     if ( (reply_with = msg.getReplyWith()) != null )
350        errorMsg.setInReplyTo(reply_with);
351     errorMsg.setContent(content + " " + msg);
352     if ( (time = context.currentTime()) != null )
353       errorMsg.setSendTime(time);
354     server.newMsg(errorMsg);
355   }
356     
357 
358   public void sendMsg(Performative msg) {
359     postman[0].push(msg);
360     notifyMonitors(msg,QUEUE);
361   }
362 
363 /***
364     redundant now
365     */
366   public  Performative nextMsg() {
367     Object obj = inMail.dequeue();
368     try { 
369         Performative perf = (Performative) obj;
370         return (perf);
371     } catch (Exception e) { 
372         e.printStackTrace(); 
373         return new Performative (obj.toString()); 
374         }
375         
376  
377   }
378 
379   public Vector listAddresses() {
380     Vector result = new Vector(10);
381     Enumeration enum = context.AddressBook().elements();
382     Address addr;
383     
384     while( enum.hasMoreElements() ) {
385       addr = (Address) enum.nextElement();
386       result.addElement(new ZeusAddress(addr));
387     }
388     return result;
389   }
390 
391   /***
392    * Use this method to add a MessageMonitor if your code needs to react to
393    * changes in the state of the mailbox. This is programatic alternative to
394    * writing reaction rules
395    */
396   public void addMessageMonitor(MessageMonitor monitor, long event_type) {
397       if ( (event_type & MessageEvent.RECEIVE_MASK) != 0 )
398          eventMonitor[RECEIVE].add(monitor);
399       if ( (event_type & MessageEvent.QUEUE_MASK) != 0 )
400          eventMonitor[QUEUE].add(monitor);
401       if ( (event_type & MessageEvent.DISPATCH_MASK) != 0 )
402          eventMonitor[DISPATCH].add(monitor);
403       if ( (event_type & MessageEvent.NOT_DISPATCH_MASK) != 0 )
404          eventMonitor[NOT_DISPATCH].add(monitor);
405   }
406 
407   public void removeMessageMonitor(MessageMonitor monitor, long event_type) {
408       if ( (event_type & MessageEvent.RECEIVE_MASK) != 0 )
409          eventMonitor[RECEIVE].remove(monitor);
410       if ( (event_type & MessageEvent.QUEUE_MASK) != 0 )
411          eventMonitor[QUEUE].remove(monitor);
412       if ( (event_type & MessageEvent.DISPATCH_MASK) != 0 )
413          eventMonitor[DISPATCH].remove(monitor);
414       if ( (event_type & MessageEvent.NOT_DISPATCH_MASK) != 0 )
415          eventMonitor[NOT_DISPATCH].remove(monitor);
416    }
417    
418    
419    public void notifyMonitors(Performative message, int type) {
420       if ( eventMonitor[type].isEmpty() ) return;
421 
422       MessageMonitor monitor;
423       MessageEvent event;
424       Enumeration enum = eventMonitor[type].elements();
425       switch(type) {
426          case RECEIVE:
427               event = new MessageEvent(this,message,MessageEvent.RECEIVE_MASK);
428               while( enum.hasMoreElements() ) {
429                  monitor = (MessageMonitor)enum.nextElement();
430                  monitor.messageReceivedEvent(event);
431               }
432               break;
433          case QUEUE:
434               event = new MessageEvent(this,message,MessageEvent.QUEUE_MASK);
435               while( enum.hasMoreElements() ) {
436                  monitor = (MessageMonitor)enum.nextElement();
437                  monitor.messageQueuedEvent(event);
438               }
439               break;
440          case DISPATCH:
441               event = new MessageEvent(this,message,MessageEvent.DISPATCH_MASK);
442               while( enum.hasMoreElements() ) {
443                  monitor = (MessageMonitor)enum.nextElement();
444                  monitor.messageDispatchedEvent(event);
445               }
446               break;
447          case NOT_DISPATCH:
448               event = new MessageEvent(this,message,MessageEvent.NOT_DISPATCH_MASK);
449               while( enum.hasMoreElements() ) {
450                  monitor = (MessageMonitor)enum.nextElement();
451                  monitor.messageNotDispatchedEvent(event);
452               }
453               break;
454       }
455    }
456    
457    
458    
459    /*** 
460     added so that transports can be accessed 
461     @since 1.1
462     @author Simon "guilty party" Thompson
463     */ 
464    public InTray getInTray () { 
465     return server; 
466    }
467 }