001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org)
002 // The Molly framework is freely distributable under the terms of an
003 // MIT-style license. For details, see the molly pages web site at:
004 // http://www.mollypages.org/. Use, modify, have fun !
005
006 package fc.web.servlet;
007
008 import javax.servlet.*;
009 import javax.servlet.http.*;
010 import java.io.*;
011 import java.util.*;
012 import java.sql.*;
013
014 import fc.io.*;
015 import fc.jdbc.*;
016 import fc.util.*;
017 import fc.util.cache.*;
018 import fc.web.*;
019 import fc.web.forms.*;
020
021 /**
022 Application level global object running within a servlet web application
023 (i.e., a servlet context). Global webapp data can be stored/retrieved
024 via the put/get methods.
025 <p>
026 Initializes and stores various variables useful for all servlets/pages
027 running in our JVM. Implements {@link javax.servlet.ServletContextListener}
028 and initializes itself at context initialization.
029 <p>
030 It's optional to use this class. If it is used, it's configured by
031 adding the following to the appropriate sections of WEB-INF/web.xml:
032 <blockquote>
033 <pre>
034 <context-param>
035 <param-name>configfile</param-name>
036 <param-value>app.conf</param-value>
037 </context-param>
038
039 <listener>
040 <listener-class>fc.web.servlet.WebApp</listener-class>
041 </listener>
042 </pre>
043 </blockquote>
044 <p>
045 If initialization is not successful, it tries to shut down the servlet JVM
046 by calling <tt>System.exit</tt>. (The idea being it's better to fail early
047 and safely then continue beyond this point).
048 <p>
049 If used, this class requires the following context configuration
050 parameter:
051 <blockquote>
052 <ul>
053 <li><tt>configfile</tt>: Path/name of the application configuration
054 file. If the path starts with a '/', it is an absolute file system path.
055 Otherwise, it is relative to this context root's WEB-INF directory.</li>
056 </ul>
057 </blockquote>
058 <p>
059 This class can also be subclassed to initialize/contain website specific
060 data and background/helper processing threads. Alternatively, along with
061 this class as-is, additional independent site-specific
062 ServletContextListener classes can be created and used as necessary.
063
064 @author hursh jain
065 */
066 public class WebApp implements ServletContextListener
067 {
068 //IMPL NOTE: synchronize stuff that is set here with AdminServlet
069 /*
070 Contains all the {@link fc.dbo.ConnectionMgr ConnectionManagers}
071 for this application.
072 */
073 public static Map connectionManagers = new HashMap();
074 public static ConnectionMgr defaultConnectionManager;
075 public static long default_dbcache_time = MemoryCache.TWO_MIN;
076 public static ThreadLocalCalendar default_tlcal;
077 public static ThreadLocalDateFormat default_tldf;
078 public static ThreadLocalNumberFormat default_tlnf;
079 public static ThreadLocalRandom default_tlrand;
080 public static Map appMap = new Hashtable(); //hashtable is sync'ed
081 public static Map tlcalMap = new Hashtable(); //ht is sync'ed
082 public static Map tldfMap = new Hashtable(); //ht is sync'ed
083 public static Map tlnfMap = new Hashtable(); //ht is sync'ed
084 public static Map tlrandMap = new Hashtable(); //ht is sync'ed
085
086 /**
087 A {@link SystemLog} object. Servlets typically create their own loggers
088 (with servlet specific logging levels) but can alternatively use this
089 default appLog. This appLog is used by non-servlet classes such
090 as this class itself, various listeners etc.
091 */
092 public static SystemLog appLog = Log.getDefault();
093 public static PropertyMgr propertyMgr;
094 static long cache_time; //in ms
095
096 /**
097 A (initially empty) Map that servlets can use to store a reference to
098 themselves. This is required because the servlet API has deprecated a
099 similar API call (the servlet API authors are brain damaged 'tards).
100 */
101 public static Map allServletsMap = new HashMap();
102
103 /**
104 Basic implementation of the web application cleanup upon context creation.
105
106 If this method is subclassed, the subclassed method must also invoke this
107 method via <tt>super.contextInitialized</tt>. This call should typically
108 be at the beginning of the subclassed method, which allows this implementation
109 to create connections, logs etc, which can then be used by the subclass to
110 finish it's further initialization as needed.
111 */
112 public void contextInitialized(ServletContextEvent sce)
113 {
114 ServletContext context = sce.getServletContext();
115
116 java.util.Date now = Calendar.getInstance().getTime();
117 System.out.println("fc.web.servlet.WebApp: starting initialization on: " + now);
118
119 try {
120 this.appLog = Log.getDefault(); //could be anything else too.
121
122 String sconf = WebUtil.getRequiredParam(context, "configfile");
123
124 File conf = new File(sconf);
125
126 //We print to system.out to ensure we see this regardless of loglevel.
127 //That's because this is very useful information in case of problems
128 //starting the webapp.
129 System.out.println("-> WebApp: WEB-INF directory: " + context.getRealPath("/WEB-INF"));
130 System.out.println("-> WebApp: Configuration file: " + conf.getPath());
131
132 if (! conf.isAbsolute()) {
133 conf = new File(context.getRealPath("/WEB-INF"), conf.getPath());
134 }
135
136 propertyMgr = new FilePropertyMgr(conf);
137
138 String level = WebApp.propertyMgr.get("log.level", null);
139 if (level != null) {
140 appLog.setLevel(level);
141 Log.setDefaultLevel(level);
142 }
143
144 ConnectionMgr defaultCmgr = null;
145
146 String dbdefault_str = propertyMgr.get("db.default");
147 appLog.info("WebApp: Default database = ",
148 dbdefault_str == null ? "Not specified" : dbdefault_str);
149
150 String dblist_str = propertyMgr.get("db.list");
151 appLog.info("WebApp: All databases: ", dblist_str == null ?
152 "None specified" : dblist_str);
153
154 if (dblist_str == null) {
155 if (dbdefault_str != null) {
156 throw new IllegalArgumentException("Since a default database was specified, a dblist must also be specified (but was null)");
157 }
158 }
159 else{
160 String[] cmgrnames = dblist_str.split(",");
161 for (String dbname : cmgrnames)
162 {
163 dbname = dbname.trim();
164 String poolsize = propertyMgr.get(dbname + ".pool.size");
165 String jdbc_url = propertyMgr.get(dbname + ".jdbc.url");
166 String jdbc_driver = propertyMgr.get(dbname + ".jdbc.driver");
167 String jdbc_user = propertyMgr.get(dbname + ".jdbc.user");
168 String jdbc_password = propertyMgr.get(dbname + ".jdbc.password");
169 String jdbc_catalog = propertyMgr.get(dbname + ".jdbc.catalog", "");
170
171 ConnectionMgr cmgr = new PooledConnectionMgr(
172 jdbc_url, jdbc_driver, jdbc_user, jdbc_password,
173 jdbc_catalog, Integer.parseInt(poolsize));
174
175 connectionManagers.put(dbname, cmgr);
176 if (dbname.equalsIgnoreCase(dbdefault_str))
177 defaultConnectionManager = cmgr;
178 }
179 }
180
181 String cache_time_str = propertyMgr.get("db.cache_time_seconds");
182
183 if (cache_time_str != null)
184 cache_time = Long.parseLong(cache_time_str) * 1000;
185 else
186 cache_time = default_dbcache_time;
187
188 appLog.info("WebApp: db.cache_time = ", cache_time/1000, " seconds");
189
190 //initialize other classes that need a static init
191 //method to be called
192 JDBCSession.init(null, appLog);
193 QueryUtil.init(appLog);
194
195 appLog.info("===> WebApp finished [success]");
196 }
197 catch (Exception e)
198 {
199 System.err.println("**** ERROR: exception in " + getClass().getName() + " *****");
200 System.err.println("**** Shutting down the Web Server ****");
201 System.err.flush();
202 e.printStackTrace(System.err); //send email to me here...?
203 System.exit(1);
204 }
205 } //~WebAppialized
206
207
208 /**
209 Basic implementation of the web application cleanup upon context destruction.
210
211 If this method is subclassed, the subclassed method must also invoke this
212 method via <tt>super.contextInitialized</tt>. This invokation should typically
213 be at the <b>end</b> of the subclassed method (which allows the subclass to
214 use connections etc., before they are closed by this superclass method).
215 */
216 public void contextDestroyed(ServletContextEvent sce)
217 {
218 appLog.info("WebApp closing at: " + new java.util.Date());
219 Iterator it = connectionManagers.values().iterator();
220
221 while (it.hasNext()) {
222 ConnectionMgr cmgr = (ConnectionMgr) it.next();
223 cmgr.close();
224 }
225 }
226
227 /**
228 Returns the connection manager corresponding to the database
229 name. (specified in the dblist property of app.conf).
230
231 @throws IllegalArgumentException if the specified database is not found
232 */
233 public static ConnectionMgr getConnectionMgr(String databasename)
234 {
235 ConnectionMgr cm = (ConnectionMgr) connectionManagers.get(databasename);
236 if (cm == null)
237 throw new IllegalArgumentException("The specified connectionManager [" + databasename + "] does not exist or has not been initialized.");
238 return cm;
239 }
240
241 /**
242 Returns the connection manager corresponding to the default database
243 name. (specified in the dbdefault property of app.conf). Returns <tt>null</tt>
244 if no default database has been initialized.
245 */
246 public static ConnectionMgr getConnectionMgr()
247 {
248 return defaultConnectionManager;
249 }
250
251 /*
252 returns a connection from the connection pool to the database specified.
253 blocks until a connection is available.
254
255 @param databasename database from the dblist property of app.conf
256 @throws IllegalArgumentException if the specified database is not found
257 */
258 public static Connection getConnection(String databasename) throws SQLException
259 {
260 return getConnectionMgr(databasename).getConnection();
261 }
262
263 /*
264 returns a connection from the connection pool to the default database.
265 blocks until a connection is available.
266
267 */
268 public static Connection getConnection() throws SQLException
269 {
270 if (defaultConnectionManager == null) {
271 throw new SQLException("The ConnectionManager in " + WebApp.class + " has not been intialized properly. So how can I return a connection ?");
272 }
273 return defaultConnectionManager.getConnection();
274 }
275
276 /*
277 Returns the default application log.
278 */
279 public static SystemLog getAppLog()
280 {
281 return appLog;
282 }
283
284 /**
285 Convenience method to return a form stored previously via the {@link
286 putForm} method. Returns <tt>null</tt> if no form with the specified name
287 was found.
288 */
289 public static Form getForm(String name)
290 {
291 return (Form) appMap.get(name);
292 }
293
294 public static void putForm(Form f)
295 {
296 appMap.put(f.getName(), f);
297 }
298
299 public static void removeForm(String name)
300 {
301 if (appMap.containsKey(name))
302 appMap.remove(name);
303 }
304
305
306 /**
307 Convenience method to get the pre-created application cache. This
308 can be used for caching database results as necessary. Each entry
309 in the cache can be stored for entry-specific time-to-live (see {@link Cache)
310 but if no entry-specific-time-to-live is specified, then entries are
311 cached for a default time of 5 minutes. Of course, cached entries should
312 always be invalidated sooner whenever the database is modified.
313 */
314 public static Cache getDBCache()
315 {
316 Object obj = appMap.get("_dbcache");
317
318 if (obj != null)
319 return (Cache) obj;
320
321 Cache cache = null;
322
323 synchronized (WebApp.class) { //2 or more threads could be running in a page
324 cache = new MemoryCache(
325 Log.get("fc.util.cache"), "_dbcache", cache_time);
326 appMap.put("_dbcache", cache);
327 }
328
329 return cache;
330 }
331
332 /**
333 Returns the ThreadLocalDateFormat object corresponding to the specified
334 name (a new ThreadLocalDateFormat is created if it does not exist).
335 */
336 public static ThreadLocalDateFormat getThreadLocalDateFormat(String name)
337 {
338 ThreadLocalDateFormat tldf = (ThreadLocalDateFormat) tldfMap.get(name);
339
340 if (tldf == null) {
341 tldf = new ThreadLocalDateFormat();
342 tldfMap.put(tldf, name);
343 }
344
345 return tldf;
346 }
347
348 /**
349 Returns the default threadLocalDateFormat object (a new
350 ThreadLocalDateFormat is created if it does not exist).
351 */
352 public static ThreadLocalDateFormat getThreadLocalDateFormat()
353 {
354 if (default_tldf == null) {
355 default_tldf = new ThreadLocalDateFormat();
356 }
357
358 return default_tldf;
359 }
360
361
362
363 /**
364 Returns the ThreadLocalNumberFormat object corresponding to the specified
365 name (a new ThreadLocalNumberFormat is created if it does not exist).
366 */
367 public static ThreadLocalNumberFormat getThreadLocalNumberFormat(String name)
368 {
369 ThreadLocalNumberFormat tlnf = (ThreadLocalNumberFormat) tlnfMap.get(name);
370
371 if (tlnf == null) {
372 tlnf = new ThreadLocalNumberFormat();
373 tlnfMap.put(tlnf, name);
374 }
375
376 return tlnf;
377 }
378
379 /**
380 Returns the default ThreadLocalNumberFormat object (a new
381 ThreadLocalNumberFormat is created if it does not exist).
382 */
383 public static ThreadLocalNumberFormat getThreadLocalNumberFormat()
384 {
385 if (default_tlnf == null) {
386 default_tlnf = new ThreadLocalNumberFormat();
387 }
388
389 return default_tlnf;
390 }
391
392 /**
393 Returns the ThreadLocalCalendar object corresponding to the
394 specified name (a new ThreadLocalCalendar is created if it does
395 not exist).
396 */
397 public static ThreadLocalCalendar getThreadLocalCalendar(String name)
398 {
399 ThreadLocalCalendar tlcal = (ThreadLocalCalendar) tlcalMap.get(name);
400
401 if (tlcal == null) {
402 tlcal = new ThreadLocalCalendar();
403 tlcalMap.put(tlcal, name);
404 }
405
406 return tlcal;
407 }
408
409 /**
410 Returns the default ThreadLocalCalendar object (a new ThreadLocalCalendar
411 is created if it does not exist).
412 */
413 public static ThreadLocalCalendar getThreadLocalCalendar()
414 {
415 if (default_tlcal == null) {
416 default_tlcal = new ThreadLocalCalendar();
417 }
418
419 return default_tlcal;
420 }
421
422 /**
423 Returns the ThreadLocalRandom object corresponding to the
424 specified name (a new ThreadLocalRandom is created if it does
425 not exist).
426 */
427 public static ThreadLocalRandom getThreadLocalRandom(String name)
428 {
429 ThreadLocalRandom tlrand = (ThreadLocalRandom) tlrandMap.get(name);
430
431 if (tlrand == null) {
432 tlrand = new ThreadLocalRandom();
433 tlrandMap.put(tlrand, name);
434 }
435
436 return tlrand;
437 }
438
439 /**
440 Returns the default ThreadLocalRandom object (a new ThreadLocalRandom
441 is created if it does not exist).
442 */
443 public static ThreadLocalRandom getThreadLocalRandom()
444 {
445 if (default_tlrand == null) {
446 default_tlrand = new ThreadLocalRandom();
447 }
448
449 return default_tlrand;
450 }
451
452 /**
453 Returns the specified object from the global application map or
454 <tt>null</tt> if the object was not found.
455 */
456 public static Object get(Object key)
457 {
458 return appMap.get(key);
459 }
460
461 /**
462 Puts the specified key/object into the global application map.
463 */
464 public static void put(Object key, Object val)
465 {
466 appMap.put(key, val);
467 }
468
469 public String toString() {
470 return new ToString(this).reflect().render();
471 }
472 }