2012-08-10 2 views
5

La mia domanda è molto simile a How to prevent marshalling empty tags in JAXB when string is empty but not nullJAXB maresciallo stringa vuota a livello globale Null

La differenza è che io sono in grado di aggiungere l'annotazione a package-info.java come tutti i nostri tipi JAXB sono generate da schemi di ogni costruzione . Inoltre preferirei non cambiare i provider JAXB se possibile.

Quello che voglio ottenere è che l'impostazione di una stringa vuota non creerà l'elemento, ma ho bisogno di impostare questo per tutti i tipi JAXB generati da molti schemi. C'è un modo per applicarlo a tutti i campi String in tutte le classi JAXB generate?

Aggiornamento Sono riuscito a ottenere la generazione di adattatore di XML per tutte le stringhe nello schema apportando le seguenti modifiche:

Nel POM del progetto, ho aggiunto questo al Maven-jaxb2-plugin:

<bindingDirectory>src/main/resources</bindingDirectory> 
<bindingIncludes> 
    <include>bindings.xjb</include> 
</bindingIncludes> 

e qui è il mio file bindings.xjb:

<jxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.1"> 
    <jxb:globalBindings> 
        <jxb:javaType name="java.lang.String" xmlType="xs:token" 
         parseMethod="com.project.Formatter.parseString" 
         printMethod="com.project.Formatter.printString"/> 
    </jxb:globalBindings> 
</jxb:bindings> 

E la formattazione metodo:

public static String printString(final String value) 
{ 
    if (StringUtils.isBlank(value)) 
    { 
     return null; 
    } 

    return value; 
} 

Il problema è che questo causa un'eccezione Pointer nullo in profondità all'interno di JAXB. Ecco StackTrace:

Caused by: java.lang.NullPointerException 
    at com.sun.xml.bind.v2.runtime.output.SAXOutput.text(SAXOutput.java:158) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:321) 
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:210) 
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:209) 
    at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:250) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:98) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65) 
    at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168) 
    at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:152) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681) 
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:185) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:305) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:312) 
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:71) 
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:490) 
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328) 
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:257) 
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:103) 

La causa di questo problema si riduce a questo metodo:

com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.CompositeTransducedAccessorImpl.hasValue(BeanT) 

Il metodo sopra renderà dell'elemento se il valore non è nullo prima alcun adattatore viene eseguito .

C'è un modo per ignorare la funzione di accesso utilizzato in JAXB in modo che questo verrà eseguito l'adattatore prima di determinare se eseguire il rendering l'elemento? C'è un altro modo per ottenere ciò che voglio?

+0

Date un'occhiata http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.5/tutorial/doc JAXB Binding /JAXBUsing4.html – anazimok

+0

Sì, ho dato un'occhiata a questo. Sfortunatamente i documenti dello schema sono piuttosto complessi e ci sono circa 30 schemi diversi su cui avrei bisogno di applicarlo. Per quanto ne so, è necessario definire un singolo schema per ogni associazione. C'è un modo per applicare l'associazione a tutte le classi JAXB generate? –

+1

Puoi definire globalBinding vedere il link che ho postato, non riesco a ricordare quale attributo esattamente ma uno di loro aggiunge nillable a ciascun elemento. Forse questo aiuterà: http://stackoverflow.com/questions/4413281/how-do-i-prevent-jaxbelementstring-from-being-generated-in-a-cxf-web-service-c – anazimok

risposta

13

Nota: Sono in vantaggio EclipseLink JAXB (MOXy) e un membro del gruppo di esperti JAXB (JSR-222).

Quello che hai fatto è giusto, l'errore che state vedendo è a causa di quella che credo sia un bug nel JAXB reference implementation. Il RI JAXB dovrebbe essere in grado di gestire un valore nullo restituito da un XmlAdapter. Questo caso d'uso funziona con EclipseLink JAXB (MOXy), dimostrerò di seguito con un esempio.

StringAdapter

seguito è riportato un implmentation che fa circa quello che quello che si ottiene dopo aver generato il tuo modello di Java dal schema XML (vedi http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html).

package forum11894193; 

import javax.xml.bind.annotation.adapters.XmlAdapter; 

public class StringAdapter extends XmlAdapter<String, String> { 

    @Override 
    public String marshal(String string) throws Exception { 
     if("".equals(string)) { 
      return null; 
     } 
     return string; 
    } 

    @Override 
    public String unmarshal(String string) throws Exception { 
     return string; 
    } 

} 

pacchetto-info

Dal momento che si sta registrando un adattatore globale, farà riferimento da una classe package-info come quella qui sotto (vedi: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).

@XmlJavaTypeAdapters({ 
    @XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class) 
}) 
package forum11894193; 

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

Root

Qui di seguito è una classe di dominio campione con un paio di String campi. Poiché lo XmlAdapter è stato registrato a livello di pacchetto, verrà applicato a tutti i campi/proprietà String associati in quel pacchetto.

package forum11894193; 

import javax.xml.bind.annotation.*; 

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

    String a; 
    String b; 
    String c; 

} 

Demo

Nel codice demo di seguito creeremo un esempio di Root fissato un paio di campi per "" e poi il maresciallo a XML.

package forum11894193; 

import javax.xml.bind.*; 

public class Demo { 

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

     Root root = new Root(); 
     root.a = ""; 
     root.b = "b"; 
     root.c = ""; 

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

} 

output utilizzando JAXB RI

Uso del JAXB RI con questo esempio si ottiene in un NPE. La traccia dello stack è diversa, ma è più probabile che usiamo metodi di marshall diversi. Sto anche usando la versione del RI JAXB inclusa nel JDK che è riconfezionato in com.sun.xml.internal.bind.v2.

Exception in thread "main" java.lang.NullPointerException 
    at com.sun.xml.internal.bind.v2.runtime.output.Encoded.setEscape(Encoded.java:96) 
    at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.doText(UTF8XmlOutput.java:294) 
    at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:283) 
    at com.sun.xml.internal.bind.v2.runtime.output.IndentingUTF8XmlOutput.text(IndentingUTF8XmlOutput.java:141) 
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:293) 
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179) 
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:166) 
    at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:239) 
    at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:87) 
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306) 
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561) 
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290) 
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462) 
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314) 
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243) 
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75) 
    at forum11894193.Demo.main(Demo.java:17) 

output utilizzando EclipseLink JAXB (moxy)

Quando moxy viene utilizzato come provider JAXB si ottiene l'output desiderato. Per informazioni su come specificare MOXy come provider JAXB vedere: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html.

<?xml version="1.0" encoding="UTF-8"?> 
<root> 
    <b>b</b> 
</root> 
+0

Ho visto il tuo post alcune volte, ma non ho capito che il "bug" era lo stesso perché la traccia dello stack era diversa. Sono d'accordo che si tratta di un bug nell'implementazione di JAXB. Darò il passaggio a MOXy una prova. Ti aspetteresti che l'XML risultante sia identico? In caso contrario, potrebbe non essere un'opzione. –

+0

@JBarclay - Sì, il codice XML risultante dovrebbe essere identico.EclipseLink JAXB (MOXy) è ora il provider JAXB predefinito in WebLogic per darti un'idea della compatibilità che puoi aspettarti. –

+0

Sono passato a MOXy, che ha risolto il problema come già detto. Sfortunatamente, MOXy non sta andando molto bene con elementi nillable. Ho aggiornato la domanda, qualche idea? –

0

Una stringa vuota è ancora un valore. Ecco perché l'elemento è stato creato. Detto questo, cosa succede nei metodi setter impostando la variabile su null se la stringa è vuota?

Inoltre, controllare questa discussione fuori JAXB: how to make JAXB NOT to unmarshal empty string to 0

+0

Capisco che l'impostazione predefinita il comportamento è corretto, voglio cambiarlo. Non riesco a cambiare i metodi setter o utilizzare le soluzioni da quel collegamento mentre i tipi JAXB vengono costantemente rigenerati. Ho bisogno che le classi generate da JAXB includano la soluzione. –

0

Usa sottostante Codice in voi classe Marshall

public static String toXml(Object o, Class clazz, boolean isFormatted, boolean isEmptyNodes) { 
     try { 
      Map<String, Object> properties = new HashMap<String, Object>(1); 
      if(isEmptyNodes) { 
       SessionEventListener sessionEventListener = new NullPolicySessionEventListener(); 
       properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener); 
      } else { 
       SessionEventListener sessionEventListener = new DiscardEmptyTagSessionEventListener(); 
       properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener); 
      } 

      // Create a JaxBContext 
      JAXBContext jc = JAXBContext.newInstance(new Class[] {clazz}, properties); 
      StringWriter sw = new StringWriter(); 

      // Create the UnMarshaller Object using the JaxB Context 
      Marshaller marshaller = jc.createMarshaller(); 

      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormatted); 
      // remove the xml version line from the output 
      marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE); 
      // Marshal the employee object to XML and print the output to console 
      marshaller.marshal(o, sw); 

      return sw.toString(); 
     } catch (JAXBException e) { 
      throw new RuntimeException(e.getMessage(), e); 
     } 
}