]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implements SystemMessages, that can be customized. Replaces Application.get/setSessio...
authorMarc Englund <marc.englund@itmill.com>
Thu, 8 May 2008 13:14:34 +0000 (13:14 +0000)
committerMarc Englund <marc.englund@itmill.com>
Thu, 8 May 2008 13:14:34 +0000 (13:14 +0000)
Marked as Experimental API for the time being.

svn changeset:4391/svn branch:trunk

src/com/itmill/toolkit/Application.java
src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java
src/com/itmill/toolkit/terminal/gwt/client/ui/Notification.java
src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java
src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java

index 6a7758f8802ce1c1505591e2760ee4ed2d35b61c..46a67df6df785eaefbc273e203f676ab8438cec0 100644 (file)
@@ -169,11 +169,11 @@ public abstract class Application implements URIHandler, Terminal.ErrorListener
     private String logoutURL = null;
 
     /**
-     * URL where the user is redirected to when the Toolkit ApplicationServlet
-     * session expires, or null if the application is just closed without
-     * redirection.
+     * Experimental API, not finalized. The default SystemMessages (read-only).
+     * Change by overriding getSystemMessages() and returning
+     * CustomizedSystemMessages
      */
-    private String expiredURL = null;
+    private static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages();
 
     private Focusable pendingFocus;
 
@@ -1032,28 +1032,18 @@ public abstract class Application implements URIHandler, Terminal.ErrorListener
     }
 
     /**
-     * Returns the URL where user is redirected to when the Toolkit
-     * ApplicationServlet session expires. If the URL is <code>null</code>,
-     * the application is closed normally and it shows a notification to the
-     * client.
+     * Experimental API, not finalized. Gets the SystemMessages for this
+     * application. SystemMessages are used to notify the user of various
+     * critical situations that can occur, such as session expiration,
+     * client/server out of sync, and internal server error.
      * 
-     * @return the URL.
-     */
-    public String getSessionExpiredURL() {
-        return expiredURL;
-    }
-
-    /**
-     * Sets the URL where user is redirected to when the Toolkit
-     * ApplicationServlet session expires. If the URL is <code>null</code>,
-     * the application is closed normally and it shows a notification to the
-     * client.
+     * You can customize the messages by overriding this method and returning
+     * CustomizedSystemMessages.
      * 
-     * @param expiredURL
-     *                the expiredURL to set.
+     * @return the SystemMessages for this application
      */
-    public void setSessionExpiredURL(String expiredURL) {
-        this.expiredURL = expiredURL;
+    public static SystemMessages getSystemMessages() {
+        return DEFAULT_SYSTEM_MESSAGES;
     }
 
     /**
@@ -1145,4 +1135,113 @@ public abstract class Application implements URIHandler, Terminal.ErrorListener
         return "NONVERSIONED";
     }
 
+    /**
+     * Experimental API, not finalized. Contains the system messages used to
+     * notify the user about various critical situations that can occur.
+     * 
+     * Customize by overriding the static Application.getSystemMessages() and
+     * return CustomizedSystemMessages.
+     */
+    public static class SystemMessages {
+        protected String sessionExpiredURL = null;
+        protected String sessionExpiredCaption = "Session Expired";
+        protected String sessionExpiredMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
+
+        protected String internalErrorURL = null;
+        protected String internalErrorCaption = "Internal Error";
+        protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> to continue.";
+
+        protected String outOfSyncURL = null;
+        protected String outOfSyncCaption = "Out of sync";
+        protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.<br/>Take note of any unsaved data, and <u>click here</u> to re-sync.";
+
+        private SystemMessages() {
+
+        }
+
+        public String getSessionExpiredURL() {
+            return sessionExpiredURL;
+        }
+
+        public String getSessionExpiredCaption() {
+            return sessionExpiredCaption;
+        }
+
+        public String getSessionExpiredMessage() {
+            return sessionExpiredMessage;
+        }
+
+        public String getInternalErrorURL() {
+            return internalErrorURL;
+        }
+
+        public String getInternalErrorCaption() {
+            return internalErrorCaption;
+        }
+
+        public String getInternalErrorMessage() {
+            return internalErrorMessage;
+        }
+
+        public String getOutOfSyncURL() {
+            return outOfSyncURL;
+        }
+
+        public String getOutOfSyncCaption() {
+            return outOfSyncCaption;
+        }
+
+        public String getOutOfSyncMessage() {
+            return outOfSyncMessage;
+        }
+
+    }
+
+    /**
+     * Experimental API, not finalized. Contains the system messages used to
+     * notify the user about various critical situations that can occur.
+     * 
+     * Customize by overriding the static Application.getSystemMessages() and
+     * return CustomizedSystemMessages.
+     */
+    public static class CustomizedSystemMessages extends SystemMessages {
+
+        public void setSessionExpiredURL(String sessionExpiredURL) {
+            this.sessionExpiredURL = sessionExpiredURL;
+        }
+
+        public void setSessionExpiredCaption(String sessionExpiredCaption) {
+            this.sessionExpiredCaption = sessionExpiredCaption;
+        }
+
+        public void setSessionExpiredMessage(String sessionExpiredMessage) {
+            this.sessionExpiredMessage = sessionExpiredMessage;
+        }
+
+        public void setInternalErrorURL(String internalErrorURL) {
+            this.internalErrorURL = internalErrorURL;
+        }
+
+        public void setInternalErrorCaption(String internalErrorCaption) {
+            this.internalErrorCaption = internalErrorCaption;
+        }
+
+        public void setInternalErrorMessage(String internalErrorMessage) {
+            this.internalErrorMessage = internalErrorMessage;
+        }
+
+        public void setOutOfSyncURL(String outOfSyncURL) {
+            this.outOfSyncURL = outOfSyncURL;
+        }
+
+        public void setOutOfSyncCaption(String outOfSyncCaption) {
+            this.outOfSyncCaption = outOfSyncCaption;
+        }
+
+        public void setOutOfSyncMessage(String outOfSyncMessage) {
+            this.outOfSyncMessage = outOfSyncMessage;
+        }
+
+    }
+
 }
\ No newline at end of file
index 32c48bdb185123aee1902640869d8420329657ce..e794da776ffc6b4246e2bd2a088f68a9c4e87599 100755 (executable)
@@ -34,6 +34,7 @@ import com.google.gwt.user.client.ui.Widget;
 import com.itmill.toolkit.terminal.gwt.client.ui.ContextMenu;
 import com.itmill.toolkit.terminal.gwt.client.ui.IView;
 import com.itmill.toolkit.terminal.gwt.client.ui.Notification;
+import com.itmill.toolkit.terminal.gwt.client.ui.Notification.HideEvent;
 
 /**
  * Entry point classes define <code>onModuleLoad()</code>.
@@ -421,11 +422,28 @@ public class ApplicationConnection {
             }
             if (meta.containsKey("appError")) {
                 JSONObject error = meta.get("appError").isObject();
-                String caption = error.get("caption").isString().stringValue();
-                String message = error.get("message").isString().stringValue();
-                String html = "<h1>" + caption + "</h1><p>" + message + "</p>";
-                new Notification(Notification.DELAY_FOREVER).show(html,
-                        Notification.CENTERED, "error");
+                JSONValue val = error.get("caption");
+                String html = "";
+                if (val.isString() != null) {
+                    html += "<h1>" + val.isString().stringValue() + "</h1>";
+                }
+                val = error.get("message");
+                if (val.isString() != null) {
+                    html += "<p>" + val.isString().stringValue() + "</p>";
+                }
+                val = error.get("url");
+                String url = null;
+                if (val.isString() != null) {
+                    url = val.isString().stringValue();
+                }
+
+                if (html.length() != 0) {
+                    Notification n = new Notification(1000 * 60 * 45); // 45min
+                    n.addEventListener(new NotificationRedirect(url));
+                    n.show(html, Notification.CENTERED_TOP, "system");
+                } else {
+                    redirect(url);
+                }
                 applicationRunning = false;
             }
         }
@@ -438,10 +456,14 @@ public class ApplicationConnection {
         endRequest();
     }
 
-    // Redirect browser
+    // Redirect browser, null reloads current page
     private static native void redirect(String url)
     /*-{
-     $wnd.location = url;
+      if (url) {
+         $wnd.location = url;
+      } else {
+          $wnd.location = $wnd.location;
+      }
      }-*/;
 
     public void registerPaintable(String id, Paintable paintable) {
@@ -763,4 +785,22 @@ public class ApplicationConnection {
     public String getTheme() {
         return view.getTheme();
     }
+
+    /**
+     * Listens for Notification hide event, and redirects. Used for system
+     * messages, such as session expired.
+     * 
+     */
+    private class NotificationRedirect implements Notification.EventListener {
+        String url;
+
+        NotificationRedirect(String url) {
+            this.url = url;
+        }
+
+        public void notificationHidden(HideEvent event) {
+            redirect(url);
+        }
+
+    }
 }
index 8a703a7290a341917145f0b8d286b216250500ec..c66f8a635c9c36c74d154cba79ea489870e744d1 100644 (file)
@@ -4,6 +4,10 @@
 \r
 package com.itmill.toolkit.terminal.gwt.client.ui;\r
 \r
+import java.util.ArrayList;\r
+import java.util.EventObject;\r
+import java.util.Iterator;\r
+\r
 import com.google.gwt.user.client.DOM;\r
 import com.google.gwt.user.client.Element;\r
 import com.google.gwt.user.client.Event;\r
@@ -40,6 +44,8 @@ public class Notification extends ToolkitOverlay {
 \r
     private String temporaryStyle;\r
 \r
+    private ArrayList listeners;\r
+\r
     public Notification() {\r
         setStylePrimaryName(STYLENAME);\r
         sinkEvents(Event.ONCLICK);\r
@@ -94,7 +100,6 @@ public class Notification extends ToolkitOverlay {
     }\r
 \r
     public void show(int position, String style) {\r
-        hide();\r
         setOpacity(getElement(), startOpacity);\r
         if (style != null) {\r
             temporaryStyle = style;\r
@@ -113,6 +118,7 @@ public class Notification extends ToolkitOverlay {
             temporaryStyle = null;\r
         }\r
         super.hide();\r
+        fireEvent(new HideEvent(this));\r
     }\r
 \r
     public void fade() {\r
@@ -235,4 +241,36 @@ public class Notification extends ToolkitOverlay {
         return true;\r
     }\r
 \r
+    public void addEventListener(EventListener listener) {\r
+        if (listeners == null) {\r
+            listeners = new ArrayList();\r
+        }\r
+        listeners.add(listener);\r
+    }\r
+\r
+    public void removeEventListener(EventListener listener) {\r
+        if (listeners == null) {\r
+            return;\r
+        }\r
+        listeners.remove(listener);\r
+    }\r
+\r
+    private void fireEvent(HideEvent event) {\r
+        if (listeners != null) {\r
+            for (Iterator it = listeners.iterator(); it.hasNext();) {\r
+                EventListener l = (EventListener) it.next();\r
+                l.notificationHidden(event);\r
+            }\r
+        }\r
+    }\r
+\r
+    public class HideEvent extends EventObject {\r
+        public HideEvent(Object source) {\r
+            super(source);\r
+        }\r
+    }\r
+\r
+    public interface EventListener extends java.util.EventListener {\r
+        public void notificationHidden(HideEvent event);\r
+    }\r
 }\r
index d0ba830307eaf3e27e0e3bd1c512381a351afcfa..0ac7a3f2068b9d665a0447e4acec903020c97afd 100644 (file)
@@ -12,6 +12,7 @@ import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Collection;
@@ -500,39 +501,47 @@ public class ApplicationServlet extends HttpServlet {
             }
 
         } catch (final SessionExpired e) {
-            // Session has expired
-            // Get new application so we can fetch the sessionExpiredURL
-            Application app = null;
+            // Session has expired, notify user
+            Application.SystemMessages ci = Application.getSystemMessages();
             try {
-                app = (Application) applicationClass.newInstance();
-                app.init();
-            } catch (InstantiationException e1) {
-                // Should not happen
-                e1.printStackTrace();
-            } catch (IllegalAccessException e1) {
-                // Should not happen
-                e1.printStackTrace();
+                Method m = applicationClass
+                        .getMethod("getSystemMessages", null);
+                ci = (Application.CustomizedSystemMessages) m
+                        .invoke(null, null);
+            } catch (Exception e2) {
+                // Not critical, but something is still wrong; print stacktrace
+                e2.printStackTrace();
             }
 
-            // Redirect if expiredURL is found
-            if (app != null && app.getSessionExpiredURL() != null) {
-                if (UIDLrequest) {
-                    redirectUidlRequest(request, response, app
-                            .getSessionExpiredURL());
-                } else {
-                    response.sendRedirect(app.getSessionExpiredURL());
-                }
+            if (!UIDLrequest) {
+                // 'plain' http req - e.g. browser reload;
+                // just go ahead redirect the browser
+                response.sendRedirect(ci.getSessionExpiredURL());
             } else {
-                // Else show a notification to the client
-                criticalNotification(request, response,
-                        "Your session has expired.");
+                // send uidl redirect
+                criticalNotification(request, response, ci
+                        .getSessionExpiredCaption(), ci
+                        .getSessionExpiredMessage(), ci.getSessionExpiredURL());
             }
+
         } catch (final Throwable e) {
             e.printStackTrace();
             // if this was an UIDL request, response UIDL back to client
             if (UIDLrequest) {
-                criticalNotification(request, response,
-                        "Internal error. Please notify administrator.");
+                Application.SystemMessages ci = Application.getSystemMessages();
+                try {
+                    Method m = applicationClass.getMethod("getSystemMessages",
+                            null);
+                    ci = (Application.CustomizedSystemMessages) m.invoke(null,
+                            null);
+                } catch (Exception e2) {
+                    // Not critical, but something is still wrong; print
+                    // stacktrace
+                    e2.printStackTrace();
+                }
+                criticalNotification(request, response, ci
+                        .getInternalErrorCaption(), ci
+                        .getInternalErrorMessage(), ci.getInternalErrorURL());
             } else {
                 // Re-throw other exceptions
                 throw new ServletException(e);
@@ -590,31 +599,6 @@ public class ApplicationServlet extends HttpServlet {
         }
     }
 
-    /**
-     * Redirect an UIDL request to move to a new URL.
-     * 
-     * @param request
-     *                the HTTP request instance.
-     * @param response
-     *                the HTTP response to write to.
-     * @param url
-     *                the URL to redirect to.
-     * @throws IOException
-     *                 if the writing failed due to input/output error.
-     */
-    private void redirectUidlRequest(HttpServletRequest request,
-            HttpServletResponse response, String url) throws IOException {
-        // Set the response type
-        response.setContentType("application/json; charset=UTF-8");
-        final ServletOutputStream out = response.getOutputStream();
-        final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
-                new OutputStreamWriter(out, "UTF-8")));
-        outWriter.print("for(;;);[{\"redirect\":{\"url\":\"" + url + "\"}}]");
-        outWriter.flush();
-        outWriter.close();
-        out.flush();
-    }
-
     /**
      * Send notification to client's application. Used to notify client of
      * critical errors and session expiration due to long inactivity. Server has
@@ -625,19 +609,32 @@ public class ApplicationServlet extends HttpServlet {
      * @param response
      *                the HTTP response to write to.
      * @param caption
-     *                for the notification message
+     *                for the notification
+     * @param message
+     *                for the notification
+     * @param url
+     *                url to load after message, null for current page
      * @throws IOException
      *                 if the writing failed due to input/output error.
      */
-    private void criticalNotification(HttpServletRequest request,
-            HttpServletResponse response, String caption) throws IOException {
+    void criticalNotification(HttpServletRequest request,
+            HttpServletResponse response, String caption, String message,
+            String url) throws IOException {
 
         // clients JS app is still running, but server application either
         // no longer exists or it might fail to perform reasonably.
         // send a notification to client's application and link how
         // to "restart" application.
 
-        // TODO message should be localized
+        if (caption != null) {
+            caption = "\"" + caption + "\"";
+        }
+        if (message != null) {
+            message = "\"" + message + "\"";
+        }
+        if (url != null) {
+            url = "\"" + url + "\"";
+        }
 
         // Set the response type
         response.setContentType("application/json; charset=UTF-8");
@@ -645,9 +642,8 @@ public class ApplicationServlet extends HttpServlet {
         final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
                 new OutputStreamWriter(out, "UTF-8")));
         outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {"
-                + "\"appError\": {" + "\"caption\":\"" + caption + "\","
-                + "\"message\" : \"<br />You can click your browser's"
-                + " refresh button to restart your application.\""
+                + "\"appError\": {" + "\"caption\":" + caption + ","
+                + "\"message\" : " + message + "," + "\"url\" : " + url
                 + "}}, \"resources\": {}, \"locales\":[]}]");
         outWriter.flush();
         outWriter.close();
index 1fb98fa54963530d06e08329671df2734bb9d781..2e23e21853217c266b24c4aea701a04686193565 100644 (file)
@@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import com.itmill.toolkit.Application;
+import com.itmill.toolkit.Application.SystemMessages;
 import com.itmill.toolkit.external.org.apache.commons.fileupload.FileItemIterator;
 import com.itmill.toolkit.external.org.apache.commons.fileupload.FileItemStream;
 import com.itmill.toolkit.external.org.apache.commons.fileupload.FileUploadException;
@@ -241,7 +242,15 @@ public class CommunicationManager implements Paintable.RepaintRequestListener {
                 }
 
                 // Change all variables based on request parameters
-                handleVariables(request, application);
+                if (!handleVariables(request, application)) {
+                    // var inconsistency; the client is probably out-of-sync
+                    SystemMessages ci = application.getSystemMessages();
+                    applicationServlet.criticalNotification(request, response,
+                            ci.getOutOfSyncCaption(), ci.getOutOfSyncMessage(),
+                            ci.getOutOfSyncURL());
+                    // need to do a repaint all after this
+                    return;
+                }
 
                 // Removes application if it has stopped during variable changes
                 if (!application.isRunning()) {
@@ -462,9 +471,18 @@ public class CommunicationManager implements Paintable.RepaintRequestListener {
         }
     }
 
-    private Map handleVariables(HttpServletRequest request,
+    /**
+     * If this method returns false, something was submitted that we did not
+     * expect; this is probably due to the client being out-of-sync and sending
+     * variable changes for non-existing pids
+     * 
+     * @param request
+     * @param application2
+     * @return true if successful, false if there was an inconsistency
+     */
+    private boolean handleVariables(HttpServletRequest request,
             Application application2) {
-
+        boolean success = true;
         final Map params = new HashMap(request.getParameterMap());
         final String changes = (String) ((params.get("changes") instanceof String[]) ? ((String[]) params
                 .get("changes"))[0]
@@ -531,13 +549,14 @@ public class CommunicationManager implements Paintable.RepaintRequestListener {
                     } else {
                         msg += "non-existent component, VAR_PID="
                                 + variable[VAR_PID];
+                        success = false;
                     }
                     System.err.println(msg);
                     continue;
                 }
             }
         }
-        return params;
+        return success;
     }
 
     private Object convertVariableValue(char variableType, String strValue) {