Question Validation JAXB à l'aide d'annotations


Si j'ai une classe simple telle que: -

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

Est-il possible de valider un document XML correspondant SANS un schéma XML, c'est-à-dire en utilisant uniquement les annotations?


27
2018-03-02 18:28


origine


Réponses:


Bonne question. Pour autant que je sache, le required L'attribut est généré par XJC lorsqu'il trouve un type de schéma non optionnel, et je pense qu'il est également utilisé par le générateur de schéma. A l'exécution, cependant, il n'est utilisé pour rien, ne servant qu'à une annotation documentaire.

Une chose que vous pourriez considérer est le runtime JAXB options de rappel. Dans ce cas, vous pouvez simplement définir un afterUnmarshal() méthode sur MyClass qui valide par programmation l’état de l’objet, en lançant une exception s’il ne l’aime pas. Voir le lien ci-dessus pour d'autres options, y compris l'enregistrement de classes de validateur distinctes.

Cela dit, la validation par rapport à un schéma est vraiment la meilleure solution. Si vous n'en avez pas, vous devriez envisager d'en écrire un. le schemagen Cet outil peut générer un schéma à partir de votre modèle objet, que vous pouvez ensuite modifier pour ajouter les contraintes que vous souhaitez. Espérons que schemagen va générer des éléments de schéma obligatoires de votre required=true champs de classe.


15
2018-03-02 22:26



Grande question, surtout compte tenu de la popularité du développement objet-premier, schéma-jamais. Je voudrais aussi valider des objets en exploitant les annotations existantes avant le marshaling.

Alors que soit nous attendons JAXB-430, ou devenir des contributeurs acceptés à Java, ce qui suit est une tentative locale extrêmement limitée concernant uniquement XmlElement(required=true}. Notez que cela ne fonctionnera pas avec une stratégie de sécurité autre que celle par défaut en raison de Field.setAccessible().

Test de cas d'utilisation

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

la mise en oeuvre

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

Et oui ... il ne fait pas d'inspection en profondeur. Je n'étais pas sûr si JAXB gère les graphiques cycliques, donc je n'ai pas tenté la récursivité sans savoir si cela devait être traité. Je sauverai cela pour un cher lecteur ou un prochain montage.


8
2018-01-13 08:39