Reputation: 3458
I am constructing object out of configuration using jaxb. Until now I wrote custom function for validation but I would like to move into annotations.
e.g.:
@XmlElement
public void setNumber(Integer i){
if (i<10 || i>20) throw new IllegalArgumentException(...);
this.number=i;
}
The exceptions from this above approach were descriptive and gave me the position of the error in the xml.
I want to move into this:
@XmlElement
@Min(10)
@Max(20)
public void setNumber(Integer i){
this.number=i;
}
I can verify this by reading annotations in afterMarshal and running validation functions according to property annotations, but then I lose the actual place (in the xml) where the error occurred.
Do you have any though, should I use a different approach/framework for this problem?
EDIT: just to clarify, I must use annotation approach because I need the properties constraints metadata for the configuration editor I am writing
Upvotes: 5
Views: 3332
Reputation: 12491
You should look at https://github.com/krasa/krasa-jaxb-tools which is improved and mavenized version of Vicente's code.
Upvotes: 2
Reputation: 1357
Here is a XJC plugin that I have used to solve this problem myself (in my case I needed annotations to do validation independant of XML schema, as I also have JMS endpoints):
What it does:
-It generates @valid annotation for objects that are not in the xs default schema (so annotations are cascaded)
-It generates @NotNull annotation for objects that has a MinOccur value >= 1 or for attributes with required use
-It generates @Size for lists that have minOccurs > 1
-It generates @Size if there is a maxLength or minLength restriction
-@DecimalMax for maxInclusive restriction
-@DecimalMin for minInclusive restriction
-@Digits if there is a totalDigits or fractionDigits restriction.
-@Pattern if there is a Pattern restriction
Please note that minExclusive and maxExclusive restrictions are excluded.
To use it, you have to package the class file along a META-INF/services/com.sun.tools.xjc.Plugin file with the content "com.sun.tools.xjc.addon.jaxb.JaxbValidationsPlugins" (that is, the fully qualified name of the class) and call XJC with the -XValidate switch.
It's not really difficult to implement, but I hope it will be useful for someone. The source code is attached as a TXT file. Enjoy!
package com.sun.tools.xjc.addon.jaxb;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.xml.sax.ErrorHandler;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JFieldVar;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.model.CAttributePropertyInfo;
import com.sun.tools.xjc.model.CElementPropertyInfo;
import com.sun.tools.xjc.model.CPropertyInfo;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
import com.sun.xml.xsom.XSComponent;
import com.sun.xml.xsom.XSSimpleType;
import com.sun.xml.xsom.impl.AttributeUseImpl;
import com.sun.xml.xsom.impl.ElementDecl;
import com.sun.xml.xsom.impl.ParticleImpl;
public class JaxbValidationsPlugins extends Plugin {
public String getOptionName() {
return "Xvalidate";
}
public List<String> getCustomizationURIs() {
return Collections.singletonList(namespace);
}
private String namespace = "http://jaxb.dev.java.net/plugin/code-injector";
public boolean isCustomizationTagName(String nsUri, String localName) {
return nsUri.equals(namespace) && localName.equals("code");
}
public String getUsage() {
return " -Xvalidate : inject Bean validation annotations (JSR 303)";
}
public boolean run(Outline model, Options opt, ErrorHandler errorHandler) {
try {
for (ClassOutline co : model.getClasses()) {
for (CPropertyInfo property : co.target.getProperties()) {
if (property instanceof CElementPropertyInfo) {
recorrePropiedad((CElementPropertyInfo) property, co, model);
} else if (property instanceof CAttributePropertyInfo) {
recorrePropiedad((CAttributePropertyInfo) property, co, model);
}
}
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
static int i = 0;
/**
* XS:Element
*
* @param property
* @param clase
* @param model
*/
public void recorrePropiedad(CElementPropertyInfo property, ClassOutline clase, Outline model) {
FieldOutline field = model.getField(property);
XSComponent definicion = property.getSchemaComponent();
ParticleImpl particle = (ParticleImpl) definicion;
int maxOccurs = ((BigInteger) getField("maxOccurs", particle)).intValue();
int minOccurs = ((BigInteger) getField("minOccurs", particle)).intValue();
JFieldVar var = (JFieldVar) clase.implClass.fields().get(getField("privateName", property));
if (minOccurs < 0 || minOccurs >= 1) {
if (!hasAnnotation(var, NotNull.class)) {
System.out.println("@NotNull: " + property.getName() + " de la clase " + clase.implClass.name());
var.annotate(NotNull.class);
}
}
if(maxOccurs>1){
if (!hasAnnotation(var, Size.class)) {
System.out.println("@Size ("+minOccurs+","+maxOccurs+") " + property.getName() + " de la clase " + clase.implClass.name());
var.annotate(Size.class).param("min", minOccurs).param("max", maxOccurs);
}
}
ElementDecl declaracion = (ElementDecl) getField("term", particle);
if (declaracion.getType().getTargetNamespace().startsWith("http://hotelbeds.com")) {
if (!hasAnnotation(var, Valid.class)) {
System.out.println("@Valid: " + property.getName() + " de la clase " + clase.implClass.name());
var.annotate(Valid.class);
}
}
if (declaracion.getType() instanceof XSSimpleType) {
procesaType((XSSimpleType) declaracion.getType(), var, property.getName(), clase.implClass.name());
} else if (declaracion.getType().getBaseType() instanceof XSSimpleType) {
procesaType((XSSimpleType) declaracion.getType().getBaseType(), var, property.getName(), clase.implClass.name());
}
// if(declaracion.getType() instanceof
// if(declaracion.getType().ge)
// procesaType(declaracion.getType().getBaseType(),var);
}
/**
* XS:Attribute
*
* @param property
* @param clase
* @param model
*/
public void recorrePropiedad(CAttributePropertyInfo property, ClassOutline clase, Outline model) {
FieldOutline field = model.getField(property);
System.out.println("Tratando attributo " + property.getName() + " de la clase " + clase.implClass.name());
XSComponent definicion = property.getSchemaComponent();
AttributeUseImpl particle = (AttributeUseImpl) definicion;
JFieldVar var = (JFieldVar) clase.implClass.fields().get(getField("privateName", property));
if (particle.isRequired()) {
if (!hasAnnotation(var, NotNull.class)) {
System.out.println("@NotNull: " + property.getName() + " de la clase " + clase.implClass.name());
var.annotate(NotNull.class);
}
}
if (particle.getDecl().getType().getTargetNamespace().startsWith("http://hotelbeds.com")) {
if (!hasAnnotation(var, Valid.class)) {
System.out.println("@Valid: " + property.getName() + " de la clase " + clase.implClass.name());
var.annotate(Valid.class);
}
}
procesaType(particle.getDecl().getType(), var, property.getName(), clase.implClass.name());
}
public void procesaType(XSSimpleType tipo, JFieldVar field, String campo, String clase) {
if (tipo.getFacet("maxLength") != null || tipo.getFacet("minLength") != null) {
Integer maxLength = tipo.getFacet("maxLength") == null ? null : parseInt(tipo.getFacet("maxLength").getValue().value);
Integer minLength = tipo.getFacet("minLength") == null ? null : parseInt(tipo.getFacet("minLength").getValue().value);
if (!hasAnnotation(field, Size.class)) {
System.out.println("@Size(" + minLength + "," + maxLength + "): " + campo + " de la clase " + clase);
field.annotate(Size.class).param("min", minLength).param("max", maxLength);
}
}
/*
* <bindings multiple="true" node=
* "//xs:complexType/.//xs:element[contains(@type,'IntPercentRestriction')]"
* > <annox:annotate> <annox:annotate
* annox:class="javax.validation.constraints.Digits" integer="3"
* fraction="2" /> <annox:annotate
* annox:class="javax.validation.constraints.Min" value="-100" />
* value="100" /> </annox:annotate> </bindings>
*//*
* <xs:restriction base="xs:decimal"> <xs:fractionDigits value="2"/>
* <xs:maxInclusive value="100.00"/> <xs:minInclusive
* value="-100.00"/> <xs:totalDigits value="5"/> </xs:restriction>
*/
if (tipo.getFacet("maxInclusive") != null && tipo.getFacet("maxInclusive").getValue().value != null && !hasAnnotation(field,DecimalMax.class)){
System.out.println("@DecimalMax(" + tipo.getFacet("maxInclusive").getValue().value + "): " + campo + " de la clase " + clase);
field.annotate(DecimalMax.class).param("value", tipo.getFacet("maxInclusive").getValue().value);
}
if (tipo.getFacet("minInclusive") != null && tipo.getFacet("minInclusive").getValue().value != null && !hasAnnotation(field,DecimalMin.class)){
System.out.println("@DecimalMin(" + tipo.getFacet("minInclusive").getValue().value + "): " + campo + " de la clase " + clase);
field.annotate(DecimalMin.class).param("value", tipo.getFacet("minInclusive").getValue().value);
}
if (tipo.getFacet("totalDigits") != null) {
Integer totalDigits = tipo.getFacet("totalDigits") == null ? null : parseInt(tipo.getFacet("totalDigits").getValue().value);
int fractionDigits = tipo.getFacet("fractionDigits") == null ? 0 : parseInt(tipo.getFacet("fractionDigits").getValue().value);
if (!hasAnnotation(field, Digits.class)) {
System.out.println("@Digits(" + totalDigits + "," + fractionDigits + "): " + campo + " de la clase " + clase);
JAnnotationUse annox = field.annotate(Digits.class).param("integer", (totalDigits - fractionDigits));
if (tipo.getFacet("fractionDigits") != null) {
annox.param("fraction", fractionDigits);
}
}
}
/**
* <annox:annotate annox:class="javax.validation.constraints.Pattern"
message="Name can only contain capital letters, numbers and the simbols '-', '_', '/', ' '"
regexp="^[A-Z0-9_\s//-]*" />
*/
if(tipo.getFacet("pattern")!=null){
System.out.println("@Pattern(" +tipo.getFacet("pattern").getValue().value+ "): " + campo + " de la clase " + clase);
if (!hasAnnotation(field, Pattern.class)) {
field.annotate(Pattern.class).param("regexp", tipo.getFacet("pattern").getValue().value);
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean hasAnnotation(JFieldVar var, Class anotacion) {
List<JAnnotationUse> lista = (List<JAnnotationUse>) getField("annotations", var);
if (lista != null) {
for (JAnnotationUse uso : lista) {
if (((Class) getField("clazz._class", uso)).getCanonicalName().equals(anotacion.getCanonicalName())) {
return true;
}
}
}
return false;
}
private Integer parseInt(String valor) {
try {
Integer i = Integer.parseInt(valor);
if (i < 2147483647 && i > -2147483648) {
return i;
}
} catch (Exception e) {
try{
return (int)Math.round(Double.parseDouble(valor));
}catch(Exception ex){
;
}
}
return null;
}
/*
private Long parseLong(String valor) {
try {
Long i = Long.parseLong(valor);
if (i < 2147483647 && i > -2147483648) {
return i;
}
} catch (Exception e) {
return Math.round(Double.parseDouble(valor));
}
return null;
}
*/
private Object getField(String path, Object oo) {
try {
if (path.contains(".")) {
String field = path.substring(0, path.indexOf("."));
Field campo = oo.getClass().getDeclaredField(field);
campo.setAccessible(true);
Object result = campo.get(oo);
return getField(path.substring(path.indexOf(".") + 1), result);
} else {
Field campo = getSimpleField(path, oo.getClass());
campo.setAccessible(true);
return campo.get(oo);
}
} catch (Exception e) {
System.out.println("Field " + path + " not found on " + oo.getClass().getName());
}
return null;
}
private static Field getSimpleField(String fieldName, Class<?> clazz) {
Class<?> tmpClass = clazz;
try {
do {
for (Field field : tmpClass.getDeclaredFields()) {
String candidateName = field.getName();
if (!candidateName.equals(fieldName)) {
continue;
}
field.setAccessible(true);
return field;
}
tmpClass = tmpClass.getSuperclass();
} while (clazz != null);
} catch (Exception e) {
System.out.println("Field '" + fieldName + "' not found on class " + clazz);
}
return null;
}
}
Upvotes: 3
Reputation: 1994
This depends on how are you reading the file and how do you plan to show errors. Using IAE is better if you also plan to construct the class programatically. Using JSR-303 is better when you also use it with JavaEE 6. Also, you must think if your user needs to know the error line number.
And remember: the first (pure-POJO) way ensures that no object will ever be inconsistent (marshalling or unmarshalling), while JSR-303 requires someone to call the validation functions (as well as requiring the framework classes in classpath).
Upvotes: 0