Marked as Experimental API for the time being.
svn changeset:4391/svn branch:trunk
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;
}
/**
- * 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;
}
/**
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
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>.
}
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;
}
}
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) {
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);
+ }
+
+ }
}
\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
\r
private String temporaryStyle;\r
\r
+ private ArrayList listeners;\r
+\r
public Notification() {\r
setStylePrimaryName(STYLENAME);\r
sinkEvents(Event.ONCLICK);\r
}\r
\r
public void show(int position, String style) {\r
- hide();\r
setOpacity(getElement(), startOpacity);\r
if (style != null) {\r
temporaryStyle = style;\r
temporaryStyle = null;\r
}\r
super.hide();\r
+ fireEvent(new HideEvent(this));\r
}\r
\r
public void fade() {\r
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
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;
}
} 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);
}
}
- /**
- * 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
* @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");
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();
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;
}
// 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()) {
}
}
- 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]
} 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) {