Domanda Definisci gli spazi dei nomi JAXB di Spring senza utilizzare NamespacePrefixMapper


[Modificato pesantemente man mano che la comprensione progredisce]

È possibile ottenere Spring Jaxb2Marshaller per utilizzare un set personalizzato di prefissi di namespace (o almeno rispettare quelli forniti 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;

L'esecuzione di Main.main () restituisce il seguente risultato:

<?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 quello 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 sta funzionando, il problema passa alla configurazione di Jaxb2Marshaller in Spring (Spring 2.5.6, con spring-oxm-tiger-1.5.6 che fornisce Jaxb2Marshaller) in modo che fornisca lo stesso per mezzo di una semplice configurazione di contesto e una chiamata a marshal ().

Grazie per il vostro continuo interesse per questo problema!


25
2017-07-20 11:58


origine


risposte:


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

Bene, dopo aver grattato la testa, ho 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).

Secondo me, Sun dovrebbe affrontare questo problema o qualcuno con una conoscenza più approfondita potrebbe aggiungere a questa risposta - per favore!

Andare avanti. 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 ti trovi in ​​questa posizione, ecco uno snippet di Maven che ti farà uscire 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>

Notare il percorso del disco rigido codificato 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ì, puoi utilizzare i profili e attivare di conseguenza ...

In alternativa, in Ant puoi fare quanto segue:

<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 Jaxb2Marshaller Spring? Bene, eccolo, completo con il mio 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"/>

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

Bene, è così. Spero che questo aiuti qualcuno a evitare il dolore che ho attraversato. Oh, a proposito, potresti incorrere nella seguente eccezione se usi l'approccio malvagio di cui sopra all'interno di Jetty:

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

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

[Ulteriori modifiche per mostrare l'approccio JAXB-RI (implementazione di riferimento)]

Se sei in grado di presentare il Biblioteche JAXB-RI nel tuo codice puoi 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;

Aggiungi il seguente JAR dal download di JAXB-RI (dopo aver saltato attraverso i loop di licenza) dalla cartella / lib:

jaxb-impl.jar

L'esecuzione di Main.main () determina l'output desiderato.


12
2017-07-22 08:37



(Risposta molto modificata)

Credo che il problema nel codice sia dovuto ad alcune mancate corrispondenze URI nello spazio dei nomi. A volte stai usando "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; 

Ora eseguendo Main.main () si ottiene il seguente risultato:

<?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
2017-07-20 15:55



@ Blaise: puoi aggiornare la documentazione di MOXy con queste informazioni:

Definisci gli spazi dei nomi JAXB di Spring senza utilizzare NamespacePrefixMapper

Penso che non sia descritto come configurare i prefissi dei namespace. Grazie!


1
2017-08-05 15:20



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


0