web-dev-qa-db-fra.com

Élément JAXB qui est à la fois facultatif et pouvant être supprimé

J'ai reformaté la question pour, je l'espère, clarifier mes intentions.

Architecture
J'écris certains services Web que je publierai moi-même à l'aide de JAX-WS. Le processus que nous utilisons depuis un certain temps consiste à écrire d'abord un schéma qui définit uniquement les objets de demande et de réponse. Celui-ci est envoyé au client pour approuver la structure des messages xml. Je ne veux pas écrire le WSDL moi-même car c'est plus compliqué que le schéma de base.

Ensuite, j'utilise la commande JAXB xjc pour générer des classes en fonction des types de demande et de réponse dans mon schéma. J'utilise ensuite ces classes comme paramètres et renvoie des types sur une classe de point de terminaison annotée JAX-WS.

Cela me donne maintenant un service Web que je peux appeler. Cela me donne plus de contrôle sur le xml envoyé et retourné mais automatise également la répétition requise lors de l'écriture du wsdl complet.

Problème
Dans le schéma, j'ai un élément comme celui-ci:

<xs:element name="myElement" type="xs:string" nillable="true" minOccurs="0" /> 

Je veux donc faire la distinction entre le paramètre utilisateur nul ou vide. La classe générée a alors cet attribut.

@XmlElementRef(name = "myElement", namespace = "/mynamespace", type = JAXBElement.class)
protected JAXBElement<String> myElement;

L'effet de ceci est que l'élément ne devient ni illisible ni facultatif. Le schéma que JAX-WS écrit dans le cadre de wsdl a défini l'élément sur obligatoire et non nillable et si je désactive la validation de schéma, je ne peux toujours pas transmettre nil à mon objet.

Choses essayées
Si je le change pour qu'il soit obligatoire et qu'il soit possible de le supprimer, j'obtiens ce code généré.

@XmlElement(required = true, nillable = true)
protected String myElement;

Si je le change en optionnel et non nillable, j'obtiens ce code généré.

protected String myElement

Donc, vous pouvez avoir l'un ou l'autre mais pas les deux, semble-t-il, si vous utilisez JAXB. Vraiment décevant!

J'ai également essayé de changer manuellement la classe générée pour qu'elle ressemble à ceci.

@XmlElementRef(name = "myElement", namespace = "/mynamespace", type = JAXBElement.class, required=false)
protected JAXBElement<String> myElement;

Cela rend désormais l'élément facultatif mais je ne peux toujours pas le définir sur zéro. Cela entraîne un JAXBElement avec une valeur de chaîne vide. C'est seulement si vous désactivez la validation du schéma car le schéma/fichier wsdl/JAX-WS résultant ne définit pas l'élément comme pouvant être supprimé, donc ce n'est pas une demande valide.

Résumé
Je pense que c'est un bug avec JAXB. L'annotation @XmlElementRef a un attribut pour le définir comme non requis mais il n'y a aucun attribut pour définir le champ comme nullable.

L'annotation @XmlElement a des attributs à la fois obligatoires et nullables, mais ceux-ci se traduisent simplement par un objet null, il n'y aurait donc aucun moyen de distinguer entre un élément non inclus dans le xml ou un élément qui était inclus mais nul. C'est pourquoi vous devez utiliser @XmlElementRef avec JAXBElement.

Je pense que le bug comprend deux problèmes. La commande xjc doit d'abord générer l'élément avec required = false. Deuxièmement, il devrait y avoir un attribut sur @XmlElementRef pour définir si l'élément est nullable et cela devrait également être défini.

Quelqu'un connaît-il un correctif/une solution de contournement? J'ai essayé de googler mais je n'ai trouvé que des gens posant la même question sans réponse. Cela signifie généralement que ce n'est pas possible ... TIA.

supplémentaire
J'utilise jaxb 2.2.6 et le plugin maven est jaxb2-maven-plugin 1.5.

11
Ben Thurley

TL; DR

For

@XmlElementRef(name="foo", required=false)
protected JAXBElement<String> foo;

Un nœud absent dans le document correspondra à la nullité de ce champ. Un élément XML présent dans le document avec xsi:nil="true" correspondra à la valeur étant une instance de JAXBElement avec une valeur de null.

Vous pouvez également fournir un schéma XML au lieu d'en générer un par JAXB à l'aide de la propriété location au niveau du package @XmlSchema annotation.

@XmlSchema(
    ...
    location="http://www.example.com/schema/root.xsd")
package forum19665550;

import javax.xml.bind.annotation.XmlSchema;

Maréchal/Unmarshal

Modèle Java

Racine

Il s'agit d'un objet avec deux champs qui peuvent représenter des données facultatives et pouvant être supprimées.

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlElementRef(name="foo", required=false)
    protected JAXBElement<String> foo;

    @XmlElementRef(name="bar", required=false)
    protected JAXBElement<String> bar;

}

ObjectFactory

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {

    @XmlElementDecl(name="foo")
    public JAXBElement<String> createFoo(String foo) {
        return new JAXBElement<String>(new QName("foo"), String.class, foo);
    }

    @XmlElementDecl(name="bar")
    public JAXBElement<String> createBar(String bar) {
        return new JAXBElement<String>(new QName("bar"), String.class, bar);
    }

}

Code de démonstration

Démo

Le code de démonstration ci-dessous étudiera les différences dans les valeurs de foo et bar. Vous pouvez utiliser la classe JAXBIntrospector pour obtenir la valeur réelle d'une instance de JAXBElement. Il y a un bogue dans EclipseLink JAXB (MOXy) lié au dé-marshalling d'une instance de JAXBElement encapsulant une valeur nulle (voir: http://bugs.Eclipse.org/420746 ).

import Java.io.File;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class, ObjectFactory.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum19665550/input.xml");
        Root root = (Root) unmarshaller.unmarshal(xml);

        System.out.println("foo was set:          " + (root.foo != null));
        System.out.println("bar was set:          " + (root.bar != null));
        System.out.println("foo value:            " + root.foo);
        System.out.println("bar value:            " + root.bar);
        System.out.println("foo unwrapped value:  " + JAXBIntrospector.getValue(root.foo));
        System.out.println("bar unwrapped value:  " + JAXBIntrospector.getValue(root.bar));

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }

}

input.xml/Output

Dans la sortie résultante, nous voyons que nous pouvons faire la différence entre un élément absent du document et un élément avec `xsi: nil =" true "et que la valeur résultante soit toujours nulle.

foo was set:          false
bar was set:          true
foo value:            null
bar value:            javax.xml.bind.JAXBElement@4af42ea0
foo unwrapped value:  null
bar unwrapped value:  null
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>

Génération d'un schéma XML

Code de démonstration

GenerateSchema

Vous trouverez ci-dessous du code JAXB qui générera un schéma XML à partir du modèle annoté.

import Java.io.IOException;
import javax.xml.bind.*;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;

public class GenerateSchema {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        jc.generateSchema(new SchemaOutputResolver() {

            @Override
            public Result createOutput(String namespaceUri,
                    String suggestedFileName) throws IOException {
                StreamResult result = new StreamResult(System.out);
                result.setSystemId(suggestedFileName);
                return result;
            }

        });
    }

}

Sortie

Voici le schéma XML résultant. Vous avez raison, cela n'indique pas que les éléments foo et bar sont nillables.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="bar" type="xs:string"/>

  <xs:element name="foo" type="xs:string"/>

  <xs:element name="root" type="root"/>

  <xs:complexType name="root">
    <xs:sequence>
      <xs:element ref="foo" minOccurs="0"/>
      <xs:element ref="bar" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Fournir un schéma XML

Au lieu que JAXB dérive un schéma XML de votre modèle, vous pouvez pointer vers votre schéma existant qui contiendra plus d'informations.

info-paquet

Cela se fait en spécifiant la propriété location au niveau du package @XmlSchema annotation.

@XmlSchema(
    ...
    location="http://www.example.com/schema/root.xsd")
package forum19665550;

import javax.xml.bind.annotation.XmlSchema;
13
bdoughan

Si je vous comprends bien Ben le XSD suivant:

<xs:element name="myElement" type="xs:string" nillable="true" minOccurs="0" /> 

Devrait se traduire par:

@XmlElementRef(name = "myElement", namespace = "/mynamespace", type = JAXBElement.class, required = false)
protected JAXBElement<String> myElement;

Droite?

Mais pour l'implémentation JAXB par défaut, ce n'est pas le cas. Ressemble à un bogue dans JAXB. Je ne l'ai pas trouvé dans JAXB tracker problème . L'attribut required a été introduit dans @XmlElementRef dans JAXB 2.2 vers 2009, mais apparemment personne n'a créé de problème pour ce problème.

L'attribut required ne peut pas être modifié à l'aide des personnalisations de liaison.

Dans cette situation, vous pouvez:

  • écrivez votre propre plugin pour XJC pour ajouter un attribut manquant à @XmlElementRef annotation. Ce n'est pas si difficile. Plus d'informations ici .
  • utiliser une implémentation JAXB alternative (MOXy fonctionne bien - required = false est généré à l'aide de compilateur MOXy JAXB )
  • attendez que l'implémentation Oracle de JAXB soit corrigée.

Peu importe l'option que vous choisissez, veuillez soulever le problème dans JAXB issue tracker afin que le problème soit résolu.

MODIFIER:

Pour montrer qu'il est facile de créer un plugin, j'en ai créé un. Vous pouvez le trouver dans mon dépôt github . N'hésitez pas à utiliser/copier/modifier à volonté. Je ne garantis pas que cela fonctionne à 100%, mais pour les cas simples, cela fonctionne comme un charme.

EDIT2:

Si le schéma généré est basé sur Java et les annotations JAXB ne correspondent pas à votre interface, vous pouvez utiliser @WebService.wsdlLocation pour pointer vers vos fichiers WSDL et XSD corrects d'origine.

EDIT3:

C'est bizarre que nil soit ignoré par JAXB dans votre cas. J'ai effectué un test en utilisant JAXB 2.2.6 et 2.2.7 et nil est correctement reconnu:

JAXBContext context = JAXBContext.newInstance(SomeElement.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><ns2:someElement xmlns:ns2=\"http://www.example.org/example/\"><myElement xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/></ns2:someElement>";
SomeElement someElement = (SomeElement) unmarshaller
        .unmarshal(new StringReader(xml));
assertThat(someElement.getMyElement().isNil(), is(true));

Pourriez-vous vérifier si vous avez correctement défini l'attribut nil, par exemple:

<myElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>

S'il est correct, essayez d'exécuter le test avec votre classe.

2
Dawid Pytel

Vous pouvez personnaliser la reliure en

<jaxb:globalBindings generateElementProperty="false" />

Comme indiqué dans Liaison personnalisée , pour le même cas exact que vous demandez.

J'utilise une liaison personnalisée avec le plugin maven org.jvnet.jaxb2.maven2: maven-jaxb2-plugin

2
M-Zaiady