martes, marzo 24, 2009

Como consumuir Webservice REST JSON en Javascript

este post es continuación de este: http://marianoguerra.blogspot.com/2009/03/serializardesserializar-objetos-java.html

ahora vamos a hacer un modulo en javascript para usar la API REST JSON desde una pagina web (lo que normalmente se llama Web 2.0 o AJAX solo que la X de XML la cambiamos por la J de JSON asi que queda AJAJ).

el primer problema que debemos superar es la regla de "same origin" de javascript por la cual solo podemos hacer requests asincronicos con HttpRequest solo a la misma URL de la cual se descargo el script, y como nuestra aplicación se va a distribuir en un jar, nuestro js y html tiene que estar en ese jar y lo tiene que servir la misma aplicación, lo que significa que si le pido 0.0.0.0:9999/pagina.html la aplicación tiene que buscar en el jar el archivo en algun lugar y devolverlo. Si el archivo viene de otro lado nuestro request va a fallar.

Para solucionar esto la unica forma que encontre fue escribir un handler a mano para devolver archivos estaticos.

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

package addressbook;

import java.io.IOException;
import java.net.MalformedURLException;
import org.mortbay.jetty.handler.ResourceHandler;
import org.mortbay.resource.Resource;

/**
*
* @author mariano
*/
public class ContentHandler extends ResourceHandler{
@Override
public Resource getResource(String path) throws MalformedURLException
{
System.out.println(path);
try
{
System.out.println(getResourceBase().replaceAll("file:", "") + path);
return Resource.newResource(this.getClass().getResource(getResourceBase().replaceAll("file:", "") + path));
}
catch(IOException ex)
{
return null;
}
}
}


el archivo Main.java se modifica para agregar este handler a la lista de handlers, quedaria asi:

/*
* 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, "/*");
ContentHandler resource_handler = new ContentHandler();
resource_handler.setResourceBase("/resources");
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { resource_handler, contextWS, new DefaultHandler()});
server.setHandler(handlers);
addressbook.ws.AddressBook.fill();
server.start();
}

}


ahora creamos un paquete llamado resources en la base del proyecto en donde vamos a poner nuestros archivos estaticos.

para poder hacer nuestra pagina über AJAX vamos a necesitar dos librerias, jquery y JSON.js, la primera nos ayuda en muchas cosas, siendo una de ellas la de hacer request HTTP que funcione en todos los browsers, la segunda nos sirve para serializar/deserializar objetos javascript.

las urls son las siguientes:

http://jquery.com/

http://www.json.org/json2.js


ponemos la libreria jquery-version.js y json2.js que descargamos en el paquete resources.js, creamos un archivo addressbook.js en el mismo paquete.

tambien creamos un archivo addressbook.html en el paquete resources, osea que la estructura quedaria asi:


/resources
/resources/addressbook.html
/resources/js
/resources/js/addressbook.js
/resources/js/jquery-1.3.1.min.js
/resources/js/json2.js

en addressbook.js vamos a crear los metodos de javascript que nos permitan interactuar con el servidor, asi como las clases que debemos enviar serializadas.

ahora pongo el contenido de los archivos.

resources/addressbook.html


<html>
<head>
<title>Address Book</title>
<link rel="stylesheet" type="text/css" href="css/style.css" />

<script src="js/jquery-1.3.1.min.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script>
<script src="js/addressbook.js" type="text/javascript"></script>
<script type="text/javascript">

function isArray(obj)
{
if(typeof obj == 'object')
{
var criterion = obj.constructor.toString().match(/array/i);
return (criterion != null);
}

return false;
}

function search()
{
var query = $('#search').val();
AddressBook.search("all", query, onSearchSucceed, onSearchFailed);
}

function onSearchSucceed(response)
{
showResults(response);
}

function showResults(response)
{
var results = $('.results');
results.html("");
entries = JSON.parse(response);

if(entries == null)
{
return;
}

if(!isArray(entries["entry"]))
{
var item = entries["entry"];
results.append(item.firstName + " " + item.lastName + '<br/>');
return;
}

for(var i in entries["entry"])
{
var item = entries["entry"][i];
results.append(item.firstName + " " + item.lastName + '<br/>');
}
}

function onSearchFailed(response)
{
alert("La busqueda fallo");
}

function getAll()
{
AddressBook.getAll(onGetAllSucceeded, onGetAllFailed);
}

function onGetAllSucceeded(response)
{
showResults(response);
}

function onGetAllFailed(response)
{
alert("La busqueda fallo");
}

function create()
{
var firstName = $('#firstName').val();
var lastName = $('#lastName').val();
var address = $('#address').val();
var workPhone = $('#workPhone').val();
var homePhone = $('#homePhone').val();
var cellPhone = $('#cellPhone').val();

var entry = new AddressBook.AddressBookEntry(firstName, lastName, address, homePhone, workPhone, cellPhone);
AddressBook.create(entry, onCreateSucceeded, onCreateFailed);
}

function onCreateSucceeded(response)
{
alert("entrada añadida con exito");
}

function onCreateFailed(response)
{
alert("error añadiendo entrada");
}
</script>
</head>

<body>
<h1>Address Book</h1>

<table>
<tr><td>Nombre</td><td><input type="text" id="firstName"></td></tr>
<tr><td>Apellido</td><td><input type="text" id="lastName"></td></tr>
<tr><td>direccion</td><td><input type="text" id="address"></td></tr>
<tr><td>Telefono Trabajo</td><td><input type="text" id="workPhone"></td></tr>
<tr><td>Telefono Casa</td><td><input type="text" id="homePhone"></td></tr>
<tr><td>Telefono Celular</td><td><input type="text" id="cellPhone"></td></tr>
<tr><td></td><td><input type="button" value="Crear" onclick="create();"></td></tr>
</table>

<input type="text" id="search" />
<input type="button" value="Search" onclick="search();" />
<input type="button" value="Get All" onclick="getAll();" />
<div class="results">
</div>
</body>
</html>


resources/js/addressbook.js

// AddressBook namespace
var AddressBook = new Object();

AddressBook.AddressBookEntry = function(firstName, lastName, address, homePhone, workPhone, cellPhone) {
this.firstName = firstName;
this.lastName = lastName;
this.address = address;
this.homePhone = homePhone;
this.workPhone = workPhone;
this.cellPhone = cellPhone;
}

// serializa a JSON
AddressBook.AddressBookEntry.prototype.serialize = function()
{
return JSON.stringify(this);
}

AddressBook.AddressBook = function(entries)
{
this.entries = entries || [];
}

AddressBook.AddressBook.prototype.serialize = function()
{
return JSON.stringify(this);
}

// devuelve una funcion que hace un request REST a path, usando method, el
// metodo devuelto recibe como primer parametro una entidad que es enviada
// en el *cuerpo* del request, por ello este metodo es util para PUT y POST
AddressBook.buildCreateUpdate = function(method, path)
{
return function(entity, success_cb, error_cb)
{
$.ajax({url: path, type: method, dataType: "text", contentType: "application/json",
data: entity.serialize(),
success: success_cb, error: error_cb});
}
}

// devuelve una funcion que hace un request REST a path, usando method, el
// metodo devuelto recibe como primer un string que es agregado al path
// por ello este metodo es util para GET y DELETE
AddressBook.buildGetDelete = function(method, path)
{
return function(id, success_cb, error_cb)
{
$.ajax({url: path + id, type: method, dataType: "text",
success: success_cb, error: error_cb});
}
}

AddressBook.getAll = function(success_cb, error_cb)
{
var request = AddressBook.buildGetDelete("GET", "/ws/book/json/all");
request("", success_cb, error_cb);
}


AddressBook.search = function(type, query, success_cb, error_cb)
{
var request = AddressBook.buildGetDelete("GET", "/ws/book/json/search");
request("/" + type + "/" + query, success_cb, error_cb);
}

AddressBook.create = AddressBook.buildCreateUpdate("PUT", "/ws/book/");


tambien hice algunos cambios en addressbook.ws.AddressBook, aca esta la version actual

/*
* 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.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
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);
}

@PUT
@Consumes({"application/json", "application/xml"})
public static void add(AddressBookEntry entry)
{
addressBook.addEntry(entry);
}
}

con este ejemplo tenemos una aplicación que expone una API REST y un cliente en javascript/html que lo consume y utiliza, expandiendo la cantidad de entidades en la API podemos escalar a una aplicacion completa.

Si ando con ganas en el proximo post voy a explicar como usar un ORM (object relational mapper) para almacenar el contenido de la entidad en una base de datos.

el codigo completo de este ejemplo lo voy a colgar en github en esta direccion http://github.com/marianoguerra/examples/tree/master, ahora a aprender a usar git :)

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, marzo 23, 2009

Fallback fail

En la pagina de la afip buscar da un error y si queres reportar el error, tambien falla! :D

viernes, febrero 27, 2009

Sobre tipos y lenguajes de scripting

me llego un call for papers para este workshop http://www.cs.purdue.edu/homes/wrigstad/stop09/STOP/About.html

y se me ocurrió lo que se puede hacer con lenguajes de scripting para los "locos por los tipos"

acá va el código y después la explicación

ensure.py: provee los decoradores para asegurar tipos en parámetros y valor de retorno
import functools

ensure_types = True

def args_types(*args_types, **kwargs_types):
def wrapper(function):
@functools.wraps(function)
def replacement(*args, **kwargs):
for (count, (arg, arg_type)) in enumerate(zip(args, args_types)):
if type(arg) != arg_type:
raise ValueError('Invalid type for arg ' + str(count) +
' expected ' + str(arg_type) + ' got ' + str(type(arg)))

for name, ktype in kwargs_types.iteritems():
if name in kwargs:
if type(kwargs[name]) != ktype:
raise ValueError('Invalid type for karg "' + name +
'" expected ' + str(ktype) + ' got ' + str(type(kwargs[name])))

return function(*args, **kwargs)

if ensure_types:
replacement._is_wrapped = True
return replacement
else:
function._is_wrapped = False
return function

return wrapper

def return_type(return_type):
def wrapper(function):
@functools.wraps(function)
def replacement(*args, **kwargs):
result = function(*args, **kwargs)

if type(result) != return_type:
raise ValueError('Invalid return type expected ' +
str(return_type) + ' got ' + str(type(result)))

return result

if ensure_types:
replacement._is_wrapped = True
return replacement
else:
function._is_wrapped = False
return function

return wrapper


test_ensure.py: prueba la implementación
import ensure
# if ensure.ensure_types is set to False then the decorators
# return the same functions and don't make the type checking
# that an be done when development finished and you want to
# deliver the code (it remove the checks and make the code faster)
# ensure.ensure_types = False

@ensure.args_types(int, str, bool, float, sure=bool, price=float)

def do_something(number, name, sure=False, price=10.0):

print number, name, sure, price

@ensure.return_type(int)

def return_param(param):
return param

if do_something._is_wrapped:

print 'do_something contains type checks'
else:
print 'do_something doesn\'t contains type checks'

if return_param._is_wrapped:

print 'return_param contains type checks'
else:
print 'return_param doesn\'t contains type checks'

print do_something.__name__

print

do_something(1, "spongebob", True)
do_something(1, "spongebob", sure=False)

do_something(1, "spongebob", sure=False, price=2.0)

# Fail
try:
do_something(False, "spongebob")
except ValueError, error:

print error

try:
do_something(1, 12.3)

except ValueError, error:
print error

try:

do_something(1, "patricio", 10)
except ValueError, error:

print error

try:
do_something(1, "patricio", True, 10)

except ValueError, error:
print error

try:

do_something(1, "patricio", sure=None)
except ValueError, error:

print error

try:
do_something(1, "patricio", sure=True, price=None)

except ValueError, error:
print error

print 'returned', return_param(5)

try:
print 'returned', return_param("hi!")
except ValueError, error:

print error


la salida corriendo con ensure.ensure_types = True es:

do_something contains type checks
return_param contains type checks
do_something

1 spongebob True 10.0
1 spongebob False 10.0
1 spongebob False 2.0
Invalid type for arg 0 expected <type 'int'> got <type 'bool'>
Invalid type for arg 1 expected <type 'str'> got <type 'float'>
Invalid type for arg 2 expected <type 'bool'> got <type 'int'>
Invalid type for arg 3 expected <type 'float'> got <type 'int'>
Invalid type for karg "sure" expected <type 'bool'> got <type 'NoneType'>
Invalid type for karg "price" expected <type 'float'> got <type 'NoneType'>
returned 5
returned Invalid return type expected <type 'int'> got <type 'str'>

la salida corriendo con ensure.ensure_types = False es:

do_something doesn't contains type checks
return_param doesn't contains type checks
do_something

1 spongebob True 10.0
1 spongebob False 10.0
1 spongebob False 2.0
False spongebob False 10.0
1 12.3 False 10.0
1 patricio 10 10.0
1 patricio True 10
1 patricio None 10.0
1 patricio True None
returned 5
returned hi!

Conclusión:

con esta librería se le podría dar a los que lo quieran (no se si yo lo usaría mucho) una forma de checkear en tiempo de ejecución los tipos de datos recibidos como parámetros y los tipos devueltos por las funciones que decidan decorar.
Estas decoraciones sirven también como método para dar hints a los programadores sobre de que tipos son los valores que se reciben en la función y puede ser usado por IDEs para deducir los tipos y mostrar errores/warnings en el cuerpo del código.
Un aspecto positivo es que se puede deshabilitar el decorado seteando ensure.ensure_types = False. En tal caso no se incurre en ningún overhead en tiempo de ejecución, esto podría ser usado luego del ciclo de desarrollo y prueba, donde no se detecto ninguna "violacion de contrato" y se entrega al usuario final el código con los checks en tiempo de ejecución deshabilitados para incrementar la performance de la aplicación.

obviamente se pueden hacer mejoras en el decorador de los parámetros, pero para ser un modulo de 50 lineas y escrito en menos de una hora, esta bastante bien :)

Seguidores

Archivo del Blog