1 /************************************************************************
2 * Copyright (c) 2000-2006 The Apache Software Foundation. *
3 * All rights reserved. *
4 * ------------------------------------------------------------------- *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you *
6 * may not use this file except in compliance with the License. You *
7 * 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 *
14 * implied. See the License for the specific language governing *
15 * permissions and limitations under the License. *
16 ***********************************************************************/
17
18 package org.apache.james.util.connection;
19 import java.net.ServerSocket;
20 import java.util.HashMap;
21 import org.apache.excalibur.thread.ThreadPool;
22 import org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory;
23 import org.apache.james.services.JamesConnectionManager;
24 import org.apache.avalon.cornerstone.services.threads.ThreadManager;
25 import org.apache.avalon.framework.service.ServiceException;
26 import org.apache.avalon.framework.service.ServiceManager;
27 import org.apache.avalon.framework.service.Serviceable;
28 import org.apache.avalon.framework.configuration.Configurable;
29 import org.apache.avalon.framework.configuration.Configuration;
30 import org.apache.avalon.framework.configuration.ConfigurationException;
31 import org.apache.avalon.framework.container.ContainerUtil;
32 import org.apache.avalon.framework.activity.Disposable;
33 import org.apache.avalon.framework.logger.AbstractLogEnabled;
34 /***
35 * An implementation of ConnectionManager that supports configurable
36 * idle timeouts and a configurable value for the maximum number of
37 * client connections to a particular port.
38 *
39 */
40 public class SimpleConnectionManager
41 extends AbstractLogEnabled
42 implements JamesConnectionManager, Serviceable, Configurable, Disposable {
43 /***
44 * The default value for client socket idle timeouts. The
45 * Java default is 0, meaning no timeout. That's dangerous
46 * for a connection handler like this one, because it can
47 * easily lead to consumption of network resources. So we
48 * allow users to configure the system to allow no timeout,
49 * but if no timeout is specified in the configuration, we
50 * use a timeout of 5 minutes.
51 */
52 private static final int DEFAULT_SOCKET_TIMEOUT = 5 * 60 * 1000;
53 /***
54 * The default value for the maximum number of allowed client
55 * connections.
56 */
57 private static final int DEFAULT_MAX_CONNECTIONS = 30;
58 /***
59 * The map of connection name / server connections managed by this connection
60 * manager.
61 */
62 private final HashMap connectionMap = new HashMap();
63 /***
64 * The idle timeout for the individual sockets spawed from the server socket.
65 */
66 protected int timeout = 0;
67 /***
68 * The maximum number of client connections allowed per server connection.
69 */
70 protected int maxOpenConn = 0;
71 /***
72 * The ThreadManager component that is used to provide a default thread pool.
73 */
74 private ThreadManager threadManager;
75 /***
76 * Whether the SimpleConnectionManager has been disposed.
77 */
78 private volatile boolean disposed = false;
79 /***
80 * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
81 */
82 public void configure(final Configuration configuration) throws ConfigurationException {
83 timeout = configuration.getChild("idle-timeout").getValueAsInteger(DEFAULT_SOCKET_TIMEOUT);
84 maxOpenConn = configuration.getChild("max-connections").getValueAsInteger(DEFAULT_MAX_CONNECTIONS);
85 if (timeout < 0) {
86 StringBuffer exceptionBuffer =
87 new StringBuffer(128).append("Specified socket timeout value of ").append(timeout).append(
88 " is not a legal value.");
89 throw new ConfigurationException(exceptionBuffer.toString());
90 }
91 if (maxOpenConn < 0) {
92 StringBuffer exceptionBuffer =
93 new StringBuffer(128).append("Specified maximum number of open connections of ").append(
94 maxOpenConn).append(
95 " is not a legal value.");
96 throw new ConfigurationException(exceptionBuffer.toString());
97 }
98 if (getLogger().isDebugEnabled()) {
99 getLogger().debug(
100 "Connection timeout is " + (timeout == 0 ? "unlimited" : Long.toString(timeout)));
101 getLogger().debug(
102 "The maximum number of simultaneously open connections is "
103 + (maxOpenConn == 0 ? "unlimited" : Integer.toString(maxOpenConn)));
104 }
105 }
106 /***
107 * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
108 */
109 public void service(ServiceManager componentManager) throws ServiceException {
110 threadManager = (ThreadManager)componentManager.lookup(ThreadManager.ROLE);
111 }
112 /***
113 * Disconnects all the underlying ServerConnections
114 */
115 public void dispose() {
116 disposed = true;
117 if (getLogger().isDebugEnabled()) {
118 getLogger().debug("Starting SimpleConnectionManager dispose...");
119 }
120 final String[] names = (String[])connectionMap.keySet().toArray(new String[0]);
121 for (int i = 0; i < names.length; i++) {
122 try {
123 if (getLogger().isDebugEnabled()) {
124 getLogger().debug("Disconnecting ServerConnection " + names[i]);
125 }
126 disconnect(names[i], true);
127 } catch (final Exception e) {
128 getLogger().warn("Error disconnecting " + names[i], e);
129 }
130 }
131 if (getLogger().isDebugEnabled()) {
132 getLogger().debug("Finishing SimpleConnectionManager dispose...");
133 }
134 }
135 /***
136 * Start managing a connection.
137 * Management involves accepting connections and farming them out to threads
138 * from pool to be handled.
139 *
140 * @param name the name of connection
141 * @param socket the ServerSocket from which to
142 * @param handlerFactory the factory from which to acquire handlers
143 * @param threadPool the thread pool to use
144 * @param maxOpenConnections the maximum number of open connections allowed for this server socket.
145 * @exception Exception if an error occurs
146 */
147 public void connect(
148 String name,
149 ServerSocket socket,
150 ConnectionHandlerFactory handlerFactory,
151 ThreadPool threadPool,
152 int maxOpenConnections)
153 throws Exception {
154 if (disposed) {
155 throw new IllegalStateException("Connection manager has already been shutdown.");
156 }
157 if (null != connectionMap.get(name)) {
158 throw new IllegalArgumentException("Connection already exists with name " + name);
159 }
160 if (maxOpenConnections < 0) {
161 throw new IllegalArgumentException("The maximum number of client connections per server socket cannot be less that zero.");
162 }
163 ServerConnection runner =
164 new ServerConnection(socket, handlerFactory, threadPool, timeout, maxOpenConnections);
165 setupLogger(runner);
166 ContainerUtil.initialize(runner);
167 connectionMap.put(name, runner);
168 threadPool.execute(runner);
169 }
170 /***
171 * Start managing a connection.
172 * Management involves accepting connections and farming them out to threads
173 * from pool to be handled.
174 *
175 * @param name the name of connection
176 * @param socket the ServerSocket from which to
177 * @param handlerFactory the factory from which to acquire handlers
178 * @param threadPool the thread pool to use
179 * @exception Exception if an error occurs
180 */
181 public void connect(
182 String name,
183 ServerSocket socket,
184 ConnectionHandlerFactory handlerFactory,
185 ThreadPool threadPool)
186 throws Exception {
187 connect(name, socket, handlerFactory, threadPool, maxOpenConn);
188 }
189 /***
190 * Start managing a connection.
191 * This is similar to other connect method except that it uses default thread pool.
192 *
193 * @param name the name of connection
194 * @param socket the ServerSocket from which to
195 * @param handlerFactory the factory from which to acquire handlers
196 * @exception Exception if an error occurs
197 */
198 public void connect(String name, ServerSocket socket, ConnectionHandlerFactory handlerFactory)
199 throws Exception {
200 connect(name, socket, handlerFactory, threadManager.getDefaultThreadPool());
201 }
202 /***
203 * Start managing a connection.
204 * This is similar to other connect method except that it uses default thread pool.
205 *
206 * @param name the name of connection
207 * @param socket the ServerSocket from which to
208 * @param handlerFactory the factory from which to acquire handlers
209 * @param maxOpenConnections the maximum number of open connections allowed for this server socket.
210 * @exception Exception if an error occurs
211 */
212 public void connect(
213 String name,
214 ServerSocket socket,
215 ConnectionHandlerFactory handlerFactory,
216 int maxOpenConnections)
217 throws Exception {
218 connect(name, socket, handlerFactory, threadManager.getDefaultThreadPool(), maxOpenConnections);
219 }
220 /***
221 * This shuts down all handlers and socket, waiting for each to gracefully shutdown.
222 *
223 * @param name the name of connection
224 * @exception Exception if an error occurs
225 */
226 public void disconnect(final String name) throws Exception {
227 disconnect(name, false);
228 }
229 /***
230 * This shuts down a connection.
231 * If tearDown is true then it will forcefully the connection and try
232 * to return as soon as possible. Otherwise it will behave the same as
233 * void disconnect( String name );
234 *
235 * @param name the name of connection
236 * @param tearDown if true will forcefully tear down all handlers
237 * @exception Exception if an error occurs
238 */
239 public void disconnect(final String name, final boolean tearDown) throws Exception {
240 ServerConnection connection = (ServerConnection)connectionMap.remove(name);
241 if (null == connection) {
242 throw new IllegalArgumentException("No such connection with name " + name);
243 }
244
245 connection.dispose();
246 }
247 /***
248 * Returns the default maximum number of open connections supported by this
249 * SimpleConnectionManager
250 *
251 * @return the maximum number of connections
252 */
253 public int getMaximumNumberOfOpenConnections() {
254 return maxOpenConn;
255 }
256
257 }