/***************************************************************** 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.ObjectOutputStream; import java.io.ObjectInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileFilter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import jade.core.Profile; import jade.core.AID; import jade.lang.acl.ACLCodec; import jade.lang.acl.StringACLCodec; import jade.util.Logger; import org.apache.commons.codec.binary.Base64; class FileMessageStorage implements MessageStorage { private static final String RECEIVER_PREFIX = "AID-"; private static final String MESSAGE_PREFIX = "MSG-"; private static final String FOREVER = "FOREVER"; private Logger myLogger = Logger.getMyLogger(getClass().getName()); public void init(Profile p) { // Retrieve the base directory from the profile String s = p.getParameter(PersistentDeliveryService.PERSISTENT_DELIVERY_BASEDIR, null); if(s == null) { s = "." + File.separator + "PersistentDeliveryStore"; } baseDir = new File(s); if(!baseDir.exists()) { baseDir.mkdir(); } } public synchronized String store(GenericMessage msg, AID receiver) throws IOException { // Generate the subdirectory name by hashing the receiver AID File subDir = getMessageFolder(receiver); // Generate the file name by hashing the receiver AID and the message itself File toStore = getMessageFile(subDir, msg, receiver); // If the file is already present, increment its copies count // If the file is not present, create it and write the data into it if(toStore.exists()) { incrementCounter(toStore); } else { createMessageFile(toStore, msg, receiver); } return toStore.getName(); } public synchronized void delete(String storeName, AID receiver) throws IOException { // Generate the subdirectory name by hashing the receiver AID File subDir = getMessageFolder(receiver); // Generate the file name by hashing the receiver AID and the message itself File toDelete = new File(subDir, storeName); // Decrement the counter (if 0, it deletes the file). If the subdirectory is empty, remove it as well decrementCounter(toDelete); if(subDir.list().length == 0) { subDir.delete(); } } public synchronized void loadAll(LoadListener ll) throws IOException { // Notify the listener that the load process started ll.loadStarted(""); // Scan all its valid subdirectories. File[] subdirs = baseDir.listFiles(new FileFilter() { public boolean accept(File f) { return f.isDirectory() && f.getName().startsWith(RECEIVER_PREFIX); } }); for(int i = 0; i < subdirs.length; i++) { File subdir = subdirs[i]; // Scan all its valid files. File[] files = subdir.listFiles(new FileFilter() { public boolean accept(File f) { return !f.isDirectory() && f.getName().startsWith(MESSAGE_PREFIX); } }); for(int j = 0; j < files.length; j++) { File toRead = files[j]; // Read the file content BufferedReader in = new BufferedReader(new FileReader(toRead)); // Read the number of copies String strHowMany = in.readLine(); long howMany = 1; try { howMany = Long.parseLong(strHowMany); } catch(NumberFormatException nfe) { // Do nothing; the default value will be used } try { // NL (23/01/04) GenericMessage are now stored using Java serialization String encodedMsg = in.readLine(); // String.getBytes is, in general, an irreversible operation. However, in this case, because // the content was previously encoded Base64, we can expect that we will have only valid Base64 chars. ByteArrayInputStream istream = new ByteArrayInputStream(Base64.decodeBase64(encodedMsg.getBytes("US-ASCII"))); ObjectInputStream p = new ObjectInputStream(istream); GenericMessage message = (GenericMessage) p.readObject(); istream.close(); // Use an ACL codec to read in the receiver AID StringACLCodec codec = new StringACLCodec(in, null); // Read the receiver AID AID receiver = codec.decodeAID(); // Notify the listener that a new item was loaded for(int k = 0; k < howMany; k++) { ll.itemLoaded(toRead.getName(), message, receiver); } } catch(ACLCodec.CodecException ce) { System.err.println("Error reading file " + toRead.getName() + " [" + ce.getMessage() + "]"); } catch(ClassNotFoundException cnfe) { System.err.println("Error reading file " + toRead.getName() + " [" + cnfe.getMessage() + "]"); } finally { in.close(); } } } // Notify the listener that the load process ended ll.loadEnded(""); } private File getMessageFolder(AID receiver) throws IOException { String hashedName = RECEIVER_PREFIX + receiver.hashCode(); File folder = new File(baseDir, hashedName); if(!folder.exists()) { folder.mkdir(); } return folder; } private File getMessageFile(File subDir, GenericMessage msg, AID receiver) throws IOException { long hc1 = receiver.hashCode(); long hc2 = msg.toString().hashCode(); String hashedName = MESSAGE_PREFIX + (hc1*2 + hc2); File message = new File(subDir, hashedName); return message; } private void incrementCounter(File f) throws IOException { BufferedReader in = new BufferedReader(new FileReader(f)); File tmp = File.createTempFile("JADE", ".tmp"); String s = in.readLine(); try { long counter = Long.parseLong(s); BufferedWriter out = new BufferedWriter(new FileWriter(tmp)); try { counter++; s = Long.toString(counter); out.write(s, 0, s.length()); out.newLine(); s = in.readLine(); while(s != null) { out.write(s, 0, s.length()); out.newLine(); s = in.readLine(); } } finally { out.close(); } } catch(NumberFormatException nfe) { nfe.printStackTrace(); } finally { in.close(); } f.delete(); tmp.renameTo(f); } private void decrementCounter(File f) throws IOException { BufferedReader in = new BufferedReader(new FileReader(f)); File tmp = File.createTempFile("JADE", ".tmp"); String s = in.readLine(); try { long counter = Long.parseLong(s); counter--; if(counter == 0) { in.close(); f.delete(); } else { BufferedWriter out = new BufferedWriter(new FileWriter(tmp)); try { s = Long.toString(counter); out.write(s, 0, s.length()); out.newLine(); s = in.readLine(); while(s != null) { out.write(s, 0, s.length()); out.newLine(); s = in.readLine(); } } finally { in.close(); out.close(); } f.delete(); tmp.renameTo(f); } } catch(NumberFormatException nfe) { in.close(); nfe.printStackTrace(); } } private void createMessageFile(File toStore, GenericMessage msg, AID receiver) throws IOException { BufferedWriter out = null; try { toStore.createNewFile(); out = new BufferedWriter(new FileWriter(toStore)); // Write the number of message copies (of course is 1 to begin with) out.write("1", 0, 1); out.newLine(); // NL (23/01/04) Now write a serialized GenericMessage ByteArrayOutputStream ostream = new ByteArrayOutputStream(); ObjectOutputStream p = new ObjectOutputStream(ostream); p.writeObject(msg); String strMessage = new String(Base64.encodeBase64(ostream.toByteArray()), "US-ASCII"); ostream.close(); out.write(strMessage, 0, strMessage.length()); out.newLine(); // Write the receiver AID in string format String strReceiver = receiver.toString(); out.write(strReceiver, 0, strReceiver.length()); out.newLine(); } catch (NoClassDefFoundError er) { myLogger.log(Logger.WARNING, "*********** Cannot store message: the Persistent Delivery FileMessageStorage requires the commons-codec jar file to be in the classpath ***********"); } finally { if(out != null) { out.close(); } } } private File baseDir; }