Validación JAXB usando anotaciones


Si tengo una clase simple como: -

@XmlRootElement
public class MyClass
{
   @XmlAttribute(required=true)
   private String myattribute
}

¿Es posible validar un documento xml correspondiente SIN un esquema xml, es decir, utilizando solo las anotaciones?

Author: Martin, 2010-03-02

2 answers

Buena pregunta. Por lo que sé, el atributo required es generado por XJC cuando encuentra un tipo de esquema no opcional, y creo que también es utilizado por el generador de esquemas. En tiempo de ejecución, sin embargo, no se usa para nada, no sirve para otro propósito que una anotación documental.

Una cosa que podría considerar es las opciones de devolución de llamada del tiempo de ejecución de JAXB. En este caso, simplemente podría definir un método afterUnmarshal() en MyClass que valida programáticamente el estado del objeto, lanzando un excepción si no le gusta. Consulte el enlace anterior para otras opciones, incluido el registro de clases de validador separadas.

Dicho esto, la validación contra un esquema realmente es la mejor manera. Si no tienes uno, deberías considerar escribir uno. La herramienta schemagen puede generar un esquema a partir de su modelo de objetos, que luego puede modificar para agregar las restricciones que desee. Con suerte, schemagen generará elementos de esquema obligatorios a partir de sus campos de clase required=true.

 15
Author: skaffman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-02 22:26:18

Gran pregunta, especialmente teniendo en cuenta la popularidad del objeto-primero, el esquema-nunca el desarrollo. A mí también me gustaría validar objetos aprovechando las anotaciones existentes antes de la clasificación.

Mientras esperamos JAXB-430, o nos convertimos en colaboradores aceptados para Java, lo que sigue es un intento extremadamente limitado de producción local con respecto solo a XmlElement(required=true}. Tenga en cuenta que esto no funcionará con la política de seguridad no predeterminada debido a Field.setAccessible().

Caso de uso Prueba

import javax.xml.bind.annotation.XmlElement;
import JaxbValidator.ValidationException;
import org.testng.annotations.Test;

public class JaxbValidatorTest {

    static class Llama {
        @XmlElement(required = false)
        private final String no;

        @XmlElement(required = true)
        private final String yes;

        public Llama(String no, String yes) {
            super();
            this.no = no;
            this.yes = yes;
        }
    }
    @Test
    public void validateRequired() {
        try {
            Llama o = new Llama("a", "b");
            // THE MAIN EVENT - see if 'required' is honored
            JaxbValidator.validateRequired(o, Llama.class);
        } catch (ValidationException e) {
            assert false : "Should not have thrown validation exception.";
        }
        try {
            Llama o = new Llama(null, null);
            // Again - see if 'required' is honored
            JaxbValidator.validateRequired(o, Llama.class);
            assert false : "Should have thrown validation exception for 'yes'";
        } catch (ValidationException e) {
            assert e.getMessage() != null: "Expected validation message, got null." ;
        }
    }
}

Aplicación

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.xml.bind.annotation.XmlElement;
import org.apache.log4j.Logger;

/**
 * oh so minimal consideration of JAXB annotation
 */
public class JaxbValidator {

    private static final Logger LOG = Logger.getLogger(JaxbValidator.class);

    public static class ValidationException extends Exception {
        public ValidationException(String message, Throwable cause) {
            super(message, cause);
        }
        public ValidationException(String message) {
            super(message);
        }
    }

    /**
     * Enforce 'required' attibute.
     *
     * Requires either no security manager is used or the default security manager is employed. 
     * @see {@link Field#setAccessible(boolean)}.
     */
    public static <T> void validateRequired(T target, Class<T> targetClass)
        throws ValidationException {
        StringBuilder errors = new StringBuilder();
        Field[] fields = targetClass.getDeclaredFields();
        for (Field field : fields) {
            XmlElement annotation = field.getAnnotation(XmlElement.class);
            if (annotation != null && annotation.required()) {
                try {
                    field.setAccessible(true);
                    if (field.get(target) == null) {
                        if (errors.length() != 0) {
                            errors.append(" ");
                        }
                        String message = String.format("%s: required field '%s' is null.",
                                                       targetClass.getSimpleName(),
                                                       field.getName());
                        LOG.error(message);
                        errors.append(message);
                    }
                } catch (IllegalArgumentException e) {
                    LOG.error(field.getName(), e);
                } catch (IllegalAccessException e) {
                    LOG.error(field.getName(), e);
                }
            }
        }
        if (errors.length() != 0) {
            throw new ValidationException(errors.toString());
        }
    }

Y sí ... no hace una inspección profunda. No estaba seguro de si JAXB maneja gráficos cíclicos, así que no intenté recursividad sin saber si eso tenía que ser tratado. Lo guardaré para un querido lector o para la próxima edición.

 8
Author: David J. Liszewski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-13 17:13:43