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