From: Marc Englund Date: Thu, 8 May 2008 13:14:34 +0000 (+0000) Subject: Implements SystemMessages, that can be customized. Replaces Application.get/setSessio... X-Git-Tag: 6.7.0.beta1~4798 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0906b17cb4e4de1113e241498667339eba4bb6e4;p=vaadin-framework.git Implements SystemMessages, that can be customized. Replaces Application.get/setSessionExpiredURL(). Fixes #1550 and #1614, and also makes internal error customizable. Marked as Experimental API for the time being. svn changeset:4391/svn branch:trunk --- diff --git a/src/com/itmill/toolkit/Application.java b/src/com/itmill/toolkit/Application.java index 6a7758f880..46a67df6df 100644 --- a/src/com/itmill/toolkit/Application.java +++ b/src/com/itmill/toolkit/Application.java @@ -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 null, - * 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 null, - * 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 click here to continue."; + + protected String internalErrorURL = null; + protected String internalErrorCaption = "Internal Error"; + protected String internalErrorMessage = "Please notify the administrator.
Take note of any unsaved data, and click here 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.
Take note of any unsaved data, and click here 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 diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java b/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java index 32c48bdb18..e794da776f 100755 --- a/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ApplicationConnection.java @@ -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 onModuleLoad(). @@ -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 = "

" + caption + "

" + message + "

"; - new Notification(Notification.DELAY_FOREVER).show(html, - Notification.CENTERED, "error"); + JSONValue val = error.get("caption"); + String html = ""; + if (val.isString() != null) { + html += "

" + val.isString().stringValue() + "

"; + } + val = error.get("message"); + if (val.isString() != null) { + html += "

" + val.isString().stringValue() + "

"; + } + 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); + } + + } } diff --git a/src/com/itmill/toolkit/terminal/gwt/client/ui/Notification.java b/src/com/itmill/toolkit/terminal/gwt/client/ui/Notification.java index 8a703a7290..c66f8a635c 100644 --- a/src/com/itmill/toolkit/terminal/gwt/client/ui/Notification.java +++ b/src/com/itmill/toolkit/terminal/gwt/client/ui/Notification.java @@ -4,6 +4,10 @@ package com.itmill.toolkit.terminal.gwt.client.ui; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.Iterator; + import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; @@ -40,6 +44,8 @@ public class Notification extends ToolkitOverlay { private String temporaryStyle; + private ArrayList listeners; + public Notification() { setStylePrimaryName(STYLENAME); sinkEvents(Event.ONCLICK); @@ -94,7 +100,6 @@ public class Notification extends ToolkitOverlay { } public void show(int position, String style) { - hide(); setOpacity(getElement(), startOpacity); if (style != null) { temporaryStyle = style; @@ -113,6 +118,7 @@ public class Notification extends ToolkitOverlay { temporaryStyle = null; } super.hide(); + fireEvent(new HideEvent(this)); } public void fade() { @@ -235,4 +241,36 @@ public class Notification extends ToolkitOverlay { return true; } + public void addEventListener(EventListener listener) { + if (listeners == null) { + listeners = new ArrayList(); + } + listeners.add(listener); + } + + public void removeEventListener(EventListener listener) { + if (listeners == null) { + return; + } + listeners.remove(listener); + } + + private void fireEvent(HideEvent event) { + if (listeners != null) { + for (Iterator it = listeners.iterator(); it.hasNext();) { + EventListener l = (EventListener) it.next(); + l.notificationHidden(event); + } + } + } + + public class HideEvent extends EventObject { + public HideEvent(Object source) { + super(source); + } + } + + public interface EventListener extends java.util.EventListener { + public void notificationHidden(HideEvent event); + } } diff --git a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java index d0ba830307..0ac7a3f206 100644 --- a/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java +++ b/src/com/itmill/toolkit/terminal/gwt/server/ApplicationServlet.java @@ -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\" : \"
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(); diff --git a/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java b/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java index 1fb98fa549..2e23e21853 100644 --- a/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java +++ b/src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java @@ -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) {