Проверка JAXB с использованием аннотаций - PullRequest
27 голосов
/ 02 марта 2010

Если у меня есть простой класс, такой как: -

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

Можно ли проверить соответствующий XML-документ БЕЗ XML-схемы, т. Е. Используя только аннотации?

Ответы [ 2 ]

15 голосов
/ 03 марта 2010

Хороший вопрос. Насколько я знаю, атрибут required генерируется XJC, когда он находит необязательный тип схемы, и я думаю, что он также используется генератором схемы. Однако во время выполнения он ни для чего не используется, не преследуя никакой иной цели, кроме документальной аннотации.

Одна вещь, которую вы могли бы рассмотреть, это опции обратного вызова JAXB * . В этом случае вы можете просто определить метод afterUnmarshal() для MyClass, который программно проверяет состояние объекта, вызывая исключение, если оно ему не нравится. См. Ссылку выше для других опций, включая регистрацию отдельных классов валидаторов.

Сказав это, проверка по схеме действительно является лучшим способом. Если у вас его нет, вам стоит подумать о написании. Инструмент schemagen может сгенерировать схему из вашей объектной модели, которую вы затем можете изменить, чтобы добавить любые ограничения, которые вам нравятся. Надеемся, что schemagen сгенерирует обязательные элементы схемы из ваших required=true полей класса.

8 голосов
/ 13 января 2012

Отличный вопрос, особенно учитывая популярность объектно-первых, схемно-никогда не развивающихся. Я также хотел бы проверить объекты, используя существующие аннотации до маршалинга.

Пока мы либо ждем JAXB-430 , либо станем признанными участниками Java, последующая попытка сделать только XmlElement(required=true} крайне ограничена. Обратите внимание, что это не будет работать с политикой безопасности не по умолчанию из-за Field.setAccessible().

Use Case Test

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

Осуществление

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

И да ... это не делает глубокую проверку. Я не был уверен, обрабатывает ли JAXB циклические графы, поэтому я не пытался выполнить рекурсию, не зная, нужно ли с этим справляться. Я оставлю это для дорогого читателя или для следующего редактирования.

...