Source code: com/RuntimeCollective/webapps/servlet/InitialiserServlet.java
1 /* $Header: /home/CVS/rjp/src/com/RuntimeCollective/webapps/servlet/InitialiserServlet.java,v 1.26 2003/10/15 11:14:16 joe Exp $
2 * $Revision: 1.26 $
3 * $Date: 2003/10/15 11:14:16 $
4 *
5 * ====================================================================
6 *
7 * Josephine : http://www.runtime-collective.com/josephine/index.html
8 *
9 * Copyright (C) 2003 Runtime Collective
10 *
11 * This product includes software developed by the
12 * Apache Software Foundation (http://www.apache.org/).
13 *
14 * This library is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2.1 of the License, or (at your option) any later version.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 *
28 */
29
30 package com.RuntimeCollective.webapps.servlet;
31
32 import java.io.IOException;
33 import java.sql.SQLException;
34 import java.util.Enumeration;
35 import java.util.HashSet;
36 import java.util.Properties;
37 import java.util.StringTokenizer;
38
39 import javax.mail.Session;
40 import javax.naming.Context;
41 import javax.naming.InitialContext;
42 import javax.naming.NamingException;
43 import javax.servlet.ServletConfig;
44 import javax.servlet.ServletException;
45 import javax.servlet.http.HttpServlet;
46 import javax.sql.DataSource;
47
48 import org.apache.log4j.BasicConfigurator;
49 import org.apache.log4j.FileAppender;
50 import org.apache.log4j.HTMLLayout;
51 import org.apache.log4j.Level;
52 import org.apache.log4j.Logger;
53 import org.apache.log4j.SimpleLayout;
54 import org.apache.log4j.net.SocketAppender;
55 import org.apache.log4j.xml.XMLLayout;
56
57 import com.RuntimeCollective.webapps.EntityBeanStore;
58 import com.RuntimeCollective.webapps.EntityBeanStoreHandler;
59 import com.RuntimeCollective.webapps.RuntimeDataSource;
60 import com.RuntimeCollective.webapps.RuntimeParameters;
61 import com.RuntimeCollective.webapps.UserGroups;
62 import com.RuntimeCollective.webapps.WrappedDataSource;
63 /**
64 * This servlet initializes the Runtime webapps environment using the initialisation parameters. These parameters are of the following kinds:
65 * <ul>
66 * <li><strong>Beans</strong> -- parameters used to register beans on an EntityBeanStore, where the name of the parameter is bean.<i>bean_class_name</i> (for example, 'bean.com.RuntimeCollective.webapps.bean.SimpleUser') and the value is the name of the class of EntityBeanStore where they should be stored (for example, 'com.RuntimeCollective.webapps.SimpleEntityBeanStore').</li>.
67 * <li><strong>Parameters</strong> -- general purpose parameters registered on RuntimeParameters, where the name of the parameter is param.</i>param_name</li> and the value is that given.
68 * <li><strong>Properties</strong> -- System properties that will be set with <code>System.setProperty</code>, where the name of the parameter is property.</i>property_name</li> and the value is that given.
69 * <li><strong>Database</strong> -- One or more RDBMS data sources can be defined. The parameters for the first are defined as db.*, the second as db.1.*, and so on. Once defined, these data sources can be accessed using <code>RuntimeDataSource</code>.
70 * <ul>
71 * <li><code>db.alias</code>: Alias of the database. This will then be used by <code>RuntimeDataSource</code> to identify the database. If only one database is defined, then no alias is required.
72 * <li><code>db.JDBCDriverClass</code>: Name of JDBC driver class (eg oracle.jdbc.driver.OracleDriver).
73 * <li><code>db.maxConnections</code>: Maximum number of connections to maintain in the DB pool.
74 * <li><code>db.minConnections</code>: Minimum number of connections to maintain in the DB pool.
75 * <li><code>db.user</code>: User name for DB access
76 * <li><code>db.password</code>: Password for DB access
77 * <li><code>db.url</code>: URL of the database (eg. jdbc:oracle:thin:@sirius:1521:infodb)
78 * </ul>
79 * <li><strong>Log</strong> -- Define the logger used by this application. If none is defined then logging messages are sent to System.out by default. Otherwise, different types of log can be configured using the following parameters:
80 * <ul>
81 * <li><code>log.level</code>: The (lowest) level of log output required. Any of FATAL, ERROR, WARN, INFO, or DEBUG.
82 * <li><code>log.host</code>: The name of the host on which a log4J reader is running, and the port that it is listening to (eg 'localhost:8801'). If the port is not defined, then output is send to port 4445 by default. To read the output of this log, try using Lumbermill or Chainsaw.
83 * <li><code>log.file</code>: The name of a file to send logging messages to in Log4j SimpleLayout (human readable).
84 * <li><code>log.xmlFile</code>: The name of a file to send logging messages to in Log4j xml format (readable using Chainsaw).
85 * <li><code>log.htmlFile</code>: The name of a file to send logging messages to in Log4j html format (readable using a browser).
86 * </ul>
87 *
88 * @version $Id: InitialiserServlet.java,v 1.26 2003/10/15 11:14:16 joe Exp $
89 * @see com.RuntimeCollective.webapps.EntityBeanStore
90 * @see com.RuntimeCollective.webapps.RuntimeDataSource
91 * @see com.RuntimeCollective.webapps.RuntimeParameters
92 */
93 public final class InitialiserServlet extends HttpServlet {
94
95 /**
96 * Initialize the environment.
97 * @exception ServletException if we cannot configure ourselves correctly
98 */
99 public void init() throws ServletException {
100 ServletConfig sc = getServletConfig();
101 initLogging(sc);
102 initDataSource(sc);
103 initParameters(sc);
104 initBeanStore(sc);
105 initProperties(sc);
106 initProxy(sc);
107 initUserGroups(sc);
108 initMailSession(sc);
109 }
110
111
112 /** Initialise the logger. */
113 private void initLogging(ServletConfig sc) {
114
115 RuntimeParameters.logInfo( this,"Configuring the logger");
116
117 // Default to not logging via a log4j logger
118 RuntimeParameters.setLog4jLogging(false);
119
120 // Set the log level in RuntimeParameters (only useful for non-log4j logging)
121 String loggingLevel = sc.getInitParameter("log.level");
122 if (loggingLevel == null) {
123 // default to debug
124 loggingLevel = RuntimeParameters.LOG_LEVEL_DEBUG;
125 } else if (!loggingLevel.equals(RuntimeParameters.LOG_LEVEL_DEBUG) && !loggingLevel.equals(RuntimeParameters.LOG_LEVEL_INFO) && !loggingLevel.equals(RuntimeParameters.LOG_LEVEL_WARN) && !loggingLevel.equals(RuntimeParameters.LOG_LEVEL_ERROR) && !loggingLevel.equals(RuntimeParameters.LOG_LEVEL_FATAL)) {
126 // unrecognised error level
127 loggingLevel = RuntimeParameters.LOG_LEVEL_DEBUG;
128 }
129 RuntimeParameters.setLogLevel(loggingLevel);
130
131 // Log to a host
132 String loggingHostPort = sc.getInitParameter("log.host");
133 if (loggingHostPort != null) {
134 // Extract the host and port (defaults to 4445)
135 StringTokenizer st = new StringTokenizer( loggingHostPort, ":" );
136 String host = st.nextToken();
137 String port = "4445";
138 if ( st.hasMoreTokens() ) port = st.nextToken();
139 SocketAppender appender = new SocketAppender( host, Integer.parseInt(port) );
140 BasicConfigurator.configure(appender);
141 RuntimeParameters.setLog4jLogging(true);
142 // set the log level
143 setLogLevel(loggingLevel);
144 }
145
146 // Log to a file
147 String loggingFile = sc.getInitParameter("log.file");
148 if (loggingFile != null ) {
149 try {
150 FileAppender appender = new FileAppender( new SimpleLayout(), loggingFile);
151 BasicConfigurator.configure(appender);
152 RuntimeParameters.setLog4jLogging(true);
153 // set the log level
154 setLogLevel(loggingLevel);
155 } catch (IOException e) { throw new RuntimeException( "Unable to create file logger to "+loggingFile ); }
156 }
157
158 // Log to an XML file
159 loggingFile = sc.getInitParameter("log.xmlFile");
160 if (loggingFile != null ) {
161 try {
162 FileAppender appender = new FileAppender( new XMLLayout(), loggingFile);
163 BasicConfigurator.configure(appender);
164 RuntimeParameters.setLog4jLogging(true);
165 // set the log level
166 setLogLevel(loggingLevel);
167 } catch (IOException e) { throw new RuntimeException( "Unable to create xml file logger to "+loggingFile ); }
168 }
169
170 // Log to an HTML file
171 loggingFile = sc.getInitParameter("log.htmlFile");
172 if (loggingFile != null ) {
173 try {
174 FileAppender appender = new FileAppender( new HTMLLayout(), loggingFile);
175 BasicConfigurator.configure(appender);
176 RuntimeParameters.setLog4jLogging(true);
177 // set the log level
178 setLogLevel(loggingLevel);
179 } catch (IOException e) { throw new RuntimeException( "Unable to create html file logger to "+loggingFile ); }
180 }
181 }
182
183 /** Set the root log level, specified in web.xml as a String. */
184 private void setLogLevel(String logLevel) {
185 // the string has already been checked/defaulted in initLogging, don't do it again.
186
187 if (logLevel != null) {
188 if (logLevel.equals(RuntimeParameters.LOG_LEVEL_DEBUG)) {
189 Logger.getRootLogger().setLevel((Level) Level.DEBUG);
190 } else if (logLevel.equals(RuntimeParameters.LOG_LEVEL_INFO)) {
191 Logger.getRootLogger().setLevel((Level) Level.INFO);
192 } else if (logLevel.equals(RuntimeParameters.LOG_LEVEL_WARN)) {
193 Logger.getRootLogger().setLevel((Level) Level.WARN);
194 } else if (logLevel.equals(RuntimeParameters.LOG_LEVEL_ERROR)) {
195 Logger.getRootLogger().setLevel((Level) Level.ERROR);
196 } else if (logLevel.equals(RuntimeParameters.LOG_LEVEL_FATAL)) {
197 Logger.getRootLogger().setLevel((Level) Level.FATAL);
198 }
199 }
200 }
201
202
203 /** Initialise the parameters object. */
204 private void initParameters(ServletConfig sc) {
205 RuntimeParameters.logInfo(this, "Starting to register the init parameters");
206
207 // Add all parameters in web.xml that start with "param."
208 Enumeration params = sc.getInitParameterNames();
209 while (params.hasMoreElements()) {
210 String paramName = (String) params.nextElement();
211
212 if (paramName.startsWith("param.")) {
213 String paramClassName = paramName.substring(6);
214 String paramValue = sc.getInitParameter( paramName );
215
216 RuntimeParameters.logInfo(this, "Registering init parameter : " + paramClassName + " = " + paramValue);
217
218 // Add the parameter
219 RuntimeParameters.set( paramClassName, paramValue );
220 }
221 }
222 RuntimeParameters.logInfo(this, "Finished registering the init parameters");
223 }
224
225 /** Initialise the system properties. */
226 private void initProperties(ServletConfig sc) {
227 RuntimeParameters.logInfo(this, "Starting to set the system properties");
228
229 // Add all parameters in web.xml that start with "property."
230 Enumeration params = sc.getInitParameterNames();
231 while (params.hasMoreElements()) {
232 String paramName = (String) params.nextElement();
233
234 if (paramName.startsWith("property.")) {
235 String propertyName = paramName.substring(9);
236 String propertyValue = sc.getInitParameter( paramName );
237
238 RuntimeParameters.logInfo(this, "Setting system property : " + propertyName + " = " + propertyValue);
239
240 // Set the System.property
241 System.setProperty( propertyName, propertyValue );
242 }
243 }
244 RuntimeParameters.logInfo(this, "Finished registering the init parameters");
245 }
246
247 /** Initialise the use of an http proxy for all <code>java.net.URLConnection</code>s,
248 * if we have the web.xml parameters <code>param.proxyHost</code> and <code>proxyPort</code> */
249 private void initProxy(ServletConfig sc) {
250 String proxyHost="";
251 String proxyPort="";
252 try {
253 proxyHost = RuntimeParameters.get("proxyHost");
254 proxyPort = RuntimeParameters.get("proxyPort");
255 } catch (RuntimeException e) {
256 RuntimeParameters.logInfo(this, "No proxy information specified.");
257 return;
258 }
259
260 // Set up the http proxy
261 RuntimeParameters.logInfo(this, "Starting to set up the HTTP proxy");
262
263 if (!proxyHost.equals("") && !proxyPort.equals("")) {
264 System.getProperties().setProperty("http.proxyHost", proxyHost);
265 System.getProperties().setProperty("http.proxyPort", proxyPort);
266 RuntimeParameters.logInfo(this, "Using proxy "+proxyHost+", port "+proxyPort);
267 } else {
268 RuntimeParameters.logInfo(this, "No proxy information specified.");
269 }
270
271 RuntimeParameters.logInfo(this, "Finished setting up the HTTP proxy");
272 }
273
274
275 /** Initialise the datasource. */
276 private void initDataSource(ServletConfig sc) throws ServletException {
277 // Create a data source with the required parameters.
278
279 // Find out how many data sources we're supposed to be creating
280 // Special case: the first one (0) is not referenced by a number
281 int dbNum = 1;
282 while ( sc.getInitParameter( "db."+dbNum+".alias" ) != null ) {
283 dbNum++;
284 }
285 dbNum--;
286
287 // Inform the RuntimeDataSource of all the databases we're using
288 RuntimeParameters.logInfo( this,"Creating RuntimeDataSource instance");
289 try {
290
291 // Database info in web.xml is stored like:
292 // db.alias, db.JDBCDriverClass, etc.
293 // db.1.alias, db.1.JDBCDriverClass, etc.
294 // db.2.alias, db.2.JDBCDriverClass, etc.
295 for (int db=0; db <= dbNum; db++) {
296
297 String sep="db.";
298 // First database is just "db.*"; subsequent databases are "db.1.*"
299 if (db>0)
300 sep=sep+db+".";
301
302 if (null == sc.getInitParameter( sep+"jndi" )) {
303 RuntimeDataSource.addDb(db,
304 sc.getInitParameter( sep+"alias" ),
305 sc.getInitParameter( sep+"JDBCDriverClass" ),
306 Integer.parseInt( sc.getInitParameter(sep+"maxConnections") ),
307 Integer.parseInt( sc.getInitParameter(sep+"minConnections") ),
308 sc.getInitParameter( sep+"user" ),
309 sc.getInitParameter( sep+"password" ),
310 sc.getInitParameter( sep+"url" ),
311 sc.getInitParameter( sep+"type" ));
312 } else {
313 Context ctx = new InitialContext();
314 DataSource ds = (DataSource)
315 ctx.lookup("jdbc" +
316 sc.getInitParameter( sep+"jndi" ));
317
318 WrappedDataSource wds = new WrappedDataSource(ds);
319 wds.setType(sc.getInitParameter( sep+"type" ));
320
321 RuntimeDataSource.addDb(sc.getInitParameter( sep+"alias" ), wds);
322 }
323 }
324
325 // keep a copy of the "db.alias", used by RuntimeDataSource to get the default DB
326 RuntimeParameters.set("db.alias", sc.getInitParameter("db.alias"));
327
328 } catch (NamingException e) {
329 RuntimeParameters.logError(this,
330 "Unable to locate jndi data source",
331 e);
332 throw new ServletException("Unable to initialise RuntimeDataSource",e);
333 } catch (RuntimeException e) {
334 RuntimeParameters.logError( this,"Unable to initialise RuntimeDataSource",e);
335 throw new ServletException("Unable to initialise RuntimeDataSource",e);
336 }
337
338 // Tell the datasource to stop accepting new databases
339 RuntimeDataSource.doneAdding();
340
341 }
342
343 /** Initialise the bean store. */
344 private void initBeanStore(ServletConfig sc) throws ServletException {
345 try {
346 // GarbageCollectedCache is the default default
347 String defaultCacheClass = "com.RuntimeCollective.webapps.GarbageCollectedCache";
348
349 // Registers all beans in web.xml (all init-params starting with "bean.")
350 RuntimeParameters.logInfo( this,"Registering beans on RuntimeParameters.getStore().");
351
352 Enumeration params = sc.getInitParameterNames();
353 String paramName;
354
355 // First get a set of all the types of EntityBeanStore required by this application,
356 // in order to find out the number of entity bean stores required.
357 HashSet ebsTypes = new HashSet();
358 while (params.hasMoreElements()) {
359 paramName = (String) params.nextElement();
360
361 if (paramName.startsWith("bean.")) {
362 String paramValue = sc.getInitParameter(paramName);
363 // get the bit before the first comma
364 if (paramValue.indexOf(",") != -1)
365 paramValue = paramValue.substring(0, paramValue.indexOf(",")).trim();
366 ebsTypes.add(paramValue);
367 }
368
369 // get the default cache class
370 // (it's a Parameter, but those are initialised after EntityBeans)
371 if (paramName.equals("param.defaultCacheClass"))
372 defaultCacheClass = sc.getInitParameter(paramName);
373 }
374
375 RuntimeParameters.logInfo( this,"[InitialiserServlet.initBeanStore()] defaultCacheClass : "+defaultCacheClass);
376
377 // If there were no EBS's required, then forget about it
378 if (ebsTypes.size()==0) return;
379
380 // If there is more than one type of EBS required, then we have to use an
381 // EntityBeanStoreHandler to handle them
382 if ( ebsTypes.size()>1 ) {
383
384 String beanClassName;
385 String storeClassName;
386 String cacheClassName;
387 EntityBeanStoreHandler ebs = new EntityBeanStoreHandler();
388
389 // And register each of the beans
390 params = sc.getInitParameterNames();
391 while (params.hasMoreElements()) {
392 paramName = (String) params.nextElement();
393 if (paramName.startsWith("bean.")) {
394
395 // the bean name
396 beanClassName = paramName.substring(5);
397
398 // the store name (get the bit before the first comma)
399 storeClassName = sc.getInitParameter(paramName);
400 if (storeClassName.indexOf(",") != -1)
401 storeClassName = storeClassName.substring(0, storeClassName.indexOf(",")).trim();
402
403 // the cache name (bit after the first comma, default to defaultCacheClass)
404 cacheClassName = sc.getInitParameter(paramName);
405 if (cacheClassName.indexOf(",") != -1)
406 cacheClassName = cacheClassName.substring(cacheClassName.indexOf(",")+1).trim();
407 else
408 cacheClassName = defaultCacheClass;
409
410 RuntimeParameters.logInfo( this,"[InitialiserServlet.initBeanStore()] Registering bean\n - Class : "+beanClassName+"\n - Store : "+storeClassName+"\n - Cache : "+cacheClassName);
411 ebs.registerBean(beanClassName, cacheClassName, storeClassName);
412 }
413 }
414
415 // Initialise the store, and stick it in the toolbox
416 RuntimeParameters.logInfo( this,"Initialising bean store.");
417 ebs.init();
418 RuntimeParameters.setStore( ebs );
419 }
420
421 // If there is only one type of EBS required create an EBS of the appropriate type.
422 if ( ebsTypes.size()==1 ) {
423
424 String beanClassName;
425 String cacheClassName;
426 EntityBeanStore ebs;
427
428 try {
429 ebs = (EntityBeanStore) Class.forName( (String) ebsTypes.iterator().next() ).getConstructor( new Class[] {} ).newInstance( new Object[] {} );
430 } catch (Exception e) {
431 RuntimeParameters.logError( this,"Unable to construct EntityBeanStore of type "+ebsTypes.iterator().next(),e);
432 throw new ServletException("Unable to construct EntityBeanStore of type "+ebsTypes.iterator().next(),e);
433 }
434
435 // And register each of the beans
436 params = sc.getInitParameterNames();
437 while (params.hasMoreElements()) {
438 paramName = (String) params.nextElement();
439 if (paramName.startsWith("bean.")) {
440
441 // the bean name
442 beanClassName = paramName.substring(5);
443
444 // the cache name (bit after the first comma, default to defaultCacheClass)
445 cacheClassName = sc.getInitParameter(paramName);
446 if (cacheClassName.indexOf(",") != -1)
447 cacheClassName = cacheClassName.substring(cacheClassName.indexOf(",")+1).trim();
448 else
449 cacheClassName = defaultCacheClass;
450
451 RuntimeParameters.logInfo( this,"[InitialiserServlet.initBeanStore()] Registering bean\n - Class : "+beanClassName+"\n - Cache : "+cacheClassName);
452 ebs.registerBean(beanClassName, cacheClassName);
453 }
454 }
455
456 // Initialise the store, and stick it in the toolbox
457 RuntimeParameters.logInfo( this,"Initialising bean store.");
458 ebs.init();
459 RuntimeParameters.setStore( ebs );
460
461 }
462
463
464 } catch (RuntimeException e) {
465 RuntimeParameters.logError( this,"Unable to register beans on EntityBeanStore.", e );
466 e.printStackTrace(System.out);
467 throw new ServletException("Unable to register beans on EntityBeanStore: "+e);
468 }
469 }
470
471
472 /** Initialise the UserGroups object. */
473 private void initUserGroups(ServletConfig sc) throws ServletException {
474 RuntimeParameters.setUserGroups(new UserGroups());
475 }
476
477 /** Initialise the JavaMail Session object. */
478 private void initMailSession(ServletConfig sc) throws ServletException {
479 try {
480 if (null == sc.getInitParameter( "mail.jndi" )) {
481 Properties mailProps = new Properties();
482 mailProps.put("mail.transport.protocol", "smtp");
483 mailProps.put("mail.host", RuntimeParameters.get("smtpHost"));
484 mailProps.put("mail.from", RuntimeParameters.get("systemEmailAddress"));
485
486 // FIXME: MAYBE this should be getDefaultInstance(mailProps)... but there's no guarantee
487 // we're the first thing to try and set up a mail session. I don't think...
488 RuntimeParameters.setMailSession(Session.getInstance(mailProps, null));
489 } else {
490 Context ctx = new InitialContext();
491 Session mailSession = (Session)
492 ctx.lookup( sc.getInitParameter( "mail.jndi" ));
493
494 RuntimeParameters.setMailSession(mailSession);
495 }
496 } catch (Exception e) {
497 e.printStackTrace(System.out);
498 RuntimeParameters.logError(this, "initMailSession could not initialise a mail session; web.xml must specify \"smtpHost\" and \"systemEmailAddress\" properties: "+e);
499 }
500 }
501
502 /**
503 * Gracefully shut down this servlet, releasing any resources
504 * that were allocated at initialization.
505 */
506 public void destroy(){
507 try {
508 RuntimeDataSource.closeInstance();
509 } catch (SQLException e) {
510 RuntimeParameters.logError( this,"Unable to close RuntimeDataSource.", e );
511 }
512 }
513
514 }