Как задать префиксы для namespace в JAXB?

Написано 13 Июнь, 2013 в категории Java,Разработка ПО

Предыстория:
Жила была одна система и на выходе выплевывала она xml. Этот xml система получала путем преобразования POJO c помощью JAXB. Вторая система умела генерировать точно такие же POJO, но не умела получать из него xml. Как в первой системе происходило преобразование POJO->xml (кроме того что использовался JAXB) было неизвестно. Моя задача была в том чтобы научить вторую систему получать xml, идентичный первой системе.

Практически с первой попытки мне удалось получить похожий xml. Xml первой и второй системы отличались только namespace-префиксами, а точнее их порядком. Эту проблему и предстояло побороть...

Первый xml (от первой системы):

<ns2:data xmlns:ns2="http://myproject.com/test" xmlns:ns3="http://myproject.com/test/data" >
<ns2:result>0</ns2:result>
    <ns2:list>
        <ns3:node key="name">
            <ns3:val>Ivan</ns3:val>
        </ns3:node>
        <ns3:node key="age">
            <ns3:val>34</ns3:val>
        </ns3:node>
    </ns2:list>
</ns2:data>

Второй xml (то что получил я):

<ns3:data xmlns:ns2="http://myproject.com/test/data" xmlns:ns3="http://myproject.com/test" >
<ns3:result>0</ns3:result>
    <ns3:list>
        <ns2:node key="name">
            <ns2:val>Ivan</ns2:val>
        </ns2:node>
        <ns2:node key="age">
            <ns2:val>34</ns2:val>
        </ns2:node>
    </ns3:list>
</ns3:data>

Так я делал marshaling:

package com.myproject;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
 
...
 
	  try {
		JAXBContext jaxbContext = JAXBContext.newInstance(myPojo.getClass());
		Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
		jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		jaxbMarshaller.marshal(myPojo, System.out);
	      } catch (JAXBException e) {
		e.printStackTrace();
	      }
 
	}
}

Естественно, при сравнении двух xml c разными namespace-префиксами XMLUnit отвечал, что эти xml "не идентичны". Т.к. фактически это 2 одинаковых xml, то я решил просто "правильно" расставить свои префиксы. Конечно, это решение типа "костыль", но как временное оно вполне сгодится. Итак, я просто задал точное соответствие между namespace и префиксами в объекте класса Marshaller. Получилось так:

package com.myproject;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
 
...
 
	  try {
		JAXBContext jaxbContext = JAXBContext.newInstance(myPojo.getClass());
		Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
		jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                NamespacePrefixMapper npm = new NamespacePrefixMapper() {
 
                    private static final String ns2_uri = "http://myproject.com/exp";
                    private static final String ns3_uri = "http://myproject.com/exp/data";
 
                    @Override
                    public String getPreferredPrefix(String s, String s2, boolean b) {
                        if(ns2_uri.equals(s)) {
                            return "ns2";
                        } else if(ns3_uri.equals(s)) {
                            return "ns3";
                        } 
                        return s2;
                    }
                };
                mar.setProperty("com.sun.xml.bind.namespacePrefixMapper", npm);
		jaxbMarshaller.marshal(myPojo, System.out);
	      } catch (JAXBException e) {
		e.printStackTrace();
	      }
	}
}

Xml стали идентичными, но при этом мы потеряли гибкость маршалинга, т.е. при изменениях в namespace нам необходимо будет править наш маппинг.

Всем спасибо за внимание!