2017-06-04 5 views
0

Je veux expliquer mon problème avec un exemple simple:marshalling JAXB: traiter objet vide comme il est nul

Foo:

@SomeXMLAnnotations 
public class Foo { 
    // Bar is just a random class with its own XML annotations 
    @XmlElement(required = true) 
    Bar someBarObj; 

    boolean chosen = true; 
    boolean required = true; 

    public Foo(){ 
     chosen = false; 
    } 

    public Foo(Bar someBarObj){ 
     this.someBarObj = someBarObj; 
    } 
} 

MyClass:

@SomeXMLAnnotations 
public class MyClass { 

    @XmlElement(required = false) 
    Foo anyFooObj; 

    @XmlElement(required = true) 
    Foo anyFooObjRequired; 

    public MyClass(){ } 

    public MyClass (Foo anyFooObj, Foo anyFooObjRequired){ 
     this.anyFooObj = anyFooObj; 
     if(anyFooObj == null) 
      this.anyFooObj = new Foo(); 
     /* 
     * This is the reason why i can't let 'anyFooObj' be 'null'. 
     * So 'anyFooObj' MUST be initialized somehow. 
     * It's needed for some internal logic, not JAXB. 
     */ 
     anyFooObj.required = false; 

     this.anyFooObjRequired = anyFooObjRequired; 
    } 
} 

Exemples d'objets:

Foo fooRequired = new Foo(new Bar()); 
MyClass myObj = new MyClass(null, fooRequired); 

Quand j'essaie de maréchal myObj maintenant, il jette une exception comme ceci:

org.eclipse.persistence.oxm.record.ValidatingMarshalRecord$MarshalSAXParseException; 
cvc-complex-type.2.4.b: The content of element 'n0:anyFooObj ' is not complete. 
One of '{"AnyNamespace":someBarObj}' is expected. 

Cela se produit parce que anyFooObj est initialisé, mais il est nécessaire, membre someBarObj n'est pas.

Solution possible:

Je sais que je pourrais ajouter cette méthode pour MyClass:

void beforeMarshal(Marshaller m){ 
    if(! anyFooObj.chosen) 
     anyFooObj= null; 
    } 
} 

Mais j'ai beaucoup de classes et les classes ont beaucoup de champs ne sont pas nécessaires. Donc, cette solution prendrait des années et ne semble pas être une bonne solution.

Ma question:

est-il un moyen de dire JAXB qu'il devrait traiter des objets vides comme ils étaient null? Ou qu'il doit ignorer un élément lorsqu'il n'est pas correctement défini. Quelque chose comme ceci par exemple:

@XmlElement(required = false, ingnoreWhenNotMarshallable = true) 
Foo anyFooObj; 

REMARQUE:

Je ne suis pas le développeur du code. Je dois juste ajouter JAXB au projet et rendre tout compatible avec un fichier XSD donné. Je ne suis pas autorisé à changer la relation entre les classes.

+0

Eh bien, someBarObj a la propriété d'annotation 'required' réglée à true. Il n'y a aucun moyen qu'un objet qui est là puisse avoir l'air de ne pas être là. La propriété d'annotation doit être définie sur false ou omise. – laune

Répondre

1

Je pense que vous essayez de faire en sorte que le marshaller de JAXB fasse quelque chose pour lequel il n'est vraiment pas conçu, alors je dirais que vous êtes dans un territoire de piratage ici. Je recommande de repousser les exigences pour essayer d'éviter d'avoir ce problème en premier lieu. Cela dit, si vous devez le faire, compte tenu de votre obligation d'éviter d'écrire du code pour chaque classe/domaine, je pense que vous voudrez utiliser la réflexion pour cela - j'ai inclus un exemple ci-dessous qui inspecte valeurs de tous les champs.

extensions utiles seraient:

  • Avez envisager des méthodes getter trop
  • Faire l'opt-in comportement de réglage nul en exigeant que le champ a une annotation supplémentaire - vous pouvez nommer @JAXBNullIfEmpty

Exemple.java:

import javax.xml.bind.JAXBContext; 
import javax.xml.bind.Marshaller; 
import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlRootElement; 
import java.io.StringWriter; 
import java.lang.reflect.Field; 

public class Example 
{ 
    public abstract static class JAXBAutoNullifierForEmptyOptionalFields 
    { 
     void beforeMarshal(Marshaller x) 
     { 
      try 
      { 
       for (Field field : this.getClass().getFields()) 
       { 
        final XmlElement el = field.getAnnotation(XmlElement.class); 

        // If this is an optional field, it has a value & it has no fields populated then we should replace it with null 
        if (!el.required()) 
        { 
         if (JAXBAutoNullifierForEmptyOptionalFields.class.isAssignableFrom(field.getType())) 
         { 
          final JAXBAutoNullifierForEmptyOptionalFields val = (JAXBAutoNullifierForEmptyOptionalFields) field.get(
            this); 

          if (val != null && !val.hasAnyElementFieldsPopulated()) 
           field.set(this, null); // No fields populated, replace with null 
         } 
        } 
       } 
      } 
      catch (IllegalAccessException e) 
      { 
       throw new RuntimeException("Error determining if class has all required fields: " + this, e); 
      } 
     } 


     boolean hasAnyElementFieldsPopulated() 
     { 
      for (Field field : this.getClass().getFields()) 
      { 
       try 
       { 
        if (field.isAnnotationPresent(XmlElement.class)) 
        { 
         // Retrieve value 
         final Object val = field.get(this); 

         // If the value is non-null then at least one field has been populated 
         if (val != null) 
         { 
          return true; 
         } 
        } 
       } 
       catch (IllegalAccessException e) 
       { 
        throw new RuntimeException("Error determining if class has any populated JAXB fields: " + this, e); 
       } 
      } 

      // There were no fields with a non-null value 
      return false; 
     } 
    } 

    @XmlRootElement 
    public static class MyJAXBType extends JAXBAutoNullifierForEmptyOptionalFields 
    { 
     @XmlElement 
     public String someField; 

     @XmlElement 
     public MyJAXBType someOtherField; 


     public MyJAXBType() 
     { 
     } 


     public MyJAXBType(final String someField, MyJAXBType someOtherField) 
     { 
      this.someField = someField; 
      this.someOtherField = someOtherField; 
     } 
    } 


    public static void main(String[] args) throws Exception 
    { 
     final Marshaller marshaller = JAXBContext.newInstance(MyJAXBType.class).createMarshaller(); 

     MyJAXBType innerValue = new MyJAXBType(); // Unpopulated inner value 
     MyJAXBType value = new MyJAXBType("some text value", innerValue); 

     final StringWriter sw = new StringWriter(); 

     marshaller.marshal(value, sw); // Omits "someOtherField" 

     System.out.println(sw.toString()); 
    } 
}