2012-08-10 15 views
5

Mi pregunta es muy similar a How to prevent marshalling empty tags in JAXB when string is empty but not nullJAXB Mariscal cadena vacía en Null A nivel mundial

La diferencia es que yo soy incapaz de añadir la anotación a package-info.java ya que todos nuestros tipos JAXB son generados a partir de los esquemas con cada generación . También preferiría no cambiar los proveedores de JAXB si es posible.

Lo que quiero lograr es que establecer una Cadena vacía no creará el elemento, pero necesito establecer esto para todos los tipos JAXB generados a partir de muchos esquemas. ¿Hay alguna forma de aplicar esto a todos los campos de cadenas en todas las clases JAXB generadas?

actualización He conseguido que la generación de adaptador de XML para todas las cadenas en el esquema de hacer los siguientes cambios:

En el POM proyecto, añade esto a la experta-jaxb2-plugin:

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

Y aquí es mi archivo 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> 

Y el formato método:

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

    return value; 
} 

El problema es que esto causa una excepción de puntero nulo dentro de JAXB. Aquí está el 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 de este problema se reduce a este método:

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

El método anterior hará que el elemento si el valor no es nulo antes es ejecutar cualquier adaptador .

¿Hay alguna manera de anular el Accesorio utilizado en JAXB para que esto ejecute el adaptador antes de determinar si se debe representar el elemento? ¿Hay alguna otra manera de lograr lo que quiero?

+0

Tome una mirada http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.5/tutorial/doc JAXB Encuadernación /JAXBUsing4.html – anazimok

+0

Sí, he echado un vistazo a eso. Lamentablemente, los documentos de esquema son bastante complejos, y hay alrededor de 30 esquemas diferentes que necesitaría aplicar. Por lo que puedo decir a partir de eso, se debe definir un único esquema para cada enlace. ¿Hay alguna forma de aplicar el enlace a todas las clases JAXB generadas? –

+1

Puede definir globalBinding ver el enlace que publiqué, no puedo recordar qué atributo exactamente pero uno de ellos agrega nillable a cada elemento. Tal vez esto ayude: http://stackoverflow.com/questions/4413281/how-do-i-prevent-jaxbelementstring-from-being-generated-in-a-cxf-web-service-c – anazimok

Respuesta

13

Nota: Soy el EclipseLink JAXB (MOXy) de plomo y un miembro del grupo de expertos JAXB (JSR-222).

Lo que ha hecho es correcto, el error que está viendo se debe a lo que creo que es un error en el JAXB reference implementation. JAXB RI debería ser capaz de manejar un valor nulo devuelto desde XmlAdapter. Este caso de uso funciona con EclipseLink JAXB (MOXy), lo demostraré a continuación con un ejemplo.

StringAdapter

a continuación es un implmentation que tiene aproximadamente lo que la que obtendrá después de generar el modelo de Java desde el esquema XML (ver 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; 
    } 

} 

paquete de información-

Dado que se está registrando un adaptador mundial, se hace referencia a una clase package-info como la siguiente (ver: 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.*; 

Raíz

A continuación se muestra una clase de dominio con algunas String campos. Como el XmlAdapter se registró en el nivel de paquete, se aplicará a todos los campos/propiedades de Cadena mapeados en ese paquete.

package forum11894193; 

import javax.xml.bind.annotation.*; 

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

    String a; 
    String b; 
    String c; 

} 

demostración

En el código de demostración a continuación vamos a crear una instancia de Root ajustar un par de los campos de "" y luego Mariscal 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); 
    } 

} 

de salida utilizando JAXB RI

Uso de la JAXB RI con este ejemplo produce un NPE. El seguimiento de la pila es diferente, pero lo más probable es que usemos diferentes métodos de Mariscal. También estoy usando la versión de JAXB RI incluida en el JDK, que se vuelve a empaquetar en 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) 

salida utilizando EclipseLink JAXB (moxy)

Cuando moxy se utiliza como el proveedor de JAXB se obtiene el resultado deseado. Para obtener información sobre cómo especificar MOXy como su proveedor JAXB, consulte: 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

He visto tu publicación varias veces, pero no me di cuenta de que el 'error' era el mismo porque el seguimiento de la pila es diferente. Estoy de acuerdo en que es un error en la implementación de JAXB. Voy a intentar cambiar a MOXy. ¿Esperaría que el XML resultante sea idéntico? Si no, puede que no sea una opción. –

+0

@JBarclay - Sí, el XML resultante debe ser idéntico.EclipseLink JAXB (MOXy) ahora es el proveedor JAXB predeterminado en WebLogic para darle una idea de la compatibilidad que puede esperar. –

+0

He cambiado a MOXy, que solucionó el problema como dijiste. Desafortunadamente, a MOXy no le está yendo tan bien con los elementos anulables. He actualizado la pregunta, ¿alguna idea? –

0

Una cadena vacía sigue siendo un valor. Es por eso que se crea el elemento. Dicho esto, ¿qué pasa con los métodos setter establecer la variable a nulo si la cadena está vacía?

También, comprobar a cabo este hilo JAXB: how to make JAXB NOT to unmarshal empty string to 0

+0

Entiendo que el valor predeterminado el comportamiento es correcto, quiero cambiarlo. No puedo cambiar los métodos setter o usar las soluciones de ese enlace ya que los tipos JAXB se regeneran constantemente. Necesito las clases generadas por JAXB para incluir la solución. –

0

Uso continuación código en la clase que 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); 
     } 
} 
Cuestiones relacionadas