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