View Javadoc

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