diff options
author | Artur Signell <artur@vaadin.com> | 2015-01-27 19:47:51 +0200 |
---|---|---|
committer | Henrik Paul <henrik@vaadin.com> | 2015-02-03 12:03:56 +0000 |
commit | 9cac6602821383dc2e03607066c0ea7ec7d01af7 (patch) | |
tree | 24b7239135eff169429f9e4549742fc7a6a2066e /server | |
parent | 04d52c41f48c9e8811a9d03c7ae0204e73f1bde2 (diff) | |
download | vaadin-framework-9cac6602821383dc2e03607066c0ea7ec7d01af7.tar.gz vaadin-framework-9cac6602821383dc2e03607066c0ea7ec7d01af7.zip |
Add methods for getting invalid fields from a FieldGroup (#13775)
* Method for retrieving all failing fields exceptions from a
CommitException
* Methods for handling commit errors in Grid (#16515)
* Show editor row validation errors only on the fields (#16509)
Change-Id: Iabef662579e4ccae3803a513205e46542c41cce2
Diffstat (limited to 'server')
3 files changed, 319 insertions, 20 deletions
diff --git a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java index 069cb2e153..8f1bf8b40a 100644 --- a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java +++ b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import com.vaadin.data.Item; import com.vaadin.data.Property; @@ -465,46 +466,74 @@ public class FieldGroup implements Serializable { try { firePreCommitEvent(); - List<InvalidValueException> invalidValueExceptions = commitFields(); + Map<Field<?>, InvalidValueException> invalidValueExceptions = commitFields(); if (invalidValueExceptions.isEmpty()) { firePostCommitEvent(); commitTransactions(); } else { - throwInvalidValueException(invalidValueExceptions); + throw new FieldGroupInvalidValueException( + invalidValueExceptions); } } catch (Exception e) { rollbackTransactions(); - - throw new CommitException("Commit failed", e); + throw new CommitException("Commit failed", this, e); } } - private List<InvalidValueException> commitFields() { - List<InvalidValueException> invalidValueExceptions = new ArrayList<InvalidValueException>(); + /** + * Tries to commit all bound fields one by one and gathers any validation + * exceptions in a map, which is returned to the caller + * + * @return a propertyId to validation exception map which is empty if all + * commits succeeded + */ + private Map<Field<?>, InvalidValueException> commitFields() { + Map<Field<?>, InvalidValueException> invalidValueExceptions = new HashMap<Field<?>, InvalidValueException>(); for (Field<?> f : fieldToPropertyId.keySet()) { try { f.commit(); } catch (InvalidValueException e) { - invalidValueExceptions.add(e); + invalidValueExceptions.put(f, e); } } return invalidValueExceptions; } - private void throwInvalidValueException( - List<InvalidValueException> invalidValueExceptions) { - if (invalidValueExceptions.size() == 1) { - throw invalidValueExceptions.get(0); - } else { - InvalidValueException[] causes = invalidValueExceptions - .toArray(new InvalidValueException[invalidValueExceptions - .size()]); + /** + * Exception which wraps InvalidValueExceptions from all invalid fields in a + * FieldGroup + * + * @since 7.4 + */ + public static class FieldGroupInvalidValueException extends + InvalidValueException { + private Map<Field<?>, InvalidValueException> invalidValueExceptions; - throw new InvalidValueException(null, causes); + /** + * Constructs a new exception with the specified validation exceptions. + * + * @param invalidValueExceptions + * a property id to exception map + */ + public FieldGroupInvalidValueException( + Map<Field<?>, InvalidValueException> invalidValueExceptions) { + super(null, invalidValueExceptions.values().toArray( + new InvalidValueException[invalidValueExceptions.size()])); + this.invalidValueExceptions = invalidValueExceptions; + } + + /** + * Returns a map containing fields which failed validation and the + * exceptions the corresponding validators threw. + * + * @return a map with all the invalid value exceptions + */ + public Map<Field<?>, InvalidValueException> getInvalidFields() { + return invalidValueExceptions; } } @@ -1006,26 +1035,64 @@ public class FieldGroup implements Serializable { return fieldName.toLowerCase().replace("_", ""); } + /** + * Exception thrown by a FieldGroup when the commit operation fails. + * + * Provides information about validation errors through + * {@link #getInvalidFields()} if the cause of the failure is that all bound + * fields did not pass validation + * + */ public static class CommitException extends Exception { + private FieldGroup fieldGroup; + public CommitException() { super(); - // TODO Auto-generated constructor stub + } + + public CommitException(String message, FieldGroup fieldGroup, + Throwable cause) { + super(message, cause); + this.fieldGroup = fieldGroup; } public CommitException(String message, Throwable cause) { super(message, cause); - // TODO Auto-generated constructor stub } public CommitException(String message) { super(message); - // TODO Auto-generated constructor stub } public CommitException(Throwable cause) { super(cause); - // TODO Auto-generated constructor stub + } + + /** + * Returns a map containing the fields which failed validation and the + * exceptions the corresponding validators threw. + * + * @since 7.4 + * @return a map with all the invalid value exceptions. Can be empty but + * not null + */ + public Map<Field<?>, InvalidValueException> getInvalidFields() { + if (getCause() instanceof FieldGroupInvalidValueException) { + return ((FieldGroupInvalidValueException) getCause()) + .getInvalidFields(); + } + return new HashMap<Field<?>, InvalidValueException>(); + } + + /** + * Returns the field group where the exception occurred + * + * @since 7.4 + * @return the field group + */ + public FieldGroup getFieldGroup() { + return fieldGroup; } } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 3c0afbc484..0ba771b283 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -48,6 +48,7 @@ import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.RpcDataProviderExtension; import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper; +import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory; import com.vaadin.data.fieldgroup.FieldGroup; import com.vaadin.data.fieldgroup.FieldGroup.BindException; @@ -90,6 +91,7 @@ import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState; import com.vaadin.shared.ui.grid.HeightMode; import com.vaadin.shared.ui.grid.ScrollDestination; import com.vaadin.shared.util.SharedUtil; +import com.vaadin.ui.Notification.Type; import com.vaadin.ui.renderer.ObjectRenderer; import com.vaadin.ui.renderer.Renderer; import com.vaadin.util.ReflectTools; @@ -239,6 +241,102 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** + * Error handler for the editor + */ + public interface EditorErrorHandler extends Serializable { + + /** + * Called when an exception occurs while the editor row is being saved + * + * @param event + * An event providing more information about the error + */ + void commitError(CommitErrorEvent event); + } + + /** + * An event which is fired when saving the editor fails + */ + public static class CommitErrorEvent extends Component.Event { + + private CommitException cause; + + public CommitErrorEvent(Grid grid, CommitException cause) { + super(grid); + this.cause = cause; + } + + /** + * Retrieves the cause of the failure + * + * @return the cause of the failure + */ + public CommitException getCause() { + return cause; + } + + @Override + public Grid getComponent() { + return (Grid) super.getComponent(); + } + + /** + * Checks if validation exceptions caused this error + * + * @return true if the problem was caused by a validation error + */ + public boolean isValidationFailure() { + return cause.getCause() instanceof InvalidValueException; + } + + } + + /** + * Default error handler for the editor + * + */ + public class DefaultEditorErrorHandler implements EditorErrorHandler { + + @Override + public void commitError(CommitErrorEvent event) { + Map<Field<?>, InvalidValueException> invalidFields = event + .getCause().getInvalidFields(); + + if (!invalidFields.isEmpty()) { + // Validation error, show first failure as + // "<Column header>: <message>" + FieldGroup fieldGroup = event.getCause().getFieldGroup(); + Object propertyId = getFirstPropertyId(fieldGroup, + invalidFields.keySet()); + Field<?> field = fieldGroup.getField(propertyId); + String caption = getColumn(propertyId).getHeaderCaption(); + // TODO This should be shown in the editor component once + // there is a place for that. Optionally, all errors should be + // shown + Notification.show(caption + ": " + + invalidFields.get(field).getLocalizedMessage(), + Type.ERROR_MESSAGE); + + } else { + com.vaadin.server.ErrorEvent.findErrorHandler(Grid.this).error( + new ConnectorErrorEvent(Grid.this, event.getCause())); + } + } + + private Object getFirstPropertyId(FieldGroup fieldGroup, + Set<Field<?>> keySet) { + for (Column c : getColumns()) { + Object propertyId = c.getPropertyId(); + Field<?> f = fieldGroup.getField(propertyId); + if (keySet.contains(f)) { + return propertyId; + } + } + return null; + } + } + + /** * Selection modes representing built-in {@link SelectionModel * SelectionModels} that come bundled with {@link Grid}. * <p> @@ -2622,6 +2720,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ private boolean defaultContainer = true; + private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler(); + private static final Method SELECTION_CHANGE_METHOD = ReflectTools .findMethod(SelectionListener.class, "select", SelectionEvent.class); @@ -2861,6 +2961,15 @@ public class Grid extends AbstractComponent implements SelectionNotifier, try { saveEditor(); success = true; + } catch (CommitException e) { + try { + getEditorErrorHandler().commitError( + new CommitErrorEvent(Grid.this, e)); + } catch (Exception ee) { + // A badly written error handler can throw an exception, + // which would lock up the Grid + handleError(ee); + } } catch (Exception e) { handleError(e); } @@ -4713,6 +4822,35 @@ public class Grid extends AbstractComponent implements SelectionNotifier, editorFieldGroup.setFieldFactory(fieldFactory); } + /** + * Sets the error handler for the editor. + * + * The error handler is called whenever there is an exception in the editor. + * + * @param editorErrorHandler + * The editor error handler to use + * @throws IllegalArgumentException + * if the error handler is null + */ + public void setEditorErrorHandler(EditorErrorHandler editorErrorHandler) + throws IllegalArgumentException { + if (editorErrorHandler == null) { + throw new IllegalArgumentException( + "The error handler cannot be null"); + } + this.editorErrorHandler = editorErrorHandler; + } + + /** + * Gets the error handler used for the editor + * + * @see #setErrorHandler(com.vaadin.server.ErrorHandler) + * @return the editor error handler, never null + */ + public EditorErrorHandler getEditorErrorHandler() { + return editorErrorHandler; + } + @Override public void addItemClickListener(ItemClickListener listener) { addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, diff --git a/server/tests/src/com/vaadin/tests/server/component/fieldgroup/FieldGroupTest.java b/server/tests/src/com/vaadin/tests/server/component/fieldgroup/FieldGroupTest.java index da86c83771..d77a2e190b 100644 --- a/server/tests/src/com/vaadin/tests/server/component/fieldgroup/FieldGroupTest.java +++ b/server/tests/src/com/vaadin/tests/server/component/fieldgroup/FieldGroupTest.java @@ -15,10 +15,23 @@ */ package com.vaadin.tests.server.component.fieldgroup; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + import org.junit.Assert; import org.junit.Test; +import com.vaadin.data.Item; +import com.vaadin.data.Property; +import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.fieldgroup.FieldGroup; +import com.vaadin.data.fieldgroup.FieldGroup.CommitException; +import com.vaadin.data.util.AbstractProperty; +import com.vaadin.ui.Field; import com.vaadin.ui.TextField; /** @@ -52,4 +65,85 @@ public class FieldGroupTest { Assert.assertFalse("Field is not writable", field.isReadOnly()); } + + @Test + public void commit_validationFailed_allValidationFailuresAvailable() + throws CommitException { + FieldGroup fieldGroup = new FieldGroup(); + + fieldGroup.setItemDataSource(new TestItem()); + + TextField field1 = new TextField(); + field1.setRequired(true); + fieldGroup.bind(field1, "prop1"); + + TextField field2 = new TextField(); + field2.setRequired(true); + fieldGroup.bind(field2, "prop2"); + + Set<TextField> set = new HashSet<TextField>(Arrays.asList(field1, + field2)); + + try { + fieldGroup.commit(); + Assert.fail("No commit exception is thrown"); + } catch (CommitException exception) { + Map<Field<?>, ? extends InvalidValueException> invalidFields = exception + .getInvalidFields(); + for (Entry<Field<?>, ? extends InvalidValueException> entry : invalidFields + .entrySet()) { + set.remove(entry.getKey()); + } + Assert.assertEquals( + "Some fields are not found in the invalid fields map", 0, + set.size()); + Assert.assertEquals( + "Invalid value exception should be thrown for each field", + 2, invalidFields.size()); + } + } + + private static class TestItem implements Item { + + @Override + public Property<String> getItemProperty(Object id) { + return new StringProperty(); + } + + @Override + public Collection<?> getItemPropertyIds() { + return Arrays.asList("prop1", "prop2"); + } + + @Override + public boolean addItemProperty(Object id, Property property) + throws UnsupportedOperationException { + return false; + } + + @Override + public boolean removeItemProperty(Object id) + throws UnsupportedOperationException { + return false; + } + + } + + private static class StringProperty extends AbstractProperty<String> { + + @Override + public String getValue() { + return null; + } + + @Override + public void setValue(String newValue) throws Property.ReadOnlyException { + } + + @Override + public Class<? extends String> getType() { + return String.class; + } + } + } |