001: /*
002: * Copyright 2005 jWic group (http://www.jwic.de)
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: *
016: * de.jwic.base.JWicRuntime
017: * Created on 20.10.2004
018: * $Id: JWicRuntime.java,v 1.11 2007/05/07 11:05:57 lordsam Exp $
019: */
020: package de.jwic.base;
021:
022: import java.io.InputStream;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.Locale;
026: import java.util.Map;
027: import java.util.Properties;
028:
029: import javax.servlet.http.HttpServletRequest;
030: import javax.servlet.http.HttpSession;
031: import javax.servlet.http.HttpSessionBindingEvent;
032: import javax.servlet.http.HttpSessionBindingListener;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.apache.velocity.app.VelocityEngine;
037: import org.dom4j.Document;
038: import org.dom4j.Element;
039: import org.dom4j.io.SAXReader;
040:
041: import de.jwic.events.SessionEvent;
042: import de.jwic.renderer.velocity.BaseVelocityRenderer;
043: import de.jwic.util.Compatibility;
044: import de.jwic.util.DTDEntityResolver;
045: import de.jwic.util.XMLTool;
046:
047: /**
048: * The JWicRuntime manages the lifecycle of jWic applications.
049: *
050: * <p>The runtime takes care of the special velocity behaivior since the default jWic
051: * renderer is velocity, wich makes things easier.</p>
052: *
053: * @author Florian Lippisch
054: * @version $Revision: 1.11 $
055: */
056: public class JWicRuntime {
057:
058: public final static String DTD_PUBLICID = "-//jWic//DTD jwic-setup 3.0//EN";
059: public final static String DTD_SYSTEMID = "http://www.jwic.de/dtd/jwic-setup-3.0.dtd";
060: public final static String DTD_RESOURCEPATH = "/de/jwic/base/jwic-setup.dtd";
061:
062: private static final String ATTR_NOTIFIER = "JWicRuntime.sessionNotifier";
063: private static JWicRuntime singleton = null;
064: private static boolean initialized = false;
065:
066: protected final Log log = LogFactory.getLog(getClass());
067: private String rootPath = "./";
068: private String savePath = "./";
069: private int sessionStoreTime = 0;
070:
071: /** The map of registerd velocity engines **/
072: private Map velocityEngines = new HashMap();
073: private Map renderers = new HashMap();
074:
075: private SessionManager sessionManager = null;
076:
077: private String contextPath = null;
078:
079: /**
080: * This class is set as a session attribute to get notified when a session
081: * is closed by the server.
082: * @author Florian Lippisch
083: */
084: private class HttpSessionClosedListener implements
085: HttpSessionBindingListener {
086: private JWicRuntime runtime;
087: private String sessionID;
088:
089: public HttpSessionClosedListener(JWicRuntime rt,
090: String sessionID) {
091: this .runtime = rt;
092: this .sessionID = sessionID;
093: }
094:
095: public void valueBound(HttpSessionBindingEvent arg0) {
096: // nothing to do
097: }
098:
099: public void valueUnbound(HttpSessionBindingEvent arg0) {
100: runtime.sessionClosed(sessionID);
101: }
102: }
103:
104: /**
105: * Default, private contructor. Use getJWicRuntime() to get an instance.
106: */
107: private JWicRuntime() {
108:
109: }
110:
111: /**
112: * This method is invoked by the HttpSessionClosedListener when a session is
113: * closed.
114: * @param sessionID
115: */
116: void sessionClosed(String clientID) {
117: sessionManager.destroyClient(clientID);
118: }
119:
120: /**
121: * Returns the singleton instance of the JWicRuntime.
122: * @return
123: */
124: public static JWicRuntime getJWicRuntime() {
125: if (!initialized) {
126: synchronized (JWicRuntime.class) {
127: if (!initialized) {
128: singleton = new JWicRuntime();
129: initialized = true;
130: }
131: }
132: }
133: return singleton;
134: }
135:
136: /**
137: * Returns the SessionManager. The SessionManager should only be used
138: * for debug and diagnostic purpose.
139: *
140: * @return
141: */
142: public SessionManager getSessionManager() {
143: return sessionManager;
144: }
145:
146: /**
147: * Returns the renderer with the specified id. If no renderer can be
148: * found, a JWicException is thrown.
149: * @param rendererId
150: * @return
151: */
152: public static IControlRenderer getRenderer(String rendererId)
153: throws JWicException {
154: IControlRenderer renderer = (IControlRenderer) singleton.renderers
155: .get(rendererId);
156: ;
157: if (renderer == null) {
158: throw new JWicException("Renderer unknown: " + rendererId);
159: }
160: return renderer;
161: }
162:
163: /**
164: * Returns the session with the specified ID.
165: * @param clientID
166: * @param sessionID
167: * @return
168: */
169: public SessionContext getSessionContext(String clientID,
170: String sessionID) {
171: return getSessionContext(clientID, sessionID, null);
172: }
173:
174: /**
175: * Returns the session with the specified ID.
176: * Request is used to pass parameter to SessionEvent.
177: * @param clientID
178: * @param sessionID
179: * @param request
180: * @return
181: */
182: public SessionContext getSessionContext(String clientID,
183: String sessionID, HttpServletRequest request) {
184:
185: SessionContainer container = sessionManager.get(clientID,
186: sessionID);
187: if (container != null) {
188:
189: if (container.getState() == SessionContainer.STATE_STORED) {
190: sessionManager.deserialize(container);
191: } else if (container.getState() == SessionContainer.STATE_DESTROYED) {
192: throw new JWicException("Session is destroyed.");
193: }
194: container.access();
195: SessionContext sc = container.getSessionContext();
196: if (request.getMethod().equals("GET")) { // only fire reused event on "GET" requests.
197: log.debug("SessionReused : " + sessionID);
198: sc.fireEvent(new SessionEvent(request == null ? null
199: : Compatibility.getParameterMap(request)),
200: SessionContext.SESSION_REUSED);
201: }
202: return sc;
203:
204: }
205: return null;
206:
207: }
208:
209: /**
210: * Creates a new SessionContext with the application bean specified in the
211: * appProperties argument. The appProperties must contain the name and beanId
212: * of the application control.
213: *
214: * Sample:<br>
215: * <pre> appid=myapp.id
216: * name=app
217: * control=myapp.AppControl </pre>
218: *
219: * The request argument is optional. It can be <code>null</code> if you are creating an
220: * Application for test cases. In this case, the session is always handled as multisession.
221: *
222: * @param appProperties
223: * @return
224: */
225: public SessionContext createSessionContext(
226: IApplicationSetup appSetup, Locale locale,
227: HttpServletRequest request) {
228:
229: HttpSession session = request != null ? request.getSession()
230: : null;
231: String clientID = session != null ? session.getId() : "test"; // testenvironment
232:
233: if (session != null
234: && session.getAttribute(ATTR_NOTIFIER) == null) {
235: // Add a "listener" so we know when the session is closed.
236: session
237: .setAttribute(ATTR_NOTIFIER,
238: new HttpSessionClosedListener(this , session
239: .getId()));
240: }
241:
242: SessionContext sc = null;
243: if (appSetup.isSingleSession() && session != null) {
244: SessionContainer container = sessionManager.getByAppID(
245: clientID, appSetup.getName());
246: if (container == null) {
247: sc = setupSessionContext(appSetup, locale, request);
248: } else {
249: // notify the session that it has been "reused"
250: if (container.getState() == SessionContainer.STATE_STORED) {
251: sessionManager.deserialize(container);
252: }
253: container.access();
254: sc = container.getSessionContext();
255: sc.fireEvent(new SessionEvent(Compatibility
256: .getParameterMap(request)),
257: SessionContext.SESSION_REUSED);
258: }
259:
260: } else {
261: sc = setupSessionContext(appSetup, locale, request);
262: }
263:
264: return sc;
265: }
266:
267: /**
268: * Create a new SessionContext instance.
269: * @param appProperties
270: * @param locale
271: * @param request
272: * @return
273: */
274: private SessionContext setupSessionContext(
275: IApplicationSetup appSetup, Locale locale,
276: HttpServletRequest request) {
277:
278: log.debug("creating new SessionContext for application '"
279: + appSetup.getName() + "'.");
280:
281: SessionContext sc = new SessionContext(appSetup, locale);
282: String clientID;
283: if (request != null) {
284: clientID = request.getSession().getId();
285: HashMap parameters = new HashMap();
286: parameters.putAll(Compatibility.getParameterMap(request));
287: sc.setInitParameters(parameters);
288: } else {
289: clientID = "test";
290: }
291: sc.setClientId(clientID);
292:
293: IApplication app = appSetup.createApplication();
294: sc.setApplication(app);
295: SessionContainer container = sessionManager.create(clientID,
296: appSetup.getName());
297: boolean cleanUp = true;
298: try {
299: container.setSessionContext(sc);
300: sc.setSessionId(container.getId());
301: sc.setUserAgent(new UserAgentInfo(request));
302:
303: app.initialize(sc);
304: Control root = app.createRootControl(sc);
305: // push root control only if no control had been push during root creation
306: if (sc.getTopControl() == null) {
307: sc.pushTopControl(root);
308: }
309: sc.fireEvent(new SessionEvent(request == null ? null
310: : Compatibility.getParameterMap(request)),
311: SessionContext.SESSION_STARTED);
312: cleanUp = false;
313: } finally {
314: if (cleanUp) {
315: log.warn("Session was not created successfully!");
316: sessionManager.remove(container);
317: }
318: }
319:
320: return sc;
321:
322: }
323:
324: /**
325: * Invoked by the SessionContext when the sessionContext is destroyed.
326: * The context is then removed from the singleSession map.
327: * @param context
328: */
329: void sessionDestroyed(SessionContext context) {
330:
331: log.debug("Session " + context.getClientId() + " destroyed.");
332: // remove from local single_session cache
333: SessionContainer container = sessionManager.get(context
334: .getClientId(), context.getSessionId());
335: if (container != null) {
336: sessionManager.remove(container);
337: }
338:
339: }
340:
341: /**
342: * Setup the JWicRuntime from the jwic-setup.xml file. The setup
343: * defines the available renderer and global settings.
344: * @param in
345: */
346: public void setupRuntime(InputStream stream) {
347:
348: try {
349: SAXReader reader = new SAXReader();
350: reader.setEntityResolver(new DTDEntityResolver(
351: DTD_PUBLICID, DTD_SYSTEMID, DTD_RESOURCEPATH));
352: reader.setIncludeExternalDTDDeclarations(false);
353:
354: Document document = reader.read(stream);
355:
356: readDocument(document);
357:
358: sessionManager = new SessionManager(getSavePath());
359: sessionManager.setStoreTime(sessionStoreTime);
360:
361: } catch (NoClassDefFoundError ncdfe) {
362: if (ncdfe.getMessage().indexOf("dom4j") != -1) {
363: log
364: .error("Can not read jwic-setup.xml: the dom4j library is not in the classpath.");
365: throw new RuntimeException(
366: "Can not read jwic-setup.xml: the dom4j library is not in the classpath.",
367: ncdfe);
368: }
369: log.error("Error reading jwic-setup.xml.", ncdfe);
370: throw new RuntimeException("Error reading jwic-setup.xml: "
371: + ncdfe, ncdfe);
372: } catch (Exception e) {
373: throw new RuntimeException("Error reading jwic-setup.xml: "
374: + e, e);
375: }
376:
377: }
378:
379: /**
380: * Read the setup document.
381: * @param document
382: */
383: private void readDocument(Document document) {
384:
385: Element root = document.getRootElement();
386:
387: for (Iterator it = root.elementIterator(); it.hasNext();) {
388: Element node = (Element) it.next();
389:
390: String nodeName = node.getName();
391: // setup a velocity-engine
392: if (nodeName.equals("velocity-engine")) {
393:
394: String veId = node.attribute("id").getValue();
395: Properties prop = null;
396: for (Iterator pi = node.elementIterator(); pi.hasNext();) {
397: Element node2 = (Element) pi.next();
398: if (node2.getName().equals("properties")) {
399: prop = XMLTool.getProperties(node2);
400: }
401: }
402: // replace ${rootPath} with real path
403: ConfigurationTool.insertRootPath(prop);
404: VelocityEngine engine = new VelocityEngine();
405: try {
406: engine.init(prop);
407: } catch (Exception e) {
408: String msg = "Can not instanciate velocity engine '"
409: + veId + "'";
410: log.error(msg, e);
411: throw new RuntimeException(msg + e, e);
412: }
413: velocityEngines.put(veId, engine);
414:
415: } else if (nodeName.equals("session-swap-time")) {
416: sessionStoreTime = Integer.parseInt(node.getText());
417:
418: } else if (nodeName.equals("session-storage-path")) {
419: setSavePath(ConfigurationTool.insertRootPath(node
420: .getText()));
421:
422: } else if (nodeName.equals("renderer")) {
423: String id = node.attribute("id").getValue();
424: String type = node.attribute("type").getValue();
425: String classname = node.attribute("classname")
426: .getValue();
427:
428: IControlRenderer renderer;
429: try {
430: renderer = (IControlRenderer) Class.forName(
431: classname).newInstance();
432: } catch (Exception e) {
433: String msg = "Can not instanciate renderer '" + id
434: + "'";
435: log.error(msg, e);
436: throw new RuntimeException(msg + e, e);
437: }
438:
439: if (type.equals("velocity")) {
440: Element nEngine = node.element("engine");
441: if (nEngine != null) {
442: String engineId = nEngine.getText();
443: if (renderer instanceof BaseVelocityRenderer) {
444: BaseVelocityRenderer vr = (BaseVelocityRenderer) renderer;
445: VelocityEngine engine = (VelocityEngine) velocityEngines
446: .get(engineId);
447: if (engine == null) {
448: throw new RuntimeException(
449: "Specified velocity engine not found: "
450: + engineId);
451: }
452: vr.setVelocityEngine(engine);
453: } else {
454: throw new RuntimeException(
455: "renderer "
456: + id
457: + " is specified as type 'velocity' but is not instance of BaseVelocityRenderer");
458: }
459: }
460: }
461: renderers.put(id, renderer);
462:
463: }
464: }
465:
466: }
467:
468: /**
469: * @return Returns the contextPath.
470: */
471: public String getContextPath() {
472: return contextPath;
473: }
474:
475: /**
476: * @param contextPath The contextPath to set.
477: */
478: public void setContextPath(String contextPath) {
479: this .contextPath = contextPath;
480: }
481:
482: /**
483: * Returns the path where the serialized applications are stored.
484: * @return
485: */
486: public String getSavePath() {
487: return savePath;
488: }
489:
490: /**
491: * Set the path where the serialized applications are stored.
492: * @param savePath
493: */
494: public void setSavePath(String savePath) {
495: if (savePath.endsWith("/") || savePath.endsWith("\\")) {
496: this .savePath = savePath;
497: } else {
498: this .savePath = savePath + "/";
499: }
500: }
501:
502: /**
503: * @return Returns the rootPath.
504: */
505: public String getRootPath() {
506: return rootPath;
507: }
508:
509: /**
510: * Set the root path of the WebApplication.
511: * @param savepath
512: * @return
513: */
514: public void setRootPath(String rootPath) {
515: this .rootPath = rootPath;
516: }
517:
518: /**
519: * Destroy the runtime.
520: */
521: public void destroy() {
522:
523: log.info("JWicRuntime.destroy()");
524: sessionManager.destroy();
525: initialized = false;
526: singleton = null;
527: }
528:
529: }
|