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