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
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
186
187
188
189
190
191
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
204
205
206
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());
359 }
360 } else {
361 if (null != threadPool) {
362 connectionManager.connect(connectionName, serverSocket, this, threadPool);
363 }
364 else {
365 connectionManager.connect(connectionName, serverSocket, this);
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
430
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