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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface MessageId { | |
String getId(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>org.os890.bv.addon</groupId> | |
<artifactId>cdi-bv-label-addon</artifactId> | |
<version>1.0.0</version> | |
</dependency> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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).