Definir espacios de nombres Spring JAXB sin usar NamespacePrefixMapper


[Fuertemente editado a medida que progresa la comprensión]

¿Es posible hacer que Spring Jaxb2Marshaller use un conjunto personalizado de prefijos de espacio de nombres (o al menos respetar los dados en el archivo de esquema/anotaciones) sin tener que usar una extensión de un NamespacePrefixMapper?

La idea es tener una clase con una "tiene una" relación con otra clase que a su vez contiene una propiedad con un espacio de nombres diferente. Para ilustrar mejor esto considere el esquema siguiente del proyecto que utiliza JDK1. 6.0_12 (el último que puedo tener en mis manos en el trabajo). Tengo lo siguiente en el paquete org.ejemplo.dominio:

Main.java:

package org.example.domain;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Main {
  public static void main(String[] args) throws JAXBException {
    JAXBContext jc = JAXBContext.newInstance(RootElement.class);

    RootElement re = new RootElement();
    re.childElementWithXlink = new ChildElementWithXlink();

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

}

RootElement.java:

package org.example.domain;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(namespace = "www.example.org/abc", name="Root_Element")
public class RootElement {
  @XmlElement(namespace = "www.example.org/abc")
  public ChildElementWithXlink childElementWithXlink;

}

ChildElementWithXLink.java:

package org.example.domain;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;

@XmlRootElement(namespace="www.example.org/abc", name="Child_Element_With_XLink")
public class ChildElementWithXlink {
  @XmlAttribute(namespace = "http://www.w3.org/1999/xlink")
  @XmlSchemaType(namespace = "http://www.w3.org/1999/xlink", name = "anyURI")
  private String href="http://www.example.org";

}

Información del paquete.java:

@javax.xml.bind.annotation.XmlSchema(
    namespace = "http://www.example.org/abc",
    xmlns = {
          @javax.xml.bind.annotation.XmlNs(prefix = "abc", namespaceURI ="http://www.example.org/abc"),
          @javax.xml.bind.annotation.XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink")
            }, 
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
    package org.example.domain;

Ejecutando Main.main() da la siguiente salida:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Root_Element xmlns:ns1="http://www.w3.org/1999/xlink" xmlns:ns2="www.example.org/abc">
<ns2:childElementWithXlink ns1:href="http://www.example.org"/>
</ns2:Root_Element>

Mientras que lo que me gustaría es:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<abc:Root_Element xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:abc="www.example.org/abc">
<abc:childElementWithXlink xlink:href="http://www.example.org"/>
</abc:Root_Element>

Una vez que esta parte está funcionando, entonces el problema pasa a configurar el Jaxb2Marshaller en Primavera (Spring 2.5.6, con spring-oxm-tiger-1.5.6 proporcionando Jaxb2Marshaller) para que proporcione lo mismo mediante una configuración de contexto simple y una llamada a marshal().

Gracias por su continuo interés en este problema!

Author: Gary Rowe, 2010-07-20

4 answers

[Algunas ediciones para ofrecer una alternativa a JAXB-RI están al final de este post]

Bueno, después de mucho rascarme la cabeza finalmente he tenido que aceptar que para mi entorno (JDK1.6.0_12 en Windows XP y JDK1.6.0_20 en Mac Leopard) simplemente no puedo hacer que esto funcione sin recurrir al mal que es el NamespacePrefixMapper. ¿Por qué es malo? Porque obliga a confiar en una clase JVM interna en su código de producción. Estas clases no forman parte de una interfaz fiable entre la JVM y su código (es decir, cambian entre actualizaciones de la JVM).

En mi opinión, Sun debería abordar este tema o alguien con un conocimiento más profundo podría agregar a esta respuesta - ¡por favor, hágalo!

Continuar. Debido a que NamespacePrefixMapper no se supone que se use fuera de la JVM, no está incluido en la ruta de compilación estándar de javac (una subsección de rt.jar controlado por tc.sym). Esto significa que cualquier código que dependa de él probablemente se compilará bien en un IDE, pero fallará en el comando línea (es decir, Maven o Hormiga). Para superar esto el rt.jar el archivo debe incluirse explícitamente en la compilación, e incluso entonces Windows parece tener problemas si la ruta tiene espacios en ella.

Si te encuentras en esta posición, aquí hay un fragmento de Maven que te sacará de problemas:

<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.1.9</version>
  <scope>system</scope>
  <!-- Windows will not find rt.jar if it is in a path with spaces -->
  <systemPath>C:/temp/rt.jar</systemPath>
</dependency>

Tenga en cuenta la basura camino codificado duro a un lugar extraño para rt.jar. Usted podría conseguir alrededor de esto con una combinación de {java.inicio}/lib/rt.jar que funcionará en la mayoría de OSs, pero debido al espacio de Windows la emisión no está garantizada. Sí, puede utilizar los perfiles y activarlos en consecuencia...

Alternativamente, en Ant puedes hacer lo siguiente:

<path id="jre.classpath">
  <pathelement location="${java.home}\lib" />
</path>
// Add paths for build.classpath and define {src},{target} as usual
<target name="compile" depends="copy-resources">
  <mkdir dir="${target}/classes"/>
  <javac bootclasspathref="jre.classpath" includejavaruntime="yes" debug="on" srcdir="${src}" destdir="${target}/classes" includes="**/*">
    <classpath refid="build.classpath"/>
  </javac>
</target>    

¿Y qué hay de la configuración del muelle Jaxb2Marshaller? Bueno, aquí está, completo con mi propio NamespacePrefixMapper:

Primavera:

<!-- JAXB2 marshalling (domain objects annotated with JAXB2 meta data) -->
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPaths">
  <list>
    <value>org.example.domain</value>
  </list>
</property>
<property name="marshallerProperties">
  <map>
    <!-- Good for JDK1.6.0_6+, lose 'internal' for earlier releases - see why it's evil? -->
    <entry key="com.sun.xml.internal.bind.namespacePrefixMapper" value-ref="myCapabilitiesNamespacePrefixMapper"/>
    <entry key="jaxb.formatted.output"><value type="boolean">true</value></entry>
  </map>
</property>
</bean>

<!-- Namespace mapping prefix (ns1->abc, ns2->xlink etc) -->
<bean id="myNamespacePrefixMapper" class="org.example.MyNamespacePrefixMapper"/>

Entonces mi NamespacePrefixMapper code:

public class MyNamespacePrefixMapper extends NamespacePrefixMapper {

  public String getPreferredPrefix(String namespaceUri,
                               String suggestion,
                               boolean requirePrefix) {
    if (requirePrefix) {
      if ("http://www.example.org/abc".equals(namespaceUri)) {
        return "abc";
      }
      if ("http://www.w3.org/1999/xlink".equals(namespaceUri)) {
        return "xlink";
      }
      return suggestion;
    } else {
      return "";
    }
  }
}

Bueno, ahí está. Espero que esto ayude a alguien a evitar el dolor por el que pasé. Oh, por cierto, puede encontrarse con la siguiente excepción si usted utiliza el enfoque malvado anterior dentro de Jetty:

Java.lang.IllegalAccessError: class sun.reflejar.GeneratedConstructorAccessor23 no puede acceder a su superclase sun.reflejar.ConstructorAccessorImpl

Así que buena suerte resolviendo eso. Pista: rt.jar en el bootclasspath de su servidor web.

[Ediciones adicionales para mostrar el enfoque JAXB-RI (Implementación de referencia)]

Si eres capaz de introducir las bibliotecas JAXB-RI en tu código puedes hacer lo siguiente modificaciones para obtener el mismo efecto:

Principal:

// Add a new property that implies external access
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());

MyNamespacePrefixMapper:

// Change the import to this
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;

Agregue el siguiente JAR de la descarga de JAXB-RI (después de saltar a través de los aros de licencia) de la carpeta /lib:

jaxb-impl.jar

Ejecutando Main.main() da como resultado la salida deseada.

 12
Author: Gary Rowe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-22 13:48:36

(Respuesta muy editada)

Creo que el problema en su código se debe a algunos desajustes de URI de espacio de nombres. A veces usted está usando "http://www.example.org/abc" y otras veces "www.example.org/abc" . Lo siguiente debería hacer el truco:

Main.java

package org.example.domain;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Main {

    public static void main(String[] args) throws JAXBException { 
        JAXBContext jc = JAXBContext.newInstance(RootElement.class); 
        System.out.println(jc);

        RootElement re = new RootElement(); 
        re.childElementWithXlink = new ChildElementWithXlink(); 

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

RootElement.java

package org.example.domain; 

import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlRootElement; 

@XmlRootElement(namespace="http://www.example.org/abc", name="Root_Element") 
public class RootElement { 
  @XmlElement(namespace = "http://www.example.org/abc") 
  public ChildElementWithXlink childElementWithXlink; 

}

ChildElementWithXLink.java

package org.example.domain;

import javax.xml.bind.annotation.XmlAttribute; 
import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.XmlSchemaType; 

@XmlRootElement(namespace="http://www.example.org/abc", name="Child_Element_With_XLink") 
public class ChildElementWithXlink { 
  @XmlAttribute(namespace = "http://www.w3.org/1999/xlink") 
  @XmlSchemaType(namespace = "http://www.w3.org/1999/xlink", name = "anyURI") 
  private String href="http://www.example.org"; 

} 

Información del paquete.java

@javax.xml.bind.annotation.XmlSchema( 
    namespace = "http://www.example.org/abc", 
    xmlns = { 
          @javax.xml.bind.annotation.XmlNs(prefix = "abc", namespaceURI ="http://www.example.org/abc"), 
          @javax.xml.bind.annotation.XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink") 
            },  
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) 
    package org.example.domain; 

Ahora ejecutando Main.main() da lo siguiente salida:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<abc:Root_Element xmlns:abc="http://www.example.org/abc" xmlns:xlink="http://www.w3.org/1999/xlink">
    <abc:childElementWithXlink xlink:href="http://www.example.org"/>
</abc:Root_Element>
 9
Author: Blaise Doughan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-21 20:16:44

@Blaise: Puede actualizar la documentación de MOXy con esta información:

Definir espacios de nombres Spring JAXB sin usar NamespacePrefixMapper

Creo que no se describe allí cómo se pueden configurar los prefijos de espacio de nombres. ¡Gracias!

 1
Author: basZero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:30

La implementación de JAXB en JDK 7 soporta el prefijo de espacio de nombres. He intentado con JDK 1.6.0_21 sin suerte.

 0
Author: Sayantam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-05 15:20:59