I have a data transfer object that’s annotated with JSR-303 constraints like…
public class AssetOwnedDailyLocatableId implements Serializable, AssetOwned, HasOperatingDay, Locatable {
private static final long serialVersionUID = 1L;
@NotEmpty
@Size(min = 1, max = 30)
private String locationName;
@NotEmpty
private String operatingDay;
@NotEmpty
@Size(min = 1, max = 30)
private String assetOwner;
I am attempting to use Annotation Processing to enrich each JSR-303 constraint with a message attribute whose value would be equal to the constraint-name.class-name.member-name.
E.g., using the above, the final generated output for the locationName field’s annotations would look like…
@NotEmpty(message="{NotEmpty.AssetOwnedDailyLocatableId.locationName}")
@Size(min = 1, max = 30, message="{Size.AssetOwnedDailyLocatableId.locationName}")
private String locationName;
Why? Because I want complete control over custom validation messaging. I have well over hundreds of data transfer objects that I would like to process with something like…
/**
* ViolationConstraint message processor. During compile time it scans all DTO
* classes that have <code>javax.validation.constrants.*</code> or
* <code>org.hibernate.validator.constraints.*</code>annotated
* fields, then enriches the annotation with a <code>message</code> attribute
* where its value will be <code>constraint-name.class-name.field-name</code>.
*
* @param <T>
* any JSR-303 annotation type
*
*/
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes(value = { "javax.validation.constraints.*", "org.hibernate.validator.constraints.*" })
public class ValidationMessagesProcessor<T extends Annotation> extends AbstractProcessor {
private static final String JAVAX_PATH = "javax.validation.constraints.*";
private static final String HIBERNATE_PATH = "org.hibernate.validator.constraints/*";
private PackageUtil<T> util;
public ValidationMessagesProcessor() {
super();
util = new PackageUtil<T>();
}
/* (non-Javadoc)
* @see javax.annotation.processing.AbstractProcessor#process(java.util.Set, javax.annotation.processing.RoundEnvironment)
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
if (!roundEnvironment.processingOver()) {
String message;
message = ValidationMessagesProcessor.class.getName() + " will begin processing now...";
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
try {
final List<Class<T>> annotationTypes = new ArrayList<Class<T>>();
final List<Class<T>> jxTypes = util.listMatchingClasses(JAVAX_PATH);
final List<Class<T>> hibTypes = util.listMatchingClasses(HIBERNATE_PATH);
annotationTypes.addAll(jxTypes);
annotationTypes.addAll(hibTypes);
for (final Element e : roundEnvironment.getRootElements()) {
// TODO Do the real work!
/*message = "... JSR-303 annotation '" + a.annotationType().getClass().getName() + "' found in "
+ e.getSimpleName();
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message); */
}
} catch (final IOException ioe) {
message = "Failed to locate javax.validation.constraints or org.hibernate.validator.constraints classes on classpath!";
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message);
}
}
return true; // no further processing of this annotation type
}
}
I want to know if the above approach is feasible, or if I should try something else (that might be simpler). Furthermore, if it is feasible, some direction on what to implement within the //TODO section of the processor above. So far I’ve consulted…
- http://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html#resources
- http://blog.xebia.com/2009/07/21/testing-annotation-processors/
- The drawbacks of annotation processing in Java?
- Writing an annotation processor for maven-processor-plugin
- How to use custom annotation processor with Maven 2?
- Can I get from a TypeVariable or VariableElement to a list of Methods on the underlying class In an annotation processor at compile time
So I opted for authoring a utility based on Eclipse JDT.
Took me a while to hunt down all the dependent libs to make this work. For anyone else interested here’s the Maven dependencies:
I authored four classes one with main harness and the others a facade and utils.
The harness:
The utils:
The facade: