* </p>
*
* <p>
- * Since version 5.0 all windows can be referenced by their names in url
+ * All windows can be referenced by their names in url
* <code>http://host:port/foo/bar/</code> where
* <code>http://host:port/foo/</code> is the application url as returned by
* getURL() and <code>bar</code> is the name of the window.
* </p>
*
* <p>
+ * If for some reason user opens another window with same url that is
+ * already open, name is modified by adding "_12345678" postfix to the name,
+ * where 12345678 is a random number. One can decide to create another
+ * window-object for those windows (recommended) or to discard the postfix.
+ * If the user has two browser windows pointing to the same window-object on
+ * server, synchronization errors are likely to occur.
+ * </p>
+ *
+ * <p>
+ * If no browser-level windowing is used, all defaults are fine and this
+ * method can be left as is. In case browser-level windows are needed, it is
+ * recommended to create new window-objects on this method from their names
+ * if the super.getWindow() does not find existing windows. See below for
+ * implementation example:
+ * <code> // If we already have the requested window, use it
+ Window w = super.getWindow(name);
+ if (w == null) {
+
+ // If no window found, create it
+ w = createNewWindow(name);
+ }
+ return w;</code>
+ * </p>
+ *
+ * <p>
* The method should return null if the window does not exists (and is not
- * created as a side-effect) or if the application is not running anymore
+ * created as a side-effect) or if the application is not running anymore.
* </p>
- * .
*
* @param name
* the name of the window.
}
// If the uri is in some window, handle the window uri
- Window window = getWindow(prefix);
- if (window != null) {
- URL windowContext;
- try {
- windowContext = new URL(context, prefix + "/");
- final String windowUri = relativeUri.length() > prefix.length() + 1 ? relativeUri
- .substring(prefix.length() + 1)
- : "";
- return window.handleURI(windowContext, windowUri);
- } catch (final MalformedURLException e) {
- terminalError(new ApplicationError(e));
- return null;
+ if (prefix != null && prefix.length() > 0) {
+ Window window = getWindow(prefix);
+ if (window != null) {
+ URL windowContext;
+ try {
+ windowContext = new URL(context, prefix + "/");
+ final String windowUri = relativeUri.length() > prefix
+ .length() + 1 ? relativeUri.substring(prefix
+ .length() + 1) : "";
+ return window.handleURI(windowContext, windowUri);
+ } catch (final MalformedURLException e) {
+ terminalError(new ApplicationError(e));
+ return null;
+ }
}
}
// If the uri was not pointing to a window, handle the
// uri in main window
- window = getMainWindow();
+ Window window = getMainWindow();
if (window != null) {
return window.handleURI(context, relativeUri);
}
public class ApplicationError implements Terminal.ErrorEvent {
- private Throwable throwable;
+ private final Throwable throwable;
public ApplicationError(Throwable throwable) {
this.throwable = throwable;
public static final String VAR_BURST_SEPARATOR = "\u001d";
+ private final HashSet currentlyOpenWindowsInClient = new HashSet();
+
private static final int MAX_BUFFER_SIZE = 64 * 1024;
private final ArrayList dirtyPaintabletSet = new ArrayList();
private final Application application;
+ // Note that this is only accessed from synchronized block and
+ // thus should be thread-safe.
+ private String closingWindowName = null;
+
private List locales;
private int pendingLocalesIndex;
// Finds the window within the application
Window window = null;
if (application.isRunning()) {
- window = getApplicationWindow(request, application);
+ window = getApplicationWindow(request, application, null);
// Returns if no window found
if (window == null) {
// This should not happen, no windows exists but
paintAfterVariablechanges(request, response, applicationServlet,
repaintAll, outWriter, window);
+
+ // Mark this window to be open on client
+ currentlyOpenWindowsInClient.add(window.getName());
+ if (closingWindowName != null) {
+ currentlyOpenWindowsInClient.remove(closingWindowName);
+ closingWindowName = null;
+ }
}
out.flush();
outWriter.print("\"changes\":[");
- // re-get mainwindow - may have been changed
- Window newWindow = getApplicationWindow(request, application);
- if (newWindow != window) {
- window = newWindow;
- repaintAll = true;
- }
+ // If the browser-window has been closed - we do not need to paint it at
+ // all
+ if (!window.getName().equals(closingWindowName)) {
- JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
- !repaintAll);
+ // re-get window - may have been changed
+ Window newWindow = getApplicationWindow(request, application,
+ window);
+ if (newWindow != window) {
+ window = newWindow;
+ repaintAll = true;
+ }
- // Paints components
- ArrayList paintables;
- if (repaintAll) {
- paintables = new ArrayList();
- paintables.add(window);
+ JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
+ !repaintAll);
- // Reset sent locales
- locales = null;
- requireLocale(application.getLocale().toString());
+ // Paints components
+ ArrayList paintables;
+ if (repaintAll) {
+ paintables = new ArrayList();
+ paintables.add(window);
- } else {
- // remove detached components from paintableIdMap so they
- // can be GC'ed
- for (Iterator it = paintableIdMap.keySet().iterator(); it.hasNext();) {
- Component p = (Component) it.next();
- if (p.getApplication() == null) {
- idPaintableMap.remove(paintableIdMap.get(p));
- it.remove();
- dirtyPaintabletSet.remove(p);
- p.removeListener(this);
+ // Reset sent locales
+ locales = null;
+ requireLocale(application.getLocale().toString());
+
+ } else {
+ // remove detached components from paintableIdMap so they
+ // can be GC'ed
+ for (Iterator it = paintableIdMap.keySet().iterator(); it
+ .hasNext();) {
+ Component p = (Component) it.next();
+ if (p.getApplication() == null) {
+ idPaintableMap.remove(paintableIdMap.get(p));
+ it.remove();
+ dirtyPaintabletSet.remove(p);
+ p.removeListener(this);
+ }
}
+ paintables = getDirtyComponents(window);
}
- paintables = getDirtyComponents(window);
- }
- if (paintables != null) {
-
- // We need to avoid painting children before parent.
- // This is ensured by ordering list by depth in component
- // tree
- Collections.sort(paintables, new Comparator() {
- public int compare(Object o1, Object o2) {
- Component c1 = (Component) o1;
- Component c2 = (Component) o2;
- int d1 = 0;
- while (c1.getParent() != null) {
- d1++;
- c1 = c1.getParent();
- }
- int d2 = 0;
- while (c2.getParent() != null) {
- d2++;
- c2 = c2.getParent();
- }
- if (d1 < d2) {
- return -1;
- }
- if (d1 > d2) {
- return 1;
+ if (paintables != null) {
+
+ // We need to avoid painting children before parent.
+ // This is ensured by ordering list by depth in component
+ // tree
+ Collections.sort(paintables, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ Component c1 = (Component) o1;
+ Component c2 = (Component) o2;
+ int d1 = 0;
+ while (c1.getParent() != null) {
+ d1++;
+ c1 = c1.getParent();
+ }
+ int d2 = 0;
+ while (c2.getParent() != null) {
+ d2++;
+ c2 = c2.getParent();
+ }
+ if (d1 < d2) {
+ return -1;
+ }
+ if (d1 > d2) {
+ return 1;
+ }
+ return 0;
}
- return 0;
- }
- });
+ });
- for (final Iterator i = paintables.iterator(); i.hasNext();) {
- final Paintable p = (Paintable) i.next();
+ for (final Iterator i = paintables.iterator(); i.hasNext();) {
+ final Paintable p = (Paintable) i.next();
- // TODO CLEAN
- if (p instanceof Window) {
- final Window w = (Window) p;
- if (w.getTerminal() == null) {
- w
- .setTerminal(application.getMainWindow()
- .getTerminal());
+ // TODO CLEAN
+ if (p instanceof Window) {
+ final Window w = (Window) p;
+ if (w.getTerminal() == null) {
+ w.setTerminal(application.getMainWindow()
+ .getTerminal());
+ }
}
+ /*
+ * This does not seem to happen in tk5, but remember this
+ * case: else if (p instanceof Component) { if (((Component)
+ * p).getParent() == null || ((Component)
+ * p).getApplication() == null) { // Component requested
+ * repaint, but is no // longer attached: skip
+ * paintablePainted(p); continue; } }
+ */
+
+ // TODO we may still get changes that have been
+ // rendered already (changes with only cached flag)
+ if (paintTarget.needsToBePainted(p)) {
+ paintTarget.startTag("change");
+ paintTarget.addAttribute("format", "uidl");
+ final String pid = getPaintableId(p);
+ paintTarget.addAttribute("pid", pid);
+
+ p.paint(paintTarget);
+
+ paintTarget.endTag("change");
+ }
+ paintablePainted(p);
}
- /*
- * This does not seem to happen in tk5, but remember this case:
- * else if (p instanceof Component) { if (((Component)
- * p).getParent() == null || ((Component) p).getApplication() ==
- * null) { // Component requested repaint, but is no // longer
- * attached: skip paintablePainted(p); continue; } }
- */
-
- // TODO we may still get changes that have been
- // rendered already (changes with only cached flag)
- if (paintTarget.needsToBePainted(p)) {
- paintTarget.startTag("change");
- paintTarget.addAttribute("format", "uidl");
- final String pid = getPaintableId(p);
- paintTarget.addAttribute("pid", pid);
-
- p.paint(paintTarget);
-
- paintTarget.endTag("change");
- }
- paintablePainted(p);
}
- }
- paintTarget.close();
- outWriter.print("]"); // close changes
+ paintTarget.close();
+ outWriter.print("]"); // close changes
- outWriter.print(", \"meta\" : {");
- boolean metaOpen = false;
+ outWriter.print(", \"meta\" : {");
+ boolean metaOpen = false;
- if (repaintAll) {
- metaOpen = true;
- outWriter.write("\"repaintAll\":true");
- }
+ if (repaintAll) {
+ metaOpen = true;
+ outWriter.write("\"repaintAll\":true");
+ }
- // add meta instruction for client to set focus if it is set
- final Paintable f = (Paintable) application.consumeFocus();
- if (f != null) {
- if (metaOpen) {
- outWriter.write(",");
+ // add meta instruction for client to set focus if it is set
+ final Paintable f = (Paintable) application.consumeFocus();
+ if (f != null) {
+ if (metaOpen) {
+ outWriter.write(",");
+ }
+ outWriter.write("\"focus\":\"" + getPaintableId(f) + "\"");
}
- outWriter.write("\"focus\":\"" + getPaintableId(f) + "\"");
- }
- outWriter.print("}, \"resources\" : {");
+ outWriter.print("}, \"resources\" : {");
- // Precache custom layouts
- String themeName = window.getTheme();
- if (request.getParameter("theme") != null) {
- themeName = request.getParameter("theme");
- }
- if (themeName == null) {
- themeName = "default";
- }
-
- // TODO We should only precache the layouts that are not
- // cached already
- int resourceIndex = 0;
- for (final Iterator i = paintTarget.getPreCachedResources().iterator(); i
- .hasNext();) {
- final String resource = (String) i.next();
- InputStream is = null;
- try {
- is = applicationServlet.getServletContext()
- .getResourceAsStream(
- "/" + ApplicationServlet.THEME_DIRECTORY_PATH
- + themeName + "/" + resource);
- } catch (final Exception e) {
- // FIXME: Handle exception
- e.printStackTrace();
+ // Precache custom layouts
+ String themeName = window.getTheme();
+ if (request.getParameter("theme") != null) {
+ themeName = request.getParameter("theme");
+ }
+ if (themeName == null) {
+ themeName = "default";
}
- if (is != null) {
-
- outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
- + resource + "\" : ");
- final StringBuffer layout = new StringBuffer();
+ // TODO We should only precache the layouts that are not
+ // cached already
+ int resourceIndex = 0;
+ for (final Iterator i = paintTarget.getPreCachedResources()
+ .iterator(); i.hasNext();) {
+ final String resource = (String) i.next();
+ InputStream is = null;
try {
- final InputStreamReader r = new InputStreamReader(is,
- "UTF-8");
- final char[] buffer = new char[20000];
- int charsRead = 0;
- while ((charsRead = r.read(buffer)) > 0) {
- layout.append(buffer, 0, charsRead);
+ is = applicationServlet
+ .getServletContext()
+ .getResourceAsStream(
+ "/"
+ + ApplicationServlet.THEME_DIRECTORY_PATH
+ + themeName + "/" + resource);
+ } catch (final Exception e) {
+ // FIXME: Handle exception
+ e.printStackTrace();
+ }
+ if (is != null) {
+
+ outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
+ + resource + "\" : ");
+ final StringBuffer layout = new StringBuffer();
+
+ try {
+ final InputStreamReader r = new InputStreamReader(is,
+ "UTF-8");
+ final char[] buffer = new char[20000];
+ int charsRead = 0;
+ while ((charsRead = r.read(buffer)) > 0) {
+ layout.append(buffer, 0, charsRead);
+ }
+ r.close();
+ } catch (final java.io.IOException e) {
+ // FIXME: Handle exception
+ System.err.println("Resource transfer failed: "
+ + request.getRequestURI() + ". ("
+ + e.getMessage() + ")");
}
- r.close();
- } catch (final java.io.IOException e) {
+ outWriter.print("\""
+ + JsonPaintTarget.escapeJSON(layout.toString())
+ + "\"");
+ } else {
// FIXME: Handle exception
- System.err.println("Resource transfer failed: "
- + request.getRequestURI() + ". (" + e.getMessage()
- + ")");
+ System.err.println("CustomLayout " + "/"
+ + ApplicationServlet.THEME_DIRECTORY_PATH
+ + themeName + "/" + resource + " not found!");
}
- outWriter.print("\""
- + JsonPaintTarget.escapeJSON(layout.toString()) + "\"");
- } else {
- // FIXME: Handle exception
- System.err.println("CustomLayout " + "/"
- + ApplicationServlet.THEME_DIRECTORY_PATH + themeName
- + "/" + resource + " not found!");
}
- }
- outWriter.print("}");
-
- printLocaleDeclarations(outWriter);
+ outWriter.print("}");
- outWriter.print("}]");
+ printLocaleDeclarations(outWriter);
+ outWriter.print("}]");
+ }
outWriter.flush();
outWriter.close();
}
}
try {
owner.changeVariables(request, m);
+
+ // Special-case of closing browser-level
+ // windows: track browser-windows currently open in
+ // client
+ if (owner instanceof Window
+ && ((Window) owner).getParent() == null) {
+ final Boolean close = (Boolean) m.get("close");
+ if (close != null && close.booleanValue()) {
+ closingWindowName = ((Window) owner)
+ .getName();
+ }
+ }
} catch (Exception e) {
handleChangeVariablesError(application2,
(Component) owner, e, m);
* the HTTP Request.
* @param application
* the Application to query for window.
+ * @param assumedWindow
+ * if the window has been already resolved once, this parameter
+ * must contain the window.
* @return Window mathing the given URI or null if not found.
* @throws ServletException
* if an exception has occurred that interferes with the
* servlet's normal operation.
*/
private Window getApplicationWindow(HttpServletRequest request,
- Application application) throws ServletException {
+ Application application, Window assumedWindow)
+ throws ServletException {
Window window = null;
- // Find the window where the request is handled
- String path = request.getPathInfo();
-
- // Remove app-runner class-name!
- if (applicationServlet.isApplicationRunnerServlet) {
- path = path
- .substring(1 + applicationServlet.applicationRunnerClassname
- .length());
+ // If the client knows which window to use, use it if possible
+ String windowClientRequestedName = request.getParameter("windowName");
+ if (assumedWindow != null
+ && application.getWindows().contains(assumedWindow)) {
+ windowClientRequestedName = assumedWindow.getName();
+ }
+ if (windowClientRequestedName != null) {
+ window = application.getWindow(windowClientRequestedName);
+ if (window != null) {
+ return window;
+ }
}
- // Remove UIDL from the path
- path = path.substring("/UIDL".length());
+ // If client does not know what window it wants
+ if (window == null) {
- // Main window as the URI is empty
- if (path == null || path.length() == 0 || path.equals("/")) {
- window = application.getMainWindow();
- } else {
- String windowName = null;
- if (path.charAt(0) == '/') {
- path = path.substring(1);
+ // Get the path from URL
+ String path = request.getPathInfo();
+ if (applicationServlet.isApplicationRunnerServlet) {
+ path = path
+ .substring(1 + applicationServlet.applicationRunnerClassname
+ .length());
}
- final int index = path.indexOf('/');
- if (index < 0) {
- windowName = path;
- path = "";
- } else {
- windowName = path.substring(0, index);
- path = path.substring(index + 1);
+ path = path.substring("/UIDL".length());
+
+ // If the path is specified, create name from it
+ if (path != null && path.length() > 0 && !path.equals("/")) {
+ String windowUrlName = null;
+ if (path.charAt(0) == '/') {
+ path = path.substring(1);
+ }
+ final int index = path.indexOf('/');
+ if (index < 0) {
+ windowUrlName = path;
+ path = "";
+ } else {
+ windowUrlName = path.substring(0, index);
+ path = path.substring(index + 1);
+ }
+
+ window = application.getWindow(windowUrlName);
}
- window = application.getWindow(windowName);
+ }
+
+ // By default, use mainwindow
+ if (window == null) {
+ window = application.getMainWindow();
+ }
+
+ // If the requested window is already open, resolve conflict
+ if (currentlyOpenWindowsInClient.contains(window.getName())) {
+ String newWindowName = window.getName();
+ while (currentlyOpenWindowsInClient.contains(newWindowName)) {
+ newWindowName = window.getName() + "_"
+ + ((int) (Math.random() * 100000000));
+ }
+
+ window = application.getWindow(newWindowName);
- // By default, we use main window
+ // If everything else fails, use main window even in case of
+ // conflicts
if (window == null) {
window = application.getMainWindow();
}