View Javadoc

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