/*****************************************************************
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;
}