]> source.dussan.org Git - vaadin-framework.git/commitdiff
Fixes for #2002 and #1642 - improved exception handling in CommunicationManager
authorArtur Signell <artur.signell@itmill.com>
Fri, 22 Aug 2008 07:25:23 +0000 (07:25 +0000)
committerArtur Signell <artur.signell@itmill.com>
Fri, 22 Aug 2008 07:25:23 +0000 (07:25 +0000)
svn changeset:5242/svn branch:trunk

src/com/itmill/toolkit/Application.java
src/com/itmill/toolkit/terminal/gwt/server/ChangeVariablesErrorEvent.java [new file with mode: 0644]
src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java
src/com/itmill/toolkit/tests/tickets/Ticket2002.java [new file with mode: 0644]
src/com/itmill/toolkit/ui/AbstractField.java

index 2c072284ff2bde67b95b95659a8ac1bb1c920581..1196891014d94b0bba836d6c14a4f0d93f4cd53d 100644 (file)
@@ -5,6 +5,7 @@
 package com.itmill.toolkit;
 
 import java.net.MalformedURLException;
+import java.net.SocketException;
 import java.net.URL;
 import java.util.Collection;
 import java.util.Collections;
@@ -27,6 +28,7 @@ import com.itmill.toolkit.terminal.SystemError;
 import com.itmill.toolkit.terminal.Terminal;
 import com.itmill.toolkit.terminal.URIHandler;
 import com.itmill.toolkit.terminal.VariableOwner;
+import com.itmill.toolkit.terminal.gwt.server.ChangeVariablesErrorEvent;
 import com.itmill.toolkit.ui.AbstractComponent;
 import com.itmill.toolkit.ui.Component;
 import com.itmill.toolkit.ui.Window;
@@ -177,6 +179,12 @@ public abstract class Application implements URIHandler, Terminal.ErrorListener
 
     private Focusable pendingFocus;
 
+    /**
+     * Application wide error handler which is used by default if an error is
+     * left unhandled.
+     */
+    private Terminal.ErrorListener errorHandler = this;
+
     /**
      * <p>
      * Gets a window by name. Returns <code>null</code> if the application is
@@ -1074,8 +1082,17 @@ public abstract class Application implements URIHandler, Terminal.ErrorListener
      * @see com.itmill.toolkit.terminal.Terminal.ErrorListener#terminalError(com.itmill.toolkit.terminal.Terminal.ErrorEvent)
      */
     public void terminalError(Terminal.ErrorEvent event) {
-        // throw it to standard error stream too
-        event.getThrowable().printStackTrace();
+        Throwable t = event.getThrowable();
+        if (t instanceof SocketException) {
+            // Most likely client browser closed socket
+            System.err
+                    .println("Warning: SocketException in CommunicationManager."
+                            + " Most likely client (browser) closed socket.");
+
+        } else {
+            // throw it to standard error stream too
+            t.printStackTrace();
+        }
 
         // Finds the original source of the error/exception
         Object owner = null;
@@ -1085,6 +1102,8 @@ public abstract class Application implements URIHandler, Terminal.ErrorListener
             owner = ((URIHandler.ErrorEvent) event).getURIHandler();
         } else if (event instanceof ParameterHandler.ErrorEvent) {
             owner = ((ParameterHandler.ErrorEvent) event).getParameterHandler();
+        } else if (event instanceof ChangeVariablesErrorEvent) {
+            owner = ((ChangeVariablesErrorEvent) event).getComponent();
         }
 
         // Shows the error in AbstractComponent
@@ -1145,6 +1164,28 @@ public abstract class Application implements URIHandler, Terminal.ErrorListener
         return "NONVERSIONED";
     }
 
+    /**
+     * Gets the application error handler.
+     * 
+     * The default error handler is the application itself.
+     * 
+     * @return Application error handler
+     */
+    public Terminal.ErrorListener getErrorHandler() {
+        return errorHandler;
+    }
+
+    /**
+     * Sets the application error handler.
+     * 
+     * The default error handler is the application itself.
+     * 
+     * @param errorHandler
+     */
+    public void setErrorHandler(Terminal.ErrorListener errorHandler) {
+        this.errorHandler = errorHandler;
+    }
+
     /**
      * Experimental API, not finalized. Contains the system messages used to
      * notify the user about various critical situations that can occur.
diff --git a/src/com/itmill/toolkit/terminal/gwt/server/ChangeVariablesErrorEvent.java b/src/com/itmill/toolkit/terminal/gwt/server/ChangeVariablesErrorEvent.java
new file mode 100644 (file)
index 0000000..08182b7
--- /dev/null
@@ -0,0 +1,34 @@
+package com.itmill.toolkit.terminal.gwt.server;\r
+\r
+import java.util.Map;\r
+\r
+import com.itmill.toolkit.ui.Component;\r
+import com.itmill.toolkit.ui.AbstractField.ComponentErrorEvent;\r
+\r
+public class ChangeVariablesErrorEvent implements ComponentErrorEvent {\r
+\r
+    private Throwable throwable;\r
+    private Component component;\r
+\r
+    private Map variableChanges;\r
+\r
+    public ChangeVariablesErrorEvent(Component component, Throwable throwable,\r
+            Map variableChanges) {\r
+        this.component = component;\r
+        this.throwable = throwable;\r
+        this.variableChanges = variableChanges;\r
+    }\r
+\r
+    public Throwable getThrowable() {\r
+        return throwable;\r
+    }\r
+\r
+    public Component getComponent() {\r
+        return component;\r
+    }\r
+\r
+    public Map getVariableChanges() {\r
+        return variableChanges;\r
+    }\r
+\r
+}
\ No newline at end of file
index 86bbb39d2a5818e0a03c3b1614154fc69504096e..3b6c7882f4cd8e80b4ef52032d4ad157596effd8 100644 (file)
@@ -12,7 +12,6 @@ import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.lang.reflect.Method;
-import java.net.SocketException;
 import java.text.DateFormatSymbols;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -48,6 +47,8 @@ import com.itmill.toolkit.terminal.URIHandler;
 import com.itmill.toolkit.terminal.UploadStream;
 import com.itmill.toolkit.terminal.VariableOwner;
 import com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent;
+import com.itmill.toolkit.terminal.Terminal.ErrorEvent;
+import com.itmill.toolkit.ui.AbstractField;
 import com.itmill.toolkit.ui.Component;
 import com.itmill.toolkit.ui.Upload;
 import com.itmill.toolkit.ui.Window;
@@ -192,10 +193,11 @@ public class CommunicationManager implements Paintable.RepaintRequestListener {
      * @param request
      * @param response
      * @throws IOException
+     * @throws ServletException
      */
     public void handleUidlRequest(HttpServletRequest request,
             HttpServletResponse response, ApplicationServlet applicationServlet)
-            throws IOException {
+            throws IOException, ServletException {
 
         // repaint requested or session has timed out and new one is created
         boolean repaintAll = (request.getParameter(GET_PARAM_REPAINT_ALL) != null)
@@ -205,296 +207,274 @@ public class CommunicationManager implements Paintable.RepaintRequestListener {
         final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
                 new OutputStreamWriter(out, "UTF-8")));
 
-        try {
-
-            // The rest of the process is synchronized with the application
-            // in order to guarantee that no parallel variable handling is
-            // made
-            synchronized (application) {
-
-                // Finds the window within the application
-                Window window = null;
-                if (application.isRunning()) {
-                    window = getApplicationWindow(request, application);
-                    // Returns if no window found
-                    if (window == null) {
-                        // This should not happen, no windows exists but
-                        // application is still open.
-                        System.err
-                                .println("Warning, could not get window for application with request URI "
-                                        + request.getRequestURI());
-                        return;
-                    }
-                } else {
-                    // application has been closed
-                    endApplication(request, response, application);
+        // The rest of the process is synchronized with the application
+        // in order to guarantee that no parallel variable handling is
+        // made
+        synchronized (application) {
+
+            // Finds the window within the application
+            Window window = null;
+            if (application.isRunning()) {
+                window = getApplicationWindow(request, application);
+                // Returns if no window found
+                if (window == null) {
+                    // This should not happen, no windows exists but
+                    // application is still open.
+                    System.err
+                            .println("Warning, could not get window for application with request URI "
+                                    + request.getRequestURI());
                     return;
                 }
+            } else {
+                // application has been closed
+                endApplication(request, response, application);
+                return;
+            }
 
-                // Change all variables based on request parameters
-                if (!handleVariables(request, application)) {
-                    // var inconsistency; the client is probably out-of-sync
-                    SystemMessages ci = null;
-                    try {
-                        Method m = application.getClass().getMethod(
-                                "getSystemMessages", null);
-                        ci = (Application.SystemMessages) m.invoke(null, null);
-                    } catch (Exception e2) {
-                        // Not critical, but something is still wrong; print
-                        // stacktrace
-                        e2.printStackTrace();
-                    }
-                    if (ci != null) {
-                        String msg = ci.getOutOfSyncMessage();
-                        String cap = ci.getOutOfSyncCaption();
-                        if (msg != null || cap != null) {
-                            applicationServlet.criticalNotification(request,
-                                    response, cap, msg, ci.getOutOfSyncURL());
-                            // will reload page after this
-                            return;
-                        }
+            // Change all variables based on request parameters
+            if (!handleVariables(request, application)) {
+                // var inconsistency; the client is probably out-of-sync
+                SystemMessages ci = null;
+                try {
+                    Method m = application.getClass().getMethod(
+                            "getSystemMessages", null);
+                    ci = (Application.SystemMessages) m.invoke(null, null);
+                } catch (Exception e2) {
+                    // Not critical, but something is still wrong; print
+                    // stacktrace
+                    e2.printStackTrace();
+                }
+                if (ci != null) {
+                    String msg = ci.getOutOfSyncMessage();
+                    String cap = ci.getOutOfSyncCaption();
+                    if (msg != null || cap != null) {
+                        applicationServlet.criticalNotification(request,
+                                response, cap, msg, ci.getOutOfSyncURL());
+                        // will reload page after this
+                        return;
                     }
-                    // No message to show, let's just repaint all.
-                    System.err
-                            .println("Warning: variable inconsistency - client is probably out-of-sync, repainting all.");
-                    repaintAll = true;
-
                 }
+                // No message to show, let's just repaint all.
+                System.err
+                        .println("Warning: variable inconsistency - client is probably out-of-sync, repainting all.");
+                repaintAll = true;
 
-                // If repaint is requested, clean all ids in this root window
-                if (repaintAll) {
-                    for (final Iterator it = idPaintableMap.keySet().iterator(); it
-                            .hasNext();) {
-                        final Component c = (Component) idPaintableMap.get(it
-                                .next());
-                        if (isChildOf(window, c)) {
-                            it.remove();
-                            paintableIdMap.remove(c);
-                        }
+            }
+
+            // If repaint is requested, clean all ids in this root window
+            if (repaintAll) {
+                for (final Iterator it = idPaintableMap.keySet().iterator(); it
+                        .hasNext();) {
+                    final Component c = (Component) idPaintableMap.get(it
+                            .next());
+                    if (isChildOf(window, c)) {
+                        it.remove();
+                        paintableIdMap.remove(c);
                     }
                 }
+            }
 
-                // Removes application if it has stopped during variable changes
-                if (!application.isRunning()) {
-                    endApplication(request, response, application);
-                    return;
-                }
+            // Removes application if it has stopped during variable changes
+            if (!application.isRunning()) {
+                endApplication(request, response, application);
+                return;
+            }
 
-                // Sets the response type
-                response.setContentType("application/json; charset=UTF-8");
-                // some dirt to prevent cross site scripting
-                outWriter.print("for(;;);[{");
+            // Sets the response type
+            response.setContentType("application/json; charset=UTF-8");
+            // some dirt to prevent cross site scripting
+            outWriter.print("for(;;);[{");
 
-                outWriter.print("\"changes\":[");
+            outWriter.print("\"changes\":[");
 
-                // re-get mainwindow - may have been changed
-                Window newWindow = getApplicationWindow(request, application);
-                if (newWindow != window) {
-                    window = newWindow;
-                    repaintAll = true;
-                }
+            // re-get mainwindow - may have been changed
+            Window newWindow = getApplicationWindow(request, application);
+            if (newWindow != window) {
+                window = newWindow;
+                repaintAll = true;
+            }
 
-                JsonPaintTarget paintTarget = new JsonPaintTarget(this,
-                        outWriter, !repaintAll);
+            JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
+                    !repaintAll);
 
-                // Paints components
-                ArrayList paintables;
-                if (repaintAll) {
-                    paintables = new ArrayList();
-                    paintables.add(window);
+            // Paints components
+            ArrayList paintables;
+            if (repaintAll) {
+                paintables = new ArrayList();
+                paintables.add(window);
 
-                    // Reset sent locales
-                    locales = null;
-                    requireLocale(application.getLocale().toString());
+                // 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);
-                        }
+            } 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);
                 }
-                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;
+                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();
                         }
-                    });
-
-                    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());
-                            }
+                        int d2 = 0;
+                        while (c2.getParent() != null) {
+                            d2++;
+                            c2 = c2.getParent();
                         }
-                        /*
-                         * 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");
+                        if (d1 < d2) {
+                            return -1;
+                        }
+                        if (d1 > d2) {
+                            return 1;
+                        }
+                        return 0;
+                    }
+                });
+
+                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());
                         }
-                        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(",");
-                    }
-                    outWriter.write("\"focus\":\"" + getPaintableId(f) + "\"");
+            // 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.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";
+            // 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) {
+                    e.printStackTrace();
                 }
+                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 {
-                        is = applicationServlet
-                                .getServletContext()
-                                .getResourceAsStream(
-                                        "/"
-                                                + ApplicationServlet.THEME_DIRECTORY_PATH
-                                                + themeName + "/" + resource);
-                    } catch (final Exception e) {
-                        e.printStackTrace();
-                    }
-                    if (is != null) {
-
-                        outWriter.print((resourceIndex++ > 0 ? ", " : "")
-                                + "\"" + resource + "\" : ");
-                        final StringBuffer layout = new StringBuffer();
-
-                        try {
-                            final InputStreamReader r = new InputStreamReader(
-                                    is);
-                            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) {
-                            System.err.println("Resource transfer failed:  "
-                                    + request.getRequestURI() + ". ("
-                                    + e.getMessage() + ")");
+                        final InputStreamReader r = new InputStreamReader(is);
+                        final char[] buffer = new char[20000];
+                        int charsRead = 0;
+                        while ((charsRead = r.read(buffer)) > 0) {
+                            layout.append(buffer, 0, charsRead);
                         }
-                        outWriter.print("\""
-                                + JsonPaintTarget.escapeJSON(layout.toString())
-                                + "\"");
-                    } else {
-                        System.err.println("CustomLayout " + "/"
-                                + ApplicationServlet.THEME_DIRECTORY_PATH
-                                + themeName + "/" + resource + " not found!");
+                        r.close();
+                    } catch (final java.io.IOException e) {
+                        System.err.println("Resource transfer failed:  "
+                                + request.getRequestURI() + ". ("
+                                + e.getMessage() + ")");
                     }
+                    outWriter.print("\""
+                            + JsonPaintTarget.escapeJSON(layout.toString())
+                            + "\"");
+                } else {
+                    System.err.println("CustomLayout " + "/"
+                            + ApplicationServlet.THEME_DIRECTORY_PATH
+                            + themeName + "/" + resource + " not found!");
                 }
-                outWriter.print("}");
+            }
+            outWriter.print("}");
 
-                printLocaleDeclarations(outWriter);
+            printLocaleDeclarations(outWriter);
 
-                outWriter.print("}]");
+            outWriter.print("}]");
 
-                outWriter.flush();
-                outWriter.close();
-            }
-
-            out.flush();
-            out.close();
-        } catch (SocketException e) {
-            // Most likely client browser closed socket
-            System.err
-                    .println("Warning: SocketException in CommunicationManager."
-                            + " Most likely client (browser) closed socket.");
-        } catch (final Throwable e) {
-            e.printStackTrace();
-            // Writes the error report to client
-            // FIXME breaks UIDL response, security shouldn't reveal stack trace
-            // to client side
-            final OutputStreamWriter w = new OutputStreamWriter(out);
-            final PrintWriter err = new PrintWriter(w);
-            err
-                    .write("<html><head><title>Application Internal Error</title></head><body>");
-            err.write("<h1>" + e.toString() + "</h1><pre>\n");
-            e.printStackTrace(new PrintWriter(err));
-            err.write("\n</pre></body></html>");
-            err.close();
+            outWriter.flush();
+            outWriter.close();
         }
+
+        out.flush();
+        out.close();
     }
 
     /**
@@ -569,7 +549,12 @@ public class CommunicationManager implements Paintable.RepaintRequestListener {
                                 variable[VAR_TYPE].charAt(0),
                                 variable[VAR_VALUE]));
                     }
-                    owner.changeVariables(request, m);
+                    try {
+                        owner.changeVariables(request, m);
+                    } catch (Exception e) {
+                        handleChangeVariablesError(application2,
+                                (Component) owner, e, m);
+                    }
                 } else {
                     // Ignore variable change
                     String msg = "Warning: Ignoring variable change for ";
@@ -592,6 +577,47 @@ public class CommunicationManager implements Paintable.RepaintRequestListener {
         return success;
     }
 
+    public class ErrorHandlerErrorEvent implements ErrorEvent {
+
+        private Throwable throwable;
+
+        public ErrorHandlerErrorEvent(Throwable throwable) {
+            this.throwable = throwable;
+        }
+
+        public Throwable getThrowable() {
+            return throwable;
+        }
+
+    }
+
+    private void handleChangeVariablesError(Application application,
+            Component owner, Exception e, Map m) {
+        boolean handled = false;
+        ChangeVariablesErrorEvent errorEvent = new ChangeVariablesErrorEvent(
+                owner, e, m);
+
+        if (owner instanceof AbstractField) {
+            try {
+                handled = ((AbstractField) owner).handleError(errorEvent);
+            } catch (Exception handlerException) {
+                /*
+                 * If there is an error in the component error handler we pass
+                 * the that error to the application error handler and continue
+                 * processing the actual error
+                 */
+                application.getErrorHandler().terminalError(
+                        new ErrorHandlerErrorEvent(handlerException));
+                handled = false;
+            }
+        }
+
+        if (!handled) {
+            application.getErrorHandler().terminalError(errorEvent);
+        }
+
+    }
+
     private Object convertVariableValue(char variableType, String strValue) {
         Object val = null;
         switch (variableType) {
diff --git a/src/com/itmill/toolkit/tests/tickets/Ticket2002.java b/src/com/itmill/toolkit/tests/tickets/Ticket2002.java
new file mode 100644 (file)
index 0000000..2e69a66
--- /dev/null
@@ -0,0 +1,50 @@
+package com.itmill.toolkit.tests.tickets;\r
+\r
+import com.itmill.toolkit.Application;\r
+import com.itmill.toolkit.data.util.MethodProperty;\r
+import com.itmill.toolkit.ui.GridLayout;\r
+import com.itmill.toolkit.ui.TextField;\r
+import com.itmill.toolkit.ui.Window;\r
+\r
+public class Ticket2002 extends Application {\r
+    private Long long1 = new Long(1L);\r
+    private Long long2 = new Long(2L);\r
+\r
+    public Long getLong1() {\r
+        return long1;\r
+    }\r
+\r
+    public void setLong1(Long long1) {\r
+        this.long1 = long1;\r
+    }\r
+\r
+    public Long getLong2() {\r
+        return long2;\r
+    }\r
+\r
+    public void setLong2(Long long2) {\r
+        this.long2 = long2;\r
+    }\r
+\r
+    public void init() {\r
+        Window w = new Window(getClass().getSimpleName());\r
+        setMainWindow(w);\r
+\r
+        GridLayout layout = new GridLayout(2, 2);\r
+        layout.setSpacing(true);\r
+\r
+        TextField f1 = new TextField("Non-immediate/Long text field",\r
+                new MethodProperty(this, "long1"));\r
+        f1.setImmediate(false);\r
+        f1.setNullSettingAllowed(true);\r
+        TextField f2 = new TextField("Immediate/Long text field",\r
+                new MethodProperty(this, "long2"));\r
+        f2.setImmediate(true);\r
+        f2.setNullSettingAllowed(true);\r
+\r
+        layout.addComponent(f1);\r
+        layout.addComponent(f2);\r
+\r
+        w.setLayout(layout);\r
+    }\r
+}\r
index 71b1c4c14000d181cd9200adbe0aad95f84f04f4..70b17ad41aa5658f016965090690e6487f4fec7a 100644 (file)
@@ -21,6 +21,7 @@ import com.itmill.toolkit.terminal.CompositeErrorMessage;
 import com.itmill.toolkit.terminal.ErrorMessage;
 import com.itmill.toolkit.terminal.PaintException;
 import com.itmill.toolkit.terminal.PaintTarget;
+import com.itmill.toolkit.terminal.Terminal;
 
 /**
  * <p>
@@ -121,6 +122,8 @@ public abstract class AbstractField extends AbstractComponent implements Field,
      */
     private boolean validationVisible = true;
 
+    private ComponentErrorHandler errorHandler = null;
+
     /* Component basics ************************************************ */
 
     /*
@@ -1082,4 +1085,64 @@ public abstract class AbstractField extends AbstractComponent implements Field,
         }
     }
 
+    public interface ComponentErrorHandler {
+        /**
+         * Handle the component error
+         * 
+         * @param event
+         * @return True if the error has been handled False, otherwise
+         */
+        public boolean handleComponentError(ComponentErrorEvent event);
+    }
+
+    /**
+     * Gets the error handler for the component.
+     * 
+     * The error handler is dispatched whenever there is an error processing the
+     * data coming from the client.
+     * 
+     * @return
+     */
+    public ComponentErrorHandler getErrorHandler() {
+        return errorHandler;
+    }
+
+    /**
+     * Sets the error handler for the component.
+     * 
+     * The error handler is dispatched whenever there is an error processing the
+     * data coming from the client.
+     * 
+     * If the error handler is not set, the application error handler is used to
+     * handle the exception.
+     * 
+     * @param errorHandler
+     *                AbstractField specific error handler
+     */
+    public void setErrorHandler(ComponentErrorHandler errorHandler) {
+        this.errorHandler = errorHandler;
+    }
+
+    public boolean handleError(ComponentErrorEvent error) {
+        if (errorHandler != null) {
+            return errorHandler.handleComponentError(error);
+        }
+        return false;
+
+    }
+
+    /**
+     * Sets the current buffered source exception.
+     * 
+     * @param currentBufferedSourceException
+     */
+    public void setCurrentBufferedSourceException(
+            Buffered.SourceException currentBufferedSourceException) {
+        this.currentBufferedSourceException = currentBufferedSourceException;
+        requestRepaint();
+    }
+
+    public interface ComponentErrorEvent extends Terminal.ErrorEvent {
+    }
+
 }
\ No newline at end of file