0001: /* *************************************************************************
0002:
0003: Millstone(TM)
0004: Open Sourced User Interface Library for
0005: Internet Development with Java
0006:
0007: Millstone is a registered trademark of IT Mill Ltd
0008: Copyright (C) 2000-2005 IT Mill Ltd
0009:
0010: *************************************************************************
0011:
0012: This library is free software; you can redistribute it and/or
0013: modify it under the terms of the GNU Lesser General Public
0014: license version 2.1 as published by the Free Software Foundation.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: *************************************************************************
0026:
0027: For more information, contact:
0028:
0029: IT Mill Ltd phone: +358 2 4802 7180
0030: Ruukinkatu 2-4 fax: +358 2 4802 7181
0031: 20540, Turku email: info@itmill.com
0032: Finland company www: www.itmill.com
0033:
0034: Primary source for MillStone information and releases: www.millstone.org
0035:
0036: ********************************************************************** */
0037:
0038: package org.millstone.webadapter;
0039:
0040: import java.io.BufferedReader;
0041: import java.io.BufferedWriter;
0042: import java.io.File;
0043: import java.io.FileOutputStream;
0044: import java.io.IOException;
0045: import java.io.InputStream;
0046: import java.io.InputStreamReader;
0047: import java.io.OutputStream;
0048: import java.io.OutputStreamWriter;
0049: import java.io.PrintWriter;
0050: import java.net.MalformedURLException;
0051: import java.net.URL;
0052: import java.util.Arrays;
0053: import java.util.Collection;
0054: import java.util.Date;
0055: import java.util.Enumeration;
0056: import java.util.HashSet;
0057: import java.util.Iterator;
0058: import java.util.LinkedList;
0059: import java.util.List;
0060: import java.util.Map;
0061: import java.util.Properties;
0062: import java.util.Set;
0063: import java.util.StringTokenizer;
0064: import java.util.WeakHashMap;
0065:
0066: import javax.servlet.ServletContext;
0067: import javax.servlet.ServletException;
0068: import javax.servlet.http.HttpServlet;
0069: import javax.servlet.http.HttpServletRequest;
0070: import javax.servlet.http.HttpServletResponse;
0071: import javax.servlet.http.HttpSession;
0072: import javax.servlet.http.HttpSessionBindingEvent;
0073: import javax.servlet.http.HttpSessionBindingListener;
0074:
0075: import org.millstone.base.Application;
0076: import org.millstone.base.Application.WindowAttachEvent;
0077: import org.millstone.base.Application.WindowDetachEvent;
0078: import org.millstone.base.service.FileTypeResolver;
0079: import org.millstone.base.terminal.DownloadStream;
0080: import org.millstone.base.terminal.Paintable;
0081: import org.millstone.base.terminal.ParameterHandler;
0082: import org.millstone.base.terminal.ThemeResource;
0083: import org.millstone.base.terminal.URIHandler;
0084: import org.millstone.base.terminal.Paintable.RepaintRequestEvent;
0085: import org.millstone.base.ui.Window;
0086: import org.millstone.webadapter.ThemeSource.ThemeException;
0087:
0088: /**
0089: * This servlet is the core of the MillStone Web Adapter, that adapts the
0090: * MillStone applications to Web standards. The web adapter can be used to
0091: * represent the most MillStone application using Web browsers and corresponding
0092: * technologies.
0093: *
0094: * @author IT Mill Ltd.
0095: * @version
0096: * 3.1.1
0097: * @since 3.0
0098: */
0099:
0100: public class WebAdapterServlet extends HttpServlet implements
0101: Application.WindowAttachListener,
0102: Application.WindowDetachListener,
0103: Paintable.RepaintRequestListener {
0104:
0105: // Versions
0106: private static final int VERSION_MAJOR = 3;
0107: private static final int VERSION_MINOR = 1;
0108: private static final int VERSION_BUILD = 0;
0109: private static final String VERSION = "" + VERSION_MAJOR + "."
0110: + VERSION_MINOR + "." + VERSION_BUILD;
0111:
0112: //Configurable parameter names
0113: private static final String PARAMETER_DEBUG = "Debug";
0114: private static final String PARAMETER_DEFAULT_THEME_JAR = "DefaultThemeJar";
0115: private static final String PARAMETER_THEMESOURCE = "ThemeSource";
0116: private static final String PARAMETER_THEME_CACHETIME = "ThemeCacheTime";
0117: private static final String PARAMETER_MAX_TRANSFORMERS = "MaxTransformers";
0118: private static final String PARAMETER_TRANSFORMER_CACHETIME = "TransformerCacheTime";
0119:
0120: private static int DEFAULT_THEME_CACHETIME = 1000 * 60 * 60 * 24;
0121: private static int DEFAULT_BUFFER_SIZE = 32 * 1024;
0122: private static int DEFAULT_MAX_TRANSFORMERS = 1;
0123: private static int MAX_BUFFER_SIZE = 64 * 1024;
0124: private static String SESSION_ATTR_VARMAP = "varmap";
0125: static String SESSION_ATTR_CONTEXT = "millstone_context";
0126: static String SESSION_ATTR_APPS = "apps";
0127: private static String SESSION_BINDING_LISTENER = "bindinglistener";
0128: private static String SESSION_DEFAULT_THEME = "default";
0129: private static String RESOURCE_URI = "/RES/";
0130: private static String THEME_DIRECTORY_PATH = "WEB-INF/lib/themes/";
0131: private static String THEME_LISTING_FILE = THEME_DIRECTORY_PATH
0132: + "themes.txt";
0133: private static String DEFAULT_THEME_JAR_PREFIX = "millstone-web-themes";
0134: private static String DEFAULT_THEME_JAR = "WEB-INF/lib/"
0135: + DEFAULT_THEME_JAR_PREFIX + "-" + VERSION + ".jar";
0136: private static String DEFAULT_THEME_SNAPSHOT_JAR = "WEB-INF/lib/"
0137: + DEFAULT_THEME_JAR_PREFIX + "-" + VERSION_MAJOR + "."
0138: + VERSION_MINOR + "-SNAPSHOT.jar";
0139: private static String DEFAULT_THEME_TEMP_FILE_PREFIX = "WA_TMP_";
0140: private static String SERVER_COMMAND_PARAM = "SERVER_COMMANDS";
0141: private static int SERVER_COMMAND_STREAM_MAINTAIN_PERIOD = 15000;
0142: private static int SERVER_COMMAND_HEADER_PADDING = 2000;
0143:
0144: // Private fields
0145: private Class applicationClass;
0146: private Properties applicationProperties;
0147: private UIDLTransformerFactory transformerFactory;
0148: private CollectionThemeSource themeSource;
0149: private String resourcePath = null;
0150: //private boolean enableBrowserProbe = false;
0151: private boolean debugMode = false;
0152: private int maxConcurrentTransformers;
0153: private long transformerCacheTime;
0154: private long themeCacheTime;
0155: private WeakHashMap applicationToDirtyWindowSetMap = new WeakHashMap();
0156: private WeakHashMap applicationToServerCommandStreamLock = new WeakHashMap();
0157: private WeakHashMap applicationToLastRequestDate = new WeakHashMap();
0158: private List allWindows = new LinkedList();
0159:
0160: /**
0161: * Called by the servlet container to indicate to a servlet that the servlet
0162: * is being placed into service.
0163: *
0164: * @param servletConfig
0165: * object containing the servlet's configuration and
0166: * initialization parameters
0167: * @throws ServletException
0168: * if an exception has occurred that interferes with the
0169: * servlet's normal operation.
0170: */
0171: public void init(javax.servlet.ServletConfig servletConfig)
0172: throws javax.servlet.ServletException {
0173: super .init(servletConfig);
0174:
0175: // Get the application class name
0176: String applicationClassName = servletConfig
0177: .getInitParameter("application");
0178: if (applicationClassName == null) {
0179: Log
0180: .error("Application not specified in servlet parameters");
0181: }
0182:
0183: // Store the application parameters into Properties object
0184: this .applicationProperties = new Properties();
0185: for (Enumeration e = servletConfig.getInitParameterNames(); e
0186: .hasMoreElements();) {
0187: String name = (String) e.nextElement();
0188: this .applicationProperties.setProperty(name, servletConfig
0189: .getInitParameter(name));
0190: }
0191:
0192: // Override with server.xml parameters
0193: ServletContext context = servletConfig.getServletContext();
0194: for (Enumeration e = context.getInitParameterNames(); e
0195: .hasMoreElements();) {
0196: String name = (String) e.nextElement();
0197: this .applicationProperties.setProperty(name, context
0198: .getInitParameter(name));
0199: }
0200:
0201: // Get the debug window parameter
0202: String debug = getApplicationOrSystemProperty(PARAMETER_DEBUG,
0203: "false");
0204: // Enable application specific debug
0205: this .debugMode = debug.equals("true");
0206:
0207: // Get the maximum number of simultaneous transformers
0208: this .maxConcurrentTransformers = Integer
0209: .parseInt(getApplicationOrSystemProperty(
0210: PARAMETER_MAX_TRANSFORMERS, "-1"));
0211: if (this .maxConcurrentTransformers < 1)
0212: this .maxConcurrentTransformers = DEFAULT_MAX_TRANSFORMERS;
0213:
0214: // Get cache time for transformers
0215: this .transformerCacheTime = Integer
0216: .parseInt(getApplicationOrSystemProperty(
0217: PARAMETER_TRANSFORMER_CACHETIME, "-1")) * 1000;
0218:
0219: // Get cache time for theme resources
0220: this .themeCacheTime = Integer
0221: .parseInt(getApplicationOrSystemProperty(
0222: PARAMETER_THEME_CACHETIME, "-1")) * 1000;
0223: if (this .themeCacheTime < 0) {
0224: this .themeCacheTime = DEFAULT_THEME_CACHETIME;
0225: }
0226:
0227: // Add all specified theme sources
0228: this .themeSource = new CollectionThemeSource();
0229: List directorySources = getThemeSources();
0230: for (Iterator i = directorySources.iterator(); i.hasNext();) {
0231: this .themeSource.add((ThemeSource) i.next());
0232: }
0233:
0234: // Add the default theme source
0235: String[] defaultThemeFiles = new String[] {
0236: getApplicationOrSystemProperty(
0237: PARAMETER_DEFAULT_THEME_JAR, DEFAULT_THEME_JAR),
0238: DEFAULT_THEME_SNAPSHOT_JAR
0239:
0240: };
0241: File f = findDefaultThemeJar(defaultThemeFiles);
0242: try {
0243: // Add themes.jar if exists
0244: if (f != null && f.exists())
0245: this .themeSource.add(new JarThemeSource(f, this , ""));
0246: else {
0247: Log.warn("Default theme JAR not found in: "
0248: + Arrays.asList(defaultThemeFiles));
0249: }
0250:
0251: } catch (Exception e) {
0252: throw new ServletException(
0253: "Failed to load default theme from "
0254: + Arrays.asList(defaultThemeFiles), e);
0255: }
0256:
0257: // Check that at least one themesource was loaded
0258: if (this .themeSource.getThemes().size() <= 0) {
0259: throw new ServletException(
0260: "No themes found in specified themesources.");
0261: }
0262:
0263: // Initialize the transformer factory, if not initialized
0264: if (this .transformerFactory == null) {
0265:
0266: this .transformerFactory = new UIDLTransformerFactory(
0267: this .themeSource, this ,
0268: this .maxConcurrentTransformers,
0269: this .transformerCacheTime);
0270: }
0271:
0272: // Load the application class using the same class loader
0273: // as the servlet itself
0274: ClassLoader loader = this .getClass().getClassLoader();
0275: try {
0276: this .applicationClass = loader
0277: .loadClass(applicationClassName);
0278: } catch (ClassNotFoundException e) {
0279: throw new ServletException(
0280: "Failed to load application class: "
0281: + applicationClassName);
0282: }
0283: }
0284:
0285: /**
0286: * Get an application or system property value.
0287: *
0288: * @param parameterName
0289: * Name or the parameter
0290: * @param defaultValue
0291: * Default to be used
0292: * @return String value or default if not found
0293: */
0294: private String getApplicationOrSystemProperty(String parameterName,
0295: String defaultValue) {
0296:
0297: // Try application properties
0298: String val = this .applicationProperties
0299: .getProperty(parameterName);
0300: if (val != null) {
0301: return val;
0302: }
0303:
0304: // Try lowercased application properties for backward compability with
0305: // 3.0.2 and earlier
0306: val = this .applicationProperties.getProperty(parameterName
0307: .toLowerCase());
0308: if (val != null) {
0309: return val;
0310: }
0311:
0312: // Try system properties
0313: String pkgName;
0314: Package pkg = this .getClass().getPackage();
0315: if (pkg != null) {
0316: pkgName = pkg.getName();
0317: } else {
0318: String clazzName = this .getClass().getName();
0319: pkgName = new String(clazzName.toCharArray(), 0, clazzName
0320: .lastIndexOf('.'));
0321: }
0322: val = System.getProperty(pkgName + "." + parameterName);
0323: if (val != null) {
0324: return val;
0325: }
0326:
0327: // Try lowercased system properties
0328: val = System.getProperty(pkgName + "."
0329: + parameterName.toLowerCase());
0330: if (val != null) {
0331: return val;
0332: }
0333:
0334: return defaultValue;
0335: }
0336:
0337: /**
0338: * Get ThemeSources from given path. Construct the list of avalable themes
0339: * in path using the following sources: 1. content of THEME_PATH directory
0340: * (if available) 2. The themes listed in THEME_LIST_FILE 3. "themesource"
0341: * application parameter - "org. millstone.webadapter. themesource" system
0342: * property
0343: *
0344: * @param THEME_DIRECTORY_PATH
0345: * @return List
0346: */
0347: private List getThemeSources() throws ServletException {
0348:
0349: List returnValue = new LinkedList();
0350:
0351: // Check the list file in theme directory
0352: List sourcePaths = new LinkedList();
0353: try {
0354: BufferedReader reader = new BufferedReader(
0355: new InputStreamReader(this .getServletContext()
0356: .getResourceAsStream(THEME_LISTING_FILE)));
0357: String line = null;
0358: while ((line = reader.readLine()) != null) {
0359: sourcePaths.add(THEME_DIRECTORY_PATH + line.trim());
0360: }
0361: if (this .isDebugMode()) {
0362: Log.debug("Listed " + sourcePaths.size()
0363: + " themes in " + THEME_LISTING_FILE
0364: + ". Loading " + sourcePaths);
0365: }
0366: } catch (Exception ignored) {
0367: // If the file reading fails, just skip to next method
0368: }
0369:
0370: // If no file was found or it was empty,
0371: // try to add themes filesystem directory if it is accessible
0372: if (sourcePaths.size() <= 0) {
0373: if (this .isDebugMode()) {
0374: Log.debug("No themes listed in " + THEME_LISTING_FILE
0375: + ". Trying to read the content of directory "
0376: + THEME_DIRECTORY_PATH);
0377: }
0378:
0379: try {
0380: String path = this .getServletContext().getRealPath(
0381: THEME_DIRECTORY_PATH);
0382: if (path != null) {
0383: File f = new File(path);
0384: if (f != null && f.exists())
0385: returnValue.add(new DirectoryThemeSource(f,
0386: this ));
0387: }
0388: } catch (java.io.IOException je) {
0389: Log.info("Theme directory " + THEME_DIRECTORY_PATH
0390: + " not available. Skipped.");
0391: } catch (ThemeException e) {
0392: throw new ServletException(
0393: "Failed to load themes from "
0394: + THEME_DIRECTORY_PATH, e);
0395: }
0396: }
0397:
0398: // Add the theme sources from application properties
0399: String paramValue = getApplicationOrSystemProperty(
0400: PARAMETER_THEMESOURCE, null);
0401: if (paramValue != null) {
0402: StringTokenizer st = new StringTokenizer(paramValue, ";");
0403: while (st.hasMoreTokens()) {
0404: sourcePaths.add(st.nextToken());
0405: }
0406: }
0407:
0408: // Construct appropriate theme source instances for each path
0409: for (Iterator i = sourcePaths.iterator(); i.hasNext();) {
0410: String source = (String) i.next();
0411: File sourceFile = new File(source);
0412: try {
0413:
0414: // Relative files are treated as streams (to support
0415: // resource inside WAR files)
0416: if (!sourceFile.isAbsolute()) {
0417: returnValue.add(new ServletThemeSource(this
0418: .getServletContext(), this , source));
0419: } else if (sourceFile.isDirectory()) {
0420:
0421: // Absolute directories are read from filesystem
0422: returnValue.add(new DirectoryThemeSource(
0423: sourceFile, this ));
0424: } else {
0425:
0426: // Absolute JAR-files are read from filesystem
0427: returnValue.add(new JarThemeSource(sourceFile,
0428: this , ""));
0429: }
0430: } catch (Exception e) {
0431: // Any exception breaks the the init
0432: throw new ServletException("Invalid theme source: "
0433: + source, e);
0434: }
0435: }
0436:
0437: // Return the constructed list of theme sources
0438: return returnValue;
0439: }
0440:
0441: /**
0442: * Receives standard HTTP requests from the public service method and
0443: * dispatches them.
0444: *
0445: * @param request
0446: * object that contains the request the client made of the
0447: * servlet
0448: * @param response
0449: * object that contains the response the servlet returns to the
0450: * client
0451: * @throws ServletException
0452: * if an input or output error occurs while the servlet is
0453: * handling the TRACE request
0454: * @throws IOException
0455: * if the request for the TRACE cannot be handled
0456: */
0457: protected void service(HttpServletRequest request,
0458: HttpServletResponse response) throws ServletException,
0459: IOException {
0460:
0461: // Transformer and output stream for the result
0462: UIDLTransformer transformer = null;
0463: HttpVariableMap variableMap = null;
0464: OutputStream out = response.getOutputStream();
0465: HashSet currentlyDirtyWindowsForThisApplication = new HashSet();
0466: try {
0467:
0468: // If the resource path is unassigned, initialize it
0469: if (resourcePath == null)
0470: resourcePath = request.getContextPath()
0471: + request.getServletPath() + RESOURCE_URI;
0472:
0473: // Handle resource requests
0474: if (handleResourceRequest(request, response))
0475: return;
0476:
0477: // Handle server commands
0478: if (handleServerCommands(request, response))
0479: return;
0480:
0481: // Get the application
0482: Application application = getApplication(request);
0483:
0484: // Create application if it doesn't exist
0485: if (application == null)
0486: application = createApplication(request);
0487:
0488: // Is this a download request from application
0489: DownloadStream download = null;
0490:
0491: // Invoke context transaction listeners
0492: WebApplicationContext appContext = null;
0493: if (application != null) {
0494: appContext = (WebApplicationContext) application
0495: .getContext();
0496: }
0497: if (appContext != null) {
0498: appContext.startTransaction(application, request);
0499: }
0500:
0501: // The rest of the process is synchronized with the application
0502: // in order to guarantee that no parallel variable handling is
0503: // made
0504: synchronized (application) {
0505:
0506: // Set the last application request date
0507: applicationToLastRequestDate.put(application,
0508: new Date());
0509:
0510: // Get the variable map
0511: variableMap = getVariableMap(application, request);
0512: if (variableMap == null)
0513: return;
0514:
0515: // Change all variables based on request parameters
0516: Map unhandledParameters = variableMap.handleVariables(
0517: request, application);
0518:
0519: // Check/handle client side feature checks
0520: WebBrowserProbe.handleProbeRequest(request,
0521: unhandledParameters);
0522:
0523: // Handle the URI if the application is still running
0524: if (application.isRunning())
0525: download = handleURI(application, request, response);
0526:
0527: // If this is not a download request
0528: if (download == null) {
0529:
0530: // Window renders are not cacheable
0531: response.setHeader("Cache-Control", "no-cache");
0532: response.setHeader("Pragma", "no-cache");
0533: response.setDateHeader("Expires", 0);
0534:
0535: // Find the window within the application
0536: Window window = null;
0537: if (application.isRunning())
0538: window = getApplicationWindow(request,
0539: application);
0540:
0541: // Handle the unhandled parameters if the application is
0542: // still running
0543: if (window != null && unhandledParameters != null
0544: && !unhandledParameters.isEmpty()) {
0545: try {
0546: window
0547: .handleParameters(unhandledParameters);
0548: } catch (Throwable t) {
0549: application
0550: .terminalError(new ParameterHandlerErrorImpl(
0551: window, t));
0552: }
0553: }
0554: // Remove application if it has stopped
0555: if (!application.isRunning()) {
0556: endApplication(request, response, application);
0557: return;
0558: }
0559:
0560: // Return blank page, if no window found
0561: if (window == null) {
0562: response.setContentType("text/html");
0563: BufferedWriter page = new BufferedWriter(
0564: new OutputStreamWriter(out));
0565: page.write("<html><head><script>");
0566: page
0567: .write(ThemeFunctionLibrary
0568: .generateWindowScript(
0569: null,
0570: application,
0571: this ,
0572: WebBrowserProbe
0573: .getTerminalType(request
0574: .getSession())));
0575: page.write("</script></head><body>");
0576: page
0577: .write("The requested window has been removed from application.");
0578: page.write("</body></html>");
0579: page.close();
0580:
0581: return;
0582: }
0583:
0584: // Get the terminal type for the window
0585: WebBrowser terminalType = (WebBrowser) window
0586: .getTerminal();
0587:
0588: // Set terminal type for the window, if not already set
0589: if (terminalType == null) {
0590: terminalType = WebBrowserProbe
0591: .getTerminalType(request.getSession());
0592: window.setTerminal(terminalType);
0593: }
0594:
0595: // Find theme and initialize TransformerType
0596: UIDLTransformerType transformerType = null;
0597: if (window.getTheme() != null) {
0598: Theme activeTheme;
0599: if ((activeTheme = this .themeSource
0600: .getThemeByName(window.getTheme())) != null) {
0601: transformerType = new UIDLTransformerType(
0602: terminalType, activeTheme);
0603: } else {
0604: Log
0605: .info("Theme named '"
0606: + window.getTheme()
0607: + "' not found. Using system default theme.");
0608: }
0609: }
0610:
0611: // Use default theme if selected theme was not found.
0612: if (transformerType == null) {
0613: Theme defaultTheme = this .themeSource
0614: .getThemeByName(WebAdapterServlet.SESSION_DEFAULT_THEME);
0615: if (defaultTheme == null) {
0616: throw new ServletException(
0617: "Default theme not found in the specified theme source(s).");
0618: }
0619: transformerType = new UIDLTransformerType(
0620: terminalType, defaultTheme);
0621: }
0622:
0623: transformer = this .transformerFactory
0624: .getTransformer(transformerType);
0625:
0626: // Set the response type
0627: response.setContentType(terminalType
0628: .getContentType());
0629:
0630: // Create UIDL writer
0631: WebPaintTarget paintTarget = transformer
0632: .getPaintTarget(variableMap);
0633:
0634: // Assure that the correspoding debug window will be
0635: // repainted property
0636: // by clearing it before the actual paint.
0637: DebugWindow debugWindow = (DebugWindow) application
0638: .getWindow(DebugWindow.WINDOW_NAME);
0639: if (debugWindow != null && debugWindow != window) {
0640: debugWindow
0641: .setWindowUIDL(window, "Painting...");
0642: }
0643:
0644: // Paint window
0645: window.paint(paintTarget);
0646: paintTarget.close();
0647:
0648: // For exception handling, memorize the current dirty status
0649: Collection dirtyWindows = (Collection) applicationToDirtyWindowSetMap
0650: .get(application);
0651: if (dirtyWindows == null) {
0652: dirtyWindows = new HashSet();
0653: applicationToDirtyWindowSetMap.put(application,
0654: dirtyWindows);
0655: }
0656: currentlyDirtyWindowsForThisApplication
0657: .addAll(dirtyWindows);
0658:
0659: // Window is now painted
0660: windowPainted(application, window);
0661:
0662: // Debug
0663: if (debugWindow != null && debugWindow != window) {
0664: debugWindow.setWindowUIDL(window, paintTarget
0665: .getUIDL());
0666: }
0667:
0668: // Set the function library state for this thread
0669: ThemeFunctionLibrary.setState(application, window,
0670: transformerType.getWebBrowser(), request
0671: .getSession(), this ,
0672: transformerType.getTheme().getName());
0673:
0674: }
0675: }
0676:
0677: // For normal requests, transform the window
0678: if (download == null) {
0679:
0680: // Transform and output the result to browser
0681: // Note that the transform and transfer of the result is
0682: // not synchronized with the variable map. This allows
0683: // parallel transfers and transforms for better performance,
0684: // but requires that all calls from the XSL to java are
0685: // thread-safe
0686: transformer.transform(out);
0687: }
0688:
0689: // For download request, transfer the downloaded data
0690: else {
0691:
0692: handleDownload(download, request, response);
0693: }
0694:
0695: // Notify context of transaction end
0696: if (appContext != null) {
0697: appContext.endTransaction(application, request);
0698: }
0699:
0700: } catch (UIDLTransformerException te) {
0701:
0702: try {
0703: // Write the error report to client
0704: response.setContentType("text/html");
0705: BufferedWriter err = new BufferedWriter(
0706: new OutputStreamWriter(out));
0707: err
0708: .write("<html><head><title>Application Internal Error</title></head><body>");
0709: err.write("<h1>" + te.getMessage() + "</h1>");
0710: err.write(te.getHTMLDescription());
0711: err.write("</body></html>");
0712: err.close();
0713: } catch (Throwable t) {
0714: Log.except("Failed to write error page: " + t
0715: + ". Original exception was: ", te);
0716: }
0717:
0718: // Add previously dirty windows to dirtyWindowList in order
0719: // to make sure that eventually they are repainted
0720: Application currentApplication = getApplication(request);
0721: for (Iterator iter = currentlyDirtyWindowsForThisApplication
0722: .iterator(); iter.hasNext();) {
0723: Window dirtyWindow = (Window) iter.next();
0724: addDirtyWindow(currentApplication, dirtyWindow);
0725: }
0726:
0727: } catch (Throwable e) {
0728: // Re-throw other exceptions
0729: throw new ServletException(e);
0730: } finally {
0731:
0732: // Release transformer
0733: if (transformer != null)
0734: transformerFactory.releaseTransformer(transformer);
0735:
0736: // Clean the function library state for this thread
0737: // for security reasons
0738: ThemeFunctionLibrary.cleanState();
0739: }
0740: }
0741:
0742: /**
0743: * Handle the requested URI. An application can add handlers to do special
0744: * processing, when a certain URI is requested. The handlers are invoked
0745: * before any windows URIs are processed and if a DownloadStream is returned
0746: * it is sent to the client.
0747: *
0748: * @see org.millstone.base.terminal.URIHandler
0749: *
0750: * @param application
0751: * Application owning the URI
0752: * @param request
0753: * HTTP request instance
0754: * @param response
0755: * HTTP response to write to.
0756: * @return boolean True if the request was handled and further processing
0757: * should be suppressed, false otherwise.
0758: */
0759: private DownloadStream handleURI(Application application,
0760: HttpServletRequest request, HttpServletResponse response) {
0761:
0762: String uri = request.getPathInfo();
0763:
0764: // If no URI is available
0765: if (uri == null || uri.length() == 0 || uri.equals("/"))
0766: return null;
0767:
0768: // Remove the leading /
0769: while (uri.startsWith("/") && uri.length() > 0)
0770: uri = uri.substring(1);
0771:
0772: // Handle the uri
0773: DownloadStream stream = null;
0774: try {
0775: stream = application.handleURI(application.getURL(), uri);
0776: } catch (Throwable t) {
0777: application.terminalError(new URIHandlerErrorImpl(
0778: application, t));
0779: }
0780:
0781: return stream;
0782: }
0783:
0784: /**
0785: * Handle the requested URI. An application can add handlers to do special
0786: * processing, when a certain URI is requested. The handlers are invoked
0787: * before any windows URIs are processed and if a DownloadStream is returned
0788: * it is sent to the client.
0789: *
0790: * @see org.millstone.base.terminal.URIHandler
0791: *
0792: * @param application
0793: * Application owning the URI
0794: * @param request
0795: * HTTP request instance
0796: * @param response
0797: * HTTP response to write to.
0798: * @return boolean True if the request was handled and further processing
0799: * should be suppressed, false otherwise.
0800: */
0801: private void handleDownload(DownloadStream stream,
0802: HttpServletRequest request, HttpServletResponse response) {
0803:
0804: // Download from given stream
0805: InputStream data = stream.getStream();
0806: if (data != null) {
0807:
0808: // Set content type
0809: response.setContentType(stream.getContentType());
0810:
0811: // Set cache headers
0812: long cacheTime = stream.getCacheTime();
0813: if (cacheTime <= 0) {
0814: response.setHeader("Cache-Control", "no-cache");
0815: response.setHeader("Pragma", "no-cache");
0816: response.setDateHeader("Expires", 0);
0817: } else {
0818: response.setHeader("Cache-Control", "max-age="
0819: + cacheTime / 1000);
0820: response.setDateHeader("Expires", System
0821: .currentTimeMillis()
0822: + cacheTime);
0823: response.setHeader("Pragma", "cache"); // Required to apply
0824: // caching in some
0825: // Tomcats
0826: }
0827:
0828: // Copy download stream parameters directly
0829: // to HTTP headers.
0830: Iterator i = stream.getParameterNames();
0831: if (i != null) {
0832: while (i.hasNext()) {
0833: String param = (String) i.next();
0834: response.setHeader((String) param, stream
0835: .getParameter(param));
0836: }
0837: }
0838:
0839: int bufferSize = stream.getBufferSize();
0840: if (bufferSize <= 0 || bufferSize > MAX_BUFFER_SIZE)
0841: bufferSize = DEFAULT_BUFFER_SIZE;
0842: byte[] buffer = new byte[bufferSize];
0843: int bytesRead = 0;
0844:
0845: try {
0846: OutputStream out = response.getOutputStream();
0847:
0848: while ((bytesRead = data.read(buffer)) > 0) {
0849: out.write(buffer, 0, bytesRead);
0850: out.flush();
0851: }
0852: out.close();
0853: } catch (IOException ignored) {
0854: }
0855:
0856: }
0857:
0858: }
0859:
0860: /**
0861: * Look for default theme JAR file.
0862: *
0863: * @return Jar file or null if not found.
0864: */
0865: private File findDefaultThemeJar(String[] fileList) {
0866:
0867: // Try to find the default theme JAR file based on the given path
0868: for (int i = 0; i < fileList.length; i++) {
0869: String path = this .getServletContext().getRealPath(
0870: fileList[i]);
0871: File file = null;
0872: if (path != null && (file = new File(path)).exists()) {
0873: return file;
0874: }
0875: }
0876:
0877: // If we do not have access to individual files, create a temporary
0878: // file from named resource.
0879: for (int i = 0; i < fileList.length; i++) {
0880: InputStream defaultTheme = this .getServletContext()
0881: .getResourceAsStream(fileList[i]);
0882: // Read the content to temporary file and return it
0883: if (defaultTheme != null) {
0884: return createTemporaryFile(defaultTheme, ".jar");
0885: }
0886: }
0887:
0888: // Try to find the default theme JAR file based on file naming scheme
0889: // NOTE: This is for backward compability with 3.0.2 and earlier.
0890: String path = this .getServletContext().getRealPath(
0891: "/WEB-INF/lib");
0892: if (path != null) {
0893:
0894: File lib = new File(path);
0895: String[] files = lib.list();
0896: if (files != null) {
0897: for (int i = 0; i < files.length; i++) {
0898: if (files[i].toLowerCase().endsWith(".jar")
0899: && files[i]
0900: .startsWith(DEFAULT_THEME_JAR_PREFIX)) {
0901: return new File(lib, files[i]);
0902: }
0903: }
0904: }
0905: }
0906:
0907: // If no file was found return null
0908: return null;
0909: }
0910:
0911: /**
0912: * Create a temporary file for given stream.
0913: *
0914: * @param stream
0915: * Stream to be stored into temporary file.
0916: * @param extension
0917: * File type extension
0918: * @return File
0919: */
0920: private File createTemporaryFile(InputStream stream,
0921: String extension) {
0922: File tmpFile;
0923: try {
0924: tmpFile = File.createTempFile(
0925: DEFAULT_THEME_TEMP_FILE_PREFIX, extension);
0926: FileOutputStream out = new FileOutputStream(tmpFile);
0927: byte[] buf = new byte[1024];
0928: int bytes = 0;
0929: while ((bytes = stream.read(buf)) > 0) {
0930: out.write(buf, 0, bytes);
0931: }
0932: out.close();
0933: } catch (IOException e) {
0934: System.err
0935: .println("Failed to create temporary file for default theme: "
0936: + e);
0937: tmpFile = null;
0938: }
0939:
0940: return tmpFile;
0941: }
0942:
0943: /**
0944: * Handle theme resource file requests. Resources supplied with the themes
0945: * are provided by the WebAdapterServlet.
0946: *
0947: * @param request
0948: * HTTP request
0949: * @param response
0950: * HTTP response
0951: * @return boolean True if the request was handled and further processing
0952: * should be suppressed, false otherwise.
0953: */
0954: private boolean handleResourceRequest(HttpServletRequest request,
0955: HttpServletResponse response) throws ServletException {
0956:
0957: String resourceId = request.getPathInfo();
0958:
0959: // Check if this really is a resource request
0960: if (resourceId == null || !resourceId.startsWith(RESOURCE_URI))
0961: return false;
0962:
0963: // Check the resource type
0964: resourceId = resourceId.substring(RESOURCE_URI.length());
0965: InputStream data = null;
0966: // Get theme resources
0967: try {
0968: data = themeSource.getResource(resourceId);
0969: } catch (ThemeSource.ThemeException e) {
0970: Log.info(e.getMessage());
0971: data = null;
0972: }
0973:
0974: // Write the response
0975: try {
0976: if (data != null) {
0977: response.setContentType(FileTypeResolver
0978: .getMIMEType(resourceId));
0979:
0980: // Use default cache time for theme resources
0981: if (this .themeCacheTime > 0) {
0982: response.setHeader("Cache-Control", "max-age="
0983: + this .themeCacheTime / 1000);
0984: response.setDateHeader("Expires", System
0985: .currentTimeMillis()
0986: + this .themeCacheTime);
0987: response.setHeader("Pragma", "cache"); // Required to apply
0988: // caching in some
0989: // Tomcats
0990: }
0991: // Write the data to client
0992: byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
0993: int bytesRead = 0;
0994: OutputStream out = response.getOutputStream();
0995: while ((bytesRead = data.read(buffer)) > 0) {
0996: out.write(buffer, 0, bytesRead);
0997: }
0998: out.close();
0999: data.close();
1000: } else {
1001: response.sendError(HttpServletResponse.SC_NOT_FOUND);
1002: }
1003:
1004: } catch (java.io.IOException e) {
1005: Log.info("Resource transfer failed: "
1006: + request.getRequestURI() + ". (" + e.getMessage()
1007: + ")");
1008: }
1009:
1010: return true;
1011: }
1012:
1013: /** Get the variable map for the session */
1014: private static synchronized HttpVariableMap getVariableMap(
1015: Application application, HttpServletRequest request) {
1016:
1017: HttpSession session = request.getSession();
1018:
1019: // Get the application to variablemap map
1020: Map varMapMap = (Map) session.getAttribute(SESSION_ATTR_VARMAP);
1021: if (varMapMap == null) {
1022: varMapMap = new WeakHashMap();
1023: session.setAttribute(SESSION_ATTR_VARMAP, varMapMap);
1024: }
1025:
1026: // Create a variable map, if it does not exists.
1027: HttpVariableMap variableMap = (HttpVariableMap) varMapMap
1028: .get(application);
1029: if (variableMap == null) {
1030: variableMap = new HttpVariableMap();
1031: varMapMap.put(application, variableMap);
1032: }
1033:
1034: return variableMap;
1035: }
1036:
1037: /** Get the current application URL from request */
1038: private URL getApplicationUrl(HttpServletRequest request)
1039: throws MalformedURLException {
1040:
1041: URL applicationUrl;
1042: try {
1043: URL reqURL = new URL((request.isSecure() ? "https://"
1044: : "http://")
1045: + request.getServerName()
1046: + ":"
1047: + request.getServerPort() + request.getRequestURI());
1048: String servletPath = request.getContextPath()
1049: + request.getServletPath();
1050: if (servletPath.length() == 0
1051: || servletPath.charAt(servletPath.length() - 1) != '/')
1052: servletPath = servletPath + "/";
1053: applicationUrl = new URL(reqURL, servletPath);
1054: } catch (MalformedURLException e) {
1055: Log.error("Error constructing application url "
1056: + request.getRequestURI() + " (" + e + ")");
1057: throw e;
1058: }
1059:
1060: return applicationUrl;
1061: }
1062:
1063: /**
1064: * Get the existing application for given request. Looks for application
1065: * instance for given request based on the requested URL.
1066: *
1067: * @param request
1068: * HTTP request
1069: * @return Application instance, or null if the URL does not map to valid
1070: * application.
1071: */
1072: private Application getApplication(HttpServletRequest request)
1073: throws MalformedURLException {
1074:
1075: // Ensure that the session is still valid
1076: HttpSession session = request.getSession(false);
1077: if (session == null)
1078: return null;
1079:
1080: // Get application list for the session.
1081: LinkedList applications = (LinkedList) session
1082: .getAttribute(SESSION_ATTR_APPS);
1083: if (applications == null)
1084: return null;
1085:
1086: // Search for the application (using the application URI) from the list
1087: Application application = null;
1088: for (Iterator i = applications.iterator(); i.hasNext()
1089: && application == null;) {
1090: Application a = (Application) i.next();
1091: String aPath = a.getURL().getPath();
1092: String servletPath = request.getContextPath()
1093: + request.getServletPath();
1094: if (servletPath.length() < aPath.length())
1095: servletPath += "/";
1096: if (servletPath.equals(aPath))
1097: application = a;
1098: }
1099:
1100: // Remove stopped applications from the list
1101: if (application != null && !application.isRunning()) {
1102: applications.remove(application);
1103: application = null;
1104: }
1105:
1106: return application;
1107: }
1108:
1109: /**
1110: * Create a new application.
1111: *
1112: * @return New application instance
1113: */
1114: private Application createApplication(HttpServletRequest request)
1115: throws MalformedURLException, InstantiationException,
1116: IllegalAccessException {
1117:
1118: Application application = null;
1119:
1120: // Get the application url
1121: URL applicationUrl = getApplicationUrl(request);
1122:
1123: // Get application list.
1124: HttpSession session = request.getSession();
1125: if (session == null)
1126: return null;
1127: LinkedList applications = (LinkedList) session
1128: .getAttribute(SESSION_ATTR_APPS);
1129: if (applications == null) {
1130: applications = new LinkedList();
1131: session.setAttribute(SESSION_ATTR_APPS, applications);
1132: HttpSessionBindingListener sessionBindingListener = new SessionBindingListener(
1133: applications);
1134: session.setAttribute(SESSION_BINDING_LISTENER,
1135: sessionBindingListener);
1136: }
1137:
1138: // Create new application and start it
1139: try {
1140: application = (Application) this .applicationClass
1141: .newInstance();
1142: applications.add(application);
1143: application
1144: .addListener((Application.WindowAttachListener) this );
1145: application
1146: .addListener((Application.WindowDetachListener) this );
1147: application.setLocale(request.getLocale());
1148:
1149: // Get application context for this session
1150: WebApplicationContext context = (WebApplicationContext) session
1151: .getAttribute(SESSION_ATTR_CONTEXT);
1152: if (context == null) {
1153: context = new WebApplicationContext(session);
1154: session.setAttribute(SESSION_ATTR_CONTEXT, context);
1155: }
1156:
1157: application.start(applicationUrl,
1158: this .applicationProperties, context);
1159:
1160: } catch (IllegalAccessException e) {
1161: Log.error("Illegal access to application class "
1162: + this .applicationClass.getName());
1163: throw e;
1164: } catch (InstantiationException e) {
1165: Log.error("Failed to instantiate application class: "
1166: + this .applicationClass.getName());
1167: throw e;
1168: }
1169:
1170: return application;
1171: }
1172:
1173: /** End application */
1174: private void endApplication(HttpServletRequest request,
1175: HttpServletResponse response, Application application)
1176: throws IOException {
1177:
1178: String logoutUrl = application.getLogoutURL();
1179: if (logoutUrl == null)
1180: logoutUrl = application.getURL().toString();
1181:
1182: HttpSession session = request.getSession();
1183: if (session != null) {
1184: LinkedList applications = (LinkedList) session
1185: .getAttribute(SESSION_ATTR_APPS);
1186: if (applications != null)
1187: applications.remove(application);
1188: }
1189:
1190: response.sendRedirect(response.encodeRedirectURL(logoutUrl));
1191: }
1192:
1193: /**
1194: * Get the existing application or create a new one. Get a window within an
1195: * application based on the requested URI.
1196: *
1197: * @param request
1198: * HTTP Request.
1199: * @param application
1200: * Application to query for window.
1201: * @return Window mathing the given URI or null if not found.
1202: */
1203: private Window getApplicationWindow(HttpServletRequest request,
1204: Application application) throws ServletException {
1205:
1206: Window window = null;
1207:
1208: // Find the window where the request is handled
1209: String path = request.getPathInfo();
1210:
1211: // Main window as the URI is empty
1212: if (path == null || path.length() == 0 || path.equals("/"))
1213: window = application.getMainWindow();
1214:
1215: // Try to search by window name
1216: else {
1217: String windowName = null;
1218: if (path.charAt(0) == '/')
1219: path = path.substring(1);
1220: int index = path.indexOf('/');
1221: if (index < 0) {
1222: windowName = path;
1223: path = "";
1224: } else {
1225: windowName = path.substring(0, index);
1226: path = path.substring(index + 1);
1227: }
1228: window = application.getWindow(windowName);
1229:
1230: if (window == null) {
1231:
1232: // If the window has existed, and is now removed
1233: // send a blank page
1234: if (allWindows.contains(windowName))
1235: return null;
1236:
1237: // By default, we use main window
1238: window = application.getMainWindow();
1239: } else if (!window.isVisible()) {
1240:
1241: // Implicitly painting without actually invoking paint()
1242: window.requestRepaintRequests();
1243:
1244: // If the window is invisible send a blank page
1245: return null;
1246: }
1247: }
1248:
1249: // Create and open new debug window for application if requested
1250: if (this .debugMode
1251: && application.getWindow(DebugWindow.WINDOW_NAME) == null)
1252: try {
1253: DebugWindow debugWindow = new DebugWindow(application,
1254: request.getSession(false), this );
1255: debugWindow.setWidth(370);
1256: debugWindow.setHeight(480);
1257: application.addWindow(debugWindow);
1258: } catch (Exception e) {
1259: throw new ServletException(
1260: "Failed to create debug window for application",
1261: e);
1262: }
1263:
1264: return window;
1265: }
1266:
1267: /**
1268: * Get relative location of a theme resource.
1269: *
1270: * @param theme
1271: * Theme name
1272: * @param resource
1273: * Theme resource
1274: * @return External URI specifying the resource
1275: */
1276: public String getResourceLocation(String theme,
1277: ThemeResource resource) {
1278:
1279: if (resourcePath == null)
1280: return resource.getResourceId();
1281: return resourcePath + theme + "/" + resource.getResourceId();
1282: }
1283:
1284: /**
1285: * Check if web adapter is in debug mode. Extra output is generated to log
1286: * when debug mode is enabled.
1287: *
1288: * @return Debug mode
1289: */
1290: public boolean isDebugMode() {
1291: return debugMode;
1292: }
1293:
1294: /**
1295: * Returns the theme source.
1296: *
1297: * @return ThemeSource
1298: */
1299: public ThemeSource getThemeSource() {
1300: return themeSource;
1301: }
1302:
1303: protected void addDirtyWindow(Application application, Window window) {
1304: synchronized (applicationToDirtyWindowSetMap) {
1305: HashSet dirtyWindows = (HashSet) applicationToDirtyWindowSetMap
1306: .get(application);
1307: if (dirtyWindows == null) {
1308: dirtyWindows = new HashSet();
1309: applicationToDirtyWindowSetMap.put(application,
1310: dirtyWindows);
1311: }
1312: dirtyWindows.add(window);
1313: }
1314: }
1315:
1316: protected void removeDirtyWindow(Application application,
1317: Window window) {
1318: synchronized (applicationToDirtyWindowSetMap) {
1319: HashSet dirtyWindows = (HashSet) applicationToDirtyWindowSetMap
1320: .get(application);
1321: if (dirtyWindows != null)
1322: dirtyWindows.remove(window);
1323: }
1324: }
1325:
1326: /**
1327: * @see org.millstone.base.Application.WindowAttachListener#windowAttached(Application.WindowAttachEvent)
1328: */
1329: public void windowAttached(WindowAttachEvent event) {
1330: Window win = event.getWindow();
1331: win.addListener((Paintable.RepaintRequestListener) this );
1332:
1333: // Add to window names
1334: allWindows.add(win.getName());
1335:
1336: // Add window to dirty window references if it is visible
1337: // Or request the window to pass on the repaint requests
1338: if (win.isVisible())
1339: addDirtyWindow(event.getApplication(), win);
1340: else
1341: win.requestRepaintRequests();
1342:
1343: }
1344:
1345: /**
1346: * @see org.millstone.base.Application.WindowDetachListener#windowDetached(Application.WindowDetachEvent)
1347: */
1348: public void windowDetached(WindowDetachEvent event) {
1349: event.getWindow().removeListener(
1350: (Paintable.RepaintRequestListener) this );
1351:
1352: // Add dirty window reference for closing the window
1353: addDirtyWindow(event.getApplication(), event.getWindow());
1354: }
1355:
1356: /**
1357: * @see org.millstone.base.terminal.Paintable.RepaintRequestListener#repaintRequested(Paintable.RepaintRequestEvent)
1358: */
1359: public void repaintRequested(RepaintRequestEvent event) {
1360:
1361: Paintable p = event.getPaintable();
1362: Application app = null;
1363: if (p instanceof Window)
1364: app = ((Window) p).getApplication();
1365:
1366: if (app != null)
1367: addDirtyWindow(app, ((Window) p));
1368:
1369: Object lock = applicationToServerCommandStreamLock.get(app);
1370: if (lock != null)
1371: synchronized (lock) {
1372: lock.notifyAll();
1373: }
1374: }
1375:
1376: /** Get the list of dirty windows in application */
1377: protected Set getDirtyWindows(Application app) {
1378: HashSet dirtyWindows;
1379: synchronized (applicationToDirtyWindowSetMap) {
1380: dirtyWindows = (HashSet) applicationToDirtyWindowSetMap
1381: .get(app);
1382: }
1383: return dirtyWindows;
1384: }
1385:
1386: /** Remove a window from the list of dirty windows */
1387: private void windowPainted(Application app, Window window) {
1388: removeDirtyWindow(app, window);
1389: }
1390:
1391: /**
1392: * Generate server commands stream. If the server commands are not
1393: * requested, return false
1394: */
1395: private boolean handleServerCommands(HttpServletRequest request,
1396: HttpServletResponse response) {
1397:
1398: // Server commands are allways requested with certain parameter
1399: if (request.getParameter(SERVER_COMMAND_PARAM) == null)
1400: return false;
1401:
1402: // Get the application
1403: Application application;
1404: try {
1405: application = getApplication(request);
1406: } catch (MalformedURLException e) {
1407: return false;
1408: }
1409: if (application == null)
1410: return false;
1411:
1412: // Create continuous server commands stream
1413: try {
1414:
1415: // Writer for writing the stream
1416: PrintWriter w = new PrintWriter(response.getOutputStream());
1417:
1418: // Print necessary http page headers and padding
1419: w.println("<html><head></head><body>");
1420: for (int i = 0; i < SERVER_COMMAND_HEADER_PADDING; i++)
1421: w.print(' ');
1422:
1423: // Clock for synchronizing the stream
1424: Object lock = new Object();
1425: synchronized (applicationToServerCommandStreamLock) {
1426: Object oldlock = applicationToServerCommandStreamLock
1427: .get(application);
1428: if (oldlock != null)
1429: synchronized (oldlock) {
1430: oldlock.notifyAll();
1431: }
1432: applicationToServerCommandStreamLock.put(application,
1433: lock);
1434: }
1435: while (applicationToServerCommandStreamLock
1436: .get(application) == lock
1437: && application.isRunning()) {
1438: synchronized (application) {
1439:
1440: // Session expiration
1441: Date lastRequest = (Date) applicationToLastRequestDate
1442: .get(application);
1443: if (lastRequest != null
1444: && lastRequest.getTime()
1445: + request.getSession()
1446: .getMaxInactiveInterval()
1447: * 1000 < System.currentTimeMillis()) {
1448:
1449: // Session expired, close application
1450: application.close();
1451: } else {
1452:
1453: // Application still alive - keep updating windows
1454: Set dws = getDirtyWindows(application);
1455: if (dws != null && !dws.isEmpty()) {
1456:
1457: // For one of the dirty windows (in each
1458: // application)
1459: // request redraw
1460: Window win = (Window) dws.iterator().next();
1461: w
1462: .println("<script>\n"
1463: + ThemeFunctionLibrary
1464: .getWindowRefreshScript(
1465: application,
1466: win,
1467: WebBrowserProbe
1468: .getTerminalType(request
1469: .getSession()))
1470: + "</script>");
1471:
1472: removeDirtyWindow(application, win);
1473:
1474: // Windows that are closed immediately are "painted"
1475: // now
1476: if (win.getApplication() == null
1477: || !win.isVisible())
1478: win.requestRepaintRequests();
1479: }
1480: }
1481: }
1482:
1483: // Send the generated commands and newline immediately to
1484: // browser
1485: w.println(" ");
1486: w.flush();
1487: response.flushBuffer();
1488:
1489: synchronized (lock) {
1490: try {
1491: lock
1492: .wait(SERVER_COMMAND_STREAM_MAINTAIN_PERIOD);
1493: } catch (InterruptedException ignored) {
1494: }
1495: }
1496: }
1497: } catch (IOException ignore) {
1498:
1499: // In case of an Exceptions the server command stream is
1500: // terminated
1501: synchronized (applicationToServerCommandStreamLock) {
1502: if (applicationToServerCommandStreamLock
1503: .get(application) == application)
1504: applicationToServerCommandStreamLock
1505: .remove(application);
1506: }
1507: }
1508:
1509: return true;
1510: }
1511:
1512: private class SessionBindingListener implements
1513: HttpSessionBindingListener {
1514: private LinkedList applications;
1515:
1516: protected SessionBindingListener(LinkedList applications) {
1517: this .applications = applications;
1518: }
1519:
1520: /**
1521: * @see javax.servlet.http.HttpSessionBindingListener#valueBound(HttpSessionBindingEvent)
1522: */
1523: public void valueBound(HttpSessionBindingEvent arg0) {
1524: // We are not interested in bindings
1525: }
1526:
1527: /**
1528: * @see javax.servlet.http.HttpSessionBindingListener#valueUnbound(HttpSessionBindingEvent)
1529: */
1530: public void valueUnbound(HttpSessionBindingEvent event) {
1531:
1532: // If the binding listener is unbound from the session, the
1533: // session must be closing
1534: if (event.getName().equals(SESSION_BINDING_LISTENER)) {
1535:
1536: // Close all applications
1537: Object[] apps = applications.toArray();
1538: for (int i = 0; i < apps.length; i++) {
1539: if (apps[i] != null) {
1540:
1541: // Close app
1542: ((Application) apps[i]).close();
1543:
1544: // Stop application server commands stream
1545: Object lock = applicationToServerCommandStreamLock
1546: .get(apps[i]);
1547: if (lock != null)
1548: synchronized (lock) {
1549: lock.notifyAll();
1550: }
1551: applicationToServerCommandStreamLock
1552: .remove(apps[i]);
1553:
1554: // Remove application from applications list
1555: applications.remove(apps[i]);
1556: }
1557: }
1558: }
1559: }
1560:
1561: }
1562:
1563: /** Implementation of ParameterHandler.ErrorEvent interface. */
1564: public class ParameterHandlerErrorImpl implements
1565: ParameterHandler.ErrorEvent {
1566:
1567: private ParameterHandler owner;
1568: private Throwable throwable;
1569:
1570: private ParameterHandlerErrorImpl(ParameterHandler owner,
1571: Throwable throwable) {
1572: this .owner = owner;
1573: this .throwable = throwable;
1574: }
1575:
1576: /**
1577: * @see org.millstone.base.terminal.Terminal.ErrorEvent#getThrowable()
1578: */
1579: public Throwable getThrowable() {
1580: return this .throwable;
1581: }
1582:
1583: /**
1584: * @see org.millstone.base.terminal.ParameterHandler.ErrorEvent#getParameterHandler()
1585: */
1586: public ParameterHandler getParameterHandler() {
1587: return this .owner;
1588: }
1589:
1590: }
1591:
1592: /** Implementation of URIHandler.ErrorEvent interface. */
1593: public class URIHandlerErrorImpl implements URIHandler.ErrorEvent {
1594:
1595: private URIHandler owner;
1596: private Throwable throwable;
1597:
1598: private URIHandlerErrorImpl(URIHandler owner,
1599: Throwable throwable) {
1600: this .owner = owner;
1601: this .throwable = throwable;
1602: }
1603:
1604: /**
1605: * @see org.millstone.base.terminal.Terminal.ErrorEvent#getThrowable()
1606: */
1607: public Throwable getThrowable() {
1608: return this .throwable;
1609: }
1610:
1611: /**
1612: * @see org.millstone.base.terminal.URIHandler.ErrorEvent#getURIHandler()
1613: */
1614: public URIHandler getURIHandler() {
1615: return this.owner;
1616: }
1617: }
1618: }
|