web-dev-qa-db-fra.com

Définir les espaces de noms Spring JAXB sans utiliser NamespacePrefixMapper

[Édité au fur et à mesure que la compréhension progresse]

Est-il possible d’obtenir que Spring Jaxb2Marshaller utilise un ensemble personnalisé de préfixes d’espace de nommage (ou au moins respecte ceux donnés dans le fichier de schéma/les annotations) sans avoir à utiliser l’extension d’un NamespacePrefixMapper?

L'idée est d'avoir une classe avec une relation "a" avec une autre classe qui contient à son tour une propriété avec un espace de noms différent. Pour mieux illustrer cela, considérons le résumé de projet suivant, qui utilise JDK1.6.0_12 (le dernier en date que je peux mettre la main au travail). J'ai le suivant dans le package 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'exécution de Main.main () donne la sortie suivante:

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

alors que ce que je voudrais, c'est:

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

Une fois que cette partie fonctionne, le problème passe maintenant à la configuration de Jaxb2Marshaller au printemps (Spring 2.5.6, avec spring-oxm-tiger-1.5.6 fournissant Jaxb2Marshaller) afin qu’il fournisse la même chose au moyen d’une configuration de contexte simple. un appel à marshal (). 

Merci de votre intérêt continu pour ce problème!

25
Gary Rowe

[Certaines modifications pour offrir une alternative JAXB-RI se trouvent à la fin de cet article]

Après de nombreuses critiques, j'ai finalement dû accepter cela pour mon environnement (JDK1.6.0_12 sous Windows XP et JDK1.6.0_20 sur Mac Leopard). Je ne peux tout simplement pas faire fonctionner cela sans recourir au mal c'est le NamespacePrefixMapper. Pourquoi est-ce mal? Parce qu'il oblige à utiliser une classe JVM interne dans votre code de production. Ces classes ne font pas partie d’une interface fiable entre la machine virtuelle Java et votre code (c’est-à-dire qu’elles changent entre les mises à jour de la machine virtuelle Java). 

À mon avis, Sun devrait s’attaquer à ce problème ou une personne mieux informée pourrait ajouter quelque chose à cette réponse - faites-le! 

Passer à autre chose Comme NamespacePrefixMapper n'est pas censé être utilisé en dehors de la machine virtuelle, il n'est pas inclus dans le chemin de compilation standard de javac (une sous-section de rt.jar contrôlée par ct.sym). Cela signifie que tout code qui en dépend sera probablement bien compilé dans un IDE, mais échouera sur la ligne de commande (c'est-à-dire Maven ou Ant). Pour résoudre ce problème, le fichier rt.jar doit être explicitement inclus dans la construction. Même dans ce cas, Windows semble avoir des problèmes si le chemin contient des espaces.

Si vous vous trouvez dans cette position, voici un extrait Maven qui vous évitera des ennuis:

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

Notez le chemin codé dur vers un lieu étrange pour rt.jar. Vous pouvez résoudre ce problème avec une combinaison de {Java.home} /lib/rt.jar qui fonctionnera sur la plupart des systèmes d'exploitation, mais en raison du problème d'espace Windows, rien n'est garanti. Oui, vous pouvez utiliser les profils et les activer en conséquence ...

Sinon, dans Ant, vous pouvez effectuer les opérations suivantes:

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

Et qu'en est-il de la configuration Jaxb2Marshaller Spring? Eh bien le voici, avec mon propre NamespacePrefixMapper:

Printemps:

<!-- 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"/>

Ensuite, mon code 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 "";
    }
  }
}

Eh bien voilà. J'espère que cela aide quelqu'un à éviter la douleur que j'ai subie. Oh, au fait, vous pouvez rencontrer l'exception suivante si vous utilisez l'approche maléfique ci-dessus dans Jetty:

Java.lang.IllegalAccessError: la classe Sun.reflect.GeneratedConstructorAccessor23 ne peut pas accéder à sa super-classe Sun.reflect.ConstructorAccessorImpl

Alors, bonne chance pour résoudre ce problème. Indice: rt.jar dans le chemin de démarrage de votre serveur Web.

[Modifications supplémentaires pour illustrer l'approche JAXB-RI (Reference Implementation)]

Si vous êtes en mesure d'introduire les bibliothèques JAXB-RI dans votre code, vous pouvez apporter les modifications suivantes pour obtenir le même effet:

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;

Ajoutez le fichier JAR suivant depuis le téléchargement JAXB-RI (après avoir sauté dans les cerceaux de licence) à partir du dossier/lib:

jaxb-impl.jar

L'exécution de Main.main () donne la sortie souhaitée.

12
Gary Rowe

(Réponse fortement édité)

Je crois que le problème de votre code est dû à certaines inadéquations d'URI d'espace de noms. Parfois, vous utilisez " http://www.example.org/abc " et d'autres fois "www.example.org/abc". Ce qui suit devrait faire l'affaire:

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; 

Maintenant, exécuter Main.main () donne la sortie suivante:

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

@Blaise: Pouvez-vous mettre à jour la documentation de MOXy avec cette information:

Définir les espaces de noms Spring JAXB sans utiliser NamespacePrefixMapper

Je pense qu'il n'est pas décrit ici comment vous pouvez configurer les préfixes d'espace de nom . Merci!

1
basZero

L'implémentation JAXB dans JDK 7 prend en charge le préfixe d'espace de nom. J'ai essayé avec JDK 1.6.0_21 sans succès.

0
Sayantam