Usando JAXB para desempacotar / marechal uma List
Pergunta
Eu estou tentando criar um servidor RESTO muito simples. Eu só tenho um método de teste que irá retornar uma lista de strings. Aqui está o código:
@GET
@Path("/test2")
public List test2(){
List list=new Vector();
list.add("a");
list.add("b");
return list;
}
Ela dá o seguinte erro:
SEVERE: A message body writer for Java type, class java.util.Vector, and MIME media type, application/octet-stream, was not found
Eu estava esperando JAXB tinha uma configuração padrão para tipos simples, como String, Integer, etc. Acho que não. Aqui está o que eu imaginava:
<Strings>
<String>a</String>
<String>b</String>
</Strings>
O que é a maneira mais fácil de fazer este trabalho método?
Solução
Eu usei @ exemplo de LiorH e expandiu-lo para:
@XmlRootElement(name="List")
public class JaxbList<T>{
protected List<T> list;
public JaxbList(){}
public JaxbList(List<T> list){
this.list=list;
}
@XmlElement(name="Item")
public List<T> getList(){
return list;
}
}
Note, que ele usa os genéricos para que você possa usá-lo com outras classes que seqüência. Agora, o código do aplicativo é simples:
@GET
@Path("/test2")
public JaxbList test2(){
List list=new Vector();
list.add("a");
list.add("b");
return new JaxbList(list);
}
Por que não faz isso exist classe simples no pacote JAXB? Alguém viu nada parecido em outro lugar?
Outras dicas
@GET
@Path("/test2")
public Response test2(){
List<String> list=new Vector<String>();
list.add("a");
list.add("b");
final GenericEntity<List<String>> entity = new GenericEntity<List<String>>(list) { };
return Response.ok().entity(entity).build();
}
No caso de alguém de vocês quer escrever um wrapper lista para as listas que contenham elementos de várias classes e quero dar um nome XmlElement indivíduo de acordo com o tipo de classe, sem aulas de redação X Wrapper você poderia usar a anotação @XmlMixed
.
Ao fazê-lo nomes JAXB os itens da lista de acordo com o valor definido pelo @XmlRootElement
.
Ao fazer isso você tem que especificar quais classes poderia estar na lista usando @XmlSeeAlso
classes possíveis na lista ??p>
@XmlRootElement(name="user")
public class User {/*...*/}
@XmlRootElement(name="entry")
public class LogEntry {/*...*/}
classe Wrapper
@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
@XmlMixed
public List<T> getRecords(){
return records;
}
}
Exemplo:
List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));
XStream xStream = new XStream();
String result = xStream.toXML(l);
Resultado:
<records>
<user>...</user>
<entry>...</entry>
</records>
Alternatevily você pode especificar os nomes XmlElement diretamente dentro da classe wrapper utilizando a @XmlElementRef
anotação
@XmlRootElement(name="records")
@XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
@XmlElementRefs({
@XmlElementRef(name="item", type=Object.class),
@XmlElementRef(name="user", type=User.class),
@XmlElementRef(name="entry", type=LogEntry.class)
})
public List<T> getRecords(){
return records;
}
}
A partir de um blog pessoal pós , não é necessário criar um objeto específico JaxbList < T >
.
Assumindo um objeto com uma lista de strings:
@XmlRootElement
public class ObjectWithList {
private List<String> list;
@XmlElementWrapper(name="MyList")
@XmlElement
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
A JAXB de ida e volta:
public static void simpleExample() throws JAXBException {
List<String> l = new ArrayList<String>();
l.add("Somewhere");
l.add("This and that");
l.add("Something");
// Object with list
ObjectWithList owl = new ObjectWithList();
owl.setList(l);
JAXBContext jc = JAXBContext.newInstance(ObjectWithList.class);
ObjectWithList retr = marshallUnmarshall(owl, jc);
for (String s : retr.getList()) {
System.out.println(s);
} System.out.println(" ");
}
produz o seguinte:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
<MyList>
<list>Somewhere</list>
<list>This and that</list>
<list>Something</list>
</MyList>
</objectWithList>
Isto pode ser feito MUITO fácil usando XStream biblioteca maravilhosa . Não wrappers, há anotações.
Alvo XML
<Strings>
<String>a</String>
<String>b</String>
</Strings>
serialização
(apelido String
pode ser evitado pelo uso de tag minúsculas string
, mas eu usei o código do OP)
List <String> list = new ArrayList <String>();
list.add("a");
list.add("b");
XStream xStream = new XStream();
xStream.alias("Strings", List.class);
xStream.alias("String", String.class);
String result = xStream.toXML(list);
Deserialization
Deserialization em ArrayList
XStream xStream = new XStream();
xStream.alias("Strings", ArrayList.class);
xStream.alias("String", String.class);
xStream.addImplicitArray(ArrayList.class, "elementData");
List <String> result = (List <String>)xStream.fromXML(file);
Deserialization em String []
XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);
Nota, essa instância XStream é isenta de segmentos e pode ser pré-configurado, encolhendo quantidade código para uma das linhas.
XStream também pode ser usado como um mecanismo de serialização padrão para o serviço JAX-RS. Exemplo de ligar XStream em Jersey pode ser encontrada aqui
Eu encontrei esse padrão algumas vezes, descobri que a maneira mais fácil é definir uma classe interna com anotações JAXB. (De qualquer maneira, você provavelmente vai querer definir o nome do tag root)
para que o seu código seria algo parecido com isto
@GET
@Path("/test2")
public Object test2(){
MyResourceWrapper wrapper = new MyResourceWrapper();
wrapper .add("a");
wrapper .add("b");
return wrapper ;
}
@XmlRootElement(name="MyResource")
private static class MyResourceWrapper {
@XmlElement(name="Item")
List<String> list=new ArrayList<String>();
MyResourceWrapper (){}
public void add(String s){ list.add(s);}
}
Se você trabalha com javax.rs (JAX-RS) que retornaria objeto de resposta com o conjunto de embalagem como a sua entidade
Finalmente eu resolvi-lo usando JacksonJaxbJsonProvider
Ele requer algumas mudanças em sua context.xml
Primavera e Maven pom.xml
Em seu Primavera context.xml
add JacksonJaxbJsonProvider
ao <jaxrs:server>
:
<jaxrs:server id="restService" address="/resource">
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
</jaxrs:providers>
</jaxrs:server>
Em seu Maven pom.xml add:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.0</version>
</dependency>
O exemplo de User1 funcionou bem para mim. Mas, como um aviso, não vai funcionar com qualquer coisa que não seja simples Cordas / Os tipos inteiros, a menos que você adicionar uma anotação @XmlSeeAlso:
@XmlRootElement(name = "List")
@XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
protected List<MovieTicket> list;
Isso funciona bem, embora me impede de usar uma única classe de lista genérica em toda a minha candidatura. Ele também pode explicar por que esta classe aparentemente óbvia não existe no pacote de JAXB.
Certifique-se adicionar @XmlSeeAlso tag com suas classes específicas utilizadas dentro JaxbList. É muito importante senão ele joga HttpMessageNotWritableException
Eu teria economizado tempo se eu encontrasse Provedor Resteasy Jackson mais cedo.
Basta adicionar o Resteasy Jackson JAR Provider . Não wrappers entidade. Nenhum anotações XML. Há escritores corpo de mensagem personalizada.
Se você estiver usando Maven no add projeto jersey abaixo no pom.xml e atualização do projeto dependências para que JAXB é capaz de detectar classe de modelo e lista de converter para mídia XML tipo de aplicação:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
Para uma solução mais geral, para a serialização JAXB-XML de qualquer lista de nível superior, que requer apenas uma nova classe para ser escrito, veja a solução dada nesta pergunta:
É possível JAXB programaticamente configure?
public class Wrapper<T> {
private List<T> items = new ArrayList<T>();
@XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
//JAXBContext is thread safe and so create it in constructor or
//setter or wherever:
...
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
...
public String marshal(List<T> things, Class clazz) {
//configure JAXB and marshaller
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create wrapper based on generic list of objects
Wrapper<T> wrapper = new Wrapper<T>(things);
JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);
StringWriter result = new StringWriter();
//marshal!
m.marshal(wrapperJAXBElement, result);
return result.toString();
}