/***************************************************************** JADE - Java Agent DEvelopment Framework is a framework to develop multi-agent systems in compliance with the FIPA specifications. Copyright (C) 2000 CSELT S.p.A. GNU Lesser General Public License This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 2.1 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *****************************************************************/ package jade.core.messaging; //#J2ME_EXCLUDE_FILE import java.io.IOException; import jade.core.AID; import jade.core.Profile; import jade.util.leap.List; import jade.util.leap.LinkedList; import jade.util.leap.Map; import jade.util.leap.HashMap; import jade.util.leap.Iterator; /** * This class supports the ACL persistent delivery service, managing * actual ACL messages storage, scheduled message delivery and other * utility tasks related to the service. * * @author Giovanni Rimassa - FRAMeTech s.r.l. * @author Nicolas Lhuillier - Motorola * */ class PersistentDeliveryManager { public static synchronized PersistentDeliveryManager instance(Profile p, MessageManager.Channel ch) { if(theInstance == null) { theInstance = new PersistentDeliveryManager(); theInstance.initialize(p, ch); } return theInstance; } // How often to check for expired deliveries private static final long DEFAULT_SENDFAILUREPERIOD = 60*1000; // One minute private static final String FILE_STORAGE_SHORTCUT = "file"; // Default storage class private static final String DEFAULT_STORAGE = "jade.core.messaging.PersistentDeliveryManager$DummyStorage"; private static final String FILE_STORAGE = "jade.core.messaging.FileMessageStorage"; private static class DeliveryItem { public DeliveryItem(GenericMessage msg, AID id, MessageManager.Channel ch, String sid) { toDeliver = msg; receiver = id; channel = ch; storeName = sid; } public GenericMessage getMessage() { return toDeliver; } public AID getReceiver() { return receiver; } public MessageManager.Channel getChannel() { return channel; } public String getStoreName() { return storeName; } private GenericMessage toDeliver; private AID receiver; private MessageManager.Channel channel; private String storeName; } // End of DeliveryItem class private class ExpirationChecker implements Runnable { public ExpirationChecker(long t) { period = t; myThread = new Thread(this, "Persistent Delivery Service -- Expiration Checker Thread"); } public void run() { while(active) { try { Thread.sleep(period); synchronized(pendingMessages) { // Try to send all stored messages... // If the receiver still not exists and the due date has elapsed // the sender will get back a FAILURE Object[] keys = pendingMessages.keySet().toArray(); for(int i = 0; i < keys.length; i++) { flushMessages((AID) keys[i]); } } } catch (InterruptedException ie) { // Just do nothing } } } public void start() { active = true; myThread.start(); } public void stop() { active = false; myThread.interrupt(); } private boolean active = false; private long period; private Thread myThread; } // End of ExpirationChecker class public static class DummyStorage implements MessageStorage { public void init(Profile p) { // Do nothing } public String store(GenericMessage msg, AID receiver) throws IOException { // Do nothing return null; } public void delete(String storeName, AID receiver) throws IOException { // Do nothing } public void loadAll(LoadListener il) throws IOException { // Do nothing } } // End of DummyStorage class public void initialize(Profile p, MessageManager.Channel ch) { users = 0; myMessageManager = MessageManager.instance(p); deliveryChannel = ch; try { // Choose the persistent storage method String storageClass = p.getParameter(PersistentDeliveryService.PERSISTENT_DELIVERY_STORAGEMETHOD,DEFAULT_STORAGE); if (FILE_STORAGE_SHORTCUT.equalsIgnoreCase(storageClass)) { storageClass = FILE_STORAGE; } storage = (MessageStorage)Class.forName(storageClass).newInstance(); storage.init(p); // Load all data persisted from previous sessions storage.loadAll(new MessageStorage.LoadListener() { public void loadStarted(String storeName) { System.out.println("--> Load BEGIN <--"); } public void itemLoaded(String storeName, GenericMessage msg, AID receiver) { // Put the item into the pending messages table synchronized(pendingMessages) { List msgs = (List)pendingMessages.get(receiver); if(msgs == null) { msgs = new LinkedList(); pendingMessages.put(receiver, msgs); } DeliveryItem item = new DeliveryItem(msg, receiver, deliveryChannel, storeName); msgs.add(item); } System.out.println("Message for <" + receiver.getLocalName() + ">"); } public void loadEnded(String storeName) { System.out.println("--> Load END <--"); } }); } catch(IOException ioe) { ioe.printStackTrace(); } catch(Exception e) { e.printStackTrace(); } sendFailurePeriod = DEFAULT_SENDFAILUREPERIOD; String s = p.getParameter(PersistentDeliveryService.PERSISTENT_DELIVERY_SENDFAILUREPERIOD, null); if(s != null) { try { sendFailurePeriod = Long.parseLong(s); } catch(NumberFormatException nfe) { // Do nothing: the default value will be used... } } } public void storeMessage(String storeName, GenericMessage msg, AID receiver) throws IOException { // Store the ACL message and its receiver for later re-delivery... synchronized(pendingMessages) { List msgs = (List)pendingMessages.get(receiver); if(msgs == null) { msgs = new LinkedList(); pendingMessages.put(receiver, msgs); } String tmpName = storage.store(msg, receiver); msgs.add(new DeliveryItem(msg, receiver, deliveryChannel, tmpName)); } } public int flushMessages(AID receiver) { // Send messages for this agent, if any... int cnt = 0; List l = null; synchronized(pendingMessages) { l = (List)pendingMessages.remove(receiver); } if(l != null) { Iterator it = l.iterator(); while(it.hasNext()) { DeliveryItem item = (DeliveryItem)it.next(); retry(item); cnt++; } } return cnt; } public synchronized void start() { if(users == 0) { failureSender = new ExpirationChecker(sendFailurePeriod); failureSender.start(); } users++; } public synchronized void stop() { users--; if(users == 0) { failureSender.stop(); } } // A shared instance to have a single thread pool private static PersistentDeliveryManager theInstance; // FIXME: Maybe a table, indexed by a profile subset, would be better? private PersistentDeliveryManager() { } private void retry(DeliveryItem item) { // Remove the message from the storage try { storage.delete(item.getStoreName(), item.getReceiver()); } catch(IOException ioe) { ioe.printStackTrace(); } // Deliver it myMessageManager.deliver(item.getMessage(), item.getReceiver(), item.getChannel()); } // The component managing asynchronous message delivery and retries private MessageManager myMessageManager; // The actual channel over which messages will be sent private MessageManager.Channel deliveryChannel; // How often pending messages due date will be checked (the // message will be sent out if expired) private long sendFailurePeriod; // How many containers are sharing this active component private long users; // The table of undelivered messages to send private Map pendingMessages = new HashMap(); // The active object that periodically checks the due date of ACL // messages and sends them after it expired private ExpirationChecker failureSender; // The component performing the actual storage and retrieval from // a persistent support private MessageStorage storage; }