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