001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.javamail.store.pop3.connection; 019 020 import java.util.ArrayList; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.StringTokenizer; 025 026 import javax.mail.MessagingException; 027 import javax.mail.Session; 028 import javax.mail.Store; 029 030 import javax.mail.StoreClosedException; 031 032 import org.apache.geronimo.javamail.store.pop3.POP3Store; 033 import org.apache.geronimo.javamail.util.ProtocolProperties; 034 035 public class POP3ConnectionPool { 036 037 protected static final String MAIL_PORT = "port"; 038 039 protected static final String MAIL_SASL_REALM = "sasl.realm"; 040 protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid"; 041 042 protected static final String DEFAULT_MAIL_HOST = "localhost"; 043 044 // Our hosting Store instance 045 protected POP3Store store; 046 // our Protocol abstraction 047 protected ProtocolProperties props; 048 // POP3 is not nearly as multi-threaded as IMAP. We really just have a single folder, 049 // plus the Store, but the Store doesn't really talk to the server very much. We only 050 // hold one connection available, and on the off chance there is a situation where 051 // we need to create a new one, we'll authenticate on demand. The one case where 052 // I know this might be an issue is a folder checking back with the Store to see it if 053 // it is still connected. 054 protected POP3Connection availableConnection; 055 056 // our debug flag 057 protected boolean debug; 058 059 // the target host 060 protected String host; 061 // the target server port. 062 protected int port; 063 // the username we connect with 064 protected String username; 065 // the authentication password. 066 protected String password; 067 // the SASL realm name 068 protected String realm; 069 // the authorization id. 070 protected String authid; 071 // Turned on when the store is closed for business. 072 protected boolean closed = false; 073 074 /** 075 * Create a connection pool associated with a give POP3Store instance. The 076 * connection pool manages handing out connections for both the Store and 077 * Folder and Message usage. 078 * 079 * @param store The Store we're creating the pool for. 080 * @param props The protocol properties abstraction we use. 081 */ 082 public POP3ConnectionPool(POP3Store store, ProtocolProperties props) { 083 this.store = store; 084 this.props = props; 085 } 086 087 088 /** 089 * Manage the initial connection to the POP3 server. This is the first 090 * point where we obtain the information needed to make an actual server 091 * connection. Like the Store protocolConnect method, we return false 092 * if there's any sort of authentication difficulties. 093 * 094 * @param host The host of the mail server. 095 * @param port The mail server connection port. 096 * @param user The connection user name. 097 * @param password The connection password. 098 * 099 * @return True if we were able to connect and authenticate correctly. 100 * @exception MessagingException 101 */ 102 public synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException { 103 // NOTE: We don't check for the username/password being null at this point. It's possible that 104 // the server will send back a PREAUTH response, which means we don't need to go through login 105 // processing. We'll need to check the capabilities response after we make the connection to decide 106 // if logging in is necesssary. 107 108 // save this for subsequent connections. All pool connections will use this info. 109 // if the port is defaulted, then see if we have something configured in the session. 110 // if not configured, we just use the default default. 111 if (port == -1) { 112 // check for a property and fall back on the default if it's not set. 113 port = props.getIntProperty(MAIL_PORT, props.getDefaultPort()); 114 // it's possible that -1 might have been explicitly set, so one last check. 115 if (port == -1) { 116 port = props.getDefaultPort(); 117 } 118 } 119 120 // Before we do anything, let's make sure that we succesfully received a host 121 if ( host == null ) { 122 host = DEFAULT_MAIL_HOST; 123 } 124 125 this.host = host; 126 this.port = port; 127 this.username = username; 128 this.password = password; 129 130 // make sure we have the realm information 131 realm = props.getProperty(MAIL_SASL_REALM); 132 // get an authzid value, if we have one. The default is to use the username. 133 authid = props.getProperty(MAIL_AUTHORIZATIONID, username); 134 135 // go create a connection and just add it to the pool. If there is an authenticaton error, 136 // return the connect failure, and we may end up trying again. 137 availableConnection = createPoolConnection(); 138 if (availableConnection == null) { 139 return false; 140 } 141 // we're connected, authenticated, and ready to go. 142 return true; 143 } 144 145 /** 146 * Creates an authenticated pool connection and adds it to 147 * the connection pool. If there is an existing connection 148 * already in the pool, this returns without creating a new 149 * connection. 150 * 151 * @exception MessagingException 152 */ 153 protected POP3Connection createPoolConnection() throws MessagingException { 154 POP3Connection connection = new POP3Connection(props); 155 if (!connection.protocolConnect(host, port, authid, realm, username, password)) { 156 // we only add live connections to the pool. Sever the connections and 157 // allow it to go free. 158 connection.closeServerConnection(); 159 return null; 160 } 161 // just return this connection 162 return connection; 163 } 164 165 166 /** 167 * Get a connection from the pool. We try to retrieve a live 168 * connection, but we test the connection's liveness before 169 * returning one. If we don't have a viable connection in 170 * the pool, we'll create a new one. The returned connection 171 * will be in the authenticated state already. 172 * 173 * @return A POP3Connection object that is connected to the server. 174 */ 175 public synchronized POP3Connection getConnection() throws MessagingException { 176 // if we have an available one (common when opening the INBOX), just return it 177 POP3Connection connection = availableConnection; 178 179 if (connection != null) { 180 availableConnection = null; 181 return connection; 182 } 183 // we need an additional connection...rare, but it can happen if we've closed the INBOX folder. 184 return createPoolConnection(); 185 } 186 187 188 /** 189 * Return a connection to the connection pool. 190 * 191 * @param connection The connection getting returned. 192 * 193 * @exception MessagingException 194 */ 195 public synchronized void releaseConnection(POP3Connection connection) throws MessagingException 196 { 197 // we're generally only called if the store needed to talk to the server and 198 // then returned the connection to the pool. So it's pretty likely that we'll just cache this 199 if (availableConnection == null) { 200 availableConnection = connection; 201 } 202 else { 203 // got too many connections created...not sure how, but get rid of this one. 204 connection.close(); 205 } 206 } 207 208 209 /** 210 * Close the entire connection pool. 211 * 212 * @exception MessagingException 213 */ 214 public synchronized void close() throws MessagingException { 215 // we'll on have the single connection in reserver 216 if (availableConnection != null) { 217 availableConnection.close(); 218 availableConnection = null; 219 } 220 // turn out the lights, hang the closed sign on the wall. 221 closed = true; 222 } 223 } 224