001: //** Copyright Statement ***************************************************
002: //The Salmon Open Framework for Internet Applications (SOFIA)
003: // Copyright (C) 1999 - 2002, Salmon LLC
004: //
005: // This program is free software; you can redistribute it and/or
006: // modify it under the terms of the GNU General Public License version 2
007: // as published by the Free Software Foundation;
008: //
009: // This program is distributed in the hope that it will be useful,
010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: // GNU General Public License for more details.
013: //
014: // You should have received a copy of the GNU General Public License
015: // along with this program; if not, write to the Free Software
016: // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
017: //
018: // For more information please visit http://www.salmonllc.com
019: //** End Copyright Statement ***************************************************
020: package com.salmonllc.jsp;
021:
022: /////////////////////////
023: //$Archive: /SOFIA/SourceCode/com/salmonllc/jsp/JspServlet.java $
024: //$Author: Srufle $
025: //$Revision: 47 $
026: //$Modtime: 11/15/04 1:18p $
027: /////////////////////////
028:
029: import java.io.ByteArrayInputStream;
030: import java.io.ByteArrayOutputStream;
031: import java.io.IOException;
032: import java.io.InputStream;
033: import java.io.ObjectInputStream;
034: import java.io.ObjectOutputStream;
035: import java.io.ObjectStreamClass;
036: import java.net.HttpURLConnection;
037: import java.net.MalformedURLException;
038: import java.net.SocketException;
039: import java.net.URL;
040: import java.util.Enumeration;
041: import java.util.Hashtable;
042: import java.util.Vector;
043:
044: import javax.servlet.Servlet;
045: import javax.servlet.ServletConfig;
046: import javax.servlet.ServletContext;
047: import javax.servlet.ServletException;
048: import javax.servlet.ServletRequest;
049: import javax.servlet.ServletResponse;
050: import javax.servlet.http.Cookie;
051: import javax.servlet.http.HttpServletRequest;
052: import javax.servlet.http.HttpServletResponse;
053: import javax.servlet.http.HttpSession;
054: import javax.servlet.jsp.HttpJspPage;
055: import javax.servlet.jsp.JspFactory;
056:
057: import com.salmonllc.jsp.tags.PageTag;
058: import com.salmonllc.jsp.tags.TagContext;
059: import com.salmonllc.properties.Props;
060: import com.salmonllc.util.ApplicationContext;
061: import com.salmonllc.util.MessageLog;
062: import com.salmonllc.util.URLGenerator;
063: import com.salmonllc.html.HttpServletResponseDummy;
064: import com.salmonllc.html.events.PageListener;
065: import com.salmonllc.html.events.ServletServiceListener;
066:
067: /**
068: * This servlet should be used as the parent of each generated JSP servlet used by the framework<BR>
069: * by using the JSP Directive <@page extends="com.salmonllc.jsp.JspServlet"><br>
070: * Using the servlet will make your pages thread safe, compile response time stats on the server and make processing of salmon tags more efficient<br>
071: */
072: public abstract class JspServlet implements Servlet, HttpJspPage {
073: protected ServletConfig _servletConfig;
074: private static int _count;
075: private static long _time;
076: private static long _serverStarted = -1;
077: private static boolean _replaceFactory;
078: private static boolean _replaceFactoryInit;
079: public static Hashtable _controllerCache = new Hashtable();
080: public static boolean _cacheControllers = true;
081: public static Hashtable _timers = new Hashtable();
082: public static int _reqCount = 0;
083: private static Vector _listeners;
084:
085: public static final String SALMON_SERVLET_KEY = "&FROMSALMONSERVLET&";
086: public static final String SALMON_CREATE_PAGE_HEADER = "SalmonCreatePage";
087: public static final String SALMON_CONTEXT_TOKEN = "SalmonContextToken";
088:
089: public class JspServletObjectInputStream extends ObjectInputStream {
090: ClassLoader _loader;
091:
092: public JspServletObjectInputStream(InputStream in,
093: ClassLoader loader) throws IOException {
094: super (in);
095: _loader = loader;
096: }
097:
098: protected Class resolveClass(ObjectStreamClass desc)
099: throws IOException, ClassNotFoundException {
100: String name = desc.getName();
101: try {
102: return Class.forName(name, false, _loader);
103: } catch (ClassNotFoundException ex) {
104: return super .resolveClass(desc);
105: }
106: }
107:
108: }
109:
110: public static class Timer {
111: public int count;
112: public long totalTime;
113: }
114:
115: public abstract void _jspService(HttpServletRequest request,
116: HttpServletResponse response) throws ServletException,
117: IOException;
118:
119: /**
120: * Adds a hit to a particular activity. The average time for each named time can
121: * be seen in the connection monitor servlet
122: */
123: public static synchronized void recordTimerActivity(String name,
124: long time, Object o, boolean writeToMessageLog) {
125: Timer t = (Timer) _timers.get(name);
126: if (t == null) {
127: t = new Timer();
128: _timers.put(name, t);
129: }
130: t.count++;
131: t.totalTime += time;
132: if (writeToMessageLog)
133: MessageLog.writeDebugMessage("Timer:" + name
134: + " elapsed time (ms):" + time, o);
135: }
136:
137: /**
138: * Adds a hit to a particular activity. The average time for each named time can
139: * be seen in the connection monitor servlet
140: */
141: public static synchronized void recordTimerActivity(String name,
142: long time, Object o) {
143: recordTimerActivity(name, time, o, false);
144: }
145:
146: /**
147: * Resets all page hit stats and timers
148: */
149: public static synchronized void resetAllTimers() {
150: _count = 0;
151: _time = 0;
152: _serverStarted = -1;
153: _timers.clear();
154: }
155:
156: /**
157: * Returns a hash table of timers recorded in the system
158: * @return
159: */
160: public static Hashtable getTimersHashtable() {
161: return _timers;
162: }
163:
164: /**
165: * Adds a page hit to the hit counter
166: */
167: public static void addPageHit(long time) {
168: _count++;
169: _time += time;
170: if (_serverStarted == -1)
171: _serverStarted = System.currentTimeMillis();
172: }
173:
174: public final void destroy() {
175: jspDestroy();
176: }
177:
178: /**
179: * Returns the average time per page hit in milliseconds.
180: */
181: public static long getAverageHitTime() {
182: if (_count == 0)
183: return 0L;
184: return _time / _count;
185: }
186:
187: /**
188: * Returns the time that the first page was requested from the server.
189: */
190: public static long getFirstPageHitTime() {
191: return _serverStarted;
192: }
193:
194: /**
195: * Returns the number of page hits since the server was started.
196: */
197: public static int getPageHits() {
198: return _count;
199: }
200:
201: /**
202: * Returns the time in milliseconds since the first page hit on the server
203: */
204: public long getServerStartTime() {
205: return 0;
206: }
207:
208: /**
209: * Returns true if the controller cache is being used
210: */
211: public static boolean isControllerCacheActive() {
212: return _cacheControllers;
213: }
214:
215: /**
216: * Returns the number of controllers in the cache
217: */
218: public static int getControllerCacheCount() {
219: if (!_cacheControllers)
220: return 0;
221: return _controllerCache.size();
222: }
223:
224: /**
225: * Returns the number of bytes used by the controller cache
226: */
227: public static int getControllerCacheBytes() {
228: if (!_cacheControllers)
229: return 0;
230:
231: int count = 0;
232: Enumeration e = _controllerCache.elements();
233: while (e.hasMoreElements())
234: count += ((byte[]) e.nextElement()).length;
235: return count;
236: }
237:
238: public ServletConfig getServletConfig() {
239: return _servletConfig;
240: }
241:
242: public String getServletInfo() {
243: return "Salmon LLC JSP Base Servlet " + getClass().getName();
244: }
245:
246: /**
247: * Returns the total time per page hit in milliseconds.
248: */
249: public static long getTotalHitTime() {
250: return _time;
251: }
252:
253: public final void init(ServletConfig config)
254: throws ServletException {
255: _servletConfig = config;
256: jspInit();
257: }
258:
259: public void jspDestroy() {
260: JspFactory fact = JspFactory.getDefaultFactory();
261: if (fact instanceof com.salmonllc.jsp.engine.JspFactoryImpl)
262: ((com.salmonllc.jsp.engine.JspFactoryImpl) fact)
263: .resetFactory();
264: }
265:
266: public void jspInit() {
267:
268: }
269:
270: public void service(ServletRequest request, ServletResponse response)
271: throws ServletException, IOException {
272: setUpApplicationContext(getServletConfig().getServletContext(),
273: (HttpServletRequest) request);
274: JspController cont = null;
275: boolean sessionKeepAlive = true;
276: try {
277: long time = System.currentTimeMillis();
278: request.setAttribute(SALMON_SERVLET_KEY, this );
279: sessionKeepAlive = request.getParameter("sessionKeepAlive") != null;
280:
281: if (!_replaceFactoryInit) {
282: Props p = Props.getSystemProps();
283: _replaceFactoryInit = true;
284: _replaceFactory = p.getBooleanProperty(
285: Props.SYS_REPLACE_JSP_FACTORY, true);
286: _cacheControllers = p.getBooleanProperty(
287: Props.SYS_CACHE_CONTROLLERS, false);
288: MessageLog
289: .writeInfoMessage(
290: "***JspServlet initialized with properties: "
291: + Props.SYS_REPLACE_JSP_FACTORY
292: + "="
293: + _replaceFactory
294: + ", "
295: + Props.SYS_CACHE_CONTROLLERS
296: + "="
297: + _cacheControllers
298: + ". To reset, change the System.properties file and restart the server.***",
299: this );
300: }
301:
302: // if (_replaceFactory) {
303: // JspFactory fact = JspFactory.getDefaultFactory();
304: // if (fact == null || !fact.getClass().getName().equals("com.salmonllc.jsp.engine.JspFactoryImpl"))
305: // JspFactory.setDefaultFactory(new com.salmonllc.jsp.engine.JspFactoryImpl(fact));
306: // }
307:
308: HttpServletRequest req = (HttpServletRequest) request;
309: HttpServletResponse res = (HttpServletResponse) response;
310:
311: if (sessionKeepAlive)
312: com.salmonllc.util.MessageLog.writeInfoMessage(
313: "JspServlet.service() keepAlive - URI="
314: + req.getRequestURI(),
315: Props.LOG_LEVEL_10, this );
316: else {
317: notifyListeners((HttpServletRequest) request,
318: (HttpServletResponse) response, true);
319: com.salmonllc.util.MessageLog.writeInfoMessage(
320: "JspServlet.service() start - URI="
321: + req.getRequestURI(),
322: Props.LOG_LEVEL_10, this );
323: }
324:
325: String sessID = req.getParameter(PageTag
326: .getSessionIdentifier());
327: HttpSession sess = PageTag.getSession(sessID);
328: boolean sessValid = true;
329: if (sess == null) {
330: sessID = req.getRequestedSessionId();
331: sessValid = req.isRequestedSessionIdValid();
332: if (!sessValid && sessionKeepAlive)
333: return;
334: sess = req.getSession(true);
335: }
336:
337: boolean onSession = false;
338: boolean sessExp = false;
339: if (sessID != null && !sessValid)
340: sess.setAttribute("AppServer_SessExp",
341: new Boolean(true));
342:
343: boolean createPage = (req
344: .getHeader(SALMON_CREATE_PAGE_HEADER) != null);
345: if (_replaceFactory) {
346: Object sessToken = sess
347: .getAttribute("AppServer_SessionToken");
348: if (sessToken == null) {
349: sess.setAttribute("AppServer_SessionToken",
350: new String("tok"));
351: sessToken = sess
352: .getAttribute("AppServer_SessionToken");
353: }
354: synchronized (sessToken) {
355: String sessName = "$jsp$"
356: + com.salmonllc.jsp.tags.PageTag
357: .generateSessionName(req);
358: loadCachedController(sessName, sess);
359: onSession = sess.getAttribute(sessName) != null;
360:
361: if (!onSession)
362: _jspService(req, new HttpServletResponseDummy(
363: res, null));
364:
365: cont = (JspController) sess.getAttribute(sessName);
366: generateExpireResponseHeaders(res, cont
367: .getAddExpireHeaders());
368:
369: cacheController(sessName, cont);
370: cont.setSessionExpired(sessExp);
371: cont.setDoPostRedirected(false);
372: _jspService(req, res);
373: }
374: } else {
375: String sessName = "$jsp$"
376: + com.salmonllc.jsp.tags.PageTag
377: .generateSessionName(req);
378: String token = sessName + "$pageToken$";
379: try {
380: if (!createPage) {
381:
382: if (sess.getAttribute(token) != null) {
383:
384: /*
385: * srufle : Jun 25, 2004 4 : 26 : 38 PM
386: * This was put in to solve a thread deadlocking issue is was encountering
387: *
388: * Possible enhancements include
389: * - making vars get their values from system parameters
390: */
391: int index = 0;
392: int indexLimit = 1024;
393: int sleepCount = 0;
394: int sleepCountLimit = 4096;
395: int sleepTime = 10;
396: while (sess.getAttribute(token) != null) {
397: index++;
398: Thread.yield();
399:
400: if (index >= (indexLimit)) {
401: Thread.sleep(sleepTime);
402: index = 0;
403: sleepCount++;
404: if (sleepCount >= sleepCountLimit) {
405: throw (new ServletException(
406: "Thread Locked:Throwing to unlock"));
407: }
408: }
409: }
410: }
411: sess.setAttribute(token, token);
412: }
413:
414: loadCachedController(sessName, sess);
415: onSession = sess.getAttribute(sessName) != null;
416: if (!onSession && !createPage) {
417: createPage(req, res, sess,
418: getPageURL(req, res), sess.getId());
419: cont = (JspController) sess
420: .getAttribute(sessName);
421:
422: cacheController(sessName, cont);
423: } else
424: cont = (JspController) sess
425: .getAttribute(sessName);
426:
427: if (cont != null) {
428: generateExpireResponseHeaders(res, cont
429: .getAddExpireHeaders());
430: cont.setSessionExpired(sessExp);
431: cont.setDoPostRedirected(false);
432: synchronized (cont) {
433: _jspService(req, res);
434: }
435: } else {
436: String contextToken = req
437: .getHeader(SALMON_CONTEXT_TOKEN);
438: _jspService(req, res);
439: if (contextToken != null) {
440: TagContext t = (TagContext) req
441: .getAttribute(TagContext.TAG_CONTEXT_REQ_KEY);
442: if (t != null)
443: sess.setAttribute(contextToken, t);
444: }
445:
446: }
447: } catch (Exception e) {
448: if (cont == null
449: || cont.getPortletException() == null) {
450:
451: if (e instanceof SocketException) {
452: // ignore java.net.SocketException
453: MessageLog
454: .writeInfoMessage(
455: "SocketException would have been thrown",
456: this );
457: } else {
458: MessageLog.writeErrorMessage("service", e,
459: this );
460: throw (new ServletException(e.getMessage()));
461: }
462: }
463:
464: } finally {
465: if (!createPage)
466: sess.removeAttribute(token);
467: }
468: }
469:
470: if (!sessionKeepAlive) {
471: time = (System.currentTimeMillis() - time);
472:
473: if (!createPage)
474: addPageHit(time);
475:
476: if (Props.getSystemProps().getBooleanProperty(
477: Props.SYS_RECORD_PAGE_TIMERS))
478: recordTimerActivity(req.getRequestURI(), time,
479: this , false);
480: com.salmonllc.util.MessageLog.writeInfoMessage(
481: "JspServlet.service() end - URI="
482: + req.getRequestURI() + " Time=" + time
483: + " Init=" + (!onSession),
484: Props.LOG_LEVEL_10, this );
485: }
486: } catch (java.net.SocketException e) {
487: // ignore java.net.SocketException
488: MessageLog.writeInfoMessage(
489: "SocketException would have been thrown", this );
490: } catch (ServletException e) {
491: if (cont == null || cont.getPortletException() == null) {
492: com.salmonllc.util.MessageLog.writeErrorMessage(
493: "JspServlet.service()", e, this );
494: throw (e);
495: }
496: } catch (IOException e) {
497: com.salmonllc.util.MessageLog.writeErrorMessage(
498: "JspServlet.service()", e, this );
499: throw (e);
500: } catch (Exception e) {
501: com.salmonllc.util.MessageLog.writeErrorMessage(
502: "JspServlet.service()", e, this );
503: throw (new ServletException(e));
504: } finally {
505: try {
506: if (!sessionKeepAlive)
507: notifyListeners((HttpServletRequest) request,
508: (HttpServletResponse) response, false);
509: } catch (Exception e) {
510: com.salmonllc.util.MessageLog.writeErrorMessage(
511: "JspServlet.service()", e, this );
512: throw (new ServletException(e));
513: }
514: }
515:
516: }
517:
518: private void generateExpireResponseHeaders(HttpServletResponse res,
519: boolean expireResponse) {
520: if (expireResponse) {
521: res.setHeader("Pragma", "no-cache");
522: res.setDateHeader("Expires", 0);
523: res.setHeader("Cache-Control", "no-cache");
524: }
525: }
526:
527: private void createPage(HttpServletRequest req,
528: HttpServletResponse res, HttpSession sess, String url,
529: String sessID) {
530: try {
531: String contextToken = "$contextToken$" + _reqCount++;
532: URL u = new URL(url);
533: HttpURLConnection conn = (HttpURLConnection) u
534: .openConnection();
535: conn.setDoInput(true);
536: conn.setUseCaches(false);
537: HttpURLConnection.setFollowRedirects(false);
538: Enumeration e = req.getHeaderNames();
539: while (e.hasMoreElements()) {
540: String name = (String) e.nextElement();
541: String value = req.getHeader(name);
542: conn.setRequestProperty(name, value);
543: }
544: conn.setRequestProperty("Cookie", "sesessionid=" + sessID
545: + ";session=" + sessID + ";sessionid=" + sessID
546: + ";JSESSIONID=" + sessID);
547: conn.setRequestProperty(SALMON_CREATE_PAGE_HEADER, "true");
548: conn.setRequestProperty(SALMON_CONTEXT_TOKEN, contextToken);
549: InputStream in = conn.getInputStream();
550: while (in.read() > -1)
551: ;
552: in.close();
553: conn.disconnect();
554: Cookie cookie = new Cookie("JSESSIONID", sessID);
555: cookie.setMaxAge(-1);
556: res.addCookie(cookie);
557: TagContext tc = (TagContext) sess
558: .getAttribute(contextToken);
559: if (tc != null) {
560: sess.removeAttribute(contextToken);
561: req.setAttribute(TagContext.TAG_CONTEXT_REQ_KEY, tc);
562: }
563: } catch (Exception e) {
564: MessageLog
565: .writeErrorMessage("Error creating page", e, this );
566: }
567: }
568:
569: private String getPageURL(HttpServletRequest req,
570: HttpServletResponse res) {
571: String url = "";
572: url = URLGenerator.generateLocalHostServerURL(req);
573: String baseURL = URLGenerator.generateServletBaseURL(req);
574: if (baseURL != null && baseURL.length() > 0)
575: baseURL += "/";
576: else
577: baseURL = "";
578: String pageName = req.getRequestURI();
579: int pos = pageName.lastIndexOf("/");
580: if (pos > -1)
581: pageName = pageName.substring(pos + 1);
582: url += "/" + baseURL + pageName;
583: String path = req.getPathInfo();
584: if (path != null) {
585: int ndx = path.lastIndexOf("/" + url + "/");
586: if (ndx > 0 && !path.endsWith(url)) {
587: ndx += url.length() + 2;
588: pos = ndx + url.length() + 2;
589: url = "../" + url + "/" + path.substring(ndx);
590: while ((pos = path.indexOf("/", pos)) != -1) {
591: url = "../" + url;
592: pos = pos + 1;
593: }
594: }
595: }
596: String queryString = req.getQueryString();
597: if (queryString != null)
598: url += "?" + queryString;
599: return res.encodeURL(url);
600: }
601:
602: private void cacheController(String key, JspController cont) {
603: try {
604: if (!_cacheControllers)
605: return;
606:
607: if (cont == null)
608: return;
609:
610: synchronized (_controllerCache) {
611: if (_controllerCache.containsKey(key))
612: return;
613:
614: ByteArrayOutputStream out = new ByteArrayOutputStream();
615: ObjectOutputStream p = new ObjectOutputStream(out);
616: p.writeObject(cont);
617: p.flush();
618: byte b[] = out.toByteArray();
619: _controllerCache.put(key, b);
620: out.close();
621: }
622: } catch (IOException e) {
623: MessageLog.writeErrorMessage("Error caching controller:"
624: + cont.getClass().getName(), e, this );
625: }
626:
627: }
628:
629: private void loadCachedController(String key, HttpSession sess) {
630: if (!_cacheControllers)
631: return;
632:
633: if (sess.getAttribute(key) != null)
634: return;
635:
636: byte[] b = (byte[]) _controllerCache.get(key);
637: if (b == null)
638: return;
639:
640: try {
641: JspServletObjectInputStream in = new JspServletObjectInputStream(
642: new ByteArrayInputStream(b), Thread.currentThread()
643: .getContextClassLoader());
644: Object o = in.readObject();
645: sess.setAttribute(key, o);
646: in.close();
647: } catch (Exception e) {
648: MessageLog.writeErrorMessage(
649: "Error reading cached controller for:" + key, e,
650: this );
651: }
652: }
653:
654: public static void setUpApplicationContext(ServletContext sContext,
655: HttpServletRequest req) {
656: ApplicationContext context = ApplicationContext.getContext();
657: if (context == null) {
658: context = new ApplicationContext();
659: ApplicationContext.setContext(context);
660: }
661: String webAppName = URLGenerator.generateServletBaseURL(req);
662: int pos = webAppName.indexOf("/");
663: if (pos > -1)
664: webAppName = webAppName.substring(0, pos);
665: context.setAppDocumentRoot(sContext.getRealPath("/"));
666: if (webAppName == null)
667: webAppName = "";
668: context.setAppID(webAppName);
669: context.setLogger(MessageLog
670: .getLoggerForApplication(webAppName));
671: }
672:
673: public static void setUpApplicationContext(ServletContext sContext) {
674: ApplicationContext context = ApplicationContext.getContext();
675: if (context == null) {
676: context = new ApplicationContext();
677: ApplicationContext.setContext(context);
678: }
679: context.setAppDocumentRoot(sContext.getRealPath("/"));
680: String webAppName;
681: try {
682: webAppName = sContext.getResource("/").toString();
683: if (webAppName.endsWith("/"))
684: webAppName = webAppName.substring(0, webAppName
685: .length() - 1);
686: int pos = webAppName.lastIndexOf("/");
687: if (pos > -1)
688: webAppName = webAppName.substring(pos + 1);
689: if (webAppName == null)
690: webAppName = "";
691: context.setAppID(webAppName);
692: context.setLogger(MessageLog
693: .getLoggerForApplication(webAppName));
694:
695: } catch (MalformedURLException e) {
696: MessageLog.writeErrorMessage("setUpApplicationContext", e,
697: null);
698: }
699: }
700:
701: /**
702: * Adds a new listerner to this page to handle custom page events events.
703: */
704: public static void addServiceListener(ServletServiceListener l) {
705: if (_listeners == null)
706: _listeners = new Vector();
707:
708: for (int i = 0; i < _listeners.size(); i++) {
709: if (_listeners.elementAt(i) == l)
710: return;
711: }
712:
713: _listeners.addElement(l);
714: }
715:
716: private void notifyListeners(HttpServletRequest req,
717: HttpServletResponse res, boolean pre) throws Exception {
718: if (_listeners == null)
719: return;
720: for (int i = 0; i < _listeners.size(); i++) {
721: if (pre)
722: ((ServletServiceListener) _listeners.elementAt(i))
723: .serviceStarted(this , req, res);
724: else
725: ((ServletServiceListener) _listeners.elementAt(i))
726: .serviceEnded(this, req, res);
727: }
728:
729: }
730: }
|