¡La versión 2.0 de Play ya está lista! Ayúdanos a traducir la documentación de la útlima versión y sigue nuestro progreso.

Manuales, tutoriales & referencias

Consulte

Contenidos

Elija la versión

Buscar

Busque con google

Libros

Las Bibliotecas de Play

El paquete play.libs contiene varias bibliotecas súmamente útiles que te ayudarán a llevar adelante las tareas de programación más comunes.

La mayoría de estas bibliotecas son simples helpers que se usan de forma muy simple:

  • Codec: Utilidades para codificar y decodificar datos.
  • Crypto: Utilidades criptográficas.
  • Expression: Utilidades para evaluar expresiones dinámicas.
  • F: Programación funcional con Java.
  • Files: Ayudas para manipulación de sistemas de ficheros.
  • I18N: Ayudas para internacionalización.
  • IO: Gestión de Streams.
  • Images: Utilidades de manejo de Imágenes.
  • Mail: Funciones de correo.
  • MimeTypes: Para trabajar con los tipos MIME.
  • OAuth: Cliente de protocolo OAuth.
  • OAuth2: Cliente de protocolo OAuth2.
  • OpenID: Cliente de protocolo OpenID.
  • Time: Utilidades de tiempo y duración.
  • WS: Potente cliente para Web Services.
  • XML: Carga de estructuras XML.
  • XPath: Procesamiento de XML utilizando XPath.

Las secciones que siguen aportan más información acerca de las bibliotecas más importantes.

Procesamiento de XML con XPath

XPath es probablemente el método más sencillo para procesar un documento XML sin tener que usar herramientas de generación de código. La biblioteca play.libs.XPath ofrece todas las primitivas necesarias para efectuar estas tareas.

Las operaciones XPath operan sobre todos los tipos de org.w3.dom.Node:

org.w3.dom.Document xmlDoc = … // recuperar un documento de algún sitio
 
for(Node event: XPath.selectNodes("events//event", xmlDoc)) {
 
    String name = XPath.selectText("name", event);
    String data = XPath.selectText("@date", event);
    for(Node place: XPath.selectNodes("//place", event)) {
        String place = XPath.selectText("@city", place);
        …
    }
 
    …
}

Cliente de Web Services

play.libs.WS aporta un poderoso cliente HTTP. Internamente utiliza Async HTTP client.

Efectuar un pedido HTTP es muy sencillo:

HttpResponse res = WS.url("http://www.google.com").get();

Una vez que tienes el objeto HttpResponse puedes acceder a todas las propiedades del response:

int status = res.getStatus();
String type = res.getContentType();

También puedes recuperar el contenido del cuerpo de varios tipos de contenidos:

String content = res.getString();
Document xml = res.getXml();
JsonElement json = res.getJson();
InputStream is = res.getStream();

También puedes usar el API async para hacer solicitudes HTTP de forma no bloqueante. Entonces, recibirás un Promise<HttpResponse>. Cuando se ejecute, podrás usar el HttpResponse como de costumbre:

Promise<HttpResponse> futureResponse = WS.url(
    "http://www.google.com"
).getAsync();

Programación funcional con Java

La biblioteca play.libs.F aporta varias estructuras súmamente útiles que vienen de la programación funcional. Estas estructuras se usan para manejar casos de abstracción compleja. Para los que están acostumbrados a la programación funcional, tenemos:

  • Option<T> (un valor T que puede inicializarse o no)
  • Either<A,B> (contiene un valor A o bien un valor B)
  • Tuple<A,B> (contiene tanto el valor A como el B)

Option<T>, Some<T> y None<T>

Cuando escribes una función que puede no devolver un resultado en algunos casos (por ejemplo, un método find), un patrón común (malo) en Java es devolver null cuando no hay resultado. Esta práctica es peligrosa porque el tipo de retorno de la función no muestra de manera evidente que puede no devolver un objeto, e incluso ha sido reconocido por el inventor de estas referencias anulables (nullable references) como un “error de mil millones de dólares“.

Option<T> es una solución elegante a este problema: en vez de devolver un objeto de tipo T, la función devuelve un Option<T>. Si la función tiene éxito, devuelve un objeto de tipo Some<T> (envolviendo al resultado real). Si no, se devuelve un objeto del tipo None<T>, siendo ambos subtipos de Option<T>.

Veamos un ejemplo:

/* Division segura (nunca arrojara un error ArithmeticException) */
public Option<Double> div(double a, double b) {
    if (b == 0)
        return None();
    else
        return Some(a / b);
}

Y esta es una forma de usar esta función:

Option<Double> q = div(42, 5);
if (q.isDefined()) {
    Logger.info("q = %s", q.get()); // "q = 8.4"
}

Pero hay una sintaxis mejor para usarla, basada en el hecho de que @Option implementa Iterable<T>:

for (double q : div(42, 5)) {
    Logger.info("q = %s", q); // "q = 8.4"
}

El cuerpo del bucle se ejecuta una vez, sólo si la función div tiene éxito.

Tuple<A, B>

La clase Tuple<A, B> envuelve a dos objetos del tipo A y B. Pueden recuperarse estos objetos usando los campos _1 y _2, respectivamente. Por ejemplo:

public Option<Tuple<String, String>> parseEmail(String email) {
    final Matcher matcher = Pattern.compile("(\\w+)@(\\w+)").matcher(email);
    if (matcher.matches()) {
        return Some(Tuple(matcher.group(1), matcher.group(2)));
    }
    return None();
}

Luego:

for (Tuple<String, String> email : parseEmail("foo@bar.com")) {
    Logger.info("name = %s", email._1); // "name = foo"
    Logger.info("server = %s", email._2); // "server = bar.com"
}

La clase T2<A, B> es un alias para Tuple<A, B>. Para manejar tuplas de 3 elementos usa T3<A, B, C> y así hasta T5<A, B, C, D, E>.

Coincidencia de patrones (Pattern matching)

A veces sentimos que necesitamos contar con coincidencia de patrones (pattern matching) en Java. Desafortunadamente, Java no trae incluida esta funcionalidad, y debido a la falta de construcciones funcionales, es difícil añadirlo como una biblioteca. De cualquier forma, nosotros hemos desarrollado una solución que no es tan mala.

Nuestra idea fue utilizar la más reciente sintaxis de bucles for para lograr implementar una coincidencia de patrones básica para Java. La coincidencia de patrones ha de comprobar si el objeto cumple las condiciones obligatorias y al mismo tiempo ha de extraer el valor que estamos buscando. La biblioteca de coincidencia de patrones para Play es parte de play.libs.F.

Veamos un ejemplo sencillo; tenemos una referencia de tipo Object y queremos comprobar si se trata de una cadena que empieza por ‘command:’.

El modo estándar sería:

Object o = anything();
 
if(o instanceof String && ((String)o).startsWith("command:")) {
    String s = (String)o;
    System.out.println(s.toUpperCase());
}

Usando la biblioteca de coincidencia de patrones de Play puede escribirse como:

for(String s: String.and(StartsWith("command:")).match(o)) {
    System.out.println(s.toUpperCase());
}

El bucle for se ejecuta una vez, sólo si la condición se cumple, y automáticamente extrae el valor String sin necesidad de hacer casting. Como no hay casting explícito, todo es seguro en tipos (type-safe) y verificado por el compilador.

Promesas

Las Promise son tipos particularizados por Play del tipo Future. De hecho un Promise<T> es también un Future<T> de modo que puede usarse como un Future estándar. Pero también tiene una propiedad interesante: la capacidad de registrar un método callback usando onRedeem(…) que será invocado tan pronto como el valor prometido esté disponible.

Las instancias de Promise se usan en todas partes en Play en vez de las instancias de Future (para Jobs, WS.async, etc…).

Pueden conbinarse de varias formas. Por ejemplo:

Promise p = Promise.waitAll(p1, p2, p3)
Promise p = Promise.waitAny(p1, p2, p3)
Promise p = Promise.waitEither(p1, p2, p3)

OAuth

OAuth es un protocolo abierto para un API de autorización segura, usando un enfoque simple y estándar, tanto para las aplicaciones de escritorio como para las aplicaciones web.

Hay dos especificaciones diferentes: OAuth 1.0 y OAuth 2.0. Play incluye bibliotecas para conectarse como consumidor a servicios que soporten cualquiera de estas dos especificaciones.

El proceso general es el siguiente:

  • Redirigir al usuario a la página de autorización del proveedor
  • Una vez que el usuario consigue la autorización, se le redirige de nuevo al servidor junto con un token no-autorizado
  • Nuestro servidor intercambia el token no-autorizado por un token de acceso específico para el usuario actual, que ha de ser salvado para ejecutar requests al servicio. Este paso se hace en una comunicacion servidor-a-servidor.

Afortunafamente, Play se ocupa de la mayor parte del proceso.

OAuth 1.0

La funcionalidad para OAuth 1.0 está en la clase play.libs.OAuth y se basa en oauth-signpost. La usan servicios como Twitter o Google

Para conectarte a un servicio, tienes que crear una instancia de OAuth.ServiceInfo con la siguiente información, obtenida del proveedor del servicio:

  • URL del token de petición
  • URL del token de acceso
  • URL de autorización
  • clave de consumidor
  • “secret” de consumidor

El token de acceso puede ser obtenido de la siguiente manera:

public static void authenticate() {
    // TWITTER es un objeto OAuth.ServiceInfo
    // getUser() es un metodo que devuelve el usuario actual 
    if (OAuth.isVerifierResponse()) {
        // Ya hemos obtenido el verificador;
        // ahora conseguimos los tokens de acceso usando los tokens de request
        OAuth.Response resp = OAuth.service(TWITTER).retrieveAccessToken(
            getUser().token, getUser().secret
        );
        // los guardamos y volvemos al indice
        getUser().token = resp.token; getUser().secret = resp.secret;
        getUser().save()
        index();
    }
    OAuth twitt = OAuth.service(TWITTER);
    Response resp = twitt.retrieveRequestToken();
    // Hemos recibido los tokens de no-autorizado
    // tenemos que guardarlos antes de continuar
    getUser().token = resp.token; getUser().secret = resp.secret;
    getUser().save()
    // Redirigimos al usuario a la pagina de autorizacion
    redirect(twitt.redirectUrl(resp.token));
}

Ahora podemos hacer llamadas firmando las requests con la pareja de token:

mentions = WS.url(url).oauth(TWITTER, getUser().token, getUser().secret).get().getString();

Este ejemplo no controla errores, pero en producción deberías hacerlo. El objeto OAuth.Response tiene un campo de errores que es no nulo cuando ha habido algún error. Si lo hay suele ser que el usuario no ha conseguido acceder, o que el proveedor está muerto o tiene problemas.

Hay un ejemplo completo de uso de esta librería en samples-and-tests/twitter-oauth.

OAuth 2.0

OAuth 2.0 es mucho más sencilla que OAuth 1.0 porque no necesita firmar las requests. Lo usan Facebook y 37signals.

La funcionalidad para esto viene en play.libs.OAuth2.

Para conectarse a un servicio, hay que crear una instancia de OAuth2 con la siguiente información, que se obtiene del proveedor del servicio:

  • URL del token de acceso
  • URL de autorización
  • Client ID
  • Secret
public static void auth() {
    // FACEBOOK es un objeto OAuth2
    if (OAuth2.isCodeResponse()) {
        // authUrl ha de ser la misma que en la llamada a retrieveVerificationCode
        OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl);
        // nulo si ha habido error
        String accessToken = response.accessToken;
        // nulo si la llamada ha tenido exito
        OAuth2.Error = response.error;
        // salvar accessToker, lo necesitaremos para solicitar el servicio
        index();
    }
    // authUrl es una String que contiene una URL absoluta donde el servicio
    // tendra que redirigir al usuario
    // Esto levantara una redireccion
    FACEBOOK.requestVerificationCode(authUrl);
}

Una vez que tienes el token de acceso asociado al usuario actual, puede usarse para solicitar al servicio en nombre del usuario:

WS.url(
    "https://graph.facebook.com/me?access_token=%s", access_token
).get().getJson();

Hay un ejemplo de uso completo en samples-and-tests/facebook-oauth2.

OpenID

OpenID es un sistema de identidades abierto y descentralizado. Puedes aceptar fácilmente nuevos usuarios en la aplicación sin tener que guardar información específica de cada uno de ellos. Basta con que conserves el registro de los usuarios autorizados mediante su OpenID.

Este ejemplo aporta una vista de alto nivel de cómo puede usarse la autentificación OpenID con una aplicación Play:

  • Para cada request, comprobar si el usuario está conectado
  • Si no, mostrar una página donde el usuario pueda introducir su OpenID
  • Redirigir al usuario al proveedor OpenID
  • Cuando el usuario vuelva, obtener el OpenID verificado y salvarlo en la sesión HTTP.

La funcionalidad para OpenID está en la clase play.libs.OpenID.

@Before(unless={"login", "authenticate"})
static void checkAuthenticated() {
    if(!session.contains("user")) {
        login();
    }
}
 
public static void index() {
    render("Hello %s!", session.get("user"));
}
     
public static void login() {
    render();
}
    
public static void authenticate(String user) {
    if(OpenID.isAuthenticationResponse()) {
        UserInfo verifiedUser = OpenID.getVerifiedID();
        if(verifiedUser == null) {
            flash.error("Oops. La autentificación ha fallado");
            login();
        } 
        session.put("user", verifiedUser.id);
        index();
    } else {
        if(!OpenID.id(user).verify()) { // redirigimos al usuario
            flash.error("Cannot verify your OpenID");
            login();
        } 
    }
}

Y la plantilla login.html:

#{if flash.error}
<h1>${flash.error}</h1>
#{/if}
 
<form action="@{Application.authenticate()}" method="POST">
    <label for="user">¿Cuál es su OpenID?</label>
    <input type="text" name="user" id="user" />
    <input type="submit" value="login…" />
</form>
</code>

Y finalmente las definiciones de routes:

GET   /                     Application.index
GET   /login                Application.login
*     /authenticate         Application.authenticate

Próximos pasos

Ahora veremos cómo ejecutar operaciones fuera de cualquier pedido HTTP utilizando Tareas aincrónicas (Jobs).