/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.imapserver;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.phoenix.BlockContext;
import org.apache.james.AccessControlException;
import org.apache.james.AuthorizationException;
import org.apache.james.core.MimeMessageWrapper;
import org.apache.james.services.UsersRepository;
import org.apache.james.services.UsersStore;
import org.apache.james.util.Assert;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import java.io.*;
import java.util.*;
/**
* Object representing an IMAP4rev1 mailbox (folder) on a local file system.
* The mailbox contains messages, message attributes and an access control
* list.
*
* <p> from Interface Mailbox
*
* <p>Mailbox Related Flags (rfc2060 name attributes)
* <br> \Noinferiors It is not possible for any child levels of hierarchy
* to exist under this name; no child levels exist now and none can be created
* in the future.
* <br> \Noselect It is not possible to use this name as a selectable
* mailbox.
* <br> \Marked The mailbox has been marked "interesting" by the
* server; the mailbox probably contains messages that have been added since
* the last time the mailbox was selected.
* <br> \Unmarked The mailbox does not contain any additional
* messages since the last time the mailbox was selected.
*
* <p>Message related flags.
* <br>The flags allowed per message are specific to each mailbox.
* <br>The minimum list (rfc2060 system flags) is:
* <br> \Seen Message has been read
* <br> \Answered Message has been answered
* <br> \Flagged Message is "flagged" for urgent/special attention
* <br> \Deleted Message is "deleted" for removal by later EXPUNGE
* <br> \Draft Message has not completed composition (marked as a draft).
* <br> \Recent Message is "recently" arrived in this mailbox. This
* session is the first session to have been notified about this message;
* subsequent sessions will not see \Recent set for this message. This flag
* can not be altered by the client.
* <br> If it is not possible to determine whether or not this
* session is the first session to be notified about a message, then that
* message SHOULD be considered recent.
*
* <p> From interface ACL </p>
*
* The standard rights in RFC2086 are:
* <br>l - lookup (mailbox is visible to LIST/LSUB commands)
* <br>r - read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL, SEARCH,
* COPY from mailbox)
* <br>s - keep seen/unseen information across sessions (STORE SEEN flag)
* <br>w - write (STORE flags other than SEEN and DELETED)
* <br>i - insert (perform APPEND, COPY into mailbox)
* <br>p - post (send mail to submission address for mailbox, not enforced by
* IMAP4 itself)
* <br>c - create (CREATE new sub-mailboxes in any implementation-defined
* hierarchy)
* <br>d - delete (STORE DELETED flag, perform EXPUNGE)
* <br>a - administer (perform SETACL)
*
* <p> Serialization. Not all fields are serialized. Dispose() must be called to
* write serialized fiels to disc before finishing with this object.
*
* <p> Deserialization. On recover from disc, configure, compose,
* contextualize and reInit must be called before object is ready for use.
*
* Reference: RFC 2060
* @author <a href="mailto:charles@benett1.demon.co.uk">Charles Benett</a>
* @version 0.1 on 14 Dec 2000
*/
public class FileMailbox
extends AbstractLogEnabled
implements ACLMailbox, Serializable {
public static final String MAILBOX_FILE_NAME = "mailbox.mbr";
private static final String MESSAGE_EXTENSION = ".msg";
private static final String ATTRIBUTES_EXTENSION = ".att";
private static final String FLAGS_EXTENSION = ".flags";
private static final int NUMBER_OF_RIGHTS = 9;
// Map string identities to boolean[NUMBER_OF_RIGHTS] arrays of rights
//The rights are, in order: l,r,s,w,i,p,c,d,a
private static final int LOOKUP = 0;
private static final int READ = 1;
private static final int KEEP_SEEN = 2;
private static final int WRITE = 3;
private static final int INSERT = 4;
private static final int POST = 5;
private static final int CREATE = 6;
private static final int DELETE = 7;
private static final int ADMIN = 8;
private static final boolean[] NO_RIGHTS
= {false, false, false, false, false, false, false, false, false};
private static final boolean[] ALL_RIGHTS
= {true, true, true, true, true, true, true, true, true};
private static final String DENY_ACCESS = "Access denied by ACL";
private static final String DENY_AUTH = "Action not authorized for: ";
private static final String OPTIONAL_RIGHTS = "l r s w i p c d a";
private static final char[] DELETE_MODS
= {'-', 'l', 'r', 's', 'w', 'i', 'p', 'c', 'd', 'a'};
/* Transient fields - reInit must be called on recover from disc. */
private transient BlockContext context;
private transient Configuration conf;
private transient ComponentManager compMgr;
private transient UsersRepository localUsers;
private transient HashSet listeners;
/* Serialized fileds */
private String path; // does not end with File.separator
private File directory ;
private String owner;
private String absoluteName;
private Map acl;
private String name;
private int uidValidity;
private int highestUID;
private int mailboxSize; // octets
private boolean inferiorsAllowed;
private boolean marked;
private boolean notSelectableByAnyone;
// The message sequence number of a msg is its index in List sequence + 1
private List sequence; //List of UIDs of messages in mailbox
// Set of UIDs of messages with recent flag set
private Set recentMessages;
// Set of UIDs of messages with delete flag set
private Set messagesForDeletion;
//map of user String to Integer uid, 0 for no unseen messages
private Map oldestUnseenMessage;
// Set of subscribed users.
private Set subscribedUsers = new HashSet(1000);
public void configure(Configuration conf) throws ConfigurationException {
this.conf = conf;
}
public void contextualize(Context context) {
this.context = (BlockContext)context;
}
public void compose(ComponentManager comp) {
compMgr = comp;
}
public void prepareMailbox(String user,
String absName,
String initialAdminUser,
int uidValidity )
{
Assert.isTrue( Assert.ON &&
user != null && user.length() > 0 );
owner = user;
Assert.isTrue( Assert.ON &&
absName != null && (absName.length() > 0));
absoluteName = absName;
Assert.isTrue( Assert.ON &&
initialAdminUser != null && initialAdminUser.length() > 0 );
acl = new HashMap(7);
acl.put(initialAdminUser, ALL_RIGHTS);
//acl = new SimpleACL(initialAdminUser);
Assert.isTrue( Assert.ON &&
uidValidity > 0 );
this.uidValidity = uidValidity;
}
public void initialize() throws Exception {
highestUID = 0;
mailboxSize = 0;
inferiorsAllowed = true;
marked = false;
notSelectableByAnyone = false;
oldestUnseenMessage = new HashMap();
listeners = new HashSet();
sequence = new ArrayList();
recentMessages = new HashSet();
messagesForDeletion = new HashSet();
getLogger().info("FileMailbox init for " + absoluteName);
UsersStore usersStore = (UsersStore)compMgr.lookup( "org.apache.james.services.UsersStore" );
localUsers = usersStore.getRepository("LocalUsers");
// String rootPath = context.getBaseDirectory().getAbsolutePath() + File.separator
// + conf.getChild("mailboxRepository").getValue();
String rootPath = conf.getChild("mailboxRepository").getValue();
if (!rootPath.endsWith(File.separator)) {
rootPath = rootPath + File.separator;
}
// Configuration namespaces = conf.getChild("namespaces");
// String namespaceToken = namespaces.getAttribute("token");
// String privateNamespace
// = namespaces.getChild("privateNamespace").getValue();
// String privateNamespaceSeparator
// = namespaces.getChild("privateNamespace").getAttribute("separator");
// String sharedNamespace
// = namespaces.getChild("sharedNamespace").getValue();
// String sharedNamespaceSeparator
// = namespaces.getChild("sharedNamespace").getAttribute("separator");
path = getPath( absoluteName, owner, rootPath );
name = absoluteName.substring(absoluteName.lastIndexOf( JamesHost.HIERARCHY_SEPARATOR ) + 1);
if (name.equals(owner)) {
name = "";
}
// if ( absoluteName.startsWith( JamesHost.USER_NAMESPACE_PREFIX )) {
// String path1
// = absoluteName.substring(privateNamespace.length()
// + privateNamespaceSeparator.length()
// + owner.length());
// path = rootPath + owner
// + path1.replace(privateNamespaceSeparator.charAt(0),
// File.separatorChar);
// } else if (absoluteName.startsWith(sharedNamespace)) {
// String path2 = absoluteName.substring(namespaceToken.length());
// path = rootPath + path2.replace(sharedNamespaceSeparator.charAt(0),
// File.separatorChar);
// name = absoluteName.substring(absoluteName.lastIndexOf(sharedNamespaceSeparator) + 1);
// if (name.equals(sharedNamespace)) {
// name = "";
// }
// } else {
// getLogger().error("FileMailbox init error: unknown namespace - "
// + absoluteName);
// throw new RuntimeException("Unknown namespace for absoluteName"
// +" argument for a FileMailbox"
// +" constructor." + absoluteName);
// }
//Check for writable directory
File mailboxDir = new File(path);
if (mailboxDir.exists()) {
throw new RuntimeException("Error: Attempt to overwrite mailbox directory at " + path);
} else if (! mailboxDir.mkdir()){
throw new RuntimeException("Error: Cannot create mailbox directory at " + path);
} else if (!mailboxDir.canWrite()) {
throw new RuntimeException("Error: Cannot write to directory at " + path);
}
writeMailbox();
getLogger().info("FileMailbox init complete: " + absoluteName);
}
/**
* Return the file-system path to a given absoluteName mailbox.
*
* @param absoluteName the user-independent name of the mailbox
* @param owner string name of owner of mailbox
*/
private String getPath( String absoluteName, String owner, String rootPath )
{
Assert.isTrue( Assert.ON &&
absoluteName.startsWith( JamesHost.NAMESPACE_TOKEN ) );
// Remove the leading '#' and replace Hierarchy separators with file separators.
String filePath = absoluteName.substring( JamesHost.NAMESPACE_TOKEN.length() );
filePath = filePath.replace( JamesHost.HIERARCHY_SEPARATOR_CHAR, File.separatorChar );
return rootPath + filePath;
// String path;
// if (absoluteName.startsWith(privateNamespace)) {
// String path1 = rootPath + owner;
// String path2
// = absoluteName.substring(privateNamespace.length()
// + privateNamespaceSeparator.length()
// + owner.length());
// path = path1 + path2.replace(privateNamespaceSeparator.charAt(0), File.separatorChar);
// } else if (absoluteName.startsWith(sharedNamespace)) {
// String path3 = absoluteName.substring(namespaceToken.length());
// path = rootPath + File.separator + path3.replace(privateNamespaceSeparator.charAt(0), File.separatorChar);
// } else {
// path = null;
// }
// return path;
}
/**
* Re-initialises mailbox after reading from file-system. Cannot assume that this is the same instance of James that wrote it.
*
* <p> Contract is that re-init must be called after configure, contextualize, compose.
*/
public void reinitialize() throws Exception {
listeners = new HashSet();
getLogger().info("FileMailbox reInit for " + absoluteName);
UsersStore usersStore = (UsersStore)compMgr.lookup( "org.apache.james.services.UsersStore" );
localUsers = usersStore.getRepository("LocalUsers");
String rootPath
= conf.getChild("mailboxRepository").getValue();
if (!rootPath.endsWith(File.separator)) {
rootPath = rootPath + File.separator;
}
}
/**
* Call when host has finished with a mailbox.
* This is really a stop rather than destroy.
* Writes current mailbox object to disc.
*/
public void dispose() {
writeMailbox();
getLogger().info("FileMailbox object destroyed: " + absoluteName);
}
/**
* Permanently deletes the mailbox.
*/
public void removeMailbox()
{
// First delete the mailbox file
String mailboxRecordFile = path + File.separator + MAILBOX_FILE_NAME;
File mailboxRecord = new File( mailboxRecordFile );
Assert.isTrue( Assert.ON &&
mailboxRecord.exists() &&
mailboxRecord.isFile() );
mailboxRecord.delete();
// Check for empty directory before deleting.
File mailboxDir = new File(path);
Assert.isTrue( Assert.ON &&
mailboxDir.exists() );
Assert.isTrue( Assert.ON &&
mailboxDir.isDirectory() );
Assert.isTrue( Assert.ON &&
mailboxDir.list().length == 0 );
mailboxDir.delete();
}
/**
* Returns true once this Mailbox has been checkpointed.
* This implementation just writes its mailbox record to disc. Unless something is
* broken all messages added, amended or removed from this mailbox will have been
* handled by this object.
* <br> This implementation always returns true.
*
* @returns true
*/
public synchronized boolean checkpoint() {
writeMailbox();
getLogger().info("FileMailbox: " + absoluteName + " checkpointed.");
return true;
}
/**
* Remove \Recent flag from all messages in mailbox. Should be called
* whenever a user session finishes.
*/
public synchronized void unsetRecent() {
Iterator it = recentMessages.iterator();
while(it.hasNext()) {
Integer uidObj =(Integer)it.next();
int uid = uidObj.intValue();
Flags flags = readFlags(uid);
if (flags != null) {
flags.setRecent(false);
writeFlags(uid, flags);
}
}
recentMessages.clear();
}
// Methods that don't involve the ACL ------------------
/**
* Returns name of this mailbox relative to its parent in the mailbox
* hierarchy.
* Example: 'NewIdeas'
*
* @returns String name of mailbox relative to its immeadiate parent in
* the mailbox hierarchy.
*/
public String getName() {
return name;
}
/**
* Returns absolute, that is user-independent, hierarchical name of
* mailbox (including namespace)
* Example: '#mail.fred.flintstone.apache.James.NewIdeas'
*
* @returns String name of mailbox in absolute form
*/
public String getAbsoluteName() {
return absoluteName;
}
/** Returns namespace starting with namespace token.
* Example: '#mail'
*
* @returns String containing user-independent namespace of this mailbox.
*/
// public String getNamespace();
/** Returns true if the argument is the relative or absolute name of
* this mailbox
*
* @param name possible name for this Mailbox
* @returns true if name matches either getName() or getAbsoluteName()
*/
public boolean matchesName(String testName) {
return (name == testName || name == absoluteName);
}
/**
* Returns the current unique id validity value of this mailbox.
*
* @returns int current 32 bit unique id validity value of this mailbox
*/
public int getUIDValidity() {
return uidValidity;
}
/**
* Returns the 32 bit uid available for the next message.
*
* @returns int the next UID that would be used.
*/
public int getNextUID() {
return highestUID + 1;
}
/**
* Returns mailbox size in octets. Should only include actual messages
* and not any implementation-specific data, such as message attributes.
*
* @returns int mailbox size in octets
*/
public synchronized int getMailboxSize() {
return mailboxSize;
}
/**
* Indicates if child folders may be created. It does not indicate which
* users can create child folders.
*
* @returns boolean TRUE if inferiors aree allowed
*/
public boolean getInferiorsAllowed() {
return inferiorsAllowed;
}
/**
* Indicates that messages have been added since this mailbox was last
* selected by any user.
*
* @returns boolean TRUE if new messages since any user last selected
* mailbox
*/
public synchronized boolean isMarked() {
return marked;
}
/**
* Returns all flags supported by this mailbox.
* e.g. \Answered \Deleted
*
* @returns String a space seperated list of message flags which are
* supported by this mailbox.
*/
public String getSupportedFlags() {
return SYSTEM_FLAGS;
}
/**
* Indicates no of messages with \Recent flag set
*
* @returns int no of messages with \Recent flag set
*/
public synchronized int getRecent() {
return recentMessages.size();
}
/**
* Indicates the oldest unseen message for the specified user.
*
* @returns int Message Sequence Number of first message without \Seen
* flag set for this User. 0 means no unseen messages in this mailbox.
*/
public synchronized int getOldestUnseen(String user) {
int response = 0;
if (oldestUnseenMessage.containsKey(user)) {
Integer uidObj = ((Integer)oldestUnseenMessage.get(user));
if (! (uidObj.intValue() == 0)) {
response = sequence.indexOf(uidObj) + 1;
}
} else {
if (sequence.size() > 0) {
response = 1;
oldestUnseenMessage.put(user, (Integer)sequence.get(0));
} else {
oldestUnseenMessage.put(user, (new Integer(0)));
}
}
return response;
}
/**
* Indicates number of messages in folder
*
* @returns int number of messages
*/
public synchronized int getExists() {
return sequence.size();
}
/**
* Indicates the number of unseen messages for the specified user.
*
* @returns int number of messages without \Seen flag set for this User.
*/
public int getUnseen(String user) {
if (oldestUnseenMessage.containsKey(user)) {
int response = 0; //indicates no unseen messages
Integer uidObj = ((Integer)oldestUnseenMessage.get(user));
int oldUID = uidObj.intValue();
if (oldUID != 0) {
ListIterator lit
= sequence.listIterator(sequence.indexOf(uidObj));
while (lit.hasNext() ) {
int uid = ((Integer)lit.next()).intValue();
Flags flags = readFlags(uid);
if (!flags.isSeen(user)) {
response ++;
}
}
}
return response;
} else { // user has never selected mailbox
return sequence.size();
}
}
/** Mailbox Events are used to inform registered listeners of events in the Mailbox.
* E.g. if mail is delivered to an Inbox or if another user appends/ deletes a message.
*/
public synchronized void addMailboxEventListener(MailboxEventListener mel) {
listeners.add(mel);
}
public synchronized void removeMailboxEventListener(MailboxEventListener mel) {
listeners.remove(mel);
}
/**
* Mark this mailbox as not selectable by anyone.
* Example folders at the roots of hierarchies, e. #mail for each user.
*
* @param state true if folder is not selectable by anyone
*/
public void setNotSelectableByAnyone(boolean state) {
notSelectableByAnyone = state;
}
public boolean isNotSelectableByAnyone() {
return notSelectableByAnyone;
}
// Methods for the embedded ACL ------------------------
/**
* Store access rights for a given identity.
* The setter is the user setting the rights, the identifier is the user
* whose rights are affected.
* The setter and identifier arguments must be non-null and non-empty.
* The modification argument must be non-null and follow the syntax of the
* third argument to a SETACL command.
* If the modification argument is an empty string, that identifier is
* removed from the ACL, if currently present.
*
* @param setter String representing user attempting to set rights, must
* be non-null and non-empty
* @param identity String representing user whose rights are being set,
* must be non-null and non-empty
* @param modification String representing the change in rights, following
* the syntax specified in rfc 2086. Note a blank string means delete all
* rights for given identity.
* @returns true if requested modification succeeded. A return value of
* false means an error other than an AccessControlException or
* AuthorizationException.
* @throws AccessControlException if setter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if specified setter does not have the
* administer right (ie the right to write ACL rights), or if the result
* of this method would leave no identities with admin rights.
*/
public boolean setRights(String setter, String identifier,
String modification)
throws AccessControlException, AuthorizationException {
boolean[] settersRights = (boolean[]) acl.get(setter);
if (settersRights == null
|| (settersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (settersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + setter);
}
boolean[] existingRights = (boolean[]) acl.get(identifier);
char[] mods = modification.toCharArray();
if (mods.length == 0) { // means delete all
mods = DELETE_MODS;
}
if(existingRights == null) {
if ( mods[0] == REMOVE_RIGHTS ) {
return false;
} else {
existingRights = new boolean[NUMBER_OF_RIGHTS];
System.arraycopy(NO_RIGHTS, 0, existingRights, 0,
NUMBER_OF_RIGHTS);
}
}
boolean change;
boolean[] rights = new boolean[NUMBER_OF_RIGHTS];
if (mods[0] == ADD_RIGHTS) {
change = true;
System.arraycopy(existingRights, 0, rights, 0,
NUMBER_OF_RIGHTS);
} else if (mods[0] == REMOVE_RIGHTS) {
change = false;
System.arraycopy(existingRights, 0, rights, 0,
NUMBER_OF_RIGHTS);
} else { // means replace
System.arraycopy(NO_RIGHTS, 0, rights, 0,
NUMBER_OF_RIGHTS);
char[] new_mods = new char[mods.length + 1];
System.arraycopy(mods, 0, new_mods, 1, mods.length);
mods = new_mods;
change = true;
}
for (int i=1; i <mods.length; i++) {
switch(mods[i]) {
case LOOKUP_RIGHTS: rights[LOOKUP] = change;
break;
case READ_RIGHTS: rights[READ] = change;
break;
case KEEP_SEEN_RIGHTS: rights[KEEP_SEEN] = change;
break;
case WRITE_RIGHTS: rights[WRITE] = change;
break;
case INSERT_RIGHTS: rights[INSERT] = change;
break;
case POST_RIGHTS: rights[POST] = change;
break;
case CREATE_RIGHTS: rights[CREATE] = change;
break;
case DELETE_RIGHTS: rights[DELETE] = change;
break;
case ADMIN_RIGHTS: rights[ADMIN] = change;
break;
default: return false;
}
}
// All rights above lookup require lookup
if(rights[LOOKUP] == false && !Arrays.equals(rights, NO_RIGHTS)) {
return false;
}
// Each right requires all the rights before it.
int count = 0;
for (int i=1; i< NUMBER_OF_RIGHTS; i++) {
if(rights[i-1] ^ rights[i]) {
count++;
}
}
switch (count) {
case 0: // now Admin or deleted
if (rights[ADMIN]) {
acl.put(identifier, rights);
break;
} else {
if (otherAdmin(identifier)) {
acl.remove(identifier);
break;
} else {
return false;
}
}
case 2: // not allowed
return false;
case 1: // not Admin, check there remains an Admin
// Iterator namesIt = acl.keySet().iterator();
//boolean otherAdmin = false;
//while(namesIt.hasNext() && !otherAdmin) {
//String name = (String)namesIt.next();
//if (name != identifier) {
// boolean[] otherRights = (boolean[]) acl.get(name);
// otherAdmin = otherRights[ADMIN];
//}
//}
if (otherAdmin(identifier)) {
acl.put(identifier, rights);
break;
} else {
return false;
}
default: // not allowed
return false;
}
writeMailbox();
return true;
}
/**
* Check there is a person other than identifier who has Admin rights.
*/
private boolean otherAdmin(String identifier) {
Iterator namesIt = acl.keySet().iterator();
boolean result = false;
while(namesIt.hasNext() && !result) {
String name = (String)namesIt.next();
if (!name.equals(identifier)) {
boolean[] otherRights = (boolean[]) acl.get(name);
result = otherRights[ADMIN];
}
}
return result;
}
/**
* Retrieve access rights for a specific identity.
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
public String getRights(String getter, String identity)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (!getter.equals(identity) && gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
boolean[] rights = (boolean[]) acl.get(identity);
if (rights == null) {
return null;
} else {
StringBuffer buf = new StringBuffer(NUMBER_OF_RIGHTS);
for (int i = 0; i<NUMBER_OF_RIGHTS; i++) {
if (rights[i]) {
buf.append(RIGHTS[i]);
}
}
return buf.toString();
}
}
/**
* Retrieves a String of one or more <identity space rights> who have
* rights in this ACL
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @returns String of rights sets usingrfc2086 syntax
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL to this getter.
*/
public String getAllRights(String getter)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if ( gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
Iterator namesIt = acl.keySet().iterator();
StringBuffer response = new StringBuffer(20*acl.size());
while(namesIt.hasNext()) {
String name = (String)namesIt.next();
response.append("<" + name + " ");
boolean[] rights = (boolean[]) acl.get(name);
for (int i = 0; i<NUMBER_OF_RIGHTS; i++) {
if (rights[i]) {
response.append(RIGHTS[i]);
}
}
response.append("> ");
}
return response.toString();
}
/**
* Retrieve rights which will always be granted to the specified identity.
*
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* guaranteed rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
public String getRequiredRights(String getter, String identity)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (!getter.equals(identity) && gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
return "\"\"";
}
/**
* Retrieve rights which may be granted to the specified identity.
* @param getter String representing user attempting to get the rights,
* must be non-null and non-empty
* @param identity String representing user whose rights are being got,
* must be non-null and non-empty
* @returns String of rights usingrfc2086 syntax, empty if identity has no
* guaranteed rights in this mailbox.
* @throws AccessControlException if getter does not have lookup rights for
* this mailbox (ie they should not know this mailbox exists).
* @throws AuthorizationException if implementation does not wish to expose
* ACL for this identity to this getter.
*/
public String getOptionalRights(String getter, String identity)
throws AccessControlException, AuthorizationException {
boolean[] gettersRights = (boolean[]) acl.get(getter);
if (gettersRights == null
|| (gettersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
} else if (!getter.equals(identity) && gettersRights[ADMIN] == false) {
throw new AuthorizationException(DENY_AUTH + getter);
}
return OPTIONAL_RIGHTS;
}
/**
* Helper boolean methods.
* Provided for cases where you need to check the ACL before selecting the
* mailbox.
*
* @param username String representing user
* @returns true if user has the requested right.
* &throws AccessControlException if username does not have lookup rights.
* (Except for hasLookupRights which just returns false.
*/
public boolean hasLookupRights(String username) {
boolean[] usersRights = (boolean[]) acl.get(username);
return (( usersRights == null || (usersRights[LOOKUP] == false))
? false : true);
}
public boolean hasReadRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[READ];
}
public boolean hasKeepSeenRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[KEEP_SEEN];
}
public boolean hasWriteRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[WRITE];
}
public boolean hasInsertRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[INSERT];
}
public boolean hasCreateRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[CREATE];
}
public boolean hasDeleteRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[DELETE];
}
public boolean hasAdminRights(String username)
throws AccessControlException {
boolean[] usersRights = (boolean[]) acl.get(username);
if (usersRights == null || (usersRights[LOOKUP] == false)) {
throw new AccessControlException(DENY_ACCESS);
}
return usersRights[ADMIN];
}
// Mailbox methods using the ACL ---------------------------
/**
* Indicates if this folder may be selected by the specified user. Requires
* user to have at least read rights. It does not indicate whether user
* can write to mailbox
*
* @param username String represnting user
* @returns boolean TRUE if specified user can Select mailbox.
* @throws AccessControlException if username does not have lookup rights
*/
public synchronized boolean isSelectable(String username)
throws AccessControlException {
return ( ! notSelectableByAnyone && hasReadRights(username) );
}
/**
* Indicates if specified user can change any flag on a permanent basis,
* except for \Recent which can never be changed by a user.
*
* @param username String represnting user
* @returns true if specified user can change all flags permanently.
*/
public synchronized boolean allFlags(String username)
throws AccessControlException {
// relies on implementation that each right implies those
// before it in list: l,r,s,w,i,p,c,d,a
return hasDeleteRights(username);
}
/**
* Indicates which flags this user can change permanently. If allFlags()
* returns true for this user, then this method must have the same return
* value as getSupportedFlags.
*
* @param username String represnting user
* @returns String a space seperated list of message flags which this user
* can set permanently
*/
public synchronized String getPermanentFlags(String username)
throws AccessControlException {
if (hasDeleteRights(username)) {
return SYSTEM_FLAGS;
} else if (hasWriteRights(username)) {
return "\\Seen \\Answered \\Flagged \\Draft";
} else if (hasKeepSeenRights(username)) {
return "\\Seen";
} else {
return "";
}
}
/**
* Provides a reference to the access control list for this mailbox.
*
* @returns the AccessControlList for this Mailbox
*/
// public ACL getACL();
/**
* Indicates state in which the mailbox will be opened by specified user.
* A return value of true indicates Read Only, false indicates Read-Write
* and an AccessControlException is thrown if user does not have read
* rights.
* <p>Implementations decide if Read Only means only lookup and read
* rights (lr) or lookup, read and keep seen rights (lrs). This may even
* vary between mailboxes.
*
* @param username String represnting user
* @returns true if specified user can only open the mailbox Read-Only.
* @throws AccessControlException if the user can not open this mailbox
* at least Read-Only.
*/
public synchronized boolean isReadOnly(String username)
throws AccessControlException {
return (! hasWriteRights(username));
}
// Message handling methods ---------------------------
/**
* Stores a message in this mailbox. User must have insert rights.
*
* @param message the MimeMessage to be stored
* @param username String represnting user
* @returns boolean true if successful
* @throws AccessControlException if username does not have lookup rights
* for this mailbox.
* @throws AuthorizationException if username has lookup rights but does
* not have insert rights.
*/
public synchronized boolean store(MimeMessage message, String username)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (message == null || username == null) {
getLogger().error("Null argument received in store.");
throw new IllegalArgumentException("Null argument received in store.");
}
if (!hasInsertRights(username)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to insert.");
}
SimpleMessageAttributes attrs = new SimpleMessageAttributes();
try {
setupLogger(attrs);
attrs.setAttributesFor(message);
} catch (javax.mail.MessagingException me) {
throw new RuntimeException("Exception creating SimpleMessageAttributes: " + me);
}
Flags flags = new Flags();
flags.initialize();
return store(message, username, attrs, flags);
}
/**
* Stores a message in this mailbox, using passed MessageAttributes and
* Flags. User must have insert rights.
* <br>Current implementation requires MessageAttributs to be of
* class SimpleMessageAttributes
*
* @param mail the message to be stored
* @param username String represnting user
* @param msgAttrs non-null MessageAttributes for use with this Message
* @returns boolean true if successful
* @throws AccessControlException if username does not have lookup
* rights for this mailbox.
* @throws AuthorizationException if username has lookup rights but does
* not have insert rights.
*/
public boolean store(MimeMessage message, String username,
MessageAttributes msgAttrs, Flags flags)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (msgAttrs == null || message == null || username == null) {
getLogger().error("Null argument received in store.");
throw new IllegalArgumentException("Null argument received in store.");
}
if (! (msgAttrs instanceof SimpleMessageAttributes)) {
getLogger().error("Wrong class for Attributes");
throw new IllegalArgumentException("Wrong class for Attributes");
}
SimpleMessageAttributes attrs = (SimpleMessageAttributes)msgAttrs;
int newUID = ++highestUID;
attrs.setUID(newUID);
sequence.add(new Integer(newUID));
attrs.setMessageSequenceNumber(sequence.size());
BufferedOutputStream outMsg = null;
ObjectOutputStream outAttrs = null;
try {
outMsg = new BufferedOutputStream( new FileOutputStream(path + File.separator + newUID + MESSAGE_EXTENSION));
message.writeTo(outMsg);
outMsg.close();
outAttrs = new ObjectOutputStream( new FileOutputStream(path + File.separator + newUID + ATTRIBUTES_EXTENSION));
outAttrs.writeObject(attrs);
outAttrs.close();
} catch(Exception e) {
getLogger().error("Error writing message to disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Mail: "
+ e);
} finally {
try {
outMsg.close();
outAttrs.close();
} catch (IOException ie) {
getLogger().error("Error closing streams: " + ie);
}
}
marked = true;
if (flags.isRecent()) {
recentMessages.add(new Integer(newUID));
}
if (flags.isDeleted()) {
messagesForDeletion.add(new Integer(newUID));
}
//if (!flags.isSeen(username)) {
//If a user had no unseen messages, they do, now.
Iterator it = oldestUnseenMessage.keySet().iterator();
while (it.hasNext()) {
String user = (String)it.next();
if ( ((Integer)oldestUnseenMessage.get(user)).intValue() == -1) {
oldestUnseenMessage.put(user, new Integer(newUID));
}
}
//}
writeFlags(newUID, flags);
getLogger().info("Mail " + newUID + " written in " + absoluteName);
return true;
}
/**
* Retrieves a message given a message sequence number.
*
* @param msn the message sequence number
* @param username String represnting user
* @returns an MimeMessageWrapper object containing the message, null if no message with
* the given msn.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized MimeMessageWrapper retrieve(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return retrieveUID(uid, user);
}
}
/**
* Retrieves a message given a unique identifier.
*
* @param uid the unique identifier of a message
* @param username String represnting user
* @returns an MimeMessageWrapper object containing the message, null if no message with
* the given msn.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized MimeMessageWrapper retrieveUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
MimeMessageWrapper response = null;
if (sequence.contains(new Integer(uid))) {
BufferedInputStream inMsg = null;
try {
MimeMessageFileSource source = new MimeMessageFileSource(path + File.separator + uid + MESSAGE_EXTENSION);
response = new MimeMessageWrapper(source);
inMsg.close();
} catch(Exception e) {
getLogger().error("Error reading message from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while retrieving Mail: "
+ e);
} finally {
try {
inMsg.close();
} catch (IOException ie) {
getLogger().error("Error closing streams: " + ie);
}
}
getLogger().info("MimeMessageWrapper " + uid + " read from " + absoluteName);
return response;
} else {
return null;
}
}
/**
* Marks a message for deletion given a message sequence number.
*
* @param msn the message sequence number
* @param username String represnting user
* @returns boolean true if successful.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean markDeleted(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasDeleteRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
Assert.notImplemented();
//TBD
return false;
}
/**
* Marks a message for deletion given a unique identifier.
*
* @param uidunique identifier
* @param username String represnting user
* @returns boolean true if successful, false if failed including no
* message with the given uid.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean markDeletedUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasDeleteRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
Assert.notImplemented();
//TBD
return false;
}
/**
* Returns the message attributes for a message.
*
* @param msn message sequence number
* @param username String represnting user
* @returns MessageAttributes for message, null if no such message.
* Changing the MessageAttributes object must not affect the actual
* MessageAttributes.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized MessageAttributes getMessageAttributes(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return getMessageAttributesUID(uid, user);
}
}
/**
* Returns the message attributes for a message.
*
* @param uid unique identifier
* @param username String represnting user
* @returns MessageAttributes for message, null if no such message.
* Changing the MessageAttributes object must not affect the actual
* MessageAttributes.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized MessageAttributes getMessageAttributesUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
SimpleMessageAttributes response = null;
if (sequence.contains(new Integer(uid))) {
ObjectInputStream inAttrs = null;
try {
inAttrs = new ObjectInputStream( new FileInputStream(path + File.separator + uid + ATTRIBUTES_EXTENSION));
response = (SimpleMessageAttributes)inAttrs.readObject();
setupLogger(response);
} catch(Exception e) {
getLogger().error("Error reading attributes from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while retrieving Message attributes: "
+ e);
} finally {
try {
inAttrs.close();
} catch (IOException ie) {
getLogger().error("Error closing streams: " + ie);
}
}
getLogger().info("MessageAttributes for " + uid + " read from " + absoluteName);
return response;
} else {
return null;
}
}
/**
* Updates the attributes of a message.This may be incorporated into setFlags().
*
* @param MessageAttributes of a message already in this Mailbox
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public boolean updateMessageAttributes(MessageAttributes attrs, String user)
throws AccessControlException, AuthorizationException {
if (!hasKeepSeenRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to store flags.");
}
int uid = attrs.getUID();
if (sequence.contains(new Integer(uid))) {
// Really, we should check whether the exact change is authorized.
ObjectOutputStream outAttrs = null;
try {
outAttrs = new ObjectOutputStream( new FileOutputStream(path + File.separator + uid + ATTRIBUTES_EXTENSION));
outAttrs.writeObject(attrs);
outAttrs.close();
} catch(Exception e) {
getLogger().error("Error writing message to disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Attributes: "
+ e);
} finally {
try {
outAttrs.close();
} catch (IOException ie) {
getLogger().error("Error closing streams: " + ie);
}
}
getLogger().info("MessageAttributes for " + uid + " written in " + absoluteName);
return true;
} else {
return false;
}
}
/**
* Get the IMAP-formatted String of flags for specified message.
*
* @param msn message sequence number for a message in this mailbox
* @param username String represnting user
* @returns flags for this message and user, null if no such message.
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized String getFlags(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return getFlagsUID(uid, user);
}
}
/**
* Get the IMAP-formatted String of flags for specified message.
*
* @param uid UniqueIdentifier for a message in this mailbox
* @param username String represnting user
* @returns flags for this message and user, null if no such message.
* @throws AccessControlException if user does not have lookup rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have read rights.
*/
public synchronized String getFlagsUID(int uid, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (!sequence.contains(new Integer(uid))) {
return null;
} else {
Flags flags = readFlags(uid);
return flags.getFlags(user);
}
}
/**
* Updates the flags for a message.
*
* @param msn MessageSequenceNumber of a message already in this Mailbox
* @param username String represnting user
* @param request IMAP-formatted String representing requested change to
* flags.
* @returns true if succeeded, false otherwise, including no such message
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean setFlags(int msn, String user, String request)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (!hasKeepSeenRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to store any flags.");
}
if (msn > sequence.size()) {
return false;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return setFlagsUID(uid, user, request);
}
}
/**
* Updates the flags for a message.
*
* @param uid Unique Identifier of a message already in this Mailbox
* @param username String represnting user
* @param request IMAP-formatted String representing requested change to
* flags.
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has lookup rights but does not
* have delete rights.
*/
public synchronized boolean setFlagsUID(int uid, String user, String request)
throws AccessControlException, AuthorizationException,
IllegalArgumentException {
if (!hasKeepSeenRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to store any flags.");
}
if ((request.toUpperCase().indexOf("DELETED") != -1) && (!hasDeleteRights(user))) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
if (sequence.contains(new Integer(uid))) {
Flags flags = readFlags(uid);
boolean wasRecent = flags.isRecent();
boolean wasDeleted = flags.isDeleted();
boolean wasSeen = flags.isSeen(user);
if (flags.setFlags(request, user)) {
if (flags.isDeleted()) {
if (! wasDeleted) { messagesForDeletion.add(new Integer(uid)); }
}
if (flags.isSeen(user) != wasSeen) {
if (flags.isSeen(user)) {
int previousOld = ((Integer)oldestUnseenMessage.get(user)).intValue();
if (uid == previousOld) {
int newOld = findOldestUnseen(user, previousOld);
oldestUnseenMessage.put(user, (new Integer(newOld)));
}
} else { // seen flag unset
if (uid < ((Integer)oldestUnseenMessage.get(user)).intValue()) {
oldestUnseenMessage.put(user, (new Integer(uid)));
}
}
}
writeFlags(uid, flags);
getLogger().debug("Flags for message uid " + uid + " in " + absoluteName + " updated.");
return true;
} else {
return false;
}
} else {
return false;
}
}
private int findOldestUnseen(String user, int previousOld)
throws AccessControlException, AuthorizationException {
int response = 0; //indicates no unseen messages
ListIterator lit = sequence.listIterator(previousOld);
boolean found = false;
while (!found && lit.hasNext() ) {
int uid = ((Integer)lit.next()).intValue();
Flags flags = readFlags(uid);
if (!flags.isSeen(user)) {
response = uid;
found = true;
}
}
return response;
}
private Flags readFlags(int uid) {
Flags response = null;
if (sequence.contains(new Integer(uid))) {
ObjectInputStream inFlags = null;
try {
inFlags = new ObjectInputStream( new FileInputStream(path + File.separator + uid + FLAGS_EXTENSION));
response = (Flags)inFlags.readObject();
} catch(Exception e) {
getLogger().error("Error reading flags from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while retrieving Message flags: "
+ e);
} finally {
try {
inFlags.close();
} catch (IOException ie) {
getLogger().error("Error closing streams: " + ie);
}
}
getLogger().info("Flags for " + uid + " read from " + absoluteName);
}
return response;
}
private boolean writeFlags(int uid, Flags flags) {
if (sequence.contains(new Integer(uid))) {
ObjectOutputStream outFlags = null;
try {
outFlags = new ObjectOutputStream( new FileOutputStream(path + File.separator + uid + FLAGS_EXTENSION));
outFlags.writeObject(flags);
outFlags.close();
} catch(Exception e) {
getLogger().error("Error writing message to disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Flags: "
+ e);
} finally {
try {
outFlags.close();
} catch (IOException ie) {
getLogger().error("Error closing streams: " + ie);
}
}
getLogger().info("Flags for " + uid + " written in " + absoluteName);
return true;
} else {
return false;
}
}
/**
* Removes all messages marked Deleted. User must have delete rights.
*
* @param username String represnting user
* @returns true if successful
* @throws AccessControlException if user does not have read rights for
* this mailbox.
* @throws AuthorizationException if user has delete rights but does not
* have delete rights.
*/
public synchronized boolean expunge(String user)
throws AccessControlException, AuthorizationException {
if (!hasDeleteRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to delete.");
}
Iterator it = messagesForDeletion.iterator();
while (it.hasNext()) {
Integer uidObj = (Integer)it.next();
int uid = uidObj.intValue();
if (sequence.contains(uidObj)) {
try {
final File msgFile = new File(path + File.separator + uid + MESSAGE_EXTENSION );
msgFile.delete();
final File attrFile = new File(path + File.separator + uid + ATTRIBUTES_EXTENSION );
attrFile.delete();
sequence.remove(uidObj);
getLogger().debug( "Removed message uid " + uid );
} catch ( final Exception e ) {
throw new RuntimeException( "Exception caught while removing" +
" a message: " + e );
}
}
}
for (int i = 0; i < sequence.size(); i++) {
System.err.println("Message with msn " + i + " has uid " + sequence.get(i));
}
return true;
}
private void writeMailbox() {
String mailboxRecordFile = path + File.separator + MAILBOX_FILE_NAME;
ObjectOutputStream out = null;
FileOutputStream fout = null;
try {
fout = new FileOutputStream( mailboxRecordFile );
out = new ObjectOutputStream( fout );
out.writeObject(this);
out.flush();
out.close();
fout.flush();
fout.close();
} catch(Exception e) {
if (out != null) {
try {
out.close();
} catch (Exception ignored) {
}
}
if (fout != null) {
try {
fout.close();
} catch (Exception ignored) {
}
}
e.printStackTrace();
throw new
RuntimeException("Exception caught while storing Mailbox: " + e);
}
getLogger().info("FileMailbox written: " + absoluteName);
}
/**
* Lists uids of messages in mailbox indexed by MSN.
*
* @param username String represnting user
* @returns List of Integers wrapping uids of message
*/
public List listUIDs(String user) {
return new ArrayList(Collections.unmodifiableList(sequence));
}
public Set getUsersWithLookupRights() {
Set response = new HashSet();
Iterator it = acl.keySet().iterator();
while (it.hasNext()) {
String user = (String) it.next();
boolean[] rights = (boolean[]) acl.get(user);
if (rights[LOOKUP] == true) {
response.add(user);
}
}
return response;
}
public Set getUsersWithReadRights() {
Set response = new HashSet();
Iterator it = acl.keySet().iterator();
while (it.hasNext()) {
String user = (String) it.next();
boolean[] rights = (boolean[]) acl.get(user);
if (rights[READ] == true) {
response.add(user);
}
}
return response;
}
public Map getUnseenByUser() {
Map response = new HashMap();
Iterator it = oldestUnseenMessage.keySet().iterator();
while (it.hasNext()) {
String user = (String) it.next();
Integer uidObj = ((Integer)oldestUnseenMessage.get(user));
int oldUID = uidObj.intValue();
if (oldUID == 0) {
response.put(user, uidObj);
} else {
int count = 0;
ListIterator lit
= sequence.listIterator(sequence.indexOf(uidObj));
while (lit.hasNext() ) {
int uid = ((Integer)lit.next()).intValue();
Flags flags = readFlags(uid);
if (!flags.isSeen(user)) {
count ++;
}
}
response.put(user, new Integer(count));
}
}
return response;
}
public InternetHeaders getInternetHeaders(int msn, String user)
throws AccessControlException, AuthorizationException {
if (!hasReadRights(user)) { //throws AccessControlException
throw new AuthorizationException("Not authorized to read.");
}
if (msn > sequence.size()) {
return null;
} else {
int uid = ((Integer)sequence.get(msn - 1)).intValue();
return getInternetHeadersUID(uid, user);
}
}
public InternetHeaders getInternetHeadersUID(int uid, String user)
throws AccessControlException, AuthorizationException {
InternetHeaders response = null;
if (sequence.contains(new Integer(uid))) {
BufferedInputStream inMsg = null;
try {
inMsg = new BufferedInputStream( new FileInputStream(path + File.separator + uid + MESSAGE_EXTENSION));
response = new InternetHeaders(inMsg);
inMsg.close();
} catch(Exception e) {
getLogger().error("Error reading headers of message from disc: " + e);
e.printStackTrace();
throw new
RuntimeException("Exception caughtt while retrieving InternetHeaders: " + e);
} finally {
try {
inMsg.close();
} catch (IOException ie) {
getLogger().error("Error closing streams: " + ie);
}
}
getLogger().info("InternetHeaders for message " + uid + " read from "
+ absoluteName);
return response;
} else {
return null;
}
}
/**
* Returns true if the named user is subscribed to this Mailbox.
*/
public boolean isSubscribed( String userName )
{
return subscribedUsers.contains( userName.toLowerCase() );
}
/**
* Subscribes a user to this Mailbox.
*/
public void subscribe( String userName )
{
subscribedUsers.add( userName.toLowerCase() );
}
/**
* Unsubscrive a user from this Mailbox.
*/
public void unsubscribe( String userName )
{
subscribedUsers.remove( userName.toLowerCase() );
}
}