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
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
184
185
186
187
188
189
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
202
203
204
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());
357 }
358 } else {
359 if (null != threadPool) {
360 connectionManager.connect(connectionName, serverSocket, this, threadPool);
361 }
362 else {
363 connectionManager.connect(connectionName, serverSocket, this);
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
428
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