2010-07-20 17 views
24

[fortemente modificato la comprensione progredisce]Definire namespace Primavera JAXB senza utilizzare NamespacePrefixMapper

E 'possibile ottenere primavera Jaxb2Marshaller per utilizzare un set personalizzato di prefissi di namespace (o almeno rispettare quelli dati nel file di schema/annotazioni) senza dover utilizzare un'estensione di NamespacePrefixMapper?

L'idea è di avere una classe con una relazione "ha una" con un'altra classe che a sua volta contiene una proprietà con un diverso spazio dei nomi. Per meglio illustrare questo consideriamo il seguente schema di progetto che usa JDK1.6.0_12 (l'ultimo che posso mettere in campo al lavoro). Ho il seguente nel pacchetto 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; 

esecuzione Main.main() ha pronunciato la seguente uscita:

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

mentre ciò che vorrei è:

<?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 volta che questa parte funziona, allora il problema passa alla configurazione del Jaxb2Marshaller in Spring (Spring 2.5.6, con spring-oxm-tiger-1.5.6 che fornisce Jaxb2Marshaller) in modo che fornisca lo stesso tramite una semplice configurazione del contesto e una chiamata a marshal().

Grazie per il vostro continuo interesse in questo problema!

+0

Riesci a farlo funzionare con un vanilla 'JAXBContext', vale a dire nessuna primavera coinvolta? Sembra che 'Jaxb2Marshaller' non dovrebbe essere rilevante per il problema. Inoltre, quali versioni JDK e/o JAXB stai usando? (p.s. hi Gary :) – skaffman

+0

Ehi, skaffman :-) ha apportato le modifiche necessarie. Questo sta facendo il mio dado. –

risposta

12

[Alcune modifiche per offrire un JAXB-RI alternativa sono alla fine di questo post]

Ebbene dopo molta testa graffiare ho finalmente dovuto accettare che per il mio ambiente (JDK1.6.0_12 su Windows XP e JDK1.6.0_20 su Mac Leopard) Non riesco a farlo funzionare senza ricorrere al male che è NamespacePrefixMapper. Perché è cattivo? Perché obbliga a fare affidamento su una classe JVM interna nel codice di produzione. Queste classi non fanno parte di un'interfaccia affidabile tra JVM e il tuo codice (cioè cambiano tra gli aggiornamenti della JVM).

A mio parere Sun dovrebbe risolvere questo problema o qualcuno con una conoscenza più approfondita potrebbe aggiungere a questa risposta - per favore!

In movimento. Poiché NamespacePrefixMapper non dovrebbe essere usato al di fuori della JVM, non è incluso nel percorso di compilazione standard di javac (una sottosezione di rt.jar controllata da ct.sym). Ciò significa che qualsiasi codice che dipende da esso probabilmente verrà compilato correttamente in un IDE, ma non riuscirà nella riga di comando (ad esempio Maven o Ant). Per superare questo problema, il file rt.jar deve essere incluso esplicitamente nel build e anche in questo caso Windows sembra avere problemi se il percorso contiene spazi.

Se vi trovate in questa posizione, ecco un frammento di Maven che ti porterà fuori dai guai:

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

Annotare il percorso dei rifiuti hard coded in un posto strano per rt.jar. Si potrebbe aggirare questo con una combinazione di {java.home}/lib/rt.jar che funzionerà sulla maggior parte dei sistemi operativi, ma a causa del problema di spazio di Windows non è garantito. Sì, è possibile utilizzare i profili e attivare di conseguenza ...

In alternativa, in Ant è possibile effettuare le seguenti operazioni:

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

E che dire della configurazione di Spring Jaxb2Marshaller? Beh qui è, con la mia 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"/> 

Poi il mio codice 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 ""; 
    } 
    } 
} 

Beh non v'è. Spero che questo aiuti qualcuno a evitare il dolore che ho attraversato. Oh, a proposito, si può incorrere nella seguente eccezione se si utilizza l'approccio di cui sopra entro il male Jetty:

java.lang.IllegalAccessError: classe sun.reflect.GeneratedConstructorAccessor23 non può accedere alla sua superclasse sun.reflect.ConstructorAccessorImpl

Quindi, buona fortuna, ordinarlo. Indizio: rt.jar nel percorso del percorso di avvio del tuo server web.

[modifiche extra per mostrare il JAXB-RI (Reference Implementation) approccio]

Se siete in grado di introdurre il JAXB-RI libraries nel codice è possibile apportare le seguenti modifiche per ottenere lo stesso effetto:

principale:

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

Aggiungere il seguente JAR da JAXB-RI download (dopo i salti mortali di licenza) dalla cartella/lib:

jaxb-impl.jar 

in corso risultati Main.main() in output desiderato.

+1

Un'alternativa è usare JAXB-RI piuttosto che quello integrato in JDK 1.6. In questo modo, si ottiene un percorso di classe prevedibile e 'NamespacePrefixMapper' non ha' internal' nel nome della classe :) – skaffman

9

(reponse fortemente modificati)

Credo che il problema nel codice è dovuto ad alcuni namespace URI disallineamenti. A volte si utilizza "http://www.example.org/abc" e altre volte "www.example.org/abc". Il seguente dovrebbe fare il trucco:

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; 
012.

Ora in esecuzione principale.main() ha pronunciato la seguente uscita:

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

Grazie Blaise per il tuo interesse in questo. Ho apportato alcune modifiche al post originale per rispondere meglio alle tue domande. Noterai che ho aggiunto un elemento figlio con uno spazio dei nomi diverso (quello di XLink) che non ho reso chiaro nel mio post originale (mi dispiace). –

+0

Ciao Blaise, ho provato a replicare i risultati con la classe RootElement aggiornata, ma sfortunatamente se tutto il codice è come ho inserito nel post, fallisce con uno spazio dei nomi aggiuntivo (ns3). Ti dispiacerebbe rivedere quello che ho postato per assicurarti che corrisponda ancora alla tua configurazione? Grazie per il tuo aiuto :) –

+0

Ho perso la dichiarazione dello spazio dei nomi extra ns3. Se si rimuove @XmlRootElement da ChildElementWithXLink, questo scompare (vedere la risposta modificata sopra). Hai bisogno di quell'annotazione nel tuo modello? –

0

L'implementazione JAXB in JDK 7 supporta il prefisso dello spazio dei nomi. Ho provato con JDK 1.6.0_21 senza fortuna.

Problemi correlati