Mostrando las entradas con la etiqueta xml. Mostrar todas las entradas
Mostrando las entradas con la etiqueta xml. Mostrar todas las entradas

martes, marzo 24, 2009

Serializar/desserializar objetos java a/de XML/JSON

este post es la segunda parte de este post: http://marianoguerra.blogspot.com/2009/03/webservices-rest-en-java-con-jetty-y.html


Para tener webservices REST como la gente necesitamos poder transformar nuestros objetos a una representacion que pueda ser entendida por muchos lenguajes y de vuelta de esa representacion a objetos java, para ello vamos a crear los objetos de nuetro modelo y los vamos anotar para poder serializar/desserializar a XML y JSON, yo particularmente prefiero JSON porque es mas simple, legible y interactua muy bien con javascript, que suele ser el otro extremo de cualquier aplicacion web, pero no importa ya que de la forma que voy a mostrar nos permite hacer los dos por el precio de uno.

Para ello creamos primero el paquete model dentro de nuestro proyecto, en mi caso addressbook.model y adentro creamos dos clases, AddressBook y AddressBookEntry

para poder usar las anotaciones de serializacion agregamos los siguientes jars

jaxb-api-2.1.jar
jaxb-impl-2.1.jar

para poder serializar a JSON agregamos el siguiente jar

jettison-1.0.1.jar

para buscar versiones mas nuevas podes ir a http://download.java.net/maven/1/javax.xml.bind/jars/ y http://repository.codehaus.org/org/codehaus/jettison/jettison/ respectivamente.

ahora creamos la clase AddressBookEntry que simplemente va a contener los siguientes atributos privados, todos strings:

  • firstName
  • lastName
  • address
  • homePhone
  • workPhone
  • cellPhone
creamos los getters/setters y dos constructores, uno sin argumentos que inicializa los valores por default y uno con todos los parametros.

ahora llega la hora de decorar la clase para poder serializarla, el resultado es el siguiente:


/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook.model;

import java.io.Serializable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

/**
*
* @author mariano
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="addressBookEntity")
public class AddressBookEntry implements Serializable{
private String firstName;
private String lastName;
private String address;
private String homePhone;
private String workPhone;
private String cellPhone;

public AddressBookEntry() {
}

public AddressBookEntry(String firstName, String LastName, String address, String homePhone, String workPhone, String cellPhone) {
this.firstName = firstName;
this.lastName = LastName;
this.address = address;
this.homePhone = homePhone;
this.workPhone = workPhone;
this.cellPhone = cellPhone;
}

public String getLastName() {
return lastName;
}

public void setLastName(String LastName) {
this.lastName = LastName;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getCellPhone() {
return cellPhone;
}

public void setCellPhone(String cellPhone) {
this.cellPhone = cellPhone;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getHomePhone() {
return homePhone;
}

public void setHomePhone(String homePhone) {
this.homePhone = homePhone;
}

public String getWorkPhone() {
return workPhone;
}

public void setWorkPhone(String workPhone) {
this.workPhone = workPhone;
}


}


si se fijan hay dos anotaciones nomas, uno que le dice el nombre de la entidad cuando la serialize y el otro le dice si debe buscar las anotaciones de los atributos en la definicion de los atributos o en los getters/setters.

programamos la clase que contiene muchos AddressBookEntry y la decoramos para poder serializarla:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook.model;

import java.util.Vector;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
*
* @author mariano
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="addressBook")
public class AddressBook {
@XmlElement(name="entry")
private Vector<AddressBookEntry> entries;

public AddressBook() {
entries = new Vector<AddressBookEntry>();
}

public AddressBook(Vector<AddressBookEntry> entries) {
this.entries = entries;
}

public Vector<AddressBookEntry> getEntries() {
return entries;
}

public void setEntries(Vector<AddressBookEntry> entries) {
this.entries = entries;
}

public void addEntry(AddressBookEntry entry)
{
entries.add(entry);
}

public boolean removeEntry(AddressBookEntry entry)
{
return entries.remove(entry);
}

public Vector<AddressBookEntry> search(String query)
{
Vector<AddressBookEntry> result = new Vector<AddressBookEntry>();

result.addAll(searchByFirstName(query));
result.addAll(searchByLastName(query));
return result;
}

public Vector<AddressBookEntry> searchByFirstName(String query)
{
Vector<AddressBookEntry> result = new Vector<AddressBookEntry>();
for(AddressBookEntry entry: entries)
{
if(entry.getFirstName().indexOf(query) != -1)
{
result.add(entry);
}
}

return result;
}

public Vector<AddressBookEntry> searchByLastName(String query)
{
Vector<AddressBookEntry> result = new Vector<AddressBookEntry>();
for(AddressBookEntry entry: entries)
{
if(entry.getLastName().indexOf(query) != -1)
{
result.add(entry);
}
}

return result;
}
}


Lo unico que cambia con respecto al anterior es que a la coleccion le digo cual es el tag de cada elemento interno de la coleccion.

con este decorador

    @XmlElement(name="entry")

le agregue algunos metodos de busqueda que nos van a servir mientras no tengamos persistencia.

por ultimo modifique la clase que expone el webservice para que genere json y xml y cree un metodo temporal para llenar con datos el addressbook (hasta que haya persistencia).

aca esta el codigo de addressbook.ws.AddressBook:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook.ws;

import addressbook.model.AddressBookEntry;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

/**
*
* @author mariano
*/
@Path("book")
public class AddressBook {
private static addressbook.model.AddressBook addressBook;

public static addressbook.model.AddressBook search(String type, String query)
{
if(type.equals("firstName"))
{
return new addressbook.model.AddressBook(addressBook.searchByFirstName(query));
}
else if(type.equals("lastName"))
{
return new addressbook.model.AddressBook(addressBook.searchByLastName(query));
}
else
{
return new addressbook.model.AddressBook(addressBook.search(query));
}
}

public static addressbook.model.AddressBook getAddressBook()
{
if(addressBook == null)
addressBook = new addressbook.model.AddressBook();

return addressBook;
}

public static void fill()
{
getAddressBook();
addressBook.addEntry(new AddressBookEntry("Bob", "Esponja", "Fondo de Bikini 1", "123", "321", "5551"));
addressBook.addEntry(new AddressBookEntry("Gary", "Caracol", "Fondo de Bikini 2", "234", "432", "5552"));
addressBook.addEntry(new AddressBookEntry("Arenita", "Ardilla", "Fondo de Bikini 3", "345", "543", "5553"));
addressBook.addEntry(new AddressBookEntry("Patricio", "Estrella", "Fondo de Bikini 4", "456", "654", "5554"));
addressBook.addEntry(new AddressBookEntry("Calamardo", "Calamar", "Fondo de Bikini 5", "567", "765", "5555"));
}

@GET
@Path("xml/all")
@Produces("application/xml")
public static addressbook.model.AddressBook getAllXml()
{
return addressBook;
}

@GET
@Path("json/all")
@Produces("application/json")
public static addressbook.model.AddressBook getAllJson()
{
return addressBook;
}

@GET
@Path("xml/search/{type}/{query}")
@Produces("application/xml")
public static addressbook.model.AddressBook searchXml(@PathParam("type") String type, @PathParam("query") String query)
{
return search(type, query);
}

@GET
@Path("json/search/{type}/{query}")
@Produces("application/json")
public static addressbook.model.AddressBook searchJson(@PathParam("type") String type, @PathParam("query") String query)
{
return search(type, query);
}
}


en el main agregue una linea antes de server.start(); en la que llamo a addressbook.ws.AddressBook.fill();

ahora con todo eso puesto, si corremos el ejemplo vamos a obtener resultados serializados, ejemplos de urls para probar los gets:

para obtener todos en xml:

http://0.0.0.0:9999/ws/book/xml/all

para obtener todos en json:

http://0.0.0.0:9999/ws/book/json/all

para buscar los que contienen en el nombre "ob" en xml:

http://0.0.0.0:9999/ws/book/xml/search/firstName/ob/

para buscar los que contienen en el apellido "a" en json:

http://0.0.0.0:9999/ws/book/json/search/lastName/a/

para buscar los que contienen en el nombre o apellido "e" en xml:

http://0.0.0.0:9999/ws/book/xml/search/all/e/

supongo que van entendiendo ;)

bueno, eso es todo por ahora en la proxima como escribir un modulo para javascript para consumir los webservices json y transformarlos en objetos y tambien como "dar de alta" nuevos AddressBookEntry.

Webservices REST en Java con Jetty y Jersey

En este post voy a explicar como hacer una aplicacion Java SE que exponga una API REST tanto JSON como XML.

primero empezamos creando un proyecto comun en netbeans, le ponemos el nombre que se nos ocurra, te recomiendo addressbook asi no tenes que cambiar el codigo :D.

despues tenemos que agregar todos los jars que vamos a usar en el proyecto.

aca doy una lista de las ultimas versiones, pero pueden quedar desactualizadas rapido, asi que fijense si no hay mas nuevas.

Primero que todo necesitamos un servidor web embebido, para ello vamos a usar jetty, el cual bajamos de aca: http://dist.codehaus.org/jetty/ yo baje este http://dist.codehaus.org/jetty/jetty-6.1.15/jetty-6.1.15.zip al descomprimir el zip, en la carpeta lib estan los jars que hacen falta

Tambien necesitamos una libraria para poder exponer nuestros webservices REST, para ello vamos a usar jersey, yo baje el bundle (contiene todos los jars adentro que requiere) de aca http://download.java.net/maven/2/com/sun/jersey/jersey-bundle/, particularmente yo baje la version 1.0.2 de aca http://download.java.net/maven/2/com/sun/jersey/jersey-bundle/1.0.2/jersey-bundle-1.0.2.jar

el jar asm lo podemos descargar de aca http://maven.objectweb.org/maven2/asm/asm/ yo baje la version 3.1 de aca http://maven.objectweb.org/maven2/asm/asm/3.1/asm-3.1.jar

el jar jsr311 se encuentra en http://download.java.net/maven/2/javax/ws/rs/jsr311-api/ yo baje http://download.java.net/maven/2/javax/ws/rs/jsr311-api/1.0/jsr311-api-1.0.jar

Para empezar agregamos los siguientes jar al proyecto:

jersey-bundle-1.0.2.jar
jetty-util-6.1.15.jar
servlet-api-2.5-20081211.jar
jetty-6.1.15.jar
jsr311-api-1.0.jar
asm-3.1.jar

Creamos un archivo main con el siguiente contenido

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook;

import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.handler.DefaultHandler;
import org.mortbay.jetty.handler.HandlerList;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;

/**
*
* @author mariano
*/
public class Main {

/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception{
org.mortbay.jetty.Server server;
ServletHolder holder;
Context contextWS;

holder = new ServletHolder(ServletContainer.class);

holder.setInitParameter("com.sun.jersey.config.property.packages",
"addressbook.ws");

server = new org.mortbay.jetty.Server(9999);

contextWS = new Context(server, "/ws", Context.SESSIONS);
contextWS.addServlet(holder, "/*");
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { contextWS, new DefaultHandler()});
server.setHandler(handlers);

server.start();
}

}



lo que hace es inicializar el server en el puerto 9999 y pasarle un handler para los requests que estan bajo el path /ws.

la linea

holder.setInitParameter("com.sun.jersey.config.property.packages", "addressbook.ws");


le dice que busque los manejadores de ws en el paquete addressbook.ws, por lo tanto el siguiente paso es crear el paquete addressbook.ws.

Por lo que veran el ejemplo va a ser un libro de direcciones o cosas por el estilo.

despues de crear el paquete creamos una clase adentro que llamamos AddressBook

el contenido es el siguiente:

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/

package addressbook.ws;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;

/**
*
* @author mariano
*/
@Path("book")
public class AddressBook {

@GET
@Path("all")
public static String getAll()
{
return "all";
}

@GET
@Path("search/{type}/{query}")
public static String getAll(@PathParam("type") String type, @PathParam("query") String query)
{
return type + " " + query;
}
}



El codigo es bastante simple, en la clase le decimos cual es el path base de esa clase, y en cada metodo le decimos que tipo de request responde (en lenguaje REST tenemos GET, POST, PUT y DELETE) y tambien podemos especificarle parametros los cuales van entre llaves en @Path y tambien en el metodo debemos anotar cual parametro del path se asigna a que parametro del metodo.

Por ahora los metodos devuelven cosas simples, en otro post voy a mostrar como serializar/descerializar objetos java de/a XML y JSON

si lo corremos y vamos con el browser a http://0.0.0.0:9999/ws/book/all vamos a ver el texto "all", si vamos a http://0.0.0.0:9999/ws/book/search/name/bob vamos a ver "name bob"

lunes, diciembre 22, 2008

Xml -> dict -> objetos

En este ejemplo se muestra como convertir un string a una estructura de diccionarios y listas anidadas usando expat, tambien se proveen dos clases que permiten manipular el resultado como si fueran objetos.

primero el codigo

import xml.parsers.expat

class XmlParser(object):
'''a class that parses a xml string an generates a nested
dict/list structure
'''

def __init__(self, text):
'''constructor'''
self.parser = xml.parsers.expat.ParserCreate()
self.parser.buffer_text = True

self.result = None
self.stack = []
self.current = None

self.parser.StartElementHandler = self.start_element
self.parser.EndElementHandler = self.end_element
self.parser.CharacterDataHandler = self.char_data
self.parser.Parse(text)

def start_element(self, name, attrs):
'''Start xml element handler'''
if self.current != None:
self.stack.append(self.current)

self.current = {}

for (key, value) in attrs.iteritems():
self.current[str(key)] = value

self.current['tag'] = name
self.current['childs'] = []

def end_element(self, name):
'''End xml element handler'''
if len(self.stack):
current = self.stack.pop()
current['childs'].append(self.current)
self.current = current
else:
self.result = self.current

def char_data(self, data):
'''Char xml element handler.
buffer_text is enabled, so this is the whole text element'''
self.current['childs'].append(data)

class DictObj(dict):
'''a class that allows to access a dict as an object
'''

def __init__(self, kwargs):
'''constructor'''
dict.__init__(self, kwargs)

def __getattribute__(self, name):
if name in self:
obj = self[name]

if type(obj) == dict:
return DictObj(obj)
elif type(obj) == list:
return ListObj(obj)

return obj
else:
return None

class ListObj(list):
'''a class that allows to access dicts inside a list as objects
'''

def __init__(self, args):
'''constructor'''
list.__init__(self, args)

def __getitem__(self, index):
if index > len(self):
raise IndexError('list index out of range')

obj = list.__getitem__(self, index)

if type(obj) == dict:
return DictObj(obj)
elif type(obj) == list:
return ListObj(obj)

return obj

def __iter__(self):
'''iterate over the list'''

count = 0

while count < len(self):
yield self[count]
count += 1

def raw_string(dct_):
'''return a string containing just the string parts removing all the
xml stuff'''

def helper(dct):
result = []

for child in dct.childs:
if type(child) == str or type(child) == unicode:
result.append(str(child))
else:
result = result + helper(child)

return result

return ''.join(helper(dct_))



Simplemente creamos un objeto de tipo XmlParser pasandole el string y obtenemos el resultado parseado en la variable result. Si no queremos andar preguntado si las llaves existen antes de accederlas para evitar excepciones podemos usar la clase DictObj que nos permite acceder a las llaves como si fueran atributos, las variables que no existan como llaves contendran None. Aca va un ejemplo en la consola interactiva


>>> import XmlParser
>>> p = XmlParser.XmlParser('google test foo !!')
>>> r = p.result
>>> d = XmlParser.DictObj(r)
>>> d
{'childs': [{'childs': [u'go', {'childs': [u'o'], 'tag': u's'}, u'gle'], 'href': u'google.com', 'tag': u'a'}, u' ', {'childs': [u'test'], 'tag': u'i'}, u' ', {'childs': [], 'src': u'foo.png', 'alt': u'foo', 'tag': u'img'}, u' ', {'childs': [u'!'], 'tag': u'u'}, {'childs': [u'!'], 'tag': u's'}], 'tag': u'span'}
>>> d.childs
[{'childs': [u'go', {'childs': [u'o'], 'tag': u's'}, u'gle'], 'href': u'google.com', 'tag': u'a'}, u' ', {'childs': [u'test'], 'tag': u'i'}, u' ', {'childs': [], 'src': u'foo.png', 'alt': u'foo', 'tag': u'img'}, u' ', {'childs': [u'!'], 'tag': u'u'}, {'childs': [u'!'], 'tag': u's'}]
>>> d.childs[0]
{'childs': [u'go', {'childs': [u'o'], 'tag': u's'}, u'gle'], 'href': u'google.com', 'tag': u'a'}
>>> d.childs[0].tag
u'a'
>>> d.childs[0].childs[0]
u'go'
>>> d.childs[0].childs[1].tag
u's'

Seguidores

Archivo del Blog