/* 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 javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.web.util.BrowserLauncher; import org.mortbay.http.HttpListener; import org.mortbay.http.HttpServer; import org.mortbay.jetty.servlet.ServletHandler; import org.mortbay.util.InetAddrPort; /** * 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 { /** * 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. */ 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. */ 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) { return handlerForRequest( aRequest ).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 = requestHandlerForKey( aRequest.requestHandlerKey() ); if ( aRequest == null ) result = defaultRequestHandler(); 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" ).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.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. */ Class getLocalClass( String aName ) { Class result = null; if ( getClass() != WOApplication.class ) { result = loadLocalClass( getClass(), aName ); } if ( result == null ) { result = loadLocalClass( WOApplication.class, aName ); } if ( result == null ) { result = loadLocalClass( null, aName ); } return result; } 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 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 ) { 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. * * */