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.core;
019:
020: import org.apache.avalon.framework.activity.Disposable;
021: import org.apache.avalon.framework.activity.Initializable;
022: import org.apache.avalon.framework.configuration.Configurable;
023: import org.apache.avalon.framework.configuration.Configuration;
024: import org.apache.avalon.framework.configuration.ConfigurationException;
025: import org.apache.avalon.framework.logger.LogEnabled;
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:
030: import org.apache.excalibur.thread.ThreadPool;
031: import org.apache.avalon.cornerstone.services.threads.ThreadManager;
032:
033: import org.apache.avalon.cornerstone.services.connection.AbstractHandlerFactory;
034: import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
035: import org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory;
036: import org.apache.avalon.cornerstone.services.sockets.ServerSocketFactory;
037: import org.apache.avalon.cornerstone.services.sockets.SocketManager;
038:
039: import org.apache.james.services.JamesConnectionManager;
040: import org.apache.james.util.watchdog.ThreadPerWatchdogFactory;
041: import org.apache.james.util.watchdog.WatchdogFactory;
042:
043: import java.net.InetAddress;
044: import java.net.ServerSocket;
045: import java.net.UnknownHostException;
046:
047: /**
048: * Server which creates connection handlers. All new James service must
049: * inherit from this abstract implementation.
050: *
051: */
052: public abstract class AbstractJamesService extends
053: AbstractHandlerFactory implements Serviceable, Configurable,
054: Disposable, Initializable, ConnectionHandlerFactory {
055:
056: /**
057: * The default value for the connection timeout.
058: */
059: protected static final int DEFAULT_TIMEOUT = 5 * 60 * 1000;
060:
061: /**
062: * The name of the parameter defining the connection timeout.
063: */
064: protected static final String TIMEOUT_NAME = "connectiontimeout";
065:
066: /**
067: * The default value for the connection backlog.
068: */
069: protected static final int DEFAULT_BACKLOG = 5;
070:
071: /**
072: * The name of the parameter defining the connection backlog.
073: */
074: protected static final String BACKLOG_NAME = "connectionBacklog";
075:
076: /**
077: * The name of the parameter defining the service hello name.
078: */
079: public static final String HELLO_NAME = "helloName";
080:
081: /**
082: * The ConnectionManager that spawns and manages service connections.
083: */
084: private JamesConnectionManager connectionManager;
085:
086: /**
087: * The name of the thread group to be used by this service for
088: * generating connections
089: */
090: protected String threadGroup;
091:
092: /**
093: * The thread pool used by this service that holds the threads
094: * that service the client connections.
095: */
096: protected ThreadPool threadPool = null;
097:
098: /**
099: * The server socket type used to generate connections for this server.
100: */
101: protected String serverSocketType = "plain";
102:
103: /**
104: * The port on which this service will be made available.
105: */
106: protected int port = -1;
107:
108: /**
109: * Network interface to which the service will bind. If not set,
110: * the server binds to all available interfaces.
111: */
112: protected InetAddress bindTo = null;
113:
114: /*
115: * The server socket associated with this service
116: */
117: protected ServerSocket serverSocket;
118:
119: /**
120: * The name of the connection used by this service. We need to
121: * track this so we can tell the ConnectionManager which service
122: * to disconnect upon shutdown.
123: */
124: protected String connectionName;
125:
126: /**
127: * The maximum number of connections allowed for this service.
128: */
129: protected Integer connectionLimit;
130:
131: /**
132: * The connection idle timeout. Used primarily to prevent server
133: * problems from hanging a connection.
134: */
135: protected int timeout;
136:
137: /**
138: * The connection backlog.
139: */
140: protected int backlog;
141:
142: /**
143: * The hello name for the service.
144: */
145: protected String helloName;
146:
147: /**
148: * The component manager used by this service.
149: */
150: private ServiceManager compMgr;
151:
152: /**
153: * Whether this service is enabled.
154: */
155: private volatile boolean enabled;
156:
157: /**
158: * Flag holding the disposed state of the component.
159: */
160: private boolean m_disposed = false;
161:
162: /**
163: * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
164: */
165: public void service(ServiceManager comp) throws ServiceException {
166: super .service(comp);
167: compMgr = comp;
168: connectionManager = (JamesConnectionManager) compMgr
169: .lookup(JamesConnectionManager.ROLE);
170: }
171:
172: /**
173: * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
174: */
175: public void configure(Configuration conf)
176: throws ConfigurationException {
177: enabled = conf.getAttributeAsBoolean("enabled", true);
178: if (!enabled) {
179: getLogger().info(
180: getServiceType() + " disabled by configuration");
181: return;
182: }
183:
184: Configuration handlerConfiguration = conf.getChild("handler");
185:
186: // Send the handler subconfiguration to the super class. This
187: // ensures that the handler config is passed to the handlers.
188: //
189: // TODO: This should be rationalized. The handler element of the
190: // server configuration doesn't really make a whole lot of
191: // sense. We should modify the config to get rid of it.
192: // Keeping it for now to maintain backwards compatibility.
193: super .configure(handlerConfiguration);
194:
195: port = conf.getChild("port")
196: .getValueAsInteger(getDefaultPort());
197:
198: Configuration serverSocketTypeConf = conf.getChild(
199: "serverSocketType", false);
200: String confSocketType = null;
201: if (serverSocketTypeConf != null) {
202: confSocketType = serverSocketTypeConf.getValue();
203: }
204:
205: if (confSocketType == null) {
206: // Only load the useTLS parameter if a specific socket type has not
207: // been specified. This maintains backwards compatibility while
208: // allowing us to have more complex (i.e. multiple SSL configuration)
209: // deployments
210: final boolean useTLS = conf.getChild("useTLS")
211: .getValueAsBoolean(isDefaultTLSEnabled());
212: if (useTLS) {
213: serverSocketType = "ssl";
214: }
215: } else {
216: serverSocketType = confSocketType;
217: }
218:
219: StringBuffer infoBuffer;
220: threadGroup = conf.getChild("threadGroup").getValue(null);
221: if (threadGroup != null) {
222: infoBuffer = new StringBuffer(64).append(getServiceType())
223: .append(" uses thread group: ").append(threadGroup);
224: getLogger().info(infoBuffer.toString());
225: } else {
226: getLogger().info(
227: getServiceType() + " uses default thread group.");
228: }
229:
230: try {
231: final String bindAddress = conf.getChild("bind").getValue(
232: null);
233: if (null != bindAddress) {
234: bindTo = InetAddress.getByName(bindAddress);
235: infoBuffer = new StringBuffer(64).append(
236: getServiceType()).append(" bound to: ").append(
237: bindTo);
238: getLogger().info(infoBuffer.toString());
239: }
240: } catch (final UnknownHostException unhe) {
241: throw new ConfigurationException(
242: "Malformed bind parameter in configuration of service "
243: + getServiceType(), unhe);
244: }
245:
246: String hostName = null;
247: try {
248: hostName = InetAddress.getLocalHost().getHostName();
249: } catch (UnknownHostException ue) {
250: hostName = "localhost";
251: }
252:
253: infoBuffer = new StringBuffer(64).append(getServiceType())
254: .append(" is running on: ").append(hostName);
255: getLogger().info(infoBuffer.toString());
256:
257: Configuration helloConf = handlerConfiguration
258: .getChild(HELLO_NAME);
259: boolean autodetect = helloConf.getAttributeAsBoolean(
260: "autodetect", true);
261: if (autodetect) {
262: helloName = hostName;
263: } else {
264: helloName = helloConf.getValue("localhost");
265: }
266: infoBuffer = new StringBuffer(64).append(getServiceType())
267: .append(" handler hello name is: ").append(helloName);
268: getLogger().info(infoBuffer.toString());
269:
270: timeout = handlerConfiguration.getChild(TIMEOUT_NAME)
271: .getValueAsInteger(DEFAULT_TIMEOUT);
272:
273: infoBuffer = new StringBuffer(64).append(getServiceType())
274: .append(" handler connection timeout is: ").append(
275: timeout);
276: getLogger().info(infoBuffer.toString());
277:
278: backlog = conf.getChild(BACKLOG_NAME).getValueAsInteger(
279: DEFAULT_BACKLOG);
280:
281: infoBuffer = new StringBuffer(64).append(getServiceType())
282: .append(" connection backlog is: ").append(backlog);
283: getLogger().info(infoBuffer.toString());
284:
285: if (connectionManager instanceof JamesConnectionManager) {
286: String connectionLimitString = conf.getChild(
287: "connectionLimit").getValue(null);
288: if (connectionLimitString != null) {
289: try {
290: connectionLimit = new Integer(connectionLimitString);
291: } catch (NumberFormatException nfe) {
292: getLogger()
293: .error(
294: "Connection limit value is not properly formatted.",
295: nfe);
296: }
297: if (connectionLimit.intValue() < 0) {
298: getLogger()
299: .error(
300: "Connection limit value cannot be less than zero.");
301: throw new ConfigurationException(
302: "Connection limit value cannot be less than zero.");
303: }
304: } else {
305: connectionLimit = new Integer(
306: ((JamesConnectionManager) connectionManager)
307: .getMaximumNumberOfOpenConnections());
308: }
309: infoBuffer = new StringBuffer(128).append(getServiceType())
310: .append(" will allow a maximum of ").append(
311: connectionLimit.intValue()).append(
312: " connections.");
313: getLogger().info(infoBuffer.toString());
314: }
315: }
316:
317: /**
318: * @see org.apache.avalon.framework.activity.Initializable#initialize()
319: */
320: public void initialize() throws Exception {
321: if (!isEnabled()) {
322: getLogger().info(getServiceType() + " Disabled");
323: System.out.println(getServiceType() + " Disabled");
324: return;
325: }
326: getLogger().debug(getServiceType() + " init...");
327:
328: SocketManager socketManager = (SocketManager) compMgr
329: .lookup(SocketManager.ROLE);
330:
331: ThreadManager threadManager = (ThreadManager) compMgr
332: .lookup(ThreadManager.ROLE);
333:
334: if (threadGroup != null) {
335: threadPool = threadManager.getThreadPool(threadGroup);
336: } else {
337: threadPool = threadManager.getDefaultThreadPool();
338: }
339:
340: ServerSocketFactory factory = socketManager
341: .getServerSocketFactory(serverSocketType);
342: ServerSocket serverSocket = factory.createServerSocket(port,
343: backlog, bindTo);
344:
345: if (null == connectionName) {
346: final StringBuffer sb = new StringBuffer();
347: sb.append(serverSocketType);
348: sb.append(':');
349: sb.append(port);
350:
351: if (null != bindTo) {
352: sb.append('/');
353: sb.append(bindTo);
354: }
355: connectionName = sb.toString();
356: }
357:
358: if ((connectionLimit != null)
359: && (connectionManager instanceof JamesConnectionManager)) {
360: if (null != threadPool) {
361: ((JamesConnectionManager) connectionManager).connect(
362: connectionName, serverSocket, this , threadPool,
363: connectionLimit.intValue());
364: } else {
365: ((JamesConnectionManager) connectionManager).connect(
366: connectionName, serverSocket, this ,
367: connectionLimit.intValue()); // default pool
368: }
369: } else {
370: if (null != threadPool) {
371: connectionManager.connect(connectionName, serverSocket,
372: this , threadPool);
373: } else {
374: connectionManager.connect(connectionName, serverSocket,
375: this ); // default pool
376: }
377: }
378:
379: getLogger().debug(getServiceType() + " ...init end");
380:
381: StringBuffer logBuffer = new StringBuffer(64).append(
382: getServiceType()).append(" started ").append(
383: connectionName);
384: String logString = logBuffer.toString();
385: System.out.println(logString);
386: getLogger().info(logString);
387: }
388:
389: /**
390: * @see org.apache.avalon.framework.activity.Disposable#dispose()
391: */
392: public void dispose() {
393:
394: if (!isEnabled()) {
395: return;
396: }
397:
398: if (m_disposed) {
399: if (getLogger().isWarnEnabled()) {
400: getLogger().warn(
401: "ignoring disposal request - already disposed");
402: }
403: return;
404: }
405:
406: if (getLogger().isDebugEnabled()) {
407: getLogger().debug("disposal");
408: }
409:
410: m_disposed = true;
411: if (getLogger().isDebugEnabled()) {
412: StringBuffer infoBuffer = new StringBuffer(64).append(
413: getServiceType()).append(" dispose... ").append(
414: connectionName);
415: getLogger().debug(infoBuffer.toString());
416: }
417:
418: try {
419: connectionManager.disconnect(connectionName, true);
420: } catch (final Exception e) {
421: StringBuffer warnBuffer = new StringBuffer(64).append(
422: "Error disconnecting ").append(getServiceType())
423: .append(": ");
424: getLogger().warn(warnBuffer.toString(), e);
425: }
426:
427: compMgr = null;
428:
429: connectionManager = null;
430: threadPool = null;
431:
432: // This is needed to make sure sockets are promptly closed on Windows 2000
433: // TODO: Check this - shouldn't need to explicitly gc to force socket closure
434: System.gc();
435:
436: getLogger().debug(getServiceType() + " ...dispose end");
437: }
438:
439: /**
440: * This constructs the WatchdogFactory that will be used to guard
441: * against runaway or stuck behavior. Should only be called once
442: * by a subclass in its initialize() method.
443: *
444: * @return the WatchdogFactory to be employed by subclasses.
445: */
446: protected WatchdogFactory getWatchdogFactory() {
447: WatchdogFactory theWatchdogFactory = null;
448: theWatchdogFactory = new ThreadPerWatchdogFactory(threadPool,
449: timeout);
450: if (theWatchdogFactory instanceof LogEnabled) {
451: ((LogEnabled) theWatchdogFactory)
452: .enableLogging(getLogger());
453: }
454: return theWatchdogFactory;
455: }
456:
457: /**
458: * Describes whether this service is enabled by configuration.
459: *
460: * @return is the service enabled.
461: */
462: public final boolean isEnabled() {
463: return enabled;
464: }
465:
466: /**
467: * Overide this method to create actual instance of connection handler.
468: *
469: * @return the new ConnectionHandler
470: * @exception Exception if an error occurs
471: */
472: protected abstract ConnectionHandler newHandler() throws Exception;
473:
474: /**
475: * Get the default port for this server type.
476: *
477: * It is strongly recommended that subclasses of this class
478: * override this method to specify the default port for their
479: * specific server type.
480: *
481: * @return the default port
482: */
483: protected int getDefaultPort() {
484: return 0;
485: }
486:
487: /**
488: * Get whether TLS is enabled for this server's socket by default.
489: *
490: * @return the default port
491: */
492: protected boolean isDefaultTLSEnabled() {
493: return false;
494: }
495:
496: /**
497: * This method returns the type of service provided by this server.
498: * This should be invariant over the life of the class.
499: *
500: * Subclasses may override this implementation. This implementation
501: * parses the complete class name and returns the undecorated class
502: * name.
503: *
504: * @return description of this server
505: */
506: public String getServiceType() {
507: String name = getClass().getName();
508: int p = name.lastIndexOf(".");
509: if (p > 0 && p < name.length() - 2) {
510: name = name.substring(p + 1);
511: }
512: return name;
513: }
514:
515: /**
516: * Returns the port that the service is bound to
517: *
518: * @return int The port number
519: */
520: public int getPort() {
521: return port;
522: }
523:
524: /**
525: * Returns the address if the network interface the socket is bound to
526: *
527: * @return String The network interface name
528: */
529: public String getNetworkInterface() {
530: if (bindTo == null) {
531: return "All";
532: } else {
533: return bindTo.getHostAddress();
534: }
535: }
536:
537: /**
538: * Returns the server socket type, plain or SSL
539: *
540: * @return String The scoekt type, plain or SSL
541: */
542: public String getSocketType() {
543: return serverSocketType;
544: }
545: }
|