You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by no...@apache.org on 2006/09/16 22:12:02 UTC

svn commit: r446936 - /james/server/trunk/src/java/org/apache/james/mailrepository/javamail/

Author: norman
Date: Sat Sep 16 13:12:01 2006
New Revision: 446936

URL: http://svn.apache.org/viewvc?view=rev&rev=446936
Log:
Commit javamailstore mailrepository contributed by Joachim Draeger. Thx for the great work!

Added:
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/AbstractJavamailStoreMailRepository.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderAdapter.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeper.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeperImpl.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderInterface.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/HashJavamailStoreMailRepository.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockAdapter.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockInterface.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/StoreAware.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolder.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderAdapter.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderMailRepository.java
    james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UidToKeyBidiMap.java

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/AbstractJavamailStoreMailRepository.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/AbstractJavamailStoreMailRepository.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/AbstractJavamailStoreMailRepository.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/AbstractJavamailStoreMailRepository.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,348 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Random;
+
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.NoSuchProviderException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+
+import org.apache.avalon.framework.activity.Initializable;
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.james.services.MailRepository;
+import org.apache.mailet.Mail;
+
+/**
+ * MailRepository implementation to store mail in a Javamail store
+ * 
+ * This implementation should be considered as EXPERIMENTAL. <br>
+ * <br>
+ * TODO examine for thread-safety
+ */
+
+public abstract class AbstractJavamailStoreMailRepository extends
+        AbstractLogEnabled implements MailRepository, StoreAware, Configurable,
+        Initializable {
+
+    /**
+     * Whether 'deep debugging' is turned on.
+     */
+    protected final static boolean DEEP_DEBUG = true;
+
+    private static final String TYPE = "MAIL";
+
+    /**
+     * Assembled JavaMail destinationURL, only kept here for debugging via
+     * getDestination()
+     */
+    private String destination;
+
+    protected Logger log;
+
+    /**
+     * The underlaying Store can be used externaly via the StoreAware.getStore()
+     * Method
+     */
+    private javax.mail.Store mailStore = null;
+
+    /** 
+     * used internally to generate keys 
+     */
+    private static Random random;
+
+    /**
+     * this has not been tested yet, so it is not configurable. But it is likely
+     * that there would be memory leaks
+     */
+    protected boolean cacheMessages = false;
+
+    /**
+     * A lock used to control access to repository elements, locking access
+     * based on the key
+     */
+    private LockInterface lock;
+
+    /**
+     * inbox only accessable through a FolderGateKeeper
+     */
+    private FolderGateKeeper folderGateKeeper;
+
+    /**
+     * builds destination from attributes destinationURL and postfix.
+     * at the moment james does not hand over additional parameters like postfix.
+     */
+    public void configure(Configuration conf) throws ConfigurationException {
+        log.debug("JavamailStoreMailRepository configure");
+        destination = conf.getAttribute("destinationURL");
+        log.debug("JavamailStoreMailRepository.destinationURL: " + destination);
+        if (!destination.endsWith("/")) {
+            destination += "/";
+        }
+        String postfix = conf.getAttribute("postfix", "");
+        if (postfix.length() > 0) {
+            if (postfix.startsWith("/")) {
+                postfix = postfix.substring(1);
+            }
+            if (!postfix.endsWith("/")) {
+                postfix += "/";
+            }
+            destination += postfix;
+
+        }
+
+        /*
+         * Ugly hack to get the right folder to store the maildir I guess this
+         * could be also configured in config.xml
+         */
+        final int pi = destination.indexOf(':');
+        if (pi < 0) {
+            throw new ConfigurationException("protocol prefix is missing "
+                    + destination);
+        }
+        String withoutProtocol = destination.substring(pi);
+        final String protocol = destination.substring(0, pi);
+        if (!withoutProtocol.startsWith(":///")) {
+            withoutProtocol = ":///../apps/james/" + withoutProtocol.substring(3);
+
+        }
+        destination = protocol + withoutProtocol;
+        log.debug("destination: " + destination);
+        Properties mailSessionProps  = new Properties();
+
+        // That seems to be deprecated
+        // mailSessionProps.put("mail.store.maildir.autocreatedir", "true");
+        Session mailSession = Session.getDefaultInstance(mailSessionProps);
+        try {
+            mailStore = mailSession.getStore(new URLName(destination));
+        } catch (NoSuchProviderException e) {
+            throw new ConfigurationException("cannot find store provider for "
+                    + destination, e);
+        }
+
+        String checkType = conf.getAttribute("type");
+        if (!checkType.equals(TYPE)) {
+            String exceptionString = "Attempt to configure JavaMailStoreMailRepository as "
+                    + checkType;
+            log.warn(exceptionString);
+            throw new ConfigurationException(exceptionString);
+        }
+        log.debug("JavaMailStoreMailRepository configured");
+    }
+
+    /**
+     * connect the mailStore
+     * @see Initializable#initialize()
+     */
+    public void initialize() throws Exception {
+    mailStore.connect();
+        log.debug("JavaMailStoreMailRepository initialized");
+    }
+
+    /**
+     * gets the Lock and creates it, if not present. LockInterface offers functionality
+     * of org.apache.james.util.Lock
+     */
+    protected LockInterface getLock() {
+        if (lock==null) {
+            lock = new LockAdapter();
+        }
+        return lock;
+    }
+
+    /**
+     * possibility to replace Lock implementation. At the moment only used for testing 
+     */
+    void setLock(LockInterface lock) {
+        this.lock=lock;
+    }
+    
+    /**
+     * used in unit tests 
+     */
+    String getDestination() {
+        return destination;
+    }
+
+    /**
+     * Obtains a lock on a message identified by a key
+     * 
+     * @param key
+     *            the key of the message to be locked
+     * 
+     * @return true if successfully obtained the lock, false otherwise
+     */
+    public boolean lock(String key) {
+        if (getLock().lock(key)) {
+            if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
+                StringBuffer debugBuffer = new StringBuffer(256).append(
+                        "Locked ").append(key).append(" for ").append(
+                        Thread.currentThread().getName()).append(" @ ").append(
+                        new java.util.Date(System.currentTimeMillis()));
+                getLogger().debug(debugBuffer.toString());
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Releases a lock on a message identified by a key
+     * 
+     * @param key
+     *            the key of the message to be unlocked
+     * 
+     * @return true if successfully released the lock, false otherwise
+     */
+    public boolean unlock(String key) {
+        if (getLock().unlock(key)) {
+            if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
+                StringBuffer debugBuffer = new StringBuffer(256).append(
+                        "Unlocked ").append(key).append(" for ").append(
+                        Thread.currentThread().getName()).append(" @ ").append(
+                        new java.util.Date(System.currentTimeMillis()));
+                getLogger().debug(debugBuffer.toString());
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Removes a specified message
+     * 
+     * @param mail
+     *            the message to be removed from the repository
+     * @throws MessagingException
+     */
+    public void remove(Mail mail) throws MessagingException {
+        log.debug("UIDPlusFolder remove by Mail");
+        remove(mail.getName());
+    }
+
+    /**
+     * Remove a list of messages from disk The collection is simply a list of
+     * mails to delete
+     * 
+     * @param mails
+     * @throws MessagingException
+     */
+    public void remove(final Collection mails) throws MessagingException {
+        log.debug("UIDPlusFolder remove by Collection " + mails.size());
+        if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
+            StringBuffer logBuffer = new StringBuffer(128).append(
+                    this.getClass().getName()).append(
+                    " Removing entry for key ").append(mails);
+
+            getLogger().debug(logBuffer.toString());
+        }
+        Iterator mailList = mails.iterator();
+
+        /*
+         * remove every email from the Collection
+         */
+        while (mailList.hasNext()) {
+            remove(((Mail) mailList.next()).getName());
+        }
+    }
+
+    /**
+     * offers the underlaying Store for external use
+     */
+    public Store getStore() {
+        return mailStore;
+    }
+
+    /**
+     * lazy-loads random 
+     */
+    protected static synchronized Random getRandom() {
+        if (random == null) {
+            random = new Random();
+        }
+        return new Random();
+
+    }
+    
+    /**
+     * sets log
+     */
+    public void enableLogging(Logger log) {
+        super.enableLogging(log);
+        this.log=log;
+        
+    }
+    /**
+     * possibility to replace FolderGateKeeper implementation. Only used for
+     * testing
+     */
+    void setFolderGateKeeper(FolderGateKeeper gk) {
+        this.folderGateKeeper=gk;
+        
+    }
+    
+    /**
+     * used by getFolderGateKeeper to get the right FolderInterface implementation
+     * 
+     * @param folder JavaMail folder
+     * @return 
+     * @throws NoSuchMethodException if the Folder is not suitable
+     */
+    protected  abstract FolderInterface createAdapter(Folder folder) throws NoSuchMethodException;
+    /**
+     * Lazy-load FolderGateKeeper with inbox folder. Inbox folder is created if
+     * not present
+     * @return FolderGateKeeper offering inbox folder for this mail repository
+     */
+    protected FolderGateKeeper getFolderGateKeeper() {
+        if (folderGateKeeper == null) {
+            try {
+                Folder inbox = getStore().getFolder("INBOX");
+                FolderInterface myInbox=createAdapter(inbox);
+                folderGateKeeper = new FolderGateKeeperImpl(myInbox);
+
+                /*
+                 * Check whether the folder exists, if not create it
+                 */
+                if (inbox.exists() == false) {
+                    inbox.create(Folder.HOLDS_MESSAGES);
+                }
+            } catch (MessagingException e) {
+                throw new RuntimeException(
+                        "cannot retrieve inbox for this repository", e);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException(
+                        "Folder implementation is not suitable", e);
+            }
+        }
+        return folderGateKeeper;
+    }
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderAdapter.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderAdapter.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderAdapter.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderAdapter.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,256 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import javax.mail.FetchProfile;
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Store;
+import javax.mail.URLName;
+import javax.mail.event.ConnectionListener;
+import javax.mail.event.FolderListener;
+import javax.mail.event.MessageChangedListener;
+import javax.mail.event.MessageCountListener;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Simple 1:1 wrapper that holds a javax.mail.Folder internally to be accessed
+ * through the FolderInterface
+ */
+
+public class FolderAdapter implements FolderInterface {
+    
+    protected Folder folder;
+
+    public FolderAdapter(Folder folder) {
+        this.folder=folder;
+    }
+
+    public void addConnectionListener(ConnectionListener arg0) {
+        folder.addConnectionListener(arg0);
+    }
+
+    public void addFolderListener(FolderListener arg0) {
+        folder.addFolderListener(arg0);
+    }
+
+    public void addMessageChangedListener(MessageChangedListener arg0) {
+        folder.addMessageChangedListener(arg0);
+    }
+
+    public void addMessageCountListener(MessageCountListener arg0) {
+        folder.addMessageCountListener(arg0);
+    }
+
+    public void appendMessages(Message[] arg0) throws MessagingException {
+        folder.appendMessages(arg0);
+    }
+
+    public void close(boolean arg0) throws MessagingException {
+        folder.close(arg0);
+    }
+
+    public void copyMessages(Message[] arg0, Folder arg1) throws MessagingException {
+        folder.copyMessages(arg0, arg1);
+    }
+
+    public boolean create(int arg0) throws MessagingException {
+        return folder.create(arg0);
+    }
+
+    public boolean delete(boolean arg0) throws MessagingException {
+        return folder.delete(arg0);
+    }
+
+    public boolean equals(Object arg0) {
+        return folder.equals(arg0);
+    }
+
+    public boolean exists() throws MessagingException {
+        return folder.exists();
+    }
+
+    public Message[] expunge() throws MessagingException {
+        return folder.expunge();
+    }
+
+    public void fetch(Message[] arg0, FetchProfile arg1) throws MessagingException {
+        folder.fetch(arg0, arg1);
+    }
+
+    public int getDeletedMessageCount() throws MessagingException {
+        return folder.getDeletedMessageCount();
+    }
+
+    public Folder getFolder(String arg0) throws MessagingException {
+        return folder.getFolder(arg0);
+    }
+
+    public String getFullName() {
+        return folder.getFullName();
+    }
+
+    public Message getMessage(int arg0) throws MessagingException {
+        return folder.getMessage(arg0);
+    }
+
+    public int getMessageCount() throws MessagingException {
+        return folder.getMessageCount();
+    }
+
+    public Message[] getMessages() throws MessagingException {
+        return folder.getMessages();
+    }
+
+    public Message[] getMessages(int arg0, int arg1) throws MessagingException {
+        return folder.getMessages(arg0, arg1);
+    }
+
+    public Message[] getMessages(int[] arg0) throws MessagingException {
+        return folder.getMessages(arg0);
+    }
+
+    public int getMode() {
+        return folder.getMode();
+    }
+
+    public String getName() {
+        return folder.getName();
+    }
+
+    public int getNewMessageCount() throws MessagingException {
+        return folder.getNewMessageCount();
+    }
+
+    public Folder getParent() throws MessagingException {
+        return folder.getParent();
+    }
+
+    public Flags getPermanentFlags() {
+        return folder.getPermanentFlags();
+    }
+
+    public char getSeparator() throws MessagingException {
+        return folder.getSeparator();
+    }
+
+    public Store getStore() {
+        return folder.getStore();
+    }
+
+    public int getType() throws MessagingException {
+        return folder.getType();
+    }
+
+    public int getUnreadMessageCount() throws MessagingException {
+        return folder.getUnreadMessageCount();
+    }
+
+    public URLName getURLName() throws MessagingException {
+        return folder.getURLName();
+    }
+
+    public int hashCode() {
+        return folder.hashCode();
+    }
+
+    public boolean hasNewMessages() throws MessagingException {
+        return folder.hasNewMessages();
+    }
+
+    public boolean isOpen() {
+        return folder.isOpen();
+    }
+
+    public boolean isSubscribed() {
+        return folder.isSubscribed();
+    }
+
+    public Folder[] list() throws MessagingException {
+        return folder.list();
+    }
+
+    public Folder[] list(String arg0) throws MessagingException {
+        return folder.list(arg0);
+    }
+
+    public Folder[] listSubscribed() throws MessagingException {
+        return folder.listSubscribed();
+    }
+
+    public Folder[] listSubscribed(String arg0) throws MessagingException {
+        return folder.listSubscribed(arg0);
+    }
+
+    public void open(int arg0) throws MessagingException {
+        folder.open(arg0);
+    }
+
+    public void removeConnectionListener(ConnectionListener arg0) {
+        folder.removeConnectionListener(arg0);
+    }
+
+    public void removeFolderListener(FolderListener arg0) {
+        folder.removeFolderListener(arg0);
+    }
+
+    public void removeMessageChangedListener(MessageChangedListener arg0) {
+        folder.removeMessageChangedListener(arg0);
+    }
+
+    public void removeMessageCountListener(MessageCountListener arg0) {
+        folder.removeMessageCountListener(arg0);
+    }
+
+    public boolean renameTo(Folder arg0) throws MessagingException {
+        return folder.renameTo(arg0);
+    }
+
+    public Message[] search(SearchTerm arg0, Message[] arg1) throws MessagingException {
+        return folder.search(arg0, arg1);
+    }
+
+    public Message[] search(SearchTerm arg0) throws MessagingException {
+        return folder.search(arg0);
+    }
+
+    public void setFlags(int arg0, int arg1, Flags arg2, boolean arg3) throws MessagingException {
+        folder.setFlags(arg0, arg1, arg2, arg3);
+    }
+
+    public void setFlags(int[] arg0, Flags arg1, boolean arg2) throws MessagingException {
+        folder.setFlags(arg0, arg1, arg2);
+    }
+
+    public void setFlags(Message[] arg0, Flags arg1, boolean arg2) throws MessagingException {
+        folder.setFlags(arg0, arg1, arg2);
+    }
+
+    public void setSubscribed(boolean arg0) throws MessagingException {
+        folder.setSubscribed(arg0);
+    }
+
+    public String toString() {
+        return folder.toString();
+    }
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeper.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeper.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeper.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeper.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,94 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import javax.mail.MessagingException;
+
+/**
+ * offers access to an underlaying Folder and manages open/close operations.<br>
+ * The FolderGateKeeper can be handed over to different threads.
+ * <br>
+ * Clients have to call use() one time before and free() one time after they are
+ * operating on the folder. When use() has been called free() has to be called
+ * afterwards in any circumstance usally in a finally block.<br>
+ * 
+ * <pre>
+ * try {
+ *     use();
+ *     getFolder().doSomething();
+ *     getFolder().doSomething();
+ * } finally {
+ *     free();
+ * }
+ * </pre>
+ * 
+ * It is not allowed to open/close Folder from outside.
+ */
+
+public interface FolderGateKeeper {
+
+    /**
+     * increments count of users
+     */
+    public void use();
+
+    /**
+     * decrements count of users and closes folder if 0 and folder is open.
+     * 
+     * @throws MessagingException
+     *             if something went wrong closing the Folder
+     * @throws IllegalStateException
+     *             if there are already 0 users
+     * @throws IllegalStateException
+     *             if the state of the folder differs from the last known
+     */
+    public void free() throws MessagingException;
+
+    /**
+     * Gets the Folder and opens it, if necessary
+     * 
+     * @return an open Folder
+     * @throws MessagingException
+     *             if something went wron opening the Folder
+     * @throws IllegalStateException
+     *             if the state of the folder differs from the last known
+     * @throws IllegalStateException
+     *             if there are only 0 users
+     */
+    public FolderInterface getOpenFolder() throws MessagingException;
+
+    /**
+     * used to test whether everyone has freed it
+     * 
+     * @return number of users
+     */
+    public int getUseCount();
+    
+    /**
+     * Gets the Folder and don't care whether it is open or closed.
+     * 
+     * @return a open or closed Folder
+     * @throws IllegalStateException
+     *             if the state of the folder differs from the last known
+     * @throws IllegalStateException
+     *             if there are only 0 users
+     */
+    public FolderInterface getFolder();
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeperImpl.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeperImpl.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeperImpl.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderGateKeeperImpl.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,112 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+
+
+/**
+ * offers access to an underlaying Folder and manages open/close operations.<br>
+ * The FolderGateKeeper can be handed over to different threads.
+ * 
+ * @sse FolderGateKeeper
+ *
+ */
+public class FolderGateKeeperImpl implements FolderGateKeeper {
+
+    private FolderInterface folder;
+
+    private int inUse = 0;
+
+    boolean open = false;
+
+    public FolderGateKeeperImpl(FolderInterface folder) {
+        if (folder.isOpen()) {
+            throw new IllegalStateException(
+                    "FolderGateKeeper: initial state must be closed");
+        }
+        this.folder = folder;
+    }
+
+    public synchronized void use() {
+        inUse++;
+    }
+
+    public synchronized void free() throws MessagingException {
+        if (inUse < 1) {
+            throw new IllegalStateException(
+                    "called free() but FolderGateKeeper is not in use");
+        } else {
+            inUse--;
+            if (inUse == 0) {
+                if (open != folder.isOpen()) {
+                    throw new IllegalStateException(
+                            "free(): folder state not equals last known state: open="
+                                    + open);
+                }
+                if (open) {
+                    // TODO expunge should be configurable
+                    folder.close(true);
+                    open = false;
+                }
+            }
+        }
+
+    }
+
+    public synchronized FolderInterface getOpenFolder() throws MessagingException {
+        if (inUse < 1) {
+            throw new IllegalStateException(
+                    "called getFolder() but folder is not in use");
+        }
+        if (open != folder.isOpen()) {
+            throw new IllegalStateException(
+                    "getFolder() folder state not equals last known state: open="
+                            + open);
+        }
+        if (!open) {
+            folder.open(Folder.READ_WRITE);
+            open = true;
+        }
+        return folder;
+
+    }
+
+    public synchronized int getUseCount() {
+        return inUse;
+    }
+
+    public synchronized FolderInterface getFolder() {
+        if (inUse < 1) {
+            throw new IllegalStateException(
+                    "called getFolder() but folder is not in use");
+        }
+        if (open != folder.isOpen()) {
+            throw new IllegalStateException(
+                    "getFolder() folder state not equals last known state: open="
+                            + open);
+        }
+        return folder;
+    }
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderInterface.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderInterface.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderInterface.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/FolderInterface.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * Interface to javax.mail.Folder functionality to be able to replace 
+ * implementation or using Mocks when testing 
+ */
+
+public interface FolderInterface {
+
+    public Message[] getMessages() throws MessagingException;
+
+    public Message getMessage(int no) throws MessagingException;
+
+    public int getMessageCount() throws MessagingException;
+
+    public void appendMessages(Message[] messages) throws MessagingException;
+
+    public boolean isOpen();
+
+    public void open(int status) throws MessagingException;
+
+    public void close(boolean b) throws MessagingException;
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/HashJavamailStoreMailRepository.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/HashJavamailStoreMailRepository.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/HashJavamailStoreMailRepository.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/HashJavamailStoreMailRepository.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,557 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.UIDFolder;
+import javax.mail.Flags.Flag;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.core.MailImpl;
+import org.apache.james.mailrepository.javamail.HashJavamailStoreMailRepository.KeyToMsgMap.MsgObj;
+import org.apache.mailet.Mail;
+
+import com.sun.mail.util.CRLFOutputStream;
+
+/**
+ * MailRepository implementation to store mail in a Javamail store. <br>
+ * should work with every JavamailStore implementation that has deterministic
+ * message content. (checksum save). This implementation should be considered as
+ * EXPERIMENTAL.
+ * 
+ * @author Joachim Draeger <jd at joachim-draeger.de>
+ * 
+ * TODO examine for thread-safety
+ */
+public class HashJavamailStoreMailRepository extends
+        AbstractJavamailStoreMailRepository {
+
+    /**
+     * tridirectional map of messages key, hash and number saved in internaly
+     * used class MsgObj
+     */
+    protected KeyToMsgMap keyToMsgMap = null;
+
+    
+    private boolean getMessageCountOnClosed =true;
+    
+    
+    protected int getMessageCount() throws MessagingException {
+        try {
+            getFolderGateKeeper().use();
+            int n=-1;
+            if (getMessageCountOnClosed) {
+                n=getFolderGateKeeper().getFolder().getMessageCount();
+                if (n==-1) {
+                    getMessageCountOnClosed=false;
+                }
+            }
+            if (!getMessageCountOnClosed) {
+                n=getFolderGateKeeper().getOpenFolder().getMessageCount();
+            }
+            return n;
+        } finally {
+            getFolderGateKeeper().free();
+        }
+    }
+    
+    /**
+     * Stores a message by Javamails appendMessages method. Tries to guess
+     * resulting messagenumber and saves result in keyToMsgMap. If Folder
+     * supports getMessageCount on closed folder, this could be quite efficient
+     */
+    public synchronized void store(Mail mc) throws MessagingException {
+
+        final String key = mc.getName();
+        boolean wasLocked = true;
+        log.debug("Store: (hash) " + key);
+        if (!mc.getMessage().isSet(Flag.RECENT)) {
+            log.debug("Message didn't have RECENT flag");
+            mc.getMessage().setFlag(Flag.RECENT,true);
+        }
+        // because we use/free/use we need to know the state in finally
+        boolean use=false;
+        try {
+            
+            // Shouldn't we care when another Thread has locked this key and
+            // stop here?
+            synchronized (this) {
+                wasLocked = getLock().isLocked(key);
+                if (!wasLocked) {
+                    // If it wasn't locked, we want a lock during the store
+                    lock(key);
+                }
+            }
+            
+            
+            // Yes, appendMessages works on a closed inbox. But we need
+            // getMessageCount() and that is allowed
+            // to be -1 on a closed Folder when counting messages is expensive
+
+            int countBefore = getMessageCount();
+            
+            getFolderGateKeeper().use();
+            use=true;
+            // insert or update, don't call remove(key) because of locking
+            if (getKeyToMsgMap().contains(key)) {
+                log.debug("store is a update");
+                Message mm = getMessageFromInbox(key);
+                if (mm != null) {
+                    countBefore--;
+                    mm.setFlag(Flags.Flag.DELETED, true);
+                    mc.getMessage().setFlag(Flags.Flag.RECENT, false);
+                }
+                getKeyToMsgMap().removeByKey(key, true);
+            }
+            getFolderGateKeeper().getFolder().appendMessages(new Message[] { mc.getMessage() });
+            use=false;
+            getFolderGateKeeper().free();
+
+            // Try to guess resulting message number
+            int no = -1;
+            int count=getMessageCount();
+            if (count - countBefore == 1) {
+                no = count;
+                log.debug("Assigned message number "+ count);
+            } else {
+                log.debug("count - countBefore = "+ (count - countBefore ));
+            }
+
+            getKeyToMsgMap().put(mc.getMessage(), mc.getName(), no);
+        } catch (MessagingException e) {
+            log.error("Exception in HashJavamailStore: ", e);
+            throw e;
+        } finally {
+            if (!wasLocked) {
+                // If it wasn't locked, we need to unlock now
+                unlock(key);
+                synchronized (this) {
+                    notify();
+                }
+            }
+            if (use) {
+                getFolderGateKeeper().free();
+            }
+            log.debug("closed.");
+        }
+        log.debug("store finished");
+    }
+
+    /**
+     * calls rehash and uses stored keys in KeyToMsgMap
+     */
+    public Iterator list() throws MessagingException {
+        try {
+            getFolderGateKeeper().use();
+            log.debug("list()");
+            rehash(null);
+            final String[] keys = getKeyToMsgMap().getKeys();
+            final Iterator it = Arrays.asList(keys).iterator();
+            return it;
+        } catch (MessagingException e) {
+            throw e;
+        } finally {
+            getFolderGateKeeper().free();
+        }
+
+    }
+
+    /**
+     * uses getMessageFromInbox, returns null if not found
+     */
+    public Mail retrieve(String key) throws MessagingException {
+        log.debug("retrieve: " + key);
+        Mail m = null;
+        try {
+            getFolderGateKeeper().use();
+            MimeMessage mm = getMessageFromInbox(key);
+            if (mm != null) {
+                m = new MailImpl(mm);
+                m.setName(key);
+            } else {
+                log.debug("could not retrieve a MimeMessage from folder");
+            }
+
+        } catch (MessagingException e) {
+            throw e;
+        } finally {
+           getFolderGateKeeper().free();
+        }
+        return m;
+    }
+
+    /**
+     * Removes a message identified by key. uses getMessageFromInbox and does a
+     * setFlag(Flags.Flag.DELETED, true); on message. removes message from
+     * KeyToMsgMap. Messagenumbers are recalculated for next guesses.
+     * 
+     * @param key
+     *            the key of the message to be removed from the repository
+     */
+    public synchronized void  remove(String key) throws MessagingException {
+        log.debug("HashJavamailStore remove key:" + key);
+        if (lock(key)) {
+            try {
+                getFolderGateKeeper().use();
+                Message mm = getMessageFromInbox(key);
+                if (mm != null) {
+                    // will be deleted on expunge
+                    mm.setFlag(Flags.Flag.DELETED, true);
+                }
+                getKeyToMsgMap().removeByKey(key, true);
+            } catch (MessagingException e) {
+                throw e;
+            } finally {
+                unlock(key);
+                getFolderGateKeeper().free();
+            }
+        } else {
+            log.debug("could not optain lock");
+            throw new MessagingException("could not optain lock for remove");
+        }
+    }
+
+    /**
+     * Calls getMessages(); on Folder and rehashes messages an renews message
+     * numbers calls retainAllListedAndAddedByKeys on KeyToMsgMap to remove keys
+     * not found in Folder
+     * 
+     * @param filterkey
+     *            key of message that should be returned, can be null
+     * @return a message if found by filterkey
+     * @throws MessagingException
+     */
+    protected MimeMessage rehash(String filterkey) throws MessagingException {
+        if (DEEP_DEBUG)
+            log.debug("doing rehash");
+        String[] keysBefore = getKeyToMsgMap().getKeys();
+        MimeMessage mm = null;
+        Message[] msgs = getFolderGateKeeper().getOpenFolder().getMessages();
+        String[] keys = new String[msgs.length];
+        for (int i = 0; i < msgs.length; i++) {
+            Message message = msgs[i];
+            MsgObj mo = getKeyToMsgMap()
+                    .put((MimeMessage) message, null, i + 1);
+            keys[i] = mo.key;
+            if (DEEP_DEBUG)
+                log.debug("No " + mo.no + " key:" + mo.key);
+            if (mo.key.equals(filterkey)) {
+                if (DEEP_DEBUG)
+                    log.debug("Found message!");
+                mm = (MimeMessage) message;
+            }
+        }
+        getKeyToMsgMap().retainAllListedAndAddedByKeys(keysBefore, keys);
+        return mm;
+    }
+
+    /**
+     * Fetches a message from inbox. Fast fails if key is unknown in
+     * KeyToMsgMap. Tries to get message at last known position, if that was not
+     * successfull calls rehash
+     * 
+     * @param key
+     *            message key
+     * @return message if found, otherwise null
+     * @throws MessagingException
+     */
+    protected MimeMessage getMessageFromInbox(String key)
+            throws MessagingException {
+        MsgObj mo = getKeyToMsgMap().getByKey(key);
+        if (mo == null) {
+            log.debug("Key not found");
+            return null;
+        }
+        MimeMessage mm = null;
+        if (cacheMessages && mo.message != null) {
+            // not used at the moment
+            mm = mo.message;
+        } else {
+            try {
+                getFolderGateKeeper().use();
+                Object hash = null;
+                if (mo.no >= 0) {
+                    try {
+                        mm = (MimeMessage) getFolderGateKeeper().getOpenFolder()
+                                .getMessage(mo.no);
+                        hash = calcMessageHash(mm);
+                        if (!hash.equals(mo.hash)) {
+                            log
+                                    .debug("Message at guessed position does not match "
+                                            + mo.no);
+                            mm = null;
+                        }
+                    } catch (IndexOutOfBoundsException e) {
+                        log.debug("no Message found at guessed position "
+                                + mo.no);
+                    }
+                } else {
+                    log.debug("cannot guess message number");
+                }
+                if (mm == null) {
+                    mm = rehash(mo.key);
+                    if (mm == null)
+                        log.debug("rehashing was fruitless");
+                }
+            } finally {
+                getFolderGateKeeper().free();
+            }
+        }
+        return mm;
+    }
+
+    /**
+     * lazy loads KeyToMsgMap
+     * 
+     * @return
+     */
+    protected KeyToMsgMap getKeyToMsgMap() {
+        if (keyToMsgMap == null) {
+            keyToMsgMap = new KeyToMsgMap();
+        }
+        return keyToMsgMap;
+    }
+
+    protected class KeyToMsgMap {
+        protected SortedMap noToMsgObj;
+
+        protected Map keyToMsgObj;
+
+        protected Map hashToMsgObj;
+
+        protected KeyToMsgMap() {
+            noToMsgObj = new TreeMap();
+            keyToMsgObj = new HashMap();
+            hashToMsgObj = new HashMap();
+        }
+
+        public synchronized boolean contains(String key) {
+            return keyToMsgObj.containsKey(key);
+        }
+
+        /**
+         * Cleans up database after rehash. Only keys listed by rehash or added
+         * in the meantime are retained
+         * 
+         * @param keysBefore
+         *            keys that have exist before rehash was called
+         * @param listed
+         *            keys that have listed by rehash
+         */
+        public synchronized void retainAllListedAndAddedByKeys(
+                String[] keysBefore, String[] listed) {
+            if (DEEP_DEBUG)
+                log.debug("stat before retain: " + getStat());
+            Set added = new HashSet(keyToMsgObj.keySet());
+            added.removeAll(Arrays.asList(keysBefore));
+
+            Set retain = new HashSet(Arrays.asList(listed));
+            retain.addAll(added);
+
+            Collection remove = new HashSet(keyToMsgObj.keySet());
+            remove.removeAll(retain);
+            // huh, are we turning in circles? :-)
+
+            for (Iterator iter = remove.iterator(); iter.hasNext();) {
+                removeByKey((String) iter.next(), false);
+            }
+            if (DEEP_DEBUG)
+                log.debug("stat after retain: " + getStat());
+        }
+
+        /**
+         * only used for debugging
+         * 
+         * @return a String representing the sizes of the internal maps
+         */
+        public String getStat() {
+            String s = "keyToMsgObj:" + keyToMsgObj.size() + " hashToMsgObj:"
+                    + hashToMsgObj.size() + " noToMsgObj:" + noToMsgObj.size();
+            return s;
+        }
+
+        /**
+         * removes a message from the maps.
+         * 
+         * @param key
+         *            key of message
+         * @param decrease
+         *            if true, all message number greater than this are
+         *            decremented
+         */
+        public synchronized void removeByKey(String key, boolean decrement) {
+            MsgObj mo = getByKey(key);
+            keyToMsgObj.remove(mo.key);
+            noToMsgObj.remove(new Integer(mo.no));
+            hashToMsgObj.remove(mo.hash);
+            if (decrement) {
+                // tailMap finds all entries that have message number greater
+                // than removed one and decrements them
+                MsgObj[] dmos = (MsgObj[]) noToMsgObj.tailMap(
+                        new Integer(mo.no)).values().toArray(new MsgObj[0]);
+                for (int i = 0; i < dmos.length; i++) {
+                    MsgObj dmo = dmos[i];
+                    noToMsgObj.remove(new Integer(dmo.no));
+                    dmo.no--;
+                    noToMsgObj.put(new Integer(dmo.no), dmo);
+                }
+            }
+        }
+
+        public synchronized String[] getKeys() {
+            return (String[]) keyToMsgObj.keySet().toArray(new String[0]);
+        }
+
+        public synchronized MsgObj getByKey(String key) {
+            return (MsgObj) keyToMsgObj.get(key);
+        }
+
+        /**
+         * At first it tries to lookup message by key otherwise by hash or
+         * stores it as new
+         * 
+         * @param mm
+         *            message
+         * @param key
+         *            if null it will be generated when not found by hash
+         * @param no
+         *            current number of this message
+         * @return fetched/created MsgObj
+         * @throws MessagingException
+         */
+
+        public synchronized MsgObj put(final MimeMessage mm, String key,
+                final int no) throws MessagingException {
+            final Object hash = calcMessageHash(mm);
+            MsgObj mo;
+            if (key != null) {
+                mo = getMsgObj(key);
+            } else {
+                mo = (MsgObj) hashToMsgObj.get(hash);
+                if (mo == null) {
+                    key = generateKey(hash.toString());
+                }
+            }
+            if (mo == null) {
+                mo = new MsgObj();
+                keyToMsgObj.put(key, mo);
+                mo.key = key;
+            }
+            if (!hash.equals(mo.hash)) {
+                if (mo.hash != null) {
+                    hashToMsgObj.remove(mo.hash);
+                }
+                mo.hash = hash;
+                hashToMsgObj.put(hash, mo);
+            }
+            if (no != mo.no) {
+                if (mo.no > -1) {
+                    noToMsgObj.remove(new Integer(mo.no));
+                }
+                mo.no = no;
+                noToMsgObj.put(new Integer(no), mo);
+            }
+            if (cacheMessages) {
+                mo.message = mm;
+            }
+            return mo;
+
+        }
+
+        public synchronized MsgObj getMsgObj(String key) {
+            return (MsgObj) keyToMsgObj.get(key);
+        }
+
+        /**
+         * used to internal represent a message
+         * 
+         */
+        protected class MsgObj {
+            MimeMessage message;
+
+            int no = -1;
+
+            Object hash;
+
+            String key;
+        }
+
+    }
+
+    /**
+     * currently uses Arrays.hashCode to build an Integer. Resulting Method
+     * should provide a good hashCode and a correct equals method
+     * 
+     * @param mm
+     *            message to hash
+     * @return an Object provides and correct equals method.
+     * @throws MessagingException
+     */
+    protected static Object calcMessageHash(MimeMessage mm)
+            throws MessagingException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try {
+            mm.writeTo(new CRLFOutputStream(baos));
+        } catch (IOException e) {
+            throw new MessagingException("error while calculating hash ", e);
+        }
+        Integer i = new Integer(Arrays.hashCode(baos.toByteArray()));
+        return i;
+    }
+
+    /**
+     * builds a key for unknow messages
+     * 
+     * @param hash Hash to be included in key
+     * @return contains "james-hashed:", the hash, the time and a random long
+     */
+    protected static String generateKey(String hash) {
+        String key = "james-hashed:" + hash + ";" + System.currentTimeMillis()
+                + ";" + getRandom().nextLong();
+        return key;
+    }
+
+    /**
+     * just uses a FolderAdapter to wrap around folder
+     * 
+     */
+    protected FolderInterface createAdapter(Folder folder)
+            throws NoSuchMethodException {
+        return new FolderAdapter(folder);
+    }
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockAdapter.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockAdapter.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockAdapter.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockAdapter.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,48 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import org.apache.james.util.Lock;
+/**
+ * Just a simple 1:1 wrapper for org.apache.james.util.Lock that implements the LockInterface
+ * 
+ * @see org.apache.james.util.Lock 
+ * @see org.apache.james.mailrepository.javamail.LockInterface
+ */
+public class LockAdapter implements LockInterface {
+        Lock lock= new Lock();
+
+        public boolean isLocked(Object key) {
+            return lock.isLocked(key);
+        }
+
+        public boolean lock(Object key) {
+            return lock.lock(key);
+        }
+
+        public boolean unlock(Object key) {
+            return lock.unlock(key);
+        }
+        
+    
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockInterface.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockInterface.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockInterface.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/LockInterface.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,56 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+/**
+ * Interface for org.apache.james.util.Lock functionality to be able to replace
+ * implementation or using Mock-objects at tests
+ * 
+ * @see org.apache.james.util.Lock
+ */
+public interface LockInterface {
+
+    /**
+     * Check to see if the object is locked
+     *
+     * @param key the Object on which to check the lock
+     * @return true if the object is locked, false otherwise
+     */
+    public boolean isLocked(final Object key);
+
+    /**
+     * Lock on a given object.
+     *
+     * @param key the Object on which to lock
+     * @return true if the locking was successful, false otherwise
+     */
+    public boolean lock(final Object key);
+
+    /**
+     * Release the lock on a given object.
+     *
+     * @param key the Object on which the lock is held
+     * @return true if the unlocking was successful, false otherwise
+     */
+    public boolean unlock(final Object key);
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/StoreAware.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/StoreAware.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/StoreAware.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/StoreAware.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,37 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import javax.mail.Store;
+
+/**
+ * Bridge, that allows JavamailStoreMailRepository implementations to offer the
+ * underlaying Javamail Store
+ * 
+ * @author Joachim Draeger <jd at joachim-draeger.de>
+ */
+
+public interface StoreAware {
+
+    public Store getStore();
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolder.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolder.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolder.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolder.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.UIDFolder;
+
+/**
+ * Interim interface to provide access to UID PLUS methods reflecting RFC 2359,
+ * until official Javamail API offers this.
+ */
+
+public interface UIDPlusFolder extends UIDFolder, FolderInterface {
+    /**
+     * Appends the given messages to the folder and returns corresponding uids.<br>
+     * Implementations may require the folder to be open.
+     * 
+     * @see javax.mail.Folder#appendMessages(javax.mail.Message[])
+     * 
+     * @param msgs
+     *            messages to append
+     * @return array of same size and sequenze of msgs containing corresponding
+     *         uids or -1, if something went wrong
+     * @throws MessagingException
+     * @throws IllegalStateException when folder has to be open
+     */
+    public long[] addUIDMessages(Message[] msgs) throws MessagingException;
+
+    /**
+     * Appends the given messages to the folder and returns corresponding
+     * instances of the appended messages.<br>
+     * Implementations may require the folder to be open.
+     * 
+     * @see javax.mail.Folder#appendMessages(javax.mail.Message[])
+     * 
+     * @param msgs
+     *            messages to append
+     * @return array of same size and sequenze of msgs containing corresponding
+     *         added messages or null, if something went wrong
+     * @throws MessagingException
+     * @throws IllegalStateException when folder has to be open
+     */
+    public Message[] addMessages(Message[] msgs) throws MessagingException;
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderAdapter.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderAdapter.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderAdapter.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderAdapter.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,93 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.UIDFolder;
+
+/**
+ * Simple 1:1 wrapper for original JavaMail Folder and UIDFolder. uses
+ * reflection to call UIDPlusFolder methods on a JavaMail Folder.
+ */
+public class UIDPlusFolderAdapter extends FolderAdapter implements UIDPlusFolder {
+    
+    private Method addUIDMessagesMethod;
+    
+    public UIDPlusFolderAdapter(Folder folder) throws NoSuchMethodException {
+        super(folder);
+        try {
+            addUIDMessagesMethod=folder.getClass().getMethod("addUIDMessages",
+                    new Class[] { Message[].class });
+        } catch (SecurityException e) {
+            throw new RuntimeException(e);
+        } 
+    }
+
+    public long[] addUIDMessages(Message[] msgs) throws MessagingException {
+        try {
+            return (long[]) addUIDMessagesMethod.invoke(folder, new Object[] { msgs });
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (InvocationTargetException e) {
+            Throwable cause=e.getCause();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException)cause;
+            }
+            throw new RuntimeException(e);
+        }
+    }
+
+    public Message[] addMessages(Message[] msgs) throws MessagingException {
+       throw new RuntimeException("Method addMessages(Message[] msgs) not implemented");
+    }
+
+    public long getUIDValidity() throws MessagingException {
+        return ((UIDFolder)folder).getUIDValidity();
+    }
+
+    public Message getMessageByUID(long arg0) throws MessagingException {
+        return ((UIDFolder)folder).getMessageByUID(arg0);
+    }
+
+    public Message[] getMessagesByUID(long arg0, long arg1) throws MessagingException {
+        return ((UIDFolder)folder).getMessagesByUID(arg0, arg1);
+    }
+
+    public Message[] getMessagesByUID(long[] arg0) throws MessagingException {
+        return ((UIDFolder)folder).getMessagesByUID(arg0);
+    }
+
+    public long getUID(Message arg0) throws MessagingException {
+        return ((UIDFolder)folder).getUID(arg0);
+    }
+    
+    
+    
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderMailRepository.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderMailRepository.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderMailRepository.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UIDPlusFolderMailRepository.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,379 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.UIDFolder;
+import javax.mail.Flags.Flag;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.core.MailImpl;
+import org.apache.mailet.Mail;
+
+/**
+ * MailRepository implementation to store mail in a Javamail store which
+ * provides the UID Plus method public long[] addUIDMessages.<br>
+ * <br>
+ * This implementation should be considered as EXPERIMENTAL.
+ * 
+ * TODO examine for thread-safety
+ */
+public class UIDPlusFolderMailRepository extends
+        AbstractJavamailStoreMailRepository {
+
+    /**
+     * used to map keys to uid and vice versa
+     */
+    private UidToKeyBidiMap uidToKeyBidiMap = null;
+
+    public static final int DELIVERY_MODE_CLOSED = 0;
+    public static final int DELIVERY_MODE_TRY = 1;
+    public static final int DELIVERY_MODE_OPEN = 2;
+    
+    private int deliveryMode=DELIVERY_MODE_TRY;
+    
+    protected long addUIDMessage(Message message) throws MessagingException {
+        try {
+            getFolderGateKeeper().use();
+            long[] uids = null;
+            if (deliveryMode != DELIVERY_MODE_OPEN) {
+                try {
+                    log.debug("Doing a addUIDMessages on maybe closed Folder: isopen="+getFolderGateKeeper().getFolder().isOpen());
+                    uids = ((UIDPlusFolder) getFolderGateKeeper().getFolder())
+                            .addUIDMessages(new Message[] { message });
+                } catch (IllegalStateException e) {
+                    if (deliveryMode == DELIVERY_MODE_CLOSED) {
+                        log.error("deliveryMode=DELIVERY_MODE_CLOSED",e);
+                        throw e;
+                    } else {
+                        log.debug("switching to DELIVERY_MODE_OPEN",e);
+                        deliveryMode = DELIVERY_MODE_OPEN;
+                    }
+
+                }
+            }
+            if (deliveryMode == DELIVERY_MODE_OPEN) {
+                log.debug("Doing a addUIDMessages on a open Folder");
+                uids = ((UIDPlusFolder) getFolderGateKeeper().getOpenFolder())
+                        .addUIDMessages(new Message[] { message });
+            }
+            if (uids == null || uids.length != 1) {
+                throw new RuntimeException(
+                        "Fatal error while storing Message Container: Message was not Appendet");
+            }
+            return uids[0];
+        } finally {
+            getFolderGateKeeper().free();
+        }
+
+    }
+    
+
+
+    /**
+     * Stores a message in this repository.
+     * 
+     * @param mc
+     *            the mail message to store
+     * @throws MessagingException
+     */
+    public void store(Mail mc) throws MessagingException {
+        
+        log.debug("UIDPlusFolder store key:" + mc.getName());
+        if (!mc.getMessage().isSet(Flag.RECENT)) {
+            log.debug("Message didn't have RECENT flag");
+            mc.getMessage().setFlag(Flag.RECENT,true);
+        }
+        String key = mc.getName();
+        boolean wasLocked = true;
+        try {
+            getFolderGateKeeper().use();
+            MimeMessage message = mc.getMessage();
+            synchronized (this) {
+                wasLocked = getLock().isLocked(key);
+                if (!wasLocked) {
+                    // If it wasn't locked, we want a lock during the store
+                    lock(key);
+                }
+            }
+
+            // insert or update, don't call remove(key) because of locking
+            if (getUidToKeyBidiMap().containsKey(key)) {
+                // message gets updated an folder stays open. 
+                Message mm = getMessageFromInbox(key,
+                        (UIDFolder) getFolderGateKeeper().getOpenFolder());
+                if (mm != null) {
+                    mm.setFlag(Flags.Flag.DELETED, true);
+                    message.setFlag(Flags.Flag.RECENT, false);
+                }
+            }
+            long uid = addUIDMessage(message);
+            getUidToKeyBidiMap().put(key, uid);
+
+            log.info("message stored: UID: " + uid + " Key:" + mc.getName());
+        } finally {
+            getFolderGateKeeper().free();
+            if (!wasLocked) {
+                // If it wasn't locked, we need to unlock now
+                unlock(key);
+                synchronized (this) {
+                    notify();
+                }
+            }
+
+        }
+    }
+
+    /**
+     * lazy loads UidToKeyBidiMap
+     * @return
+     */
+    protected UidToKeyBidiMap getUidToKeyBidiMap() {
+        if (uidToKeyBidiMap == null) {
+            uidToKeyBidiMap = new UidToKeyBidiMapImpl();
+        }
+        return uidToKeyBidiMap;
+    }
+
+    /**
+     * Used for testing
+     * 
+     * @param uidToKeyBidiMap
+     */
+    void setUidToKeyBidiMap(UidToKeyBidiMap uidToKeyBidiMap) {
+        this.uidToKeyBidiMap = uidToKeyBidiMap;
+    }
+
+    /**
+     * Retrieves a message given a key. At the moment, keys can be obtained from
+     * list() in superinterface Store.Repository
+     * 
+     * @param key
+     *            the key of the message to retrieve
+     * @return the mail corresponding to this key, null if none exists
+     * @throws MessagingException 
+     */
+
+    public Mail retrieve(String key) throws MessagingException {
+        log.debug("UIDPlusFolder retrieve " + key);
+        try {
+            getFolderGateKeeper().use();
+            MimeMessage mm = getMessageFromInbox(key,
+                    (UIDFolder) getFolderGateKeeper().getOpenFolder());
+            if (mm == null)
+                return null;
+            Mail mail = new MailImpl(mm);
+            mail.setName(key);
+            return mail;
+        } finally {
+            getFolderGateKeeper().free();
+        }
+    }
+
+    /**
+     * Removes a message identified by key.
+     * 
+     * @param key
+     *            the key of the message to be removed from the repository
+     */
+    public void remove(String key) throws MessagingException {
+
+        log.debug("UIDFolder remove key:" + key);// , new Throwable());
+        if (lock(key)) {
+            getFolderGateKeeper().use();
+            try {
+                Message mm = getMessageFromInbox(key,
+                        (UIDFolder) getFolderGateKeeper().getOpenFolder());
+                if (mm != null) {
+                    mm.setFlag(Flags.Flag.DELETED, true);
+                }
+                getUidToKeyBidiMap().removeByKey(key);
+            } finally {
+                unlock(key);
+                getFolderGateKeeper().free();
+            }
+        } else {
+            log.debug("could not optain lock");
+            throw new MessagingException("could not optain lock for remove");
+        }
+    }
+
+    /**
+     * List string keys of messages in repository.
+     * 
+     * @return an <code>Iterator</code> over the list of keys in the
+     *         repository
+     * @throws MessagingException 
+     * 
+     */
+    public Iterator list() throws MessagingException {
+        log.debug("UIDPlusFolder list");
+        try {
+            getFolderGateKeeper().use();
+            // needed for retainAllListedAndAddedByKeys(String[], Collection)
+            String[] keysBefore = getUidToKeyBidiMap().getKeys();
+            // get the messages
+            Message[] msgs = getFolderGateKeeper().getOpenFolder().getMessages();
+            Collection keys = new ArrayList(msgs.length);
+            if (msgs == null)
+                throw new RuntimeException("inbox.getMessages returned null");
+            for (int i = 0; i < msgs.length; i++) {
+                try {
+                    long uidvalidity = ((UIDFolder) getFolderGateKeeper().getOpenFolder()).getUIDValidity();
+                    // lookup uid
+                    long uid = ((UIDFolder) getFolderGateKeeper().getOpenFolder()).getUID(msgs[i]);
+                    String key = getUidToKeyBidiMap().getByUid(uid);
+                    if (key == null) {
+                        // generate new key
+                        key = "james-uid:" + uidvalidity + ";" + uid + ";"
+                                + System.currentTimeMillis() + ";"
+                                + getRandom().nextLong();
+                        getUidToKeyBidiMap().put(key, uid);
+                    }
+                    keys.add(key);
+                    log.info("list: UID: " + uid + " Key:" + key);
+                } catch (NoSuchElementException e) {
+                    // no problem, messages could have been deleted in the
+                    // meantime
+                }
+            }
+            // retain only listed keys, and keys added in the meantime (it would
+            // be fatal to loose those)
+            // I don't care about meanwhile removed, those will fail on next
+            // access
+            // it's a good idea to keep count of cached small
+            getUidToKeyBidiMap()
+                    .retainAllListedAndAddedByKeys(keysBefore, keys);
+            return keys.iterator();
+        } catch (MessagingException e) {
+            throw new RuntimeException(e);
+        } finally {
+            getFolderGateKeeper().free();
+        }
+    }
+
+    private MimeMessage getMessageFromInbox(String key, UIDFolder inbox)
+            throws MessagingException {
+
+        long uid = getUidToKeyBidiMap().getByKey(key);
+        if (uid < 0) {
+            return null;
+        }
+
+        MimeMessage mm = (MimeMessage) inbox.getMessageByUID(uid);
+        log.info("getMessageFromInbox: UID: " + uid + " Key:" + key);
+        if (mm == null) {
+            getUidToKeyBidiMap().removeByKey(key);
+            log.info("Message not Found");
+        }
+        return mm;
+    }
+
+    /**
+     * 
+     * maybe it could be replaced by BidiMap from commons-collections 3.0+
+     * 
+     * @author Joachim Draeger <jd at joachim-draeger.de>
+     */
+    private class UidToKeyBidiMapImpl implements UidToKeyBidiMap {
+
+        private Map keyToUid;
+
+        private Map uidToKey;
+
+        public UidToKeyBidiMapImpl() {
+            keyToUid = new HashMap();
+            uidToKey = new HashMap();
+        }
+
+        public synchronized String[] getKeys() {
+            final ArrayList al = new ArrayList(keyToUid.keySet());
+            final String[] keys = (String[]) al.toArray(new String[0]);
+            return keys;
+        }
+
+        public synchronized void retainAllListedAndAddedByKeys(
+                final String[] before, final Collection listed) {
+            Collection added = new HashSet(keyToUid.keySet());
+            added.removeAll(Arrays.asList(before));
+            Collection retain = new HashSet(listed);
+            retain.addAll(added);
+            keyToUid.keySet().retainAll(retain);
+            uidToKey.keySet().retainAll(keyToUid.values());
+        }
+
+        public synchronized void removeByKey(String key) {
+            long uid = getByKey(key);
+            if (uid > -1) {
+                uidToKey.remove(new Long(uid));
+            }
+            keyToUid.remove(key);
+        }
+
+        public synchronized long getByKey(String key) {
+            Long lo = (Long) keyToUid.get(key);
+            long l = -1;
+            if (lo != null) {
+                l = lo.longValue();
+            }
+            return l;
+        }
+
+        public synchronized String getByUid(long uid) {
+
+            return (String) uidToKey.get(new Long(uid));
+        }
+
+        public synchronized boolean containsKey(String key) {
+            return keyToUid.containsKey(key);
+        }
+
+        public synchronized void put(String key, long uid) {
+            keyToUid.put(key, new Long(uid));
+            uidToKey.put(new Long(uid), key);
+        }
+
+    }
+
+    /**
+     * returns a UIDPlusFolderAdapter wrapper
+     * 
+     * @see UIDPlusFolderAdapter
+     */
+    protected FolderInterface createAdapter(Folder folder) throws NoSuchMethodException {
+        return new UIDPlusFolderAdapter(folder);
+    }
+
+
+}

Added: james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UidToKeyBidiMap.java
URL: http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UidToKeyBidiMap.java?view=auto&rev=446936
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UidToKeyBidiMap.java (added)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/javamail/UidToKeyBidiMap.java Sat Sep 16 13:12:01 2006
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.james.mailrepository.javamail;
+
+import java.util.Collection;
+
+/**
+ * Used to map uids to keys and keys to uids 
+ *
+ */
+
+public interface UidToKeyBidiMap {
+
+    boolean containsKey(String key);
+
+    void put(String key, long uid);
+
+    void removeByKey(String key);
+
+    String[] getKeys();
+
+    String getByUid(long uid);
+
+    void retainAllListedAndAddedByKeys(String[] keysBefore, Collection keys);
+
+    long getByKey(String key);
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org