/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.web; import java.lang.reflect.Constructor; import java.util.List; import java.util.Locale; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableDictionary; /** * A pure java implementation of WOApplication.
*
* * The application is responsible for creating and managing sessions and * dispatching requests to the appropriate handlers.
*
* * This implementation extends HttpServlet, so the application itself is a * servlet and can be configured and managed as such by the servlet container. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOApplication extends HttpServlet { private static final long serialVersionUID = 2417528899972745905L; /** * A tricky way to allow multiple WOApplications in the same servlet container. */ static private ThreadLocal threadLocal; // static private WOApplication application; /** * Determines application-wide page caching. Pages may individually prevent * caching. */ static private boolean cachingEnabled = false; private static boolean autoOpenInBrowser = true; private String name; private WORequestHandler defaultRequestHandler; private NSMutableDictionary requestHandlers; private WOSessionStore sessionStore; private WOResourceManager resourceManager; private boolean pageRefreshOnBacktrack; private int pageCacheSize; private int permanentPageCacheSize; public static final String WOApplicationWillFinishLaunchingNotification = "WOApplicationWillFinishLaunchingNotification"; public static final String WOApplicationDidFinishLaunchingNotification = "WOApplicationDidFinishLaunchingNotification"; public static final String WOGarbageCollectionPeriodKey = "WOGarbageCollectionPeriodKey"; static String _DirectActionRequestHandlerKey = "_DirectActionRequestHandlerKey"; static String _ComponentRequestHandlerKey = "_ComponentRequestHandlerKey"; static String _ResourceRequestHandlerKey = "_ResourceRequestHandlerKey"; static String WOPort = "WOPort"; static String WOSMTPHost = "WOSMTPHost"; static final String ELEMENT_CLASS = "elementClass"; public WOApplication() { if (threadLocal == null) { threadLocal = new ThreadLocal<>(); } threadLocal.set(this); // application = this; resourceManager = createResourceManager(); requestHandlers = new NSMutableDictionary<>(); defaultRequestHandler = new WODirectActionRequestHandler(); registerRequestHandler(defaultRequestHandler, directActionRequestHandlerKey()); registerRequestHandler(new WOComponentRequestHandler(), componentRequestHandlerKey()); registerRequestHandler(new WOResourceRequestHandler(), resourceRequestHandlerKey()); sessionStore = WOSessionStore.serverSessionStore(); pageRefreshOnBacktrack = true; pageCacheSize = 30; permanentPageCacheSize = 30; threadLocal.set(null); } /** * Dispatches the request and updates the specified response as appropriate. * This implementation creates a new WORequest and WOContext from the request, * sends the response to the appropriate WORequestHandler, and then updates the * servlet response from the resulting WOResponse. */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException { threadLocal.set(this); WORequest request = new WORequest(req, this); WOResponse response = dispatchRequest(request); response.generateServletResponse(resp); } /** * Handles post requests by calling doGet(), since the framework handles both * gets and posts similarly. Override to handle post requests in a different * manner. */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, java.io.IOException { doGet(req, resp); } // obtaining attributes /** * Returns the singleton instance of this application. */ public static WOApplication application() { return (WOApplication) threadLocal.get(); // return application; } /** * Returns the name of the application. This implementation returns the name the * jar file or directory from which the class was loaded, with no extensions. */ public String name() { if (name == null) { name = path(); int i; if (name.endsWith("/")) { // path name = name.substring(0, name.length() - 1); } else { // jar file i = name.lastIndexOf('.'); if (i != -1) name = name.substring(0, i); } i = name.lastIndexOf('/'); if (i != -1) name = name.substring(i + 1); } return name; } /** * Returns the absolute path to the application on the local file system. Note * that the application might be embedded inside of a jar file. */ public String path() { return getClass().getProtectionDomain().getCodeSource().getLocation().toString(); } /** * Returns the path to the application on the local file system relative to the * server's document root. */ public String baseURL() { String root = getServletContext().getRealPath("/"); String result = path(); if (result.endsWith("/")) { // path if (result.startsWith(root)) { // relative to root result = result.substring(root.length()); } // else leave as absolute reference } // jar or war file: leave as absolute reference return result; } // concurrent request handling /** * Returns whether this application allows request to be handled concurrently. * This implementation returns true. Subclasses may override to return false to * force single-threaded request handling, although this is not implemented. */ public boolean allowsConcurrentRequestHandling() { return true; } /** * Returns whether this application allows request to be handled concurrently. * This implementation returns true. */ public boolean adaptorsDispatchRequestsConcurrently() { return true; } /** * Returns whether this application allows request to be handled concurrently. * This implementation returns true. */ public boolean isConcurrentRequestHandlingEnabled() { return true; } // handling requests /** * Invoked first in the request-response cycle. Override to perform any kind of * initialization at the start of a request. This implementation does nothing. */ public void awake() { } /** * Invoked to start the first phase of the request-response cycle, after all * calls to awake() have been completed. */ public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) { aContext.session().takeValuesFromRequest(aRequest, aContext); } /** * Invoked to start the second phase of the request-response cycle, after all * calls to takeValuesFromRequest have finished. */ public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) { return aContext.session().invokeAction(aRequest, aContext); } /** * Invoked to start the third phase of the request-response cycle, after * invokeAction() has completed and returned a WOResponse. */ public void appendToResponse(WOResponse aResponse, WOContext aContext) { aContext.session().appendToResponse(aResponse, aContext); } /** * Invoked last in the request-response cycle. Override to perform any kind of * clean-up at the end of a request. This implementation does nothing. */ public void sleep() { } /** * Dispatches the request to the appropriate handler. */ public WOResponse dispatchRequest(WORequest aRequest) { WORequestHandler handlerForRequest = handlerForRequest(aRequest); return handlerForRequest.handleRequest(aRequest); } // request handling /** * Returns the default request handler used if the requested handler isn't * specified or cannot be found. (This defaults to the * WODirectActionRequestHandler.) */ public WORequestHandler defaultRequestHandler() { return defaultRequestHandler; } /** * Sets the default request handler used if the requested handler isn't * specified or cannot be found. */ public void setDefaultRequestHandler(WORequestHandler aRequestHandler) { defaultRequestHandler = aRequestHandler; } /** * Registers the specified request handler for the specified key. */ public void registerRequestHandler(WORequestHandler aRequestHandler, String aKey) { requestHandlers.setObjectForKey(aRequestHandler, aKey); } /** * Unregisters any existing request handler for the specified key returning the * existing request handler, if any. */ public WORequestHandler removeRequestHandlerForKey(String aKey) { WORequestHandler result = requestHandlerForKey(aKey); requestHandlers.removeObjectForKey(aKey); return result; } /** * Returns the keys under which request handlers are registered. */ public NSArray registeredRequestHandlerKeys() { return requestHandlers.allKeys(); } /** * Returns the request handler registered for the specified key, or null if no * request handler is registered for that key. */ public WORequestHandler requestHandlerForKey(String aKey) { return (WORequestHandler) requestHandlers.objectForKey(aKey); } /** * Returns the request handler that would best service the specified request. */ public WORequestHandler handlerForRequest(WORequest aRequest) { WORequestHandler result; if (aRequest == null) result = defaultRequestHandler(); // TODO verify it was correct to move this down one String requestHandlerKey = aRequest.requestHandlerKey(); result = requestHandlerForKey(requestHandlerKey); if (result == null) { System.err.println("Unable to find request handler for key " + requestHandlerKey); } return result; } // handling errors public WOResponse handleSessionCreationErrorInContext(WOContext aContext) { WOResponse response = new WOResponse(); response.setStatus(500); // internal server error // TODO: add more useful information to the response System.err.println("Failed to create session: " + aContext); new RuntimeException().printStackTrace(); // remove me return response; } public WOResponse handleSessionRestorationErrorInContext(WOContext aContext) { WOResponse response = new WOResponse(); response.setStatus(500); // internal server error // TODO: add more useful information to the response System.err.println("Failed to restore session: " + aContext); new RuntimeException().printStackTrace(); // remove me return response; } public WOResponse handlePageRestorationErrorInContext(WOContext aContext) { WOResponse response = new WOResponse(); response.setStatus(500); // internal server error // TODO: add more useful information to the response System.err.println("Failed to restore page: " + aContext); new RuntimeException().printStackTrace(); // remove me return response; } public WOResponse handleException(Throwable aThrowable, WOContext aContext) { WOResponse response = new WOResponse(); response.setStatus(500); // internal server error System.err.println("Exception occurred: " + aContext); if (aThrowable.getMessage() != null) { response.appendContentString(aThrowable.getMessage()); aThrowable.printStackTrace(); } else { response.appendContentString(aThrowable.toString()); aThrowable.printStackTrace(); } aThrowable.printStackTrace(); return response; } // managing pages /** * Sets the number of pages that will be retained in the user's session. Set to * zero to disable page caching. */ public void setPageCacheSize(int aPositiveInt) { pageCacheSize = aPositiveInt; } /** * Returns the number of pages that will be retained in the user's session. The * default page cache size is 30. */ public int pageCacheSize() { return pageCacheSize; } /** * Returns the number of pages that will be retained in the longer-term * "permanent" page cache in the user's session, which is typically used for * navigation bars in frames, etc. The default permanent page cache size is 30. */ public int permanentPageCacheSize() { return permanentPageCacheSize; } /** * Returns the number of pages that will be retained in the longer-term * "permanent" page cache in the user's session, which is typically used for * navigation bars in frames, etc. Set to zero to disable permanent page * caching. */ public void setPermanentPageCacheSize(int aPositiveInt) { permanentPageCacheSize = aPositiveInt; } /** * Returns whether a "backtrack" for an existing page should simply call * generateResponse() on the existing page instance. If false, a new page is * created instead. The default is true. */ public void setPageRefreshOnBacktrackEnabled(boolean enabled) { pageRefreshOnBacktrack = enabled; } /** * Returns whether a "backtrack" for an existing page should simply call * generateResponse() on the existing page instance. If false, a new page is * created instead. The default is true. */ public boolean isPageRefreshOnBacktrackEnabled() { return pageRefreshOnBacktrack; } // managing sessions /** * Sets the session store used by this application to persist sessions between * request-response transactions. */ public void setSessionStore(WOSessionStore aSessionStore) { sessionStore = aSessionStore; } /** * Returns the session store used by this application to persist sessions * between request-response transactions. */ public WOSessionStore sessionStore() { return sessionStore; } /** * Called at the end of the request-response cycle to persist the current * session until the user's next request. */ public void saveSessionForContext(WOContext aContext) { sessionStore.saveSessionForContext(aContext); } /** * Called at the beginning of the request-response cycle to obtain the current * session from the user's last request. Returns null if no such session has * been created. This method sets the context of the session to the specified * context. */ public WOSession restoreSessionWithID(String aSessionID, WOContext aContext) { WORequest request = aContext.request(); WOSession session = sessionStore.restoreSessionWithID(aSessionID, request); if (session != null) { session.setContext(aContext); session.setServletSession(request.servletRequest().getSession()); } return session; } /** * Called to create a session for a new request. This implementation looks for a * class in the same package as the application class called "Session" and * failing that returns a WOSession. */ public WOSession createSessionForRequest(WORequest aRequest) { WOSession result = null; try { // using our class loader, which is hopefully dynamic. result = (WOSession) getLocalClass("Session").getDeclaredConstructor().newInstance(); } catch (Throwable t) { // ignore: fall back to WOSession // t.printStackTrace(); } if (result == null) { result = new WOSession(); } result.setServletSession(aRequest.servletRequest().getSession(true)); return result; } /** * Returns the page component with the specified name. A context is created with * the specified request, along with a session if necessary. */ public WOComponent pageWithName(String aName, WORequest aRequest) { return pageWithName(aName, WOContext.contextWithRequest(aRequest)); } /** * Called to retrieve a component for the specified context. */ public WOComponent pageWithName(String aName, WOContext aContext) { if (aName == null) { throw new IllegalArgumentException("WOApplication.pageWithName: name is null"); } WOComponent result = null; try { // using our class loader, which is hopefully dynamic. Class c = getLocalClass(aName); if (c != null) { // get constructor Constructor ctor; try { ctor = c.getConstructor(new Class[] { WOContext.class }); } catch (NoSuchMethodException nsme) { ctor = null; } // create instance of class if (ctor != null) { result = (WOComponent) ctor.newInstance(new Object[] { aContext }); } else // call back on default constructor (deprecated) { result = (WOComponent) c.getDeclaredConstructor().newInstance(); } } } catch (Throwable t) { // ignore for now // TODO: Throw appropriate exception here // System.err.println( "Not found: pageWithName: " + aName ); t.printStackTrace(); } if (result != null && aContext != null) { // this is where components get their context result.ensureAwakeInContext(aContext); } else if (result == null) { System.err.println("Not found: pageWithName: " + aName); } return result; } /** * Returns a class in the same package as the Application class, or, failing * that, from the WOApplication package, or finally from the root of the class * path. Returns null if not found. */ @SuppressWarnings("unchecked") Class getLocalClass(String aName) { Class result = null; if (getClass() != WOApplication.class) { result = (Class) loadLocalClass(getClass(), aName); } if (result == null) { result = (Class) loadLocalClass(WOApplication.class, aName); } if (result == null) { result = loadLocalClass(null, aName); } return result; } @SuppressWarnings("unchecked") private static final Class loadLocalClass(Class aClass, String aName) { ClassLoader loader; String packageName = ""; if (aClass != null) { loader = aClass.getClassLoader(); packageName = aClass.getName(); int index = packageName.lastIndexOf("."); if (index > -1) { packageName = packageName.substring(0, index + 1); } else { packageName = ""; } } else { loader = WOApplication.class.getClassLoader(); } try { return (Class) loader.loadClass(packageName + aName); } catch (ClassNotFoundException e) { return null; } } // creating elements /** * Returns either a dynamic element or a component for the specified name. */ public WOElement dynamicElementWithName(String anElementName, NSDictionary anAssociationMap, WOElement aBodyElement, List aLanguageList) { WOElement element = null; Class c = null; try { c = getLocalClass(anElementName); if (c == null) { System.out.println("Not found: dynamicElementWithName: " + "could not find WODynamicElement subclass: " + anElementName); c = WODynamicElement.class; } // get constructor Class[] params = new Class[] { String.class, NSDictionary.class, WOElement.class }; Constructor ctor = c.getConstructor(params); // create instance of class if (ctor != null) { element = (WODynamicElement) ctor .newInstance(new Object[] { anElementName, anAssociationMap, aBodyElement }); } } catch (Throwable t) { // ignore: not a dynamic element // System.out.println( "Not a dynamic element: dynamicElementWithName: " + t ); // exc.printStackTrace(); } // no dynamic element found: look for a component if (element == null) { WOComponent component = (WOComponent) pageWithName(anElementName, (WOContext) null); // this seems hackish: // I don't see another way of setting the bindings. component.associations = anAssociationMap; component.rootElement = aBodyElement; element = component; } return element; } // resource handling /** * Called to create the application's resource manager. Override to create a * custom resource manager. */ public WOResourceManager createResourceManager() { return new WOResourceManager(); } /** * Returns the application's current resource manager. */ public WOResourceManager resourceManager() { return resourceManager; } /** * Installs a custom resource manager into the current application. * * @deprecated Override createResourceManager() instead. */ public void setResourceManager(WOResourceManager aResourceManager) { resourceManager = aResourceManager; } /* * // request handling undocumented * * public WOComponent pageWithName (String); public void savePage (WOComponent); * public WOComponent restorePageForContextID (String); public WOContext context * (); public WOSession session (); public WOSession createSession (); public * WOSession restoreSession (); public void saveSession (WOSession); * * public WOResponse handleRequest (WORequest aRequest) { } * * // error handling undocumented * * WOResponse handleSessionCreationError (); WOResponse * handleSessionRestorationError (); WOResponse handlePageRestorationError (); * WOResponse handleException (Throwable); * * // running * * public NSRunLoop runLoop (); public void run (); public void setTimeOut * (double); public double timeOut (); public void terminate (); public boolean * isTerminating (); * * // script debugging * * public void traceScriptedMessages (boolean); public void traceAssignments * (boolean); public void traceStatements (boolean); public void * traceObjectiveCMessages (boolean); public void trace (boolean); public void * logTakeValueForDeclarationNamed (String, String, String, String, Object); * public void logSetValueForDeclarationNamed (String, String, String, String, * Object); * * // script handling * * public String scriptedClassNameWithPath (String); public String * scriptedClassNameWithPathEncoding (String, int); * * // statistics report * * public void setStatisticsStore (WOStatisticsStore); public WOStatisticsStore * statisticsStore (); public NSDictionary statistics (); * * // managing adaptors * * public WOAdaptor adaptorWithName (String, NSDictionary); public NSArray * adaptors (); * * // monitor support * * public boolean monitoringEnabled (); public int activeSessionsCount (); * public void refuseNewSessions (boolean); public boolean isRefusingNewSessions * (); public void setMinimumActiveSessionsCount (int); public int * minimumActiveSessionsCount (); public void terminateAfterTimeInterval * (double); * * // garbage collection undocumented * * int garbageCollectionPeriod (); void setGarbageCollectionPeriod (int); * * // backwards compatibility * * public boolean requiresWOF35RequestHandling (); public boolean * requiresWOF35TemplateParser (); * * public void setPrintsHTMLParserDiagnostics (boolean); public boolean * printsHTMLParserDiagnostics (); * * // configuration and defaults * * public static NSArray loadFrameworks (); public static void setLoadFrameworks * (NSArray); */ static boolean debuggingEnabled = false; /** * Returns whether the application is in "debug mode". */ public static boolean isDebuggingEnabled() { return debuggingEnabled; } /** * Sets whether the application is in "debug mode". */ public static void setDebuggingEnabled(boolean enabled) { debuggingEnabled = enabled; } /** * Sets whether templates are cached. If true, templates will only be read once * per application lifetime. Otherwise, templates will be read each time this * class is instantiated. Defaults to false. */ public static void setCachingEnabled(boolean enabled) { cachingEnabled = enabled; } /** * Returns whether templates are cached. If true, templates are read once per * application lifetime. Otherwise, templates are read each time this class is * instantiated. */ public static boolean isCachingEnabled() { return cachingEnabled; } // configuration /** * Returns the component request handler key, which is defined by the system * property _ComponentRequestHandlerKey. The default is "wo". */ public static String componentRequestHandlerKey() { return System.getProperty(_ComponentRequestHandlerKey, "wo"); } /** * Sets the component request handler key. * * @deprecated Set the system property _ComponentRequestHandlerKey. */ public static void setComponentRequestHandlerKey(String aKey) { System.setProperty(_ComponentRequestHandlerKey, aKey); } /** * Returns the direct action request handler key, which is defined by the system * property _DirectActionRequestHandlerKey. The default is "wa". */ public static String directActionRequestHandlerKey() { return System.getProperty(_DirectActionRequestHandlerKey, "wa"); } /** * Sets the direct action request handler key. * * @deprecated Set the system property _DirectActionRequestHandlerKey. */ public static void setDirectActionRequestHandlerKey(String aKey) { System.setProperty(_DirectActionRequestHandlerKey, aKey); } /** * Returns the resource request handler key, which is defined by the system * property _ResourceRequestHandlerKey. The default is "wr". */ public static String resourceRequestHandlerKey() { return System.getProperty(_ResourceRequestHandlerKey, "wr"); } /** * Sets the resource request handler key. * * @deprecated Set the system property _ResourceRequestHandlerKey. */ public static void setResourceRequestHandlerKey(String aKey) { System.setProperty(_ResourceRequestHandlerKey, aKey); } /** * Returns whether this application should attempt to open a web browser on the * host machine when launched standalone. The default is true. */ public static boolean autoOpenInBrowser() { return autoOpenInBrowser; } /** * Sets whether this application should attempt to open a web browser on the * host machine when launched standalone. */ public static void setAutoOpenInBrowser(boolean autoOpen) { autoOpenInBrowser = autoOpen; } /** * Gets the port used when run as a standalone server. Returns the value of the * system property WOPort. By default, this is zero, which causes the * application to automatically select an available port. */ public static Number port() { return Integer.getInteger(WOPort, 0); } /** * Gets the smtp server that will be used to send email. Returns the system * property WOSMTPHost. */ public static String SMTPHost() { return System.getProperty(WOSMTPHost); } /** * Sets the smtp server that will be used to send email. * * @deprecated Set the system property WOSMTPHost. */ public static void setSMTPHost(String aHost) { System.setProperty(WOSMTPHost, aHost); } /* * public static boolean isDirectConnectEnabled (); public static void * setDirectConnectEnabled (boolean); public static String cgiAdaptorURL (); * public static void setCGIAdaptorURL (String); public static String * applicationBaseURL (); public static void setApplicationBaseURL (String); * public static String frameworksBaseURL (); public static void * setFrameworksBaseURL (String); public static String recordingPath (); public * static void setRecordingPath (String); public static NSArray * projectSearchPath (); public static void setProjectSearchPath (NSArray); * public static boolean isMonitorEnabled (); public static void * setMonitorEnabled (boolean); public static String monitorHost (); public * static String adaptor (); public String number (); // deprecated public * static Number listenQueueSize (); public static void setListenQueueSize * (Number); public static NSArray additionalAdaptors (); public static void * setAdditionalAdaptors (NSArray); public static boolean * includeCommentsInResponses (); public static void * setIncludeCommentsInResponses (boolean); public static void setSessionTimeOut * (Number); public static Number sessionTimeOut (); public static void * logString (String); public static void debugString (String); public static * void logToMonitorString (String); */ /** * Main entry point for applications that do not subclass WOApplication. */ public static void main(String[] argv) { main(argv, WOApplication.class); } /** * Subclasses may call this method to start a self-hosted web server to serve * themselves directly (for testing). */ public static void main(String[] argv, Class subclass) { // TODO fix this later /*try { int port = 0; boolean open = false; try { port = ((Number) subclass.getMethod("port", new Class[0]).invoke(subclass, new Object[0])).intValue(); open = ((Boolean) subclass.getMethod("autoOpenInBrowser", new Class[0]).invoke(subclass, new Object[0])) .booleanValue(); } catch (Throwable t) { System.err.print("Error reading configuration:"); t.printStackTrace(); } HttpServer server = new HttpServer(); HttpListener listener = server.addListener(new InetAddrPort(port)); org.mortbay.http.HttpContext context = server.getContext("/"); ServletHandler handler = new ServletHandler(); handler.addServlet("/", subclass.getName()); context.addHandler(handler); server.start(); port = listener.getPort(); System.out.println("Waiting for requests: http://127.0.0.1:" + port); if (open) { BrowserLauncher.openURL("http://127.0.0.1:" + port); } } catch (Throwable t) { t.printStackTrace(); }*/ } } /* * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and * imports so it all compiles. * * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.30 2003/03/28 18:01:19 mpowers Now defaulting port to zero. * * Revision 1.29 2003/03/28 17:31:58 mpowers Implemented support for * autoselection of free port. (thanks gmuth!) * * Revision 1.28 2003/03/28 17:26:17 mpowers Implemented package support: * Applications can now live in packages. Better support for locating package * local classes. * * Revision 1.27 2003/02/21 16:40:22 mpowers Now reading port and smtp host from * system properties. Implemented WOApplication.main. * * Revision 1.26 2003/02/14 22:33:18 mpowers Better handling for standalone * mode. * * Revision 1.25 2003/02/14 15:18:27 mpowers Now launching standalone app as a * servlet, not a webapp. Disabled jetty's event logging. * * Revision 1.24 2003/02/13 22:41:04 mpowers WOApplications can now be * self-serving. Added configuration params too. * * Revision 1.23 2003/01/28 19:33:51 mpowers Implemented the rest of * WOResourceManager. Implemented support for java-style i18n. Components now * use the resource manager to load templates. * * Revision 1.22 2003/01/27 15:08:00 mpowers Implemented WOResourceManager, * using java resources for now. * * Revision 1.21 2003/01/24 20:13:22 mpowers Now accepting immutable * NSDictionary in constructor, not Map. * * Revision 1.20 2003/01/20 17:50:11 mpowers Caught a loop condition when same * declaration was used twice. * * Revision 1.19 2003/01/19 22:33:25 mpowers Fixed problems with classpath and * dynamic class loading. Dynamic elements now pass on ensureAwakeInContext. * Parser how handles tags. * * Revision 1.18 2003/01/18 23:54:50 mpowers Implemented debugging enabled. * * Revision 1.17 2003/01/17 20:58:18 mpowers Fixed up WOHyperlink. * * Revision 1.16 2003/01/17 20:34:17 mpowers Rudimentary support for resource * requests. * * Revision 1.15 2003/01/17 15:31:56 mpowers Removed spurious error message. * * Revision 1.14 2003/01/17 14:39:00 mpowers Now calling preferred constructor: * WOComponent(WOContext) * * Revision 1.13 2003/01/16 20:10:46 mpowers - components now synchronize * bindings - support for WOComponentContent - implemented performParentAction * * Revision 1.12 2003/01/16 15:50:43 mpowers More robust declaration parsing. * Subcomponents are now supported. dynamicElementWithName can now return * subcomponents. * * Revision 1.11 2003/01/15 19:50:49 mpowers Fixed issues with WOSession and * Serializable. Can now persist sessions between classloaders (hot swap of * class impls). * * Revision 1.10 2003/01/13 22:24:18 mpowers Request-response cycle is working * with session and page persistence. * * Revision 1.9 2003/01/10 20:17:41 mpowers Component action urls are now * working. * * Revision 1.8 2003/01/10 19:16:40 mpowers Implemented support for page * caching. * * Revision 1.4 2002/12/19 17:58:52 mpowers Rewrote the template parsing - no * longer confused about "root element". * * Revision 1.3 2002/12/18 14:12:38 mpowers Support for differentiated request * handlers. Support url generation for WOContext and WORequest. * * Revision 1.2 2002/12/17 14:57:41 mpowers Minor corrections to WORequests's * parsing, and updated javadocs. * * Revision 1.1.1.1 2000/12/21 15:52:50 mpowers Contributing wotonomy. * * Revision 1.2 2000/12/20 16:25:49 michael Added log to all files. * * */