2010-07-20 20 views
24

[pesadamente editado a medida que avanza la comprensión]Definir espacios de nombres primavera JAXB sin utilizar NamespacePrefixMapper

¿Es posible obtener primavera Jaxb2Marshaller utilizar un conjunto personalizado de los prefijos de espacio de nombres (o al menos respetar las dadas en el archivo de esquema/anotaciones) sin tener que usar una extensión de un NamespacePrefixMapper?

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

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"; 

} 

package-info.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; 

Correr Main.main() da el siguiente resultado:

<?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 la configuración de la Jaxb2Marshaller en Spring (Spring 2.5.6, con spring-oxm-tiger-1.5.6 que proporciona Jaxb2Marshaller) para que proporcione el mismo a través de una configuración de contexto simple y una llamada a mariscal().

¡Gracias por su continuo interés en este problema!

+0

¿Se puede hacer funcionar esto con un 'JAXBContext' vainilla, es decir, sin Spring involucrado? Parece que 'Jaxb2Marshaller' no debería ser relevante para el problema. Además, ¿qué versiones de JDK y/o JAXB estás usando? (p.s. hola Gary :) – skaffman

+0

Oye, skaffman :-) hizo los cambios necesarios. Esto está haciendo mi tuerca. –

Respuesta

12

[Algunas ediciones para ofrecer una alternativa JAXB-RI se encuentran al final de este post]

Bueno después de mucho rascarse 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) No puedo hacer que esto funcione sin recurrir al mal que es el NamespacePrefixMapper. ¿Por qué es malo? Porque obliga a depender de una clase de JVM interna en su código de producción. Estas clases no forman parte de una interfaz confiable entre la JVM y su código (es decir, cambian entre las actualizaciones de la JVM).

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

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

Si usted se encuentra en esta posición, aquí es un fragmento de Maven que les permite conocer fuera 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> 

Anote la ruta de basura duro codificado a un lugar extraño para rt.jar. Podría solucionar esto con una combinación de {java.home} /lib/rt.jar que funcionará en la mayoría de los sistemas operativos pero no se garantiza el problema de espacio de Windows. Sí, puede utilizar perfiles y activar en consecuencia ...

alternativa, en hormiga puede 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 lo de la configuración de Spring Jaxb2Marshaller? Pues aquí está, con mi propia 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 código NamespacePrefixMapper:

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 ""; 
    } 
    } 
} 

bien no lo es. Espero que esto ayude a alguien a evitar el dolor que sufrí. Ah, por cierto, es posible que encuentre la siguiente excepción si se utiliza el enfoque más mal que dentro del embarcadero:

java.lang.IllegalAccessError: Clase sun.reflect.GeneratedConstructorAccessor23 no puede acceder a su superclase sun.reflect.ConstructorAccessorImpl

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

[ediciones adicionales para mostrar la JAXB-RI (Referencia de Aplicación) Enfoque]

Si usted es capaz de introducir el JAXB-RI libraries en su código que puede hacer las siguientes modificaciones para conseguir 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; 

Añadir la siguiente JAR de JAXB-RI descarga (después de saltar a través de aros de licencia) de la carpeta/lib:

jaxb-impl.jar 

resultados Operando Main.main() en la salida deseada.

+1

Una alternativa es usar el JAXB-RI en lugar del que está incorporado a JDK 1.6. De esta forma, obtienes un classpath predecible, y el 'NamespacePrefixMapper' no tiene' internal' en el nombre de clase :) – skaffman

9

(Reponse fuertemente editado)

creo que el problema en su código es debido a algunos desajustes de espacio de nombres URI. Algunas veces está usando "http://www.example.org/abc" y otras veces "www.example.org/abc". A continuación se debe 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"; 

} 

package-info.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 el siguiente resultado:

<?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> 
+0

Gracias Blaise por su interés en esto. Realicé algunas modificaciones en la publicación original para responder mejor a sus preguntas. Notarás que agregué un elemento secundario con un espacio de nombres diferente (el de XLink) que no dejé claro en mi publicación original (lo siento). –

+0

Hola Blaise, he tratado de replicar tus resultados con la clase RootElement actualizada, pero desafortunadamente, si el resto del código es como lo he publicado, falla con un espacio de nombres adicional (ns3). ¿Le importaría simplemente revisar lo que he publicado para asegurarse de que aún coincide con su configuración? Gracias por su ayuda :) –

+0

Me perdí la declaración de espacio de nombres ns3 extra. Si elimina @XmlRootElement de ChildElementWithXLink, desaparecerá (consulte la respuesta editada más arriba). ¿Necesitas esa anotación en tu modelo? –

0

La implementación de JAXB en JDK 7 admite el prefijo del espacio de nombres. Lo he intentado con JDK 1.6.0_21 sin suerte.

Cuestiones relacionadas