jaxb и jsr303 - PullRequest
       37

jaxb и jsr303

5 голосов
/ 21 февраля 2011

Я строю объект вне конфигурации, используя jaxb.До сих пор я писал пользовательскую функцию для проверки, но я хотел бы перейти к аннотациям.

Например:

@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 ]

3 голосов
/ 27 марта 2012

Вот плагин XJC, который я сам использовал для решения этой проблемы (в моем случае мне потребовались аннотации, чтобы сделать проверку независимой от схемы XML, поскольку у меня также есть конечные точки JMS):

Что он делает:

-Он генерирует аннотацию @valid для объектов, которых нет в схеме xs по умолчанию (поэтому аннотации каскадируются)

-Он генерирует аннотацию @NotNull для объектов со значением MinOccur> = 1 или для атрибутов с обязательным использованием

- генерирует @Size для списков с minOccurs> 1

- генерирует @Size, если есть ограничение maxLength или minLength

- @ DecimalMax для ограничения maxInclusive

- @ DecimalMin для ограничения minInclusive

- @ Цифры, если есть ограничение на общую цифру или дробную цифру.

- @ Pattern, если есть ограничение Pattern

Обратите внимание, что ограничения minExclusive и maxExclusive исключены.

Чтобы использовать его, вы должны упаковать файл класса в файл META-INF / services / com.sun.tools.xjc.Plugin с содержимым «com.sun.tools.xjc.addon.jaxb.JaxbValidationsPlugins» (то есть полное имя класса) и вызовите XJC с ключом -XValidate.

Это не очень сложно реализовать, но я надеюсь, что это будет кому-то полезно. Исходный код прилагается в виде файла 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;
    }
}
2 голосов
/ 05 сентября 2013

Вы должны посмотреть на https://github.com/krasa/krasa-jaxb-tools который является улучшенной и mavenized версией кода Висенте.

0 голосов
/ 22 февраля 2011

Это зависит от того, как вы читаете файл и как вы планируете показывать ошибки. Использование IAE лучше, если вы также планируете создавать класс программно. Использование JSR-303 лучше, когда вы также используете его с JavaEE 6. Кроме того, вы должны подумать, должен ли ваш пользователь знать номер строки ошибки.

И помните: первый (чистый-POJO) способ гарантирует, что ни один объект никогда не будет несовместимым (маршалинг или демаршаллинг), в то время как JSR-303 требует, чтобы кто-то вызывал функции проверки (а также требовал наличия рамочных классов в classpath) .

...