recently i was asked if it is possible to load bv-violation-messages from a database. furthermore, it would be beneficial to re-use custom concepts for type-safe messages. in such cases apache codi and later on deltaspike helped a lot to benefit from (or implement) such features easily. however, adding such frameworks isn't always an option (although they would help a lot) and in some cases there are still some limits.
so i extracted some parts from extval, codi and deltaspike and combined it with new ideas.
without type-safe messages available with deltaspike, i usually use something like the following pragmatic approach for type-safe messages:
public interface MessageId { | |
String getId(); | |
} |
public enum MyMessage implements MessageId { | |
MSG_01(), | |
MSG_02(); | |
@Override | |
public String getId() { | |
return name(); | |
} | |
} |
to allow at least one enum-type per bv-constraint we can benefit from the composite-constraint feature which is available since v1.0. with that we can define e.g.:
@ReportAsSingleViolation | |
@Size | |
@Constraint(validatedBy = {}) | |
@Target({FIELD, METHOD, PARAMETER}) | |
@Retention(RUNTIME) | |
public @interface Length { | |
String message() default "{}"; //triggers the delegation to #messageId | |
Class<?>[] groups() default {}; | |
Class<? extends Payload>[] payload() default {}; | |
MyMessage messageId(); | |
MyLabel propertyLabel(); | |
@OverridesAttribute(constraint = Size.class, name = "min") | |
int min() default 0; | |
@OverridesAttribute(constraint = Size.class, name = "max") | |
int max() default Integer.MAX_VALUE; | |
//... | |
} |
to summarize it, we would like e.g. to store the violation-text:
The length of '{propertyLabel}' should be between '{min}' and '{max}'
in a database, create a custom enum which implements a custom interface like MessageId and optionally (type-safe) labels which support i18n (the label-texts are stored in message-sources as well).
for the property-label we also create the corresponding entries in the database.
using those parts with @Length would e.g. look like:
public class Person { | |
@Length(min = 1, max = 50, messageId = MY_LENGTH_MSG, propertyLabel = FIRST_NAME) | |
private String fName; | |
@Length(min = 2, max = 50, messageId = MY_LENGTH_MSG, propertyLabel = LAST_NAME) | |
private String lName; | |
} |
The length of 'Surname' should be between '2' and '50'
the propertyLabel attribute can be a string following the std. key-format to support i18n. alternatively it can be also a custom enum (implementing a custom interface). it isn't the same, but similar to the type-safe message-enum itself. due to the autom. interpolation the label-enum needs to be slightly different - e.g.:
public enum MyLabel implements LabelId { | |
FIRST_NAME("firstName"), LAST_NAME("lastName"); | |
private final String key; | |
MyLabel(String key) { | |
this.key = "{" + key + "}"; | |
} | |
@Override | |
public String getId() { | |
return key; | |
} | |
@Override | |
public String toString() { | |
return key; | |
} | |
} |
the (custom) LabelId interface is just used to distinguish labels from the message itself. since the label is a constraint-attribute it gets interpolated by bv itself and therefore we need to force the key-format to identify the parts which need to be resolved in an own step.
to realize all that you just need to add the ds-bv-label-addon e.g. by adding https://rawgit.com/os890/ds-bv-label-addon/public as maven-repository and afterwards the maven-dependency as well as the custom message-interpolator:
<dependency> | |
<groupId>org.os890.bv.addon</groupId> | |
<artifactId>cdi-bv-label-addon</artifactId> | |
<version>1.0.0</version> | |
</dependency> |
<validation-config | |
xmlns="http://jboss.org/xml/ns/javax/validation/configuration" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.1.xsd" | |
version="1.1"> | |
<message-interpolator>org.os890.bv.addon.label.impl.AdvancedMessageInterpolator</message-interpolator> | |
</validation-config> |
@ApplicationScoped | |
public class DbMessageSourceAdapter implements MessageSourceAdapter { | |
@Inject | |
private ViolationMessageDao violationMessageDao; | |
@Override | |
public String resolveMessage(String key, Locale locale) { | |
return violationMessageDao.findByKey(key); | |
} | |
} |
if you like to use message- and/or label-enums you can implement one or more beans like:
@ApplicationScoped | |
public class EnumLabelsResolver implements LabelResolver { | |
@Override | |
public List<String> resolveLabelsFor(ConstraintDescriptor<?> constraintDescriptor) { | |
List<String> result = new ArrayList<>(); | |
for (Method annotationMethod : constraintDescriptor.getAnnotation().annotationType().getDeclaredMethods()) { | |
if (LabelId.class.isAssignableFrom(annotationMethod.getReturnType())) { | |
try { | |
LabelId labelId = (LabelId) annotationMethod.invoke(constraintDescriptor.getAnnotation()); | |
result.add(labelId.getId()); | |
} catch (Exception e) { | |
throw ExceptionUtils.throwAsRuntimeException(e); | |
} | |
} | |
} | |
return result; | |
} | |
} |
@ApplicationScoped | |
public class EnumMessageTemplateResolver implements MessageTemplateResolver { | |
@Override | |
public String resolveMessageTemplateFor(ConstraintDescriptor<?> constraintDescriptor) { | |
for (Method annotationMethod : constraintDescriptor.getAnnotation().annotationType().getDeclaredMethods()) { | |
if (MessageId.class.isAssignableFrom(annotationMethod.getReturnType())) { | |
try { | |
MessageId messageId = (MessageId) annotationMethod.invoke(constraintDescriptor.getAnnotation()); | |
return "{" + messageId.getId() + "}"; | |
} catch (Exception e) { | |
throw ExceptionUtils.throwAsRuntimeException(e); | |
} | |
} | |
} | |
return null; | |
} | |
} |
the git-repository illustrates different use-cases including one which shows how to share one constraint between different modules without using a shared message-enum (but still keep the type-safe approach) by using a shared composite-constraint in combination with a composite-constraint per module (which delegate to the shared composite-constraint).