Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jaxb and jsr303

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

like image 461
ekeren Avatar asked Feb 21 '11 08:02

ekeren


People also ask

How do you use a JSR-303?

4.1 JSR-303 – Validate Request Body@Valid annotation in the method, will validate the Customer data model. In case of validation error system will send out an error message to the customer. Spring framework will create a Validator and this will be available for the data validation.

Why Hibernate Validator is used?

Hibernate Validator allows to express and validate application constraints. The default metadata source are annotations, with the ability to override and extend through the use of XML. It is not tied to a specific application tier or programming model and is available for both server and client application programming.

What is Bean Validation in spring?

The Bean Validation API is a Java specification which is used to apply constraints on object model via annotations. Here, we can validate a length, number, regular expression, etc. Apart from that, we can also provide custom validations. As Bean Validation API is just a specification, it requires an implementation.


1 Answers

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;
    }
}
like image 115
cocorossello Avatar answered Sep 28 '22 06:09

cocorossello