jaxb和jsr303

时间:2011-02-21 08:25:56

标签: java jaxb bean-validation

我正在使用jaxb构造对象的配置。 到目前为止,我编写了自定义函数进行验证,但我想进入注释。

e.g:

@XmlElement
public void setNumber(Integer i){
    if (i<10 || i>20) throw new IllegalArgumentException(...);
    this.number=i;
}

上述方法的例外情况是描述性的,并给出了xml中错误的位置。

我想进入这个:

@XmlElement
@Min(10)
@Max(20)
public void setNumber(Integer i){
    this.number=i;
}

我可以通过读取afterMarshal中的注释并根据属性注释运行验证函数来验证这一点,但是我失去了发生错误的实际位置(在xml中)。

你有没有,我应该为这个问题使用不同的方法/框架吗?

编辑:为了澄清,我必须使用注释方法,因为我需要为我正在编写的配置编辑器提供属性约束元数据

3 个答案:

答案 0 :(得分:3)

这是我自己用来解决这个问题的XJC插件(在我的情况下,我需要注释来进行独立于XML模式的验证,因为我还有JMS端点):

它的作用:

- 为不在xs默认模式中的对象生成@valid注释(因此注释是级联的)

- 为具有MinOccur值&gt; = 1的对象或具有所需用途的属性生成@NotNull注释

- 为具有minOccurs&gt;的列表生成@Size; 1

- 如果存在maxLength或minLength限制

,则生成@Size

- @ DecimalMax for maxInclusive restriction

- @ DecimalMin for minInclusive restriction

- @Ditits,如果有totalDigits或fractionDigits限制。

- @ Pattern如果存在模式限制

请注意,不包括minExclusive和maxExclusive限制。

要使用它,您必须将类文件打包到META-INF / services / com.sun.tools.xjc.Plugin文件中,内容为“com.sun.tools.xjc.addon.jaxb.JaxbValidationsPlugins” (即类的完全限定名称)并使用-XValidate开关调用XJC。

实施起来并不困难,但我希望它对某些人有用。源代码附加为TXT文件。享受!

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;
    }
}

答案 1 :(得分:2)

你应该看看https://github.com/krasa/krasa-jaxb-tools 这是维森特代码的改进和复仇版本。

答案 2 :(得分:0)

这取决于您如何阅读文件以及您打算如何显示错误。如果您还计划以编程方式构造类,则使用IAE会更好。当您将它与JavaEE 6一起使用时,使用JSR-303会更好。此外,您必须考虑用户是否需要知道错误行号。

请记住:第一种(纯POJO)方式确保任何对象都不会出现不一致(编组或解组),而JSR-303需要有人调用验证函数(以及需要类路径中的框架类)