blob: 3ef0275bf335df391828b988472934101835ef47 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.geronimo.javamail.store.pop3;
import java.util.List;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.FolderClosedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.MethodNotSupportedException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import javax.mail.event.ConnectionEvent;
import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
import org.apache.geronimo.javamail.store.pop3.connection.POP3StatusResponse;
/**
* The POP3 implementation of the javax.mail.Folder Note that only INBOX is
* supported in POP3
* <p>
* <url>http://www.faqs.org/rfcs/rfc1939.html</url>
* </p>
*
* @see javax.mail.Folder
*
* @version $Rev: 1610855 $ $Date: 2014-07-15 17:27:30 -0400 (Tue, 15 Jul 2014) $
*/
public class POP3Folder extends Folder {
protected boolean isFolderOpen = false;
protected int mode;
protected int msgCount;
private POP3Message[] messageCache;
// The fully qualified name of the folder. For a POP3 folder, this is either "" for the root or
// "INPUT" for the in-basket. It is possible to create other folders, but they will report that
// they don't exist.
protected String fullName;
// indicates whether this folder exists or not
protected boolean exists = false;
// indicates the type of folder this is.
protected int folderType;
/**
* Create a new folder associate with a POP3 store instance.
*
* @param store The owning Store.
* @param name The name of the folder. Note that POP3 stores only
* have 2 real folders, the root ("") and the in-basket
* ("INBOX"). It is possible to create other instances
* of Folder associated with the Store, but they will
* be non-functional.
*/
public POP3Folder(POP3Store store, String name) {
super(store);
this.fullName = name;
// if this is the input folder, this exists
if (name.equalsIgnoreCase("INBOX")) {
exists = true;
}
// by default, we're holding messages.
folderType = Folder.HOLDS_MESSAGES;
}
/**
* Retrieve the folder name. This is the simple folder
* name at the its hiearchy level. This can be invoked when the folder is closed.
*
* @return The folder's name.
*/
public String getName() {
// the name and the full name are always the same
return fullName;
}
/**
* Retrieve the folder's full name (including hierarchy information).
* This can be invoked when the folder is closed.
*
* @return The full name value.
*/
public String getFullName() {
return fullName;
}
/**
* Never return "this" as the parent folder. Somebody not familliar with
* POP3 may do something like while(getParent() != null) or something
* simmilar which will result in an infinte loop
*/
public Folder getParent() throws MessagingException {
// the default folder returns null. We return the default
// folder
return store.getDefaultFolder();
}
/**
* Indicate whether a folder exists. Only the root
* folder and "INBOX" will ever return true.
*
* @return true for real POP3 folders, false for any other
* instances that have been created.
* @exception MessagingException
*/
public boolean exists() throws MessagingException {
// only one folder truely exists...this might be it.
return exists;
}
public Folder[] list(String pattern) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
}
/**
* No sub folders, hence there is no notion of a seperator. This is always a null character.
*/
public char getSeparator() throws MessagingException {
return '\0';
}
/**
* There's no hierarchy in POP3, so the only type
* is HOLDS_MESSAGES (and only one of those exists).
*
* @return Always returns HOLDS_MESSAGES.
* @exception MessagingException
*/
public int getType() throws MessagingException {
return folderType;
}
/**
* Always returns false as any creation operation must
* fail.
*
* @param type The type of folder to create. This is ignored.
*
* @return Always returns false.
* @exception MessagingException
*/
public boolean create(int type) throws MessagingException {
return false;
}
/**
* No way to detect new messages, so always return false.
*
* @return Always returns false.
* @exception MessagingException
*/
public boolean hasNewMessages() throws MessagingException {
return false;
}
public Folder getFolder(String name) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
}
public boolean delete(boolean recurse) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be deleted");
}
public boolean renameTo(Folder f) throws MessagingException {
throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be renamed");
}
/**
* @see javax.mail.Folder#open(int)
*/
public void open(int mode) throws MessagingException {
// Can only be performed on a closed folder
checkClosed();
// get a connection object
POP3Connection connection = getConnection();
try {
POP3StatusResponse res = connection.retrieveMailboxStatus();
this.mode = mode;
this.isFolderOpen = true;
this.msgCount = res.getNumMessages();
// JavaMail API has no method in Folder to expose the total
// size (no of bytes) of the mail drop;
// NB: We use the actual message number to access the messages from
// the cache, which is origin 1. Vectors are origin 0, so we have to subtract each time
// we access a messagge.
messageCache = new POP3Message[msgCount];
} catch (Exception e) {
throw new MessagingException("Unable to execute STAT command", e);
}
finally {
// return the connection when finished
releaseConnection(connection);
}
notifyConnectionListeners(ConnectionEvent.OPENED);
}
/**
* Close a POP3 folder.
*
* @param expunge The expunge flag (ignored for POP3).
*
* @exception MessagingException
*/
public void close(boolean expunge) throws MessagingException {
// Can only be performed on an open folder
checkOpen();
// get a connection object
POP3Connection connection = getConnection();
try {
// we might need to reset the connection before we
// process deleted messages and send the QUIT. The
// connection knows if we need to do this.
connection.reset();
// clean up any messages marked for deletion
expungeDeletedMessages(connection);
} finally {
// return the connection when finished
releaseConnection(connection);
// cleanup the the state even if exceptions occur when deleting the
// messages.
cleanupFolder(false);
}
}
/**
* Mark any messages we've flagged as deleted from the
* POP3 server before closing.
*
* @exception MessagingException
*/
protected void expungeDeletedMessages(POP3Connection connection) throws MessagingException {
if (mode == READ_WRITE) {
for (int i = 0; i < messageCache.length; i++) {
POP3Message msg = messageCache[i];
if (msg != null) {
// if the deleted flag is set, go delete this
// message. NB: We adjust the index back to an
// origin 1 value
if (msg.isSet(Flags.Flag.DELETED)) {
try {
connection.deleteMessage(i + 1);
} catch (MessagingException e) {
throw new MessagingException("Exception deleting message number " + (i + 1), e);
}
}
}
}
}
}
/**
* Do folder cleanup. This is used both for normal
* close operations, and adnormal closes where the
* server has sent us a BYE message.
*
* @param expunge Indicates whether open messages should be expunged.
* @param disconnected
* The disconnected flag. If true, the server has cut
* us off, which means our connection can not be returned
* to the connection pool.
*
* @exception MessagingException
*/
protected void cleanupFolder(boolean disconnected) throws MessagingException {
messageCache = null;
isFolderOpen = false;
notifyConnectionListeners(ConnectionEvent.CLOSED);
}
/**
* Obtain a connection object for a Message attached to this Folder. This
* will be the Folder's connection, which is only available if the Folder
* is currently open.
*
* @return The connection object for the Message instance to use.
* @exception MessagingException
*/
synchronized POP3Connection getMessageConnection() throws MessagingException {
// we always get one from the store. If we're fully single threaded, then
// we can get away with just a single one.
return getConnection();
}
/**
* Release the connection object back to the Folder instance.
*
* @param connection The connection being released.
*
* @exception MessagingException
*/
void releaseMessageConnection(POP3Connection connection) throws MessagingException {
// give this back to the store
releaseConnection(connection);
}
public boolean isOpen() {
// if we're not open, we're not open
if (!isFolderOpen) {
return false;
}
try {
// we might be open, but the Store has been closed. In which case, we're not any more
// closing also changes the isFolderOpen flag.
if (!((POP3Store)store).isConnected()) {
close(false);
}
} catch (MessagingException e) {
}
return isFolderOpen;
}
public Flags getPermanentFlags() {
// unfortunately doesn't have a throws clause for this method
// throw new MethodNotSupportedException("POP3 doesn't support permanent
// flags");
// Better than returning null, save the extra condition from a user to
// check for null
// and avoids a NullPointerException for the careless.
return new Flags();
}
/**
* Get the folder message count.
*
* @return The number of messages in the folder.
* @exception MessagingException
*/
public int getMessageCount() throws MessagingException {
// NB: returns -1 if the folder isn't open.
return msgCount;
}
/**
* Checks wether the message is in cache, if not will create a new message
* object and return it.
*
* @see javax.mail.Folder#getMessage(int)
*/
public Message getMessage(int msgNum) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
if (msgNum < 1 || msgNum > getMessageCount()) {
throw new MessagingException("Invalid Message number");
}
Message msg = messageCache[msgNum - 1];
if (msg == null) {
msg = new POP3Message(this, msgNum);
messageCache[msgNum - 1] = (POP3Message)msg;
}
return msg;
}
public void appendMessages(Message[] msgs) throws MessagingException {
throw new MethodNotSupportedException("Message appending is not supported in POP3");
}
public Message[] expunge() throws MessagingException {
throw new MethodNotSupportedException("Expunge is not supported in POP3");
}
public int getMode() throws IllegalStateException {
// Can only be performed on an Open folder
checkOpen();
return mode;
}
/**
* @see javax.mail.Folder#fetch(javax.mail.Message[],
* javax.mail.FetchProfile)
*
* The JavaMail API recommends that this method be overrident to provide a
* meaningfull implementation.
*/
public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
// Can only be performed on an Open folder
checkOpen();
for (int i = 0; i < msgs.length; i++) {
Message msg = msgs[i];
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
// fetching the size and the subject will force all of the
// envelope information to load
msg.getHeader("Subject");
msg.getSize();
}
if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
// force the content to load...this also fetches the header information.
// C'est la vie.
((POP3Message)msg).loadContent();
msg.getSize();
}
// force flag loading for this message
if (fp.contains(FetchProfile.Item.FLAGS)) {
msg.getFlags();
}
if (fp.getHeaderNames().length > 0) {
// loading any header loads all headers, so just grab the header set.
msg.getHeader("Subject");
}
}
}
/**
* Retrieve the UID for a given message.
*
* @param msg The message of interest.
*
* @return The String UID value for this message.
* @exception MessagingException
*/
public synchronized String getUID(Message msg) throws MessagingException {
checkOpen();
// the Message knows how to do this
return ((POP3Message)msg).getUID();
}
/**
* Below is a list of covinience methods that avoid repeated checking for a
* value and throwing an exception
*/
/** Ensure the folder is open */
private void checkOpen() throws IllegalStateException {
if (!isFolderOpen) {
throw new IllegalStateException("Folder is not Open");
}
}
/** Ensure the folder is not open */
private void checkClosed() throws IllegalStateException {
if (isFolderOpen) {
throw new IllegalStateException("Folder is Open");
}
}
/**
* @see javax.mail.Folder#notifyMessageChangedListeners(int,
* javax.mail.Message)
*
* this method is protected and cannot be used outside of Folder, therefore
* had to explicitly expose it via a method in POP3Folder, so that
* POP3Message has access to it
*
* Bad design on the part of the Java Mail API.
*/
public void notifyMessageChangedListeners(int type, Message m) {
super.notifyMessageChangedListeners(type, m);
}
/**
* Retrieve the connection attached to this folder. Throws an
* exception if we don't have an active connection.
*
* @return The current connection object.
* @exception MessagingException
*/
protected synchronized POP3Connection getConnection() throws MessagingException {
// request a connection from the central store.
return ((POP3Store)store).getFolderConnection(this);
}
/**
* Release our connection back to the Store.
*
* @param connection The connection to release.
*
* @exception MessagingException
*/
protected void releaseConnection(POP3Connection connection) throws MessagingException {
// we need to release the connection to the Store once we're finished with it
((POP3Store)store).releaseFolderConnection(this, connection);
}
}