commit c78fd1acbf37a74fc29944db1a4cea7d227181bc
Author: Andreas Kohlbecker <a.kohlbecker@bgbm.org>
Date:   Thu Nov 8 13:55:54 2018 +0100

    improving error messaging: PopupEditorException with editor context information

diff --git a/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormat.java b/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormat.java
index 954a85e4..349ba09c 100644
--- a/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormat.java
+++ b/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormat.java
@@ -19,9 +19,14 @@ public class EditorActionContextFormat {
      */
     public boolean doClassName;
     /**
-     *
+     * add information on the target class and field if possible
      */
     public boolean doTargetInfo;
+
+    /**
+     * add on the target entity like including titlecache and uuid
+     */
+    public boolean doTargetEntity;
     /**
      *
      */
@@ -32,7 +37,7 @@ public class EditorActionContextFormat {
     public boolean doCreateOrNew;
 
     /**
-     * The name of the html tag to be used
+     * The name of the html tag to be used, may be <code>null</code> to format as plain text
      */
     public String tagName = "span";
 
diff --git a/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormatter.java b/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormatter.java
index b83bcf90..8fa4d2ae 100644
--- a/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormatter.java
+++ b/src/main/java/eu/etaxonomy/cdm/vaadin/event/EditorActionContextFormatter.java
@@ -8,12 +8,11 @@
 */
 package eu.etaxonomy.cdm.vaadin.event;
 
+import java.util.Collection;
 import java.util.Stack;
 
 import org.apache.commons.lang3.StringUtils;
 
-import com.vaadin.ui.Component;
-
 import eu.etaxonomy.cdm.model.common.CdmBase;
 import eu.etaxonomy.cdm.ref.TypedEntityReference;
 import eu.etaxonomy.cdm.vaadin.event.EditorActionContextFormat.TargetInfoType;
@@ -36,6 +35,7 @@ public class EditorActionContextFormatter {
 
         String className = null;
         String targetInfo = null;
+        String targetEntityStr = null;
         String createOrNew = null;
 
         Object parentEntity = cntxt.getParentEntity();
@@ -60,7 +60,7 @@ public class EditorActionContextFormatter {
             className += "[NULL_CLASS]";
         }
 
-        if (format.doTargetInfo && cntxt.getParentView() != null && AbstractPopupEditor.class.isAssignableFrom(cntxt.getParentView().getClass())) {
+        if ((format.doTargetInfo || format.doTargetEntity) && cntxt.getParentView() != null && AbstractPopupEditor.class.isAssignableFrom(cntxt.getParentView().getClass())) {
             // the top element is the cntxt istself!! we need to dig one step deeper to get the previous popup editor
             // TODO chaining the EditorActionContext would ease find the contexts of parent editors
             Stack<EditorActionContext> ctxtStack = ((AbstractPopupEditor)cntxt.getParentView()).getEditorActionContext();
@@ -72,27 +72,22 @@ public class EditorActionContextFormatter {
                     if (propertyIdPath != null) {
                         targetInfo = propertyIdPath.toString();
                     }
-                } else {
-                    // TargetInfoType.FIELD_CAPTION
-                    if(parentCtx.getTargetField() != null){
-                        Component captionComponent = parentCtx.getTargetField();
-                        while(captionComponent != null){
-                            targetInfo = captionComponent.getCaption();
-                            if(targetInfo != null){
-                                break;
-                            }
-                            captionComponent = captionComponent.getParent();
-                        }
-                    }
                 }
             }
         }
+        if(format.doTargetEntity){
+            targetEntityStr = formatTargetEntityString(cntxt.parentEntity, format);
+        }
 
         // create output
-        String markup = "";
+        String outStr = "";
 
         if (format.doCreateOrNew && createOrNew != null) {
-            markup += "<" + format.tagName + " class=\"operation " + format.classAttributes + "\">" + createOrNew + "</" + format.tagName + ">";
+            if(format.tagName != null){
+                outStr += "<" + format.tagName + " class=\"operation " + format.classAttributes + "\">" + createOrNew + "</" + format.tagName + ">";
+            } else {
+                outStr += createOrNew;
+            }
         }
 
         if (format.doTargetInfo) {
@@ -100,14 +95,58 @@ public class EditorActionContextFormatter {
                 targetInfo = className;
             }
             if(targetInfo != null){
-                if(!markup.isEmpty()){
-                    markup += " ";
+                if(!outStr.isEmpty()){
+                    outStr += " ";
                     targetInfo = normalizeTargetInfo(targetInfo);
                 }
-                markup += "<" + format.tagName + " class=\"target " + format.classAttributes + "\">" + targetInfo + "</" + format.tagName + ">";
+                if(format.tagName != null){
+                    outStr += "<" + format.tagName + " class=\"target " + format.classAttributes + "\">" + targetInfo + "</" + format.tagName + ">";
+                } else {
+                    outStr += targetInfo;
+                }
+            }
+        }
+        if(format.doTargetEntity && targetEntityStr != null){
+            if(format.tagName != null){
+                outStr += "<" + format.tagName + " class=\"target-entity" + format.classAttributes + "\">" + targetEntityStr + "</" + format.tagName + ">";
+            } else {
+                outStr += " (" + targetEntityStr + ")";
+            }
+        }
+        return outStr;
+    }
+
+    public String format(Collection<EditorActionContext> cntxts, EditorActionContextFormat format) {
+        String outStr = "";
+        for(EditorActionContext ctx : cntxts){
+            if(!outStr.isEmpty()){
+                outStr += " > "; // FIXME allow configuring the separator
             }
+            outStr += format(ctx, format) ;
+        }
+        return outStr;
+    }
+
+    /**
+     * @param value
+     * @param format
+     * @return
+     */
+    private String formatTargetEntityString(Object value, EditorActionContextFormat format) {
+
+        if(value == null){
+            return "NULL";
+        }
+        String outStr;
+        if(value instanceof CdmBase){
+            outStr = ((CdmBase)value).instanceToString();
+        } else if(value instanceof CdmEntityAdapterDTO) {
+            outStr = value.getClass().getSimpleName() + ": ";
+            outStr += ((CdmEntityAdapterDTO)value).cdmEntity().toString();
+        } else {
+            outStr = value.getClass().getSimpleName() + ": " + value.toString();
         }
-        return markup;
+        return outStr;
     }
 
     /**
diff --git a/src/main/java/eu/etaxonomy/cdm/vaadin/event/error/DelegatingErrorHandler.java b/src/main/java/eu/etaxonomy/cdm/vaadin/event/error/DelegatingErrorHandler.java
index 6e2c14cb..576b83bc 100644
--- a/src/main/java/eu/etaxonomy/cdm/vaadin/event/error/DelegatingErrorHandler.java
+++ b/src/main/java/eu/etaxonomy/cdm/vaadin/event/error/DelegatingErrorHandler.java
@@ -15,6 +15,7 @@ import org.apache.log4j.Logger;
 
 import com.vaadin.server.ErrorEvent;
 import com.vaadin.server.ErrorHandler;
+import com.vaadin.server.ServerRpcManager.RpcInvocationException;
 import com.vaadin.ui.Notification;
 
 /**
@@ -41,7 +42,7 @@ public class DelegatingErrorHandler implements ErrorHandler{
     @SuppressWarnings("unchecked")
     public <E extends Throwable> ErrorTypeHandler<E> findHandler(Class<E> errorClass){
         for(ErrorTypeHandler<?> h : handlers){
-            if(h.supports().equals(errorClass)){
+            if(h.supports().isAssignableFrom(errorClass)){
                 return (ErrorTypeHandler<E>) h;
             }
         }
@@ -57,6 +58,11 @@ public class DelegatingErrorHandler implements ErrorHandler{
 
         boolean handlerFound = true;
         Throwable throwable = event.getThrowable();
+        if(throwable != null && RpcInvocationException.class.isAssignableFrom(throwable.getClass())){
+            // we are only interested into the cause and will will remove RpcInvocationException > InvocationTargetException
+            throwable = throwable.getCause().getCause();
+            event.setThrowable(throwable);
+        }
         while(throwable != null){
             if(delegate(event, throwable)){
                 break;
diff --git a/src/main/java/eu/etaxonomy/cdm/vaadin/event/error/ErrorTypeErrorHandlerWrapper.java b/src/main/java/eu/etaxonomy/cdm/vaadin/event/error/ErrorTypeErrorHandlerWrapper.java
new file mode 100644
index 00000000..714840f4
--- /dev/null
+++ b/src/main/java/eu/etaxonomy/cdm/vaadin/event/error/ErrorTypeErrorHandlerWrapper.java
@@ -0,0 +1,47 @@
+/**
+* Copyright (C) 2018 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.cdm.vaadin.event.error;
+
+import com.vaadin.server.ErrorEvent;
+import com.vaadin.server.ErrorHandler;
+
+/**
+ * @author a.kohlbecker
+ * @since Nov 8, 2018
+ *
+ */
+public class ErrorTypeErrorHandlerWrapper<E extends Throwable> extends ErrorTypeHandler<E> {
+
+    private static final long serialVersionUID = 856246216519945768L;
+
+    private Class<E> errorType;
+
+    private ErrorHandler errorHandler;
+
+    public ErrorTypeErrorHandlerWrapper(Class<E> errorType, ErrorHandler errorHandler){
+        this.errorType = errorType;
+        this.errorHandler = errorHandler;
+    }
+
+    @Override
+    public void error(ErrorEvent event) {
+        errorHandler.error(event);
+    }
+
+    @Override
+    public Class<E> supports() {
+        return errorType;
+    }
+
+    @Override
+    public void exception(Throwable throwable) {
+        // nothing to do, since the errorHandler is expected to provide user feedback
+    }
+
+}
diff --git a/src/main/java/eu/etaxonomy/cdm/vaadin/ui/RegistrationUI.java b/src/main/java/eu/etaxonomy/cdm/vaadin/ui/RegistrationUI.java
index 9ca4e5e6..32c6bd89 100644
--- a/src/main/java/eu/etaxonomy/cdm/vaadin/ui/RegistrationUI.java
+++ b/src/main/java/eu/etaxonomy/cdm/vaadin/ui/RegistrationUI.java
@@ -33,6 +33,8 @@ import com.vaadin.ui.UI;
 import com.vaadin.ui.themes.ValoTheme;
 
 import eu.etaxonomy.cdm.vaadin.debug.EntityCacheDebugger;
+import eu.etaxonomy.cdm.vaadin.event.error.DelegatingErrorHandler;
+import eu.etaxonomy.cdm.vaadin.event.error.ErrorTypeErrorHandlerWrapper;
 import eu.etaxonomy.cdm.vaadin.toolbar.Toolbar;
 import eu.etaxonomy.cdm.vaadin.view.RedirectToLoginView;
 import eu.etaxonomy.cdm.vaadin.view.registration.DashBoardView;
@@ -115,11 +117,13 @@ public class RegistrationUI extends UI {
     @Override
     protected void init(VaadinRequest request) {
 
+        DelegatingErrorHandler delegatingErrorHander = new DelegatingErrorHandler();
         WindowErrorHandler errorHandler = new WindowErrorHandler(this, RegistrationUIDefaults.ERROR_CONTACT_MESSAGE_LINE + "</br></br>"
                 + "<i>To help analyzing the problem please describe your actions that lead to this error and provide the error details from below in your email. "
                 + "You also might want to add a sreenshot of the browser page in error.</i>");
-        setErrorHandler(errorHandler);
-        VaadinSession.getCurrent().setErrorHandler(errorHandler);
+        delegatingErrorHander.registerHandler(new ErrorTypeErrorHandlerWrapper<Exception>(Exception.class, errorHandler));
+        setErrorHandler(delegatingErrorHander);
+        VaadinSession.getCurrent().setErrorHandler(delegatingErrorHander);
 
         navigator.setViewDisplay(viewDisplay);
         configureAccessDeniedView();
diff --git a/src/main/java/eu/etaxonomy/vaadin/mvp/AbstractPopupEditor.java b/src/main/java/eu/etaxonomy/vaadin/mvp/AbstractPopupEditor.java
index e4b94ff6..4de64193 100644
--- a/src/main/java/eu/etaxonomy/vaadin/mvp/AbstractPopupEditor.java
+++ b/src/main/java/eu/etaxonomy/vaadin/mvp/AbstractPopupEditor.java
@@ -411,9 +411,7 @@ public abstract class AbstractPopupEditor<DTO extends Object, P extends Abstract
                 PermissionDeniedException permissionDeniedException = (PermissionDeniedException)e.getCause().getCause();
                 Notification.show("Permission denied", permissionDeniedException.getMessage(), Type.ERROR_MESSAGE);
             } else {
-//                Logger.getLogger(this.getClass()).error("Error saving", e);
-//                Notification.show("Error saving", Type.ERROR_MESSAGE);
-                throw new RuntimeException("Error saving", e);
+                throw new PopupEditorException("Error saving popup editor", this, e);
             }
         }
     }
diff --git a/src/main/java/eu/etaxonomy/vaadin/mvp/PopupEditorException.java b/src/main/java/eu/etaxonomy/vaadin/mvp/PopupEditorException.java
new file mode 100644
index 00000000..38eeba96
--- /dev/null
+++ b/src/main/java/eu/etaxonomy/vaadin/mvp/PopupEditorException.java
@@ -0,0 +1,60 @@
+/**
+* Copyright (C) 2018 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.vaadin.mvp;
+
+import eu.etaxonomy.cdm.vaadin.event.EditorActionContextFormat;
+import eu.etaxonomy.cdm.vaadin.event.EditorActionContextFormat.TargetInfoType;
+import eu.etaxonomy.cdm.vaadin.event.EditorActionContextFormatter;
+
+/**
+ * @author a.kohlbecker
+ * @since Nov 8, 2018
+ *
+ */
+public class PopupEditorException extends RuntimeException {
+
+    private static final long serialVersionUID = -4242789621156408545L;
+
+    private String contextInfo;
+
+    /**
+     * @param message
+     */
+    public PopupEditorException(String message, AbstractPopupEditor<?,?> editor, Throwable e) {
+        super(message, e);
+
+        EditorActionContextFormatter formatter = new EditorActionContextFormatter();
+        EditorActionContextFormat format = new EditorActionContextFormat(true, true, true, true, TargetInfoType.PROPERTIES, null);
+        format.tagName = null;
+        format.doTargetEntity = true;
+        contextInfo = formatter.format(editor.getEditorActionContext(), format);
+    }
+
+    public PopupEditorException(String message, AbstractPopupEditor<?,?> editor) {
+        super(message);
+
+        EditorActionContextFormatter formatter = new EditorActionContextFormatter();
+        EditorActionContextFormat format = new EditorActionContextFormat(true, true, true, true, TargetInfoType.PROPERTIES, null);
+        format.tagName = null;
+        format.doTargetEntity = true;
+        contextInfo = formatter.format(editor.getEditorActionContext(), format);
+    }
+
+    @Override
+    public String getMessage() {
+        return super.getMessage() + " - context:" + contextInfo;
+    }
+
+
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage();
+    }
+
+}
