aboutsummaryrefslogtreecommitdiffstats
path: root/server/src/main
diff options
context:
space:
mode:
authorPekka Hyvönen <pekka@vaadin.com>2016-09-12 12:05:19 +0300
committerVaadin Code Review <review@vaadin.com>2016-09-12 13:07:22 +0000
commit28f5c845793be9a0db3f2acef914a68ccc91ce80 (patch)
tree42ba7c5cffc7de3a2412ffbd3cc6fa3605ddb606 /server/src/main
parent363dce92ae948aa7912007bb024c980de820851f (diff)
downloadvaadin-framework-28f5c845793be9a0db3f2acef914a68ccc91ce80.tar.gz
vaadin-framework-28f5c845793be9a0db3f2acef914a68ccc91ce80.zip
Refactor Binder Status Handling API
BinderStatusHandler is now triggered only once per validation. Unified ValidationError and BinderResult into BinderValidationStatus. Renamed ValidationStatusChangeEvent into ValidationStatus. Unified handler names for validation status. Next patch will fix resetting of field errors on reset. Change-Id: I9536d554d781fe599fbd7e5bcb5a9ffebe675ca0
Diffstat (limited to 'server/src/main')
-rw-r--r--server/src/main/java/com/vaadin/data/BeanBinder.java4
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java269
-rw-r--r--server/src/main/java/com/vaadin/data/BinderResult.java72
-rw-r--r--server/src/main/java/com/vaadin/data/BinderStatusHandler.java5
-rw-r--r--server/src/main/java/com/vaadin/data/BinderValidationStatus.java161
-rw-r--r--server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java50
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationError.java104
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationException.java60
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatus.java146
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java93
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatusHandler.java (renamed from server/src/main/java/com/vaadin/data/StatusChangeHandler.java)21
11 files changed, 544 insertions, 441 deletions
diff --git a/server/src/main/java/com/vaadin/data/BeanBinder.java b/server/src/main/java/com/vaadin/data/BeanBinder.java
index ff7e329660..cbabdb80cb 100644
--- a/server/src/main/java/com/vaadin/data/BeanBinder.java
+++ b/server/src/main/java/com/vaadin/data/BeanBinder.java
@@ -149,7 +149,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
protected BeanBindingImpl(BeanBinder<BEAN> binder,
HasValue<FIELDVALUE> field,
Converter<FIELDVALUE, TARGET> converter,
- StatusChangeHandler statusChangeHandler) {
+ ValidationStatusHandler statusChangeHandler) {
super(binder, field, converter, statusChangeHandler);
}
@@ -318,7 +318,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
@Override
protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> createBinding(
HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter,
- StatusChangeHandler handler) {
+ ValidationStatusHandler handler) {
Objects.requireNonNull(field, "field cannot be null");
Objects.requireNonNull(converter, "converter cannot be null");
return new BeanBindingImpl<>(this, field, converter, handler);
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index af5f429252..c282a89e77 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -300,15 +300,15 @@ public class Binder<BEAN> implements Serializable {
* default behavior).
* <p>
* This is just a shorthand for
- * {@link #withStatusChangeHandler(StatusChangeHandler)} method where
- * the handler instance hides the {@code label} if there is no error and
+ * {@link #withStatusHandler(StatusChangeHandler)} method where the
+ * handler instance hides the {@code label} if there is no error and
* shows it with validation error message if validation fails. It means
* that it cannot be called after
- * {@link #withStatusChangeHandler(StatusChangeHandler)} method call or
- * {@link #withStatusChangeHandler(StatusChangeHandler)} after this
- * method call.
+ * {@link #withStatusHandler(StatusChangeHandler)} method call or
+ * {@link #withStatusHandler(StatusChangeHandler)} after this method
+ * call.
*
- * @see #withStatusChangeHandler(StatusChangeHandler)
+ * @see #withStatusHandler(StatusChangeHandler)
* @see AbstractComponent#setComponentError(ErrorMessage)
* @param label
* label to show validation status for the field
@@ -316,11 +316,10 @@ public class Binder<BEAN> implements Serializable {
*/
public default Binding<BEAN, FIELDVALUE, TARGET> withStatusLabel(
Label label) {
- return withStatusChangeHandler(event -> {
- label.setValue(event.getMessage().orElse(""));
+ return withStatusHandler(status -> {
+ label.setValue(status.getMessage().orElse(""));
// Only show the label when validation has failed
- label.setVisible(
- ValidationStatus.ERROR.equals(event.getStatus()));
+ label.setVisible(status.isError());
});
}
@@ -353,19 +352,19 @@ public class Binder<BEAN> implements Serializable {
* status change handler
* @return this binding, for chaining
*/
- public Binding<BEAN, FIELDVALUE, TARGET> withStatusChangeHandler(
- StatusChangeHandler handler);
+ public Binding<BEAN, FIELDVALUE, TARGET> withStatusHandler(
+ ValidationStatusHandler handler);
/**
- * Validates the field value and returns a {@code Result} instance
- * representing the outcome of the validation.
+ * Validates the field value and returns a {@code ValidationStatus}
+ * instance representing the outcome of the validation.
*
* @see Binder#validate()
* @see Validator#apply(Object)
*
* @return the validation result.
*/
- public Result<TARGET> validate();
+ public ValidationStatus<TARGET> validate();
}
@@ -387,7 +386,7 @@ public class Binder<BEAN> implements Serializable {
private final HasValue<FIELDVALUE> field;
private Registration onValueChange;
- private StatusChangeHandler statusChangeHandler;
+ private ValidationStatusHandler statusHandler;
private boolean isStatusHandlerChanged;
private Function<BEAN, TARGET> getter;
@@ -414,11 +413,11 @@ public class Binder<BEAN> implements Serializable {
*/
protected BindingImpl(Binder<BEAN> binder, HasValue<FIELDVALUE> field,
Converter<FIELDVALUE, TARGET> converterValidatorChain,
- StatusChangeHandler statusChangeHandler) {
+ ValidationStatusHandler statusChangeHandler) {
this.field = field;
this.binder = binder;
this.converterValidatorChain = converterValidatorChain;
- this.statusChangeHandler = statusChangeHandler;
+ this.statusHandler = statusChangeHandler;
}
@Override
@@ -451,13 +450,12 @@ public class Binder<BEAN> implements Serializable {
Objects.requireNonNull(converter, "converter cannot be null");
return getBinder().createBinding(getField(),
- converterValidatorChain.chain(converter),
- statusChangeHandler);
+ converterValidatorChain.chain(converter), statusHandler);
}
@Override
- public Binding<BEAN, FIELDVALUE, TARGET> withStatusChangeHandler(
- StatusChangeHandler handler) {
+ public Binding<BEAN, FIELDVALUE, TARGET> withStatusHandler(
+ ValidationStatusHandler handler) {
checkUnbound();
Objects.requireNonNull(handler, "handler cannot be null");
if (isStatusHandlerChanged) {
@@ -465,7 +463,7 @@ public class Binder<BEAN> implements Serializable {
"A StatusChangeHandler has already been set");
}
isStatusHandlerChanged = true;
- statusChangeHandler = handler;
+ statusHandler = handler;
return this;
}
@@ -500,33 +498,31 @@ public class Binder<BEAN> implements Serializable {
private void bind(BEAN bean) {
setFieldValue(bean);
- onValueChange = getField().addValueChangeListener(e -> {
- binder.setHasChanges(true);
- storeFieldValue(bean, true);
- });
+ onValueChange = getField()
+ .addValueChangeListener(e -> handleFieldValueChange(bean));
}
@Override
- public Result<TARGET> validate() {
- BinderResult<FIELDVALUE, TARGET> bindingResult = getTargetValue();
- getBinder().getStatusHandler().accept(Arrays.asList(bindingResult));
- return bindingResult;
+ public ValidationStatus<TARGET> validate() {
+ ValidationStatus<TARGET> status = getTargetValue();
+ getBinder().getStatusHandler()
+ .accept(new BinderValidationStatus<>(getBinder(),
+ Arrays.asList(status), Collections.emptyList()));
+ return status;
}
/**
* Returns the field value run through all converters and validators,
- * but doesn't fire a {@link ValidationStatusChangeEvent status change
- * event}.
+ * but doesn't pass the {@link ValidationStatus} to any status handler.
*
* @return a result containing the validated and converted value or
* describing an error
*/
- private BinderResult<FIELDVALUE, TARGET> getTargetValue() {
+ private ValidationStatus<TARGET> getTargetValue() {
FIELDVALUE fieldValue = field.getValue();
Result<TARGET> dataValue = converterValidatorChain.convertToModel(
fieldValue, ((AbstractComponent) field).getLocale());
- return dataValue.biMap((value, message) -> new BinderResult<>(this,
- value, message));
+ return new ValidationStatus<>(this, dataValue);
}
private void unbind() {
@@ -552,44 +548,54 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Handles the value change triggered by the bound field.
+ *
+ * @param bean
+ * the new value
+ */
+ private void handleFieldValueChange(BEAN bean) {
+ binder.setHasChanges(true);
+ // store field value if valid
+ ValidationStatus<TARGET> fieldValidationStatus = storeFieldValue(
+ bean);
+ List<Result<?>> binderValidationResults;
+ // if all field level validations pass, run bean level validation
+ if (!getBinder().bindings.stream().map(BindingImpl::getTargetValue)
+ .anyMatch(ValidationStatus::isError)) {
+ binderValidationResults = binder.validateItem(bean);
+ } else {
+ binderValidationResults = Collections.emptyList();
+ }
+ binder.getStatusHandler()
+ .accept(new BinderValidationStatus<>(binder,
+ Arrays.asList(fieldValidationStatus),
+ binderValidationResults));
+ }
+
+ /**
* Saves the field value by invoking the setter function on the given
- * bean, if the value passes all registered validators. Optionally runs
- * item level validators if all field validators pass.
+ * bean, if the value passes all registered validators.
*
* @param bean
* the bean to set the property value to
- * @param runBeanLevelValidation
- * <code>true</code> to run item level validators if all
- * field validators pass, <code>false</code> to always skip
- * item level validators
*/
- private void storeFieldValue(BEAN bean,
- boolean runBeanLevelValidation) {
+ private ValidationStatus<TARGET> storeFieldValue(BEAN bean) {
assert bean != null;
+
+ ValidationStatus<TARGET> validationStatus = getTargetValue();
if (setter != null) {
- BinderResult<FIELDVALUE, TARGET> validationResult = getTargetValue();
- getBinder().getStatusHandler()
- .accept(Arrays.asList(validationResult));
- validationResult.ifOk(value -> setter.accept(bean, value));
- }
- if (runBeanLevelValidation && !getBinder().bindings.stream()
- .map(BindingImpl::getTargetValue)
- .anyMatch(Result::isError)) {
- binder.validateItem(bean);
+ validationStatus.getResult().ifPresent(result -> result
+ .ifOk(value -> setter.accept(bean, value)));
}
+ return validationStatus;
}
private void setBeanValue(BEAN bean, TARGET value) {
setter.accept(bean, value);
}
- private void fireStatusChangeEvent(Result<?> result) {
- ValidationStatusChangeEvent event = new ValidationStatusChangeEvent(
- getField(),
- result.isError() ? ValidationStatus.ERROR
- : ValidationStatus.OK,
- result.getMessage().orElse(null));
- statusChangeHandler.accept(event);
+ private void notifyStatusChangeHandler(ValidationStatus<?> status) {
+ statusHandler.accept(status);
}
}
@@ -806,9 +812,10 @@ public class Binder<BEAN> implements Serializable {
* if some of the bound field values fail to validate
*/
public void save(BEAN bean) throws ValidationException {
- List<ValidationError<?>> errors = doSaveIfValid(bean);
- if (!errors.isEmpty()) {
- throw new ValidationException(errors);
+ BinderValidationStatus<BEAN> status = doSaveIfValid(bean);
+ if (status.hasErrors()) {
+ throw new ValidationException(status.getFieldValidationErrors(),
+ status.getBeanValidationErrors());
}
}
@@ -833,7 +840,7 @@ public class Binder<BEAN> implements Serializable {
* updated, {@code false} otherwise
*/
public boolean saveIfValid(BEAN bean) {
- return doSaveIfValid(bean).isEmpty();
+ return doSaveIfValid(bean).isOk();
}
/**
@@ -845,13 +852,15 @@ public class Binder<BEAN> implements Serializable {
* @return a list of field validation errors if such occur, otherwise a list
* of bean validation errors.
*/
- private List<ValidationError<?>> doSaveIfValid(BEAN bean) {
+ private BinderValidationStatus<BEAN> doSaveIfValid(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
// First run fields level validation
- List<ValidationError<?>> errors = validateBindings();
+ List<ValidationStatus<?>> bindingStatuses = validateBindings();
// If no validation errors then update bean
- if (!errors.isEmpty()) {
- return errors;
+ if (bindingStatuses.stream().filter(ValidationStatus::isError).findAny()
+ .isPresent()) {
+ return new BinderValidationStatus<>(this, bindingStatuses,
+ Collections.emptyList());
}
// Save old bean values so we can restore them if validators fail
@@ -859,10 +868,11 @@ public class Binder<BEAN> implements Serializable {
bindings.forEach(binding -> oldValues.put(binding,
binding.convertDataToFieldType(bean)));
- bindings.forEach(binding -> binding.storeFieldValue(bean, false));
+ bindings.forEach(binding -> binding.storeFieldValue(bean));
// Now run bean level validation against the updated bean
- List<ValidationError<?>> itemValidatorErrors = validateItem(bean);
- if (!itemValidatorErrors.isEmpty()) {
+ List<Result<?>> binderResults = validateItem(bean);
+ if (binderResults.stream().filter(Result::isError).findAny()
+ .isPresent()) {
// Item validator failed, revert values
bindings.forEach((BindingImpl binding) -> binding.setBeanValue(bean,
oldValues.get(binding)));
@@ -870,7 +880,8 @@ public class Binder<BEAN> implements Serializable {
// Save successful, reset hasChanges to false
setHasChanges(false);
}
- return itemValidatorErrors;
+ return new BinderValidationStatus<>(this, bindingStatuses,
+ binderResults);
}
/**
@@ -894,68 +905,56 @@ public class Binder<BEAN> implements Serializable {
}
/**
- * Validates the values of all bound fields and returns the result of the
- * validation as a list of validation errors.
+ * Validates the values of all bound fields and returns the validation
+ * status.
* <p>
* If all field level validators pass, and {@link #bind(Object)} has been
* used to bind to an item, item level validators are run for that bean.
* Item level validators are ignored if there is no bound item or if any
* field level validator fails.
* <p>
- * Validation is successful if the returned list is empty.
*
- * @return a list of validation errors or an empty list if validation
- * succeeded
+ * @return validation status for the binder
*/
- public List<ValidationError<?>> validate() {
- List<ValidationError<?>> errors = validateBindings();
- if (!errors.isEmpty()) {
- return errors;
- }
-
- if (bean != null) {
- return validateItem(bean);
+ public BinderValidationStatus<BEAN> validate() {
+ List<ValidationStatus<?>> bindingStatuses = validateBindings();
+
+ BinderValidationStatus<BEAN> validationStatus;
+ if (bindingStatuses.stream().filter(ValidationStatus::isError).findAny()
+ .isPresent() || bean == null) {
+ validationStatus = new BinderValidationStatus<>(this,
+ bindingStatuses, Collections.emptyList());
+ } else {
+ validationStatus = new BinderValidationStatus<>(this,
+ bindingStatuses, validateItem(bean));
}
-
- return Collections.emptyList();
+ getStatusHandler().accept(validationStatus);
+ return validationStatus;
}
/**
* Validates the bindings and returns the result of the validation as a list
- * of validation errors.
- * <p>
- * If all validators pass, the resulting list is empty.
+ * of validation statuses.
* <p>
* Does not run bean validators.
- * <p>
- * All results are passed to the {@link #getStatusHandler() status change
- * handler.}
*
* @see #validateItem(Object)
*
- * @return a list of validation errors or an empty list if validation
- * succeeded
+ * @return an immutable list of validation results for bindings
*/
- private List<ValidationError<?>> validateBindings() {
- List<BinderResult<?, ?>> results = new ArrayList<>();
+ private List<ValidationStatus<?>> validateBindings() {
+ List<ValidationStatus<?>> results = new ArrayList<>();
for (BindingImpl<?, ?, ?> binding : bindings) {
results.add(binding.getTargetValue());
}
-
- getStatusHandler().accept(Collections.unmodifiableList(results));
-
- return results.stream().filter(r -> r.isError())
- .map(r -> new ValidationError<>(r.getBinding().get(),
- r.getField().get().getValue(), r.getMessage().get()))
- .collect(Collectors.toList());
+ return results;
}
/**
* Validates the {@code item} using item validators added using
* {@link #withValidator(Validator)} and returns the result of the
- * validation as a list of validation errors.
+ * validation as a list of validation results.
* <p>
- * If all validators pass, the resulting list is empty.
*
* @see #withValidator(Validator)
*
@@ -964,20 +963,12 @@ public class Binder<BEAN> implements Serializable {
* @return a list of validation errors or an empty list if validation
* succeeded
*/
- private List<ValidationError<?>> validateItem(BEAN bean) {
+ private List<Result<?>> validateItem(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
- List<BinderResult<?, ?>> results = Collections.unmodifiableList(
+ List<Result<?>> results = Collections.unmodifiableList(
validators.stream().map(validator -> validator.apply(bean))
- .map(dataValue -> dataValue.biMap(
- (value, message) -> new BinderResult<>(null,
- value, message)))
.collect(Collectors.toList()));
- getStatusHandler().accept(results);
-
- return results.stream()
- .filter(Result::isError).map(res -> new ValidationError<>(this,
- bean, res.getMessage().get()))
- .collect(Collectors.toList());
+ return results;
}
/**
@@ -1032,7 +1023,7 @@ public class Binder<BEAN> implements Serializable {
* @throws NullPointerException
* for <code>null</code> status handler
* @see #setStatusLabel(Label)
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
+ * @see Binding#withStatusHandler(StatusChangeHandler)
*/
public void setStatusHandler(BinderStatusHandler statusHandler) {
Objects.requireNonNull(statusHandler, "Cannot set a null "
@@ -1077,7 +1068,7 @@ public class Binder<BEAN> implements Serializable {
*/
protected <FIELDVALUE, TARGET> BindingImpl<BEAN, FIELDVALUE, TARGET> createBinding(
HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter,
- StatusChangeHandler handler) {
+ ValidationStatusHandler handler) {
return new BindingImpl<>(this, field, converter, handler);
}
@@ -1117,42 +1108,48 @@ public class Binder<BEAN> implements Serializable {
/**
* Default {@link StatusChangeHandler} functional method implementation.
*
- * @param event
- * the validation event
+ * @param status
+ * the validation status
*/
- protected void handleValidationStatusChange(
- ValidationStatusChangeEvent event) {
- HasValue<?> source = event.getSource();
+ protected void handleValidationStatusChange(ValidationStatus<?> status) {
+ HasValue<?> source = status.getField();
clearError(source);
- if (Objects.equals(ValidationStatus.ERROR, event.getStatus())) {
- handleError(source, event.getMessage().get());
+ if (status.isError()) {
+ handleError(source, status.getMessage().get());
}
}
/**
+ * Returns the bindings for this binder.
+ *
+ * @return a set of the bindings
+ */
+ protected Set<BindingImpl<BEAN, ?, ?>> getBindings() {
+ return bindings;
+ }
+
+ /**
* The default binder level status handler.
* <p>
* Passes all field related results to the Binding status handlers. All
* other status changes are displayed in the status label, if one has been
* set with {@link #setStatusLabel(Label)}.
*
- * @param results
- * a list of validation results from binding and/or item level
+ * @param binderStatus
+ * status of validation results from binding and/or item level
* validators
*/
- @SuppressWarnings("unchecked")
protected void defaultHandleBinderStatusChange(
- List<BinderResult<?, ?>> results) {
+ BinderValidationStatus<?> binderStatus) {
// let field events go to binding status handlers
- results.stream().filter(br -> br.getField().isPresent())
- .forEach(br -> ((BindingImpl<BEAN, ?, ?>) br.getBinding().get())
- .fireStatusChangeEvent(br));
+ binderStatus.getFieldValidationStatuses()
+ .forEach(status -> ((BindingImpl<?, ?, ?>) status.getBinding())
+ .notifyStatusChangeHandler(status));
// show first possible error or OK status in the label if set
if (getStatusLabel().isPresent()) {
- String statusMessage = results.stream()
- .filter(r -> !r.getField().isPresent())
- .map(Result::getMessage).map(m -> m.orElse("")).findFirst()
+ String statusMessage = binderStatus.getBeanValidationErrors()
+ .stream().findFirst().flatMap(Result::getMessage)
.orElse("");
getStatusLabel().get().setValue(statusMessage);
}
diff --git a/server/src/main/java/com/vaadin/data/BinderResult.java b/server/src/main/java/com/vaadin/data/BinderResult.java
deleted file mode 100644
index 52375b88ff..0000000000
--- a/server/src/main/java/com/vaadin/data/BinderResult.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.data;
-
-import java.util.Optional;
-
-import com.vaadin.data.Binder.Binding;
-
-/**
- * A result that keeps track of the possible binding (field) it belongs to.
- *
- * @param <FIELDVALUE>
- * the value type of the field
- * @param <VALUE>
- * the result value type and the data type of the binding, matches
- * the field type if a converter has not been set
- */
-public class BinderResult<FIELDVALUE, VALUE> extends SimpleResult<VALUE> {
-
- private final Binding<?, FIELDVALUE, VALUE> binding;
-
- /**
- * Creates a new binder result.
- *
- * @param binding
- * the binding where the result originated, may be {@code null}
- * @param value
- * the resut value, can be <code>null</code>
- * @param message
- * the error message of the result, may be {@code null}
- */
- public BinderResult(Binding<?, FIELDVALUE, VALUE> binding, VALUE value,
- String message) {
- super(value, message);
- this.binding = binding;
- }
-
- /**
- * Return the binding this result originated from, or an empty optional if
- * none.
- *
- * @return the optional binding
- */
- public Optional<Binding<?, FIELDVALUE, VALUE>> getBinding() {
- return Optional.ofNullable(binding);
- }
-
- /**
- * Return the field this result originated from, or an empty optional if
- * none.
- *
- * @return the optional field
- */
- public Optional<HasValue<FIELDVALUE>> getField() {
- return binding == null ? Optional.empty()
- : Optional.ofNullable(binding.getField());
- }
-
-} \ No newline at end of file
diff --git a/server/src/main/java/com/vaadin/data/BinderStatusHandler.java b/server/src/main/java/com/vaadin/data/BinderStatusHandler.java
index 4c516a5ed6..5da5106176 100644
--- a/server/src/main/java/com/vaadin/data/BinderStatusHandler.java
+++ b/server/src/main/java/com/vaadin/data/BinderStatusHandler.java
@@ -16,7 +16,6 @@
package com.vaadin.data;
import java.io.Serializable;
-import java.util.List;
import java.util.function.Consumer;
import com.vaadin.data.Binder.Binding;
@@ -34,13 +33,13 @@ import com.vaadin.data.Binder.Binding;
*
* @see Binder#setStatusHandler(BinderStatusHandler)
* @see Binder#setStatusLabel(com.vaadin.ui.Label)
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
+ * @see Binding#withStatusHandler(StatusChangeHandler)
*
* @author Vaadin Ltd
* @since 8.0
*
*/
public interface BinderStatusHandler
- extends Consumer<List<BinderResult<?, ?>>>, Serializable {
+ extends Consumer<BinderValidationStatus<?>>, Serializable {
}
diff --git a/server/src/main/java/com/vaadin/data/BinderValidationStatus.java b/server/src/main/java/com/vaadin/data/BinderValidationStatus.java
new file mode 100644
index 0000000000..f23d17e5b1
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/BinderValidationStatus.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import com.vaadin.data.Binder.Binding;
+
+/**
+ * Binder validation status change. Represents the outcome of binder level
+ * validation. Has information about the validation results for the
+ * {@link Binding#withValidator(Validator) field level} and
+ * {@link Binder#withValidator(Validator)binder level} validation.
+ * <p>
+ * Note: if there are any field level validation errors, the bean level
+ * validation is not run.
+ * <p>
+ * Use {@link Binder#setStatusHandler(BinderStatusHandler)} to handle form level
+ * validation status changes.
+ *
+ * @author Vaadin Ltd
+ *
+ * @param <BEAN>
+ * the bean type of the binder
+ *
+ * @see BinderValidationStatusHandler
+ * @see Binder#setStatusHandler(BinderStatusHandler)
+ * @see Binder#validate()
+ * @see ValidationStatus
+ *
+ * @since 8.0
+ */
+public class BinderValidationStatus<BEAN> implements Serializable {
+
+ private final Binder<BEAN> binder;
+ private final List<ValidationStatus<?>> bindingStatuses;
+ private final List<Result<?>> binderStatuses;
+
+ /**
+ * Creates a new binder validation status for the given binder and
+ * validation results.
+ *
+ * @param source
+ * the source binder
+ * @param bindingStatuses
+ * the validation results for the fields
+ * @param binderStatuses
+ * the validation results for binder level validation
+ */
+ public BinderValidationStatus(Binder<BEAN> source,
+ List<ValidationStatus<?>> bindingStatuses,
+ List<Result<?>> binderStatuses) {
+ Objects.requireNonNull(binderStatuses,
+ "binding statuses cannot be null");
+ Objects.requireNonNull(binderStatuses,
+ "binder statuses cannot be null");
+ this.binder = source;
+ this.bindingStatuses = Collections.unmodifiableList(bindingStatuses);
+ this.binderStatuses = Collections.unmodifiableList(binderStatuses);
+ }
+
+ /**
+ * Gets whether validation for the binder passed or not.
+ *
+ * @return {@code true} if validation has passed, {@code false} if not
+ */
+ public boolean isOk() {
+ return !hasErrors();
+ }
+
+ /**
+ * Gets whether the validation for the binder failed or not.
+ *
+ * @return {@code true} if validation failed, {@code false} if validation
+ * passed
+ */
+ public boolean hasErrors() {
+ return binderStatuses.stream().filter(Result::isError).findAny()
+ .isPresent()
+ || bindingStatuses.stream().filter(ValidationStatus::isError)
+ .findAny().isPresent();
+ }
+
+ /**
+ * Gets the source binder of the status.
+ *
+ * @return the source binder
+ */
+ public Binder<BEAN> getBinder() {
+ return binder;
+ }
+
+ /**
+ * Gets both field and bean level validation errors.
+ *
+ * @return a list of all validation errors
+ */
+ public List<Result<?>> getValidationErrors() {
+ ArrayList<Result<?>> errors = new ArrayList<>(getFieldValidationErrors()
+ .stream().map(s -> s.getResult().get())
+ .collect(Collectors.toList()));
+ errors.addAll(getBeanValidationErrors());
+ return errors;
+ }
+
+ /**
+ * Gets the field level validation statuses.
+ *
+ * @return the field validation statuses
+ */
+ public List<ValidationStatus<?>> getFieldValidationStatuses() {
+ return bindingStatuses;
+ }
+
+ /**
+ * Gets the bean level validation results.
+ *
+ * @return the bean level validation results
+ */
+ public List<Result<?>> getBeanValidationResults() {
+ return binderStatuses;
+ }
+
+ /**
+ * Gets the failed field level validation statuses.
+ *
+ * @return a list of failed field level validation statuses
+ */
+ public List<ValidationStatus<?>> getFieldValidationErrors() {
+ return bindingStatuses.stream().filter(ValidationStatus::isError)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Gets the failed bean level validation statuses.
+ *
+ * @return a list of failed bean level validation statuses
+ */
+ public List<Result<?>> getBeanValidationErrors() {
+ return binderStatuses.stream().filter(Result::isError)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java b/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java
new file mode 100644
index 0000000000..9528c5a884
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * Handler for {@link BinderValidationStatus} changes.
+ * <p>
+ * {{@link Binder#setStatusHandler(BinderStatusHandler) Register} an instance of
+ * this class to be able to customize validation status handling.
+ * <p>
+ * The default handler will show
+ * {@link AbstractComponent#setComponentError(com.vaadin.server.ErrorMessage) an
+ * error message} for failed field validations. For bean level validation errors
+ * it will display the first error message in
+ * {@link Binder#setStatusLabel(com.vaadin.ui.Label) status label}, if one has
+ * been set.
+ *
+ * @author Vaadin Ltd
+ *
+ * @param <BEAN>
+ * the bean type of the binder
+ *
+ * @see BinderValidationStatus
+ * @see Binder#validate()
+ * @see ValidationStatus
+ *
+ * @since 8.0
+ */
+public interface BinderValidationStatusHandler<BEAN>
+ extends Consumer<BinderValidationStatus<BEAN>>, Serializable {
+
+}
diff --git a/server/src/main/java/com/vaadin/data/ValidationError.java b/server/src/main/java/com/vaadin/data/ValidationError.java
deleted file mode 100644
index 1555ecad2c..0000000000
--- a/server/src/main/java/com/vaadin/data/ValidationError.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.data;
-
-import java.io.Serializable;
-import java.util.Objects;
-import java.util.Optional;
-
-import com.vaadin.data.Binder.Binding;
-
-/**
- * Represents a validation error.
- * <p>
- * A validation error is either connected to a field validator (
- * {@link #getField()} returns a non-empty optional) or to an item level
- * validator ({@link #getField()} returns an empty optional).
- *
- * @author Vaadin Ltd
- * @since 8.0
- *
- * @param <V>
- * the value type
- */
-public class ValidationError<V> implements Serializable {
-
- /**
- * This is either a {@link Binding} or a {@link Binder}.
- */
- private final Object source;
- private final String message;
- /**
- * This is either HasValue<V> value (in case of Binding) or bean (in case of
- * Binder).
- */
- private final V value;
-
- /**
- * Creates a new instance of ValidationError using the provided source
- * ({@link Binding} or {@link Binder}), value and error message.
- *
- * @param source
- * the validated binding or the binder
- * @param value
- * the invalid value
- * @param message
- * the validation error message, not {@code null}
- */
- public ValidationError(Object source, V value, String message) {
- Objects.requireNonNull(message, "message cannot be null");
- this.source = source;
- this.message = message;
- this.value = value;
- }
-
- /**
- * Returns a reference to the validated field or an empty optional if the
- * validation was not related to a single field.
- *
- * @return the validated field or an empty optional
- */
- @SuppressWarnings({ "unchecked", "rawtypes" })
- public Optional<HasValue<V>> getField() {
- if (source instanceof Binding) {
- return Optional.of(((Binding) source).getField());
- } else {
- return Optional.empty();
- }
- }
-
- /**
- * Returns a validation error message.
- *
- * @return the validation error message
- */
- public String getMessage() {
- return message;
- }
-
- /**
- * Returns the invalid value.
- * <p>
- * This is either the field value (if the validator error comes from a field
- * binding) or the bean (for item validators).
- *
- * @return the source value
- */
- public V getValue() {
- return value;
- }
-
-}
diff --git a/server/src/main/java/com/vaadin/data/ValidationException.java b/server/src/main/java/com/vaadin/data/ValidationException.java
index 41017a8393..68f7ad9e33 100644
--- a/server/src/main/java/com/vaadin/data/ValidationException.java
+++ b/server/src/main/java/com/vaadin/data/ValidationException.java
@@ -15,39 +15,75 @@
*/
package com.vaadin.data;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/**
* Indicates validation errors in a {@link Binder} when save is requested.
- *
+ *
* @see Binder#save(Object)
- *
+ *
* @author Vaadin Ltd
* @since 8.0
*
*/
public class ValidationException extends Exception {
- private final List<ValidationError<?>> errors;
+ private final List<ValidationStatus<?>> bindingValidationErrors;
+ private final List<Result<?>> binderValidationErrors;
/**
* Constructs a new exception with validation {@code errors} list.
- *
- * @param errors
- * validation errors list
+ *
+ * @param bindingValidationErrors
+ * binding validation errors list
+ * @param binderValidationErrors
+ * binder validation errors list
*/
- public ValidationException(List<ValidationError<?>> errors) {
+ public ValidationException(
+ List<ValidationStatus<?>> bindingValidationErrors,
+ List<Result<?>> binderValidationErrors) {
super("Validation has failed for some fields");
- this.errors = Collections.unmodifiableList(errors);
+ this.bindingValidationErrors = Collections
+ .unmodifiableList(bindingValidationErrors);
+ this.binderValidationErrors = Collections
+ .unmodifiableList(binderValidationErrors);
}
/**
- * Returns the validation errors list which caused the exception.
- *
- * @return validation errors list
+ * Gets both field and bean level validation errors.
+ *
+ * @return a list of all validation errors
*/
- public List<ValidationError<?>> getValidationError() {
+ public List<Result<?>> getValidationErrors() {
+ ArrayList<Result<?>> errors = new ArrayList<>(getFieldValidationErrors()
+ .stream().map(s -> s.getResult().get())
+ .collect(Collectors.toList()));
+ errors.addAll(getBeanValidationErrors());
return errors;
}
+
+ /**
+ * Returns a list of the binding level validation errors which caused the
+ * exception, or an empty list if was caused by
+ * {@link #getBeanValidationErrors() binder level validation errors}.
+ *
+ * @return binding validation errors list
+ */
+ public List<ValidationStatus<?>> getFieldValidationErrors() {
+ return bindingValidationErrors;
+ }
+
+ /**
+ * Returns a list of the binding level validation errors which caused the
+ * exception, or an empty list if was caused by
+ * {@link #getBeanValidationErrors() binder level validation errors}.
+ *
+ * @return binder validation errors list
+ */
+ public List<Result<?>> getBeanValidationErrors() {
+ return binderValidationErrors;
+ }
}
diff --git a/server/src/main/java/com/vaadin/data/ValidationStatus.java b/server/src/main/java/com/vaadin/data/ValidationStatus.java
index 03f76c5127..7203655e7b 100644
--- a/server/src/main/java/com/vaadin/data/ValidationStatus.java
+++ b/server/src/main/java/com/vaadin/data/ValidationStatus.java
@@ -15,21 +15,147 @@
*/
package com.vaadin.data;
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.Optional;
+
import com.vaadin.data.Binder.Binding;
/**
- * Validation status.
- * <p>
- * The status is the part of {@link ValidationStatusChangeEvent} which indicate
- * whether the validation failed or not.
- *
- * @see ValidationStatusChangeEvent
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
- * @see StatusChangeHandler
+ * Represents the outcome of field level validation. Use
+ * {@link Binding#withStatusChangeHandler(ValidationStatusHandler)} to register
+ * a handler for field level validation status changes.
*
* @author Vaadin Ltd
+ *
+ * @param <TARGET>
+ * the target data type of the binding for which the validation
+ * status changed, matches the field type until a converter has been
+ * set
+ *
+ * @see Binding#withStatusChangeHandler(ValidationStatusHandler)
+ * @see Binding#validate()
+ * @see ValidationStatusHandler
+ * @see BinderValidationStatus
+ *
* @since 8.0
*/
-public enum ValidationStatus {
- OK, ERROR;
+public class ValidationStatus<TARGET> implements Serializable {
+
+ /**
+ * Status of the validation.
+ * <p>
+ * The status is the part of {@link ValidationStatus} which indicates
+ * whether the validation failed or not.
+ */
+ public enum Status {
+ /** Validation passed. */
+ OK,
+ /** Validation failed. */
+ ERROR
+ }
+
+ private final Status status;
+ private final Result<TARGET> result;
+ private final Binding<?, ?, TARGET> binding;
+
+ /**
+ * Creates a new validation status for the given binding and validation
+ * result.
+ *
+ * @param source
+ * the source binding
+ * @param result
+ * the result of the validation
+ */
+ public ValidationStatus(Binding<?, ?, TARGET> source,
+ Result<TARGET> result) {
+ this(source, result.isError() ? Status.ERROR : Status.OK, result);
+ }
+
+ /**
+ * Creates a new status change event.
+ * <p>
+ * The {@code message} must be {@code null} if the {@code status} is
+ * {@link Status#OK}.
+ *
+ * @param source
+ * field whose status has changed, not {@code null}
+ * @param status
+ * updated status value, not {@code null}
+ * @param result
+ * the related result, may be {@code null}
+ */
+ public ValidationStatus(Binding<?, ?, TARGET> source, Status status,
+ Result<TARGET> result) {
+ Objects.requireNonNull(source, "Event source may not be null");
+ Objects.requireNonNull(status, "Status may not be null");
+ if (Objects.equals(status, Status.OK) && result.isError()
+ || Objects.equals(status, Status.ERROR) && !result.isError()) {
+ throw new IllegalStateException(
+ "Invalid validation status " + status + " for given result "
+ + (result == null ? "null" : result.toString()));
+ }
+ binding = source;
+ this.status = status;
+ this.result = result;
+ }
+
+ /**
+ * Gets status of the validation.
+ *
+ * @return status
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * Gets whether the validation failed or not.
+ *
+ * @return {@code true} if validation failed, {@code false} if validation
+ * passed
+ */
+ public boolean isError() {
+ return status == Status.ERROR;
+ }
+
+ /**
+ * Gets error validation message if status is {@link Status#ERROR}.
+ *
+ * @return an optional validation error status or an empty optional if
+ * status is not an error
+ */
+ public Optional<String> getMessage() {
+ return Optional.ofNullable(result).flatMap(Result::getMessage);
+ }
+
+ /**
+ * Gets the validation result if status is either {@link Status#OK} or
+ * {@link Status#ERROR} or an empty optional if status is
+ * {@link Status#UNRESOLVED}.
+ *
+ * @return the validation result
+ */
+ public Optional<Result<TARGET>> getResult() {
+ return Optional.ofNullable(result);
+ }
+
+ /**
+ * Gets the source binding of the validation status.
+ *
+ * @return the source binding
+ */
+ public Binding<?, ?, TARGET> getBinding() {
+ return binding;
+ }
+
+ /**
+ * Gets the bound field for this status.
+ *
+ * @return the field
+ */
+ public HasValue<?> getField() {
+ return getBinding().getField();
+ }
}
diff --git a/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java b/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java
deleted file mode 100644
index e021398341..0000000000
--- a/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.data;
-
-import java.util.EventObject;
-import java.util.Objects;
-import java.util.Optional;
-
-import com.vaadin.data.Binder.Binding;
-
-/**
- * Validation status change event which is fired each time when validation is
- * done. Use {@link Binding#withStatusChangeHandler(StatusChangeHandler)} method
- * to add your validation handler to listen to the event.
- *
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
- * @see StatusChangeHandler
- *
- * @author Vaadin Ltd
- * @since 8.0
- *
- */
-public class ValidationStatusChangeEvent extends EventObject {
-
- private final ValidationStatus status;
- private final String message;
-
- /**
- * Creates a new status change event.
- * <p>
- * The {@code message} must be null if the {@code status} is
- * {@link ValidationStatus#OK}.
- *
- * @param source
- * field whose status has changed, not {@code null}
- * @param status
- * updated status value, not {@code null}
- * @param message
- * error message if status is ValidationStatus.ERROR, may be
- * {@code null}
- */
- public ValidationStatusChangeEvent(HasValue<?> source,
- ValidationStatus status, String message) {
- super(source);
- Objects.requireNonNull(source, "Event source may not be null");
- Objects.requireNonNull(status, "Status may not be null");
- if (Objects.equals(status, ValidationStatus.OK) && message != null) {
- throw new IllegalStateException(
- "Message must be null if status is not an error");
- }
- this.status = status;
- this.message = message;
- }
-
- /**
- * Returns validation status of the event.
- *
- * @return validation status
- */
- public ValidationStatus getStatus() {
- return status;
- }
-
- /**
- * Returns error validation message if status is
- * {@link ValidationStatus#ERROR}.
- *
- * @return an optional validation error status or an empty optional if
- * status is not an error
- */
- public Optional<String> getMessage() {
- return Optional.ofNullable(message);
- }
-
- @Override
- public HasValue<?> getSource() {
- return (HasValue<?>) super.getSource();
- }
-
-}
diff --git a/server/src/main/java/com/vaadin/data/StatusChangeHandler.java b/server/src/main/java/com/vaadin/data/ValidationStatusHandler.java
index 790ac74fb1..932fe64575 100644
--- a/server/src/main/java/com/vaadin/data/StatusChangeHandler.java
+++ b/server/src/main/java/com/vaadin/data/ValidationStatusHandler.java
@@ -19,22 +19,25 @@ import java.io.Serializable;
import java.util.function.Consumer;
import com.vaadin.data.Binder.Binding;
+import com.vaadin.ui.AbstractComponent;
/**
- * Validation status change handler.
+ * Handler for {@link ValidationStatus} changes.
* <p>
- * Register an instance of this class using
- * {@link Binding#withStatusChangeHandler(StatusChangeHandler) to be able to
- * listen to validation status updates.
- *
- * @see Binding#withStatusChangeHandler(StatusChangeHandler)
- * @see ValidationStatusChangeEvent
+ * {@link Binding#withStatusHandler(StatusChangeHandler) Register} an instance
+ * of this class to be able to override the default handling, which is to show
+ * {@link AbstractComponent#setComponentError(com.vaadin.server.ErrorMessage) an
+ * error message} for failed field validations.
*
* @author Vaadin Ltd
+ *
+ * @see Binding#withStatusHandler(StatusChangeHandler)
+ * @see ValidationStatus
+ *
* @since 8.0
*
*/
-public interface StatusChangeHandler
- extends Consumer<ValidationStatusChangeEvent>, Serializable {
+public interface ValidationStatusHandler
+ extends Consumer<ValidationStatus<?>>, Serializable {
}