1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.geronimo.javamail.store.pop3.connection;
19
20 import java.util.ArrayList;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.StringTokenizer;
25
26 import javax.mail.MessagingException;
27 import javax.mail.Session;
28 import javax.mail.Store;
29
30 import javax.mail.StoreClosedException;
31
32 import org.apache.geronimo.javamail.store.pop3.POP3Store;
33 import org.apache.geronimo.javamail.util.ProtocolProperties;
34
35 public class POP3ConnectionPool {
36
37 protected static final String MAIL_PORT = "port";
38
39 protected static final String MAIL_SASL_REALM = "sasl.realm";
40 protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid";
41
42 protected static final String DEFAULT_MAIL_HOST = "localhost";
43
44 // Our hosting Store instance
45 protected POP3Store store;
46 // our Protocol abstraction
47 protected ProtocolProperties props;
48 // POP3 is not nearly as multi-threaded as IMAP. We really just have a single folder,
49 // plus the Store, but the Store doesn't really talk to the server very much. We only
50 // hold one connection available, and on the off chance there is a situation where
51 // we need to create a new one, we'll authenticate on demand. The one case where
52 // I know this might be an issue is a folder checking back with the Store to see it if
53 // it is still connected.
54 protected POP3Connection availableConnection;
55
56 // our debug flag
57 protected boolean debug;
58
59 // the target host
60 protected String host;
61 // the target server port.
62 protected int port;
63 // the username we connect with
64 protected String username;
65 // the authentication password.
66 protected String password;
67 // the SASL realm name
68 protected String realm;
69 // the authorization id.
70 protected String authid;
71 // Turned on when the store is closed for business.
72 protected boolean closed = false;
73
74 /**
75 * Create a connection pool associated with a give POP3Store instance. The
76 * connection pool manages handing out connections for both the Store and
77 * Folder and Message usage.
78 *
79 * @param store The Store we're creating the pool for.
80 * @param props The protocol properties abstraction we use.
81 */
82 public POP3ConnectionPool(POP3Store store, ProtocolProperties props) {
83 this.store = store;
84 this.props = props;
85 }
86
87
88 /**
89 * Manage the initial connection to the POP3 server. This is the first
90 * point where we obtain the information needed to make an actual server
91 * connection. Like the Store protocolConnect method, we return false
92 * if there's any sort of authentication difficulties.
93 *
94 * @param host The host of the mail server.
95 * @param port The mail server connection port.
96 * @param user The connection user name.
97 * @param password The connection password.
98 *
99 * @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