001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.util.connection;
019:
020: import java.net.ServerSocket;
021: import java.util.HashMap;
022: import org.apache.excalibur.thread.ThreadPool;
023: import org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory;
024: import org.apache.james.services.JamesConnectionManager;
025: import org.apache.avalon.cornerstone.services.threads.ThreadManager;
026: import org.apache.avalon.framework.service.ServiceException;
027: import org.apache.avalon.framework.service.ServiceManager;
028: import org.apache.avalon.framework.service.Serviceable;
029: import org.apache.avalon.framework.configuration.Configurable;
030: import org.apache.avalon.framework.configuration.Configuration;
031: import org.apache.avalon.framework.configuration.ConfigurationException;
032: import org.apache.avalon.framework.container.ContainerUtil;
033: import org.apache.avalon.framework.activity.Disposable;
034: import org.apache.avalon.framework.logger.AbstractLogEnabled;
035:
036: /**
037: * An implementation of ConnectionManager that supports configurable
038: * idle timeouts and a configurable value for the maximum number of
039: * client connections to a particular port.
040: *
041: */
042: public class SimpleConnectionManager extends AbstractLogEnabled
043: implements JamesConnectionManager, Serviceable, Configurable,
044: Disposable {
045: /**
046: * The default value for client socket idle timeouts. The
047: * Java default is 0, meaning no timeout. That's dangerous
048: * for a connection handler like this one, because it can
049: * easily lead to consumption of network resources. So we
050: * allow users to configure the system to allow no timeout,
051: * but if no timeout is specified in the configuration, we
052: * use a timeout of 5 minutes.
053: */
054: private static final int DEFAULT_SOCKET_TIMEOUT = 5 * 60 * 1000;
055: /**
056: * The default value for the maximum number of allowed client
057: * connections.
058: */
059: private static final int DEFAULT_MAX_CONNECTIONS = 30;
060: /**
061: * The map of connection name / server connections managed by this connection
062: * manager.
063: */
064: private final HashMap connectionMap = new HashMap();
065: /**
066: * The idle timeout for the individual sockets spawed from the server socket.
067: */
068: protected int timeout = 0;
069: /**
070: * The maximum number of client connections allowed per server connection.
071: */
072: protected int maxOpenConn = 0;
073: /**
074: * The ThreadManager component that is used to provide a default thread pool.
075: */
076: private ThreadManager threadManager;
077: /**
078: * Whether the SimpleConnectionManager has been disposed.
079: */
080: private volatile boolean disposed = false;
081:
082: /**
083: * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
084: */
085: public void configure(final Configuration configuration)
086: throws ConfigurationException {
087: timeout = configuration.getChild("idle-timeout")
088: .getValueAsInteger(DEFAULT_SOCKET_TIMEOUT);
089: maxOpenConn = configuration.getChild("max-connections")
090: .getValueAsInteger(DEFAULT_MAX_CONNECTIONS);
091: if (timeout < 0) {
092: StringBuffer exceptionBuffer = new StringBuffer(128)
093: .append("Specified socket timeout value of ")
094: .append(timeout).append(" is not a legal value.");
095: throw new ConfigurationException(exceptionBuffer.toString());
096: }
097: if (maxOpenConn < 0) {
098: StringBuffer exceptionBuffer = new StringBuffer(128)
099: .append(
100: "Specified maximum number of open connections of ")
101: .append(maxOpenConn).append(
102: " is not a legal value.");
103: throw new ConfigurationException(exceptionBuffer.toString());
104: }
105: if (getLogger().isDebugEnabled()) {
106: getLogger().debug(
107: "Connection timeout is "
108: + (timeout == 0 ? "unlimited" : Long
109: .toString(timeout)));
110: getLogger().debug(
111: "The maximum number of simultaneously open connections is "
112: + (maxOpenConn == 0 ? "unlimited" : Integer
113: .toString(maxOpenConn)));
114: }
115: }
116:
117: /**
118: * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
119: */
120: public void service(ServiceManager componentManager)
121: throws ServiceException {
122: threadManager = (ThreadManager) componentManager
123: .lookup(ThreadManager.ROLE);
124: }
125:
126: /**
127: * Disconnects all the underlying ServerConnections
128: */
129: public void dispose() {
130: disposed = true;
131: if (getLogger().isDebugEnabled()) {
132: getLogger().debug(
133: "Starting SimpleConnectionManager dispose...");
134: }
135: final String[] names = (String[]) connectionMap.keySet()
136: .toArray(new String[0]);
137: for (int i = 0; i < names.length; i++) {
138: try {
139: if (getLogger().isDebugEnabled()) {
140: getLogger().debug(
141: "Disconnecting ServerConnection "
142: + names[i]);
143: }
144: disconnect(names[i], true);
145: } catch (final Exception e) {
146: getLogger().warn("Error disconnecting " + names[i], e);
147: }
148: }
149: if (getLogger().isDebugEnabled()) {
150: getLogger().debug(
151: "Finishing SimpleConnectionManager dispose...");
152: }
153: }
154:
155: /**
156: * Start managing a connection.
157: * Management involves accepting connections and farming them out to threads
158: * from pool to be handled.
159: *
160: * @param name the name of connection
161: * @param socket the ServerSocket from which to
162: * @param handlerFactory the factory from which to acquire handlers
163: * @param threadPool the thread pool to use
164: * @param maxOpenConnections the maximum number of open connections allowed for this server socket.
165: * @exception Exception if an error occurs
166: */
167: public void connect(String name, ServerSocket socket,
168: ConnectionHandlerFactory handlerFactory,
169: ThreadPool threadPool, int maxOpenConnections)
170: throws Exception {
171: if (disposed) {
172: throw new IllegalStateException(
173: "Connection manager has already been shutdown.");
174: }
175: if (null != connectionMap.get(name)) {
176: throw new IllegalArgumentException(
177: "Connection already exists with name " + name);
178: }
179: if (maxOpenConnections < 0) {
180: throw new IllegalArgumentException(
181: "The maximum number of client connections per server socket cannot be less that zero.");
182: }
183: ServerConnection runner = new ServerConnection(socket,
184: handlerFactory, threadPool, timeout, maxOpenConnections);
185: setupLogger(runner);
186: ContainerUtil.initialize(runner);
187: connectionMap.put(name, runner);
188: threadPool.execute(runner);
189: }
190:
191: /**
192: * Start managing a connection.
193: * Management involves accepting connections and farming them out to threads
194: * from pool to be handled.
195: *
196: * @param name the name of connection
197: * @param socket the ServerSocket from which to
198: * @param handlerFactory the factory from which to acquire handlers
199: * @param threadPool the thread pool to use
200: * @exception Exception if an error occurs
201: */
202: public void connect(String name, ServerSocket socket,
203: ConnectionHandlerFactory handlerFactory,
204: ThreadPool threadPool) throws Exception {
205: connect(name, socket, handlerFactory, threadPool, maxOpenConn);
206: }
207:
208: /**
209: * Start managing a connection.
210: * This is similar to other connect method except that it uses default thread pool.
211: *
212: * @param name the name of connection
213: * @param socket the ServerSocket from which to
214: * @param handlerFactory the factory from which to acquire handlers
215: * @exception Exception if an error occurs
216: */
217: public void connect(String name, ServerSocket socket,
218: ConnectionHandlerFactory handlerFactory) throws Exception {
219: connect(name, socket, handlerFactory, threadManager
220: .getDefaultThreadPool());
221: }
222:
223: /**
224: * Start managing a connection.
225: * This is similar to other connect method except that it uses default thread pool.
226: *
227: * @param name the name of connection
228: * @param socket the ServerSocket from which to
229: * @param handlerFactory the factory from which to acquire handlers
230: * @param maxOpenConnections the maximum number of open connections allowed for this server socket.
231: * @exception Exception if an error occurs
232: */
233: public void connect(String name, ServerSocket socket,
234: ConnectionHandlerFactory handlerFactory,
235: int maxOpenConnections) throws Exception {
236: connect(name, socket, handlerFactory, threadManager
237: .getDefaultThreadPool(), maxOpenConnections);
238: }
239:
240: /**
241: * This shuts down all handlers and socket, waiting for each to gracefully shutdown.
242: *
243: * @param name the name of connection
244: * @exception Exception if an error occurs
245: */
246: public void disconnect(final String name) throws Exception {
247: disconnect(name, false);
248: }
249:
250: /**
251: * This shuts down a connection.
252: * If tearDown is true then it will forcefully the connection and try
253: * to return as soon as possible. Otherwise it will behave the same as
254: * void disconnect( String name );
255: *
256: * @param name the name of connection
257: * @param tearDown if true will forcefully tear down all handlers
258: * @exception Exception if an error occurs
259: */
260: public void disconnect(final String name, final boolean tearDown)
261: throws Exception {
262: ServerConnection connection = (ServerConnection) connectionMap
263: .remove(name);
264: if (null == connection) {
265: throw new IllegalArgumentException(
266: "No such connection with name " + name);
267: }
268: // TODO: deal with tear down parameter
269: connection.dispose();
270: }
271:
272: /**
273: * Returns the default maximum number of open connections supported by this
274: * SimpleConnectionManager
275: *
276: * @return the maximum number of connections
277: */
278: public int getMaximumNumberOfOpenConnections() {
279: return maxOpenConn;
280: }
281:
282: }
|