Browse Source

Fixes for #2002 and #1642 - improved exception handling in CommunicationManager

svn changeset:5242/svn branch:trunk
tags/6.7.0.beta1
Artur Signell 16 years ago
parent
commit
92af8c26fc

+ 43
- 2
src/com/itmill/toolkit/Application.java View 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.

+ 34
- 0
src/com/itmill/toolkit/terminal/gwt/server/ChangeVariablesErrorEvent.java View File

@@ -0,0 +1,34 @@
package com.itmill.toolkit.terminal.gwt.server;
import java.util.Map;
import com.itmill.toolkit.ui.Component;
import com.itmill.toolkit.ui.AbstractField.ComponentErrorEvent;
public class ChangeVariablesErrorEvent implements ComponentErrorEvent {
private Throwable throwable;
private Component component;
private Map variableChanges;
public ChangeVariablesErrorEvent(Component component, Throwable throwable,
Map variableChanges) {
this.component = component;
this.throwable = throwable;
this.variableChanges = variableChanges;
}
public Throwable getThrowable() {
return throwable;
}
public Component getComponent() {
return component;
}
public Map getVariableChanges() {
return variableChanges;
}
}

+ 279
- 253
src/com/itmill/toolkit/terminal/gwt/server/CommunicationManager.java View 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) {

+ 50
- 0
src/com/itmill/toolkit/tests/tickets/Ticket2002.java View File

@@ -0,0 +1,50 @@
package com.itmill.toolkit.tests.tickets;
import com.itmill.toolkit.Application;
import com.itmill.toolkit.data.util.MethodProperty;
import com.itmill.toolkit.ui.GridLayout;
import com.itmill.toolkit.ui.TextField;
import com.itmill.toolkit.ui.Window;
public class Ticket2002 extends Application {
private Long long1 = new Long(1L);
private Long long2 = new Long(2L);
public Long getLong1() {
return long1;
}
public void setLong1(Long long1) {
this.long1 = long1;
}
public Long getLong2() {
return long2;
}
public void setLong2(Long long2) {
this.long2 = long2;
}
public void init() {
Window w = new Window(getClass().getSimpleName());
setMainWindow(w);
GridLayout layout = new GridLayout(2, 2);
layout.setSpacing(true);
TextField f1 = new TextField("Non-immediate/Long text field",
new MethodProperty(this, "long1"));
f1.setImmediate(false);
f1.setNullSettingAllowed(true);
TextField f2 = new TextField("Immediate/Long text field",
new MethodProperty(this, "long2"));
f2.setImmediate(true);
f2.setNullSettingAllowed(true);
layout.addComponent(f1);
layout.addComponent(f2);
w.setLayout(layout);
}
}

+ 63
- 0
src/com/itmill/toolkit/ui/AbstractField.java View 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 {
}

}

Loading…
Cancel
Save