001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.javamail.store.pop3;
021     
022    import java.io.PrintStream; 
023    import java.util.LinkedList;
024    import java.util.List;
025    
026    import javax.mail.AuthenticationFailedException;
027    import javax.mail.Folder;
028    import javax.mail.MessagingException;
029    import javax.mail.Session;
030    import javax.mail.Store;
031    import javax.mail.URLName;
032    
033    import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection; 
034    import org.apache.geronimo.javamail.store.pop3.connection.POP3ConnectionPool; 
035    import org.apache.geronimo.javamail.util.ProtocolProperties;
036    
037    /**
038     * POP3 implementation of javax.mail.Store POP protocol spec is implemented in
039     * org.apache.geronimo.javamail.store.pop3.POP3Connection
040     * 
041     * @version $Rev: 693530 $ $Date: 2008-09-09 13:57:23 -0400 (Tue, 09 Sep 2008) $
042     */
043    
044    public class POP3Store extends Store {
045        protected static final int DEFAULT_POP3_PORT = 110;
046        protected static final int DEFAULT_POP3_SSL_PORT = 995;
047        
048        
049        // our accessor for protocol properties and the holder of 
050        // protocol-specific information 
051        protected ProtocolProperties props; 
052        // our connection object    
053        protected POP3ConnectionPool connectionPool; 
054        // our session provided debug output stream.
055        protected PrintStream debugStream;
056        // the debug flag 
057        protected boolean debug; 
058        // the root folder 
059        protected POP3RootFolder root; 
060        // until we're connected, we're closed 
061        boolean closedForBusiness = true; 
062        protected LinkedList openFolders = new LinkedList(); 
063        
064        
065        public POP3Store(Session session, URLName name) {
066            this(session, name, "pop3", DEFAULT_POP3_PORT, false);
067        }
068    
069        /**
070         * Common constructor used by the POP3Store and POP3SSLStore classes
071         * to do common initialization of defaults.
072         *
073         * @param session
074         *            The host session instance.
075         * @param name
076         *            The URLName of the target.
077         * @param protocol
078         *            The protocol type ("pop3"). This helps us in
079         *            retrieving protocol-specific session properties.
080         * @param defaultPort
081         *            The default port used by this protocol. For pop3, this will
082         *            be 110. The default for pop3 with ssl is 995.
083         * @param sslConnection
084         *            Indicates whether an SSL connection should be used to initial
085         *            contact the server. This is different from the STARTTLS
086         *            support, which switches the connection to SSL after the
087         *            initial startup.
088         */
089        protected POP3Store(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
090            super(session, name);
091            
092            // create the protocol property holder.  This gives an abstraction over the different 
093            // flavors of the protocol. 
094            props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
095    
096            // get our debug settings
097            debugStream = session.getDebugOut();
098            debug = session.getDebug(); 
099            // the connection pool manages connections for the stores, folder, and message usage. 
100            connectionPool = new POP3ConnectionPool(this, props); 
101        }
102    
103    
104        /**
105         * Return a Folder object that represents the root of the namespace for the current user.
106         *
107         * Note that in some store configurations (such as IMAP4) the root folder might
108         * not be the INBOX folder.
109         *
110         * @return the root Folder
111         * @throws MessagingException if there was a problem accessing the store
112         */
113            public Folder getDefaultFolder() throws MessagingException {
114                    checkConnectionStatus();
115            // if no root yet, create a root folder instance. 
116            if (root == null) {
117                return new POP3RootFolder(this);
118            }
119            return root;
120            }
121    
122        /**
123         * Return the Folder corresponding to the given name.
124         * The folder might not physically exist; the {@link Folder#exists()} method can be used
125         * to determine if it is real.
126         * 
127         * @param name   the name of the Folder to return
128         * 
129         * @return the corresponding folder
130         * @throws MessagingException
131         *                if there was a problem accessing the store
132         */
133            public Folder getFolder(String name) throws MessagingException {
134            return getDefaultFolder().getFolder(name);
135            }
136    
137        
138        /**
139         * Return the folder identified by the URLName; the URLName must refer to this Store.
140         * Implementations may use the {@link URLName#getFile()} method to determined the folder name.
141         * 
142         * @param url
143         * 
144         * @return the corresponding folder
145         * @throws MessagingException
146         *                if there was a problem accessing the store
147         */
148            public Folder getFolder(URLName url) throws MessagingException {
149            return getDefaultFolder().getFolder(url.getFile());
150            }
151    
152        
153        /**
154         * @see javax.mail.Service#protocolConnect(java.lang.String, int,
155         *      java.lang.String, java.lang.String)
156         */
157        protected synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
158            
159            if (debug) {
160                debugOut("Connecting to server " + host + ":" + port + " for user " + username);
161            }
162    
163            // the connection pool handles all of the details here. 
164            if (connectionPool.protocolConnect(host, port, username, password)) 
165            {
166                // the store is now open 
167                closedForBusiness = false; 
168                return true; 
169            }
170            return false; 
171        }
172        
173        
174        /**
175         * Get a connection for the store. 
176         * 
177         * @return The request connection object. 
178         * @exception MessagingException
179         */
180        protected POP3Connection getConnection() throws MessagingException {
181            return connectionPool.getConnection(); 
182        }
183        
184        /**
185         * Return a connection back to the connection pool after 
186         * it has been used for a request. 
187         * 
188         * @param connection The return connection.
189         * 
190         * @exception MessagingException
191         */
192        protected void releaseConnection(POP3Connection connection) throws MessagingException {
193            connectionPool.releaseConnection(connection); 
194        }
195        
196        /**
197         * Get a connection object for a folder to use. 
198         * 
199         * @param folder The requesting folder (always the inbox for POP3).
200         * 
201         * @return An active POP3Connection. 
202         * @exception MessagingException
203         */
204        synchronized POP3Connection getFolderConnection(POP3Folder folder) throws MessagingException {
205            POP3Connection connection = connectionPool.getConnection(); 
206            openFolders.add(folder);
207            return connection; 
208        }
209        
210        /**
211         * Release a connection object after a folder is 
212         * finished with a request. 
213         * 
214         * @param folder     The requesting folder.
215         * @param connection
216         * 
217         * @exception MessagingException
218         */
219        synchronized void releaseFolderConnection(POP3Folder folder, POP3Connection connection) throws MessagingException {
220            openFolders.remove(folder); 
221            // return this back to the pool 
222            connectionPool.releaseConnection(connection); 
223        }
224        
225        /**
226         * Close all open folders.  We have a small problem here with a race condition.  There's no safe, single
227         * synchronization point for us to block creation of new folders while we're closing.  So we make a copy of
228         * the folders list, close all of those folders, and keep repeating until we're done.
229         */
230        protected void closeOpenFolders() {
231            // we're no longer accepting additional opens.  Any folders that open after this point will get an
232            // exception trying to get a connection.
233            closedForBusiness = true;
234    
235            while (true) {
236                List folders = null;
237    
238                // grab our lock, copy the open folders reference, and null this out.  Once we see a null
239                // open folders ref, we're done closing.
240                synchronized(connectionPool) {
241                    folders = openFolders;
242                    openFolders = new LinkedList();
243                }
244    
245                // null folder, we're done
246                if (folders.isEmpty()) {
247                    return;
248                }
249                // now close each of the open folders.
250                for (int i = 0; i < folders.size(); i++) {
251                    POP3Folder folder = (POP3Folder)folders.get(i);
252                    try {
253                        folder.close(false);
254                    } catch (MessagingException e) {
255                    }
256                }
257            }
258        }
259        
260    
261        /**
262         * @see javax.mail.Service#isConnected()
263         */
264        public boolean isConnected() {
265            // the connect() method of the super class checks here first.  If the connected flag 
266            // is off, then it's too early for use to try to get a connection and verify we still 
267            // have a live one.  
268            if (!super.isConnected()) {
269                return false; 
270            }
271            try {
272                POP3Connection connection = getConnection(); 
273                // a null connection likely means we had a failure establishing a 
274                // new connection to the POP3 server.  
275                if (connection == null) {
276                    return false; 
277                }
278                try {
279                    // make sure the server is really there 
280                    connection.pingServer(); 
281                    return true; 
282                }
283                finally {
284                    // return the connection to the pool when finished 
285                    if (connection != null) {
286                        releaseConnection(connection); 
287                    }
288                }
289            } catch (MessagingException e) {
290            }
291            return false; 
292        }
293    
294        /**
295         * Close the store, and any open folders associated with the 
296         * store. 
297         * 
298         * @exception MessagingException
299         */
300            public synchronized void close() throws MessagingException{
301            // if already closed, nothing to do. 
302            if (closedForBusiness) {
303                return; 
304            }
305            
306            // close the folders first, then shut down the Store. 
307            closeOpenFolders();
308            
309            connectionPool.close(); 
310            connectionPool = null; 
311    
312                    // make sure we do the superclass close operation first so 
313            // notification events get broadcast properly. 
314                    super.close();
315            }
316    
317        /**
318         * Check the status of our connection. 
319         * 
320         * @exception MessagingException
321         */
322        private void checkConnectionStatus() throws MessagingException {
323            if (!this.isConnected()) {
324                throw new MessagingException("Not connected ");
325            }
326        }
327    
328        /**
329         * Internal debug output routine.
330         *
331         * @param value  The string value to output.
332         */
333        void debugOut(String message) {
334            debugStream.println("POP3Store DEBUG: " + message);
335        }
336    
337        /**
338         * Internal debugging routine for reporting exceptions.
339         *
340         * @param message A message associated with the exception context.
341         * @param e       The received exception.
342         */
343        void debugOut(String message, Throwable e) {
344            debugOut("Received exception -> " + message);
345            debugOut("Exception message -> " + e.getMessage());
346            e.printStackTrace(debugStream);
347        }
348        
349        /**
350         * Finalizer to perform IMAPStore() cleanup when 
351         * no longer in use. 
352         * 
353         * @exception Throwable
354         */
355        protected void finalize() throws Throwable {
356            super.finalize(); 
357            close(); 
358        }
359    }