dimarts, 27 de desembre del 2011

Una aproximació a Google App Engine. Com desenvolupar al núvol de Google.

Google App Engine és la plataforma  que ofereix Google i en la que es poden executar aplicacions web pròpies desenvolupades en Java, i d'altres llenguatges, de forma gratuita fins a un cert nivell de tràfic o recursos. L'interès de Google App Engine és que a l'estar-ne integrat, permet aprofitar els serveis de la "plataforma Google"i, a més, es disposa d'una API que permet accedir a aquests serveis.

Només cal tenir un compte a Google i registrar-se amb Google App Engine per a poder accedir a aquest servei de plataforma (Platform as a Service, o PaaS) i poder desenvolupar aplicacions en el núvol personal a Google.

Google App Engine és, doncs, una plataforma de cloud computing en la que podrem desplegar aplicacions web (per exemple, un CMS com Vosao) i disposarem d'APIS que permetran l'autenticacio , amb la resta de serveis que  tinguem a Google, com Docs,o enviar correus amb GMail, o

No confondre Google App Engine amb Google Apps (amb s). Aquest últim ve  a ser la versió "personalitzada" i depenent del grau de personalització, de pagament, dels serveis de Google .  Per cert, Apps també pot ser considerat una plataforma de desenvolupament. Google Apps Script permet crear macros per automatitzar tasques amb Javascript. Poso en cartera un post per parlar d'Apps i de les seves macros.

Però tornem a l'App Engine. Hi han  restriccions que cal tenir en compte. Entre d'altres: Només es pot fer servir un subconjunt de les classes del JRE estàndard, la "llista blanca de classes de JRE"; les aplicacions no poden crear fils (Thread); Els processos de servidor en resposta a peticions no poden trigar més de 30s; només es pot executar codi en resposta a peticions rebudes per HTTP; les aplicacions estan limitades pels frameworks que imposa Google i, per exemple, es pot consultar, afegir, modificar i esborrar dades de la base de dades NO-Relacional  BigTable que proporciona Google App Engine, però no es poden fer servir bases de dades relacionals com MySQL.

A la wikipedia podem trobar una bona descripció del Google App Engine. En tot cas, les especificacions i restriccions depenen de Google.

El que m'interessa a mi és com funciona des del punt de vista del desenvolupador. Res millor que seguir els demos de l'SDK de Google App Engine (GAE).

Cal el JDK 1.6 com a mínim.
L'SDK de Google App Engine per Java i la documentació (http://code.google.com/intl/ca-ES/appengine/downloads.html)

Un cop descarregat i instal·lat tot aquest material, em dirigeixo a la java Getting Started Guide, per a compilar les proves faré servir Apache Ant.

En aquest moment tinc a /home/albert la carpeta
appengine-java-sdk
Que he renombrat eliminant-ne el sufix amb el número de versió seguint les indicacions de la guia; i la carpeta
google-appengine-docs
que conté la documentació i que també he renombrat eliminat-ne el sufix amb la data de versió.

Ara puc provar l'aplicació Guestbook que va amb les demos de l'appengine, fent
./appengine-java-sdk/bin/dev_appserver.sh appengine-java-sdk/demos/guestbook/war 
des de la carpeta /home/albert

L'aplicació engega:

albert@athena:~$ ./appengine-java-sdk/bin/dev_appserver.sh appengine-java-sdk/demos/guestbook/war
31/12/2011 19:32:49 com.google.apphosting.utils.jetty.JettyLogger info
INFO: Logging to JettyLogger(null) via com.google.apphosting.utils.jetty.JettyLogger
31/12/2011 19:32:50 com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml
INFO: Successfully processed /home/albert/appengine-java-sdk/demos/guestbook/war/WEB-INF/appengine-web.xml
31/12/2011 19:32:51 com.google.apphosting.utils.config.AbstractConfigXmlReader readConfigXml
INFO: Successfully processed /home/albert/appengine-java-sdk/demos/guestbook/war/WEB-INF/web.xml
31/12/2011 20:33:08 com.google.appengine.tools.development.DevAppServerImpl start
INFO: The server is running at http://localhost:8080/
31/12/2011 20:33:09 com.google.appengine.tools.development.DevAppServerImpl start
INFO: The admin console is running at http://localhost:8080/_ah/admin
31/12/2011 20:34:04 com.google.appengine.tools.development.LocalResourceFileServlet doGet
WARNING: No file found for: /favicon.ico

I fent http://localhost:8080


Puc deixar alguns missatges de prova:


També podem fer un cop d'ull a la consola d'administració, a http://localhost:8080/_ah/admin


Podem examinar el visor de dades El datastore viewer, o es pot examinar la finestra XMPP per als missatges de xats, el correu electrònic associat... En particular, al Datastore Viewer podrem examinar els missatges que ja hem afegit.

Com ho fa? a la carpeta appengine-java-sdk/demos/guestbook/war trobem el codi font de l'aplicació.

En un primer cop d'ull ens adonem que es tracta d'una aplicació web java gairebé estàndar: hi trobem jsp i css i una carpeta WEB-INF amb els habituals sub-carpetes classes i lib. A més també hi trobo el web.xml de les aplicacions web i, como a variació sobre el estàndar, un document appengine-web.xml.

En aquest cas,  appengine-web.xml només informa de la versió de l'aplicació i del fitxer de propietats del sistema de logs. Sobre aquest últim, dir que el sistema de logs del GAE és similar al que hom implementa amb log4j.

Vet aquí l'appengine-web.xml


<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application/>
<version>1</version>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
</appengine-web-app>


A continuació, examino la resta del codi.

Primer de tot, la pàgina guestbook.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%@ page import="com.google.appengine.api.datastore.DatastoreServiceFactory" %>
<%@ page import="com.google.appengine.api.datastore.DatastoreService" %>
<%@ page import="com.google.appengine.api.datastore.Query" %>
<%@ page import="com.google.appengine.api.datastore.Entity" %>
<%@ page import="com.google.appengine.api.datastore.FetchOptions" %>
<%@ page import="com.google.appengine.api.datastore.Key" %>
<%@ page import="com.google.appengine.api.datastore.KeyFactory" %>


<html>
  <head>
    <link type="text/css" rel="stylesheet" href="/stylesheets/main.css" />
  </head>


  <body>


<%
    String guestbookName = request.getParameter("guestbookName");
    if (guestbookName == null) {
        guestbookName = "default";
    }
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null) {
%>
<p>Hello, <%= user.getNickname() %>! (You can
<a href="<%= userService.createLogoutURL(request.getRequestURI()) %>">sign out</a>.)</p>
<%
    } else {
%>
<p>Hello!
<a href="<%= userService.createLoginURL(request.getRequestURI()) %>">Sign in</a>
to include your name with greetings you post.</p>
<%
    }
%>


<%
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);
    // Run an ancestor query to ensure we see the most up-to-date
    // view of the Greetings belonging to the selected Guestbook.
    Query query = new Query("Greeting", guestbookKey).addSort("date", Query.SortDirection.DESCENDING);
    List<Entity> greetings = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(5));
    if (greetings.isEmpty()) {
        %>
        <p>Guestbook '<%= guestbookName %>' has no messages.</p>
        <%
    } else {
        %>
        <p>Messages in Guestbook '<%= guestbookName %>'.</p>
        <%
        for (Entity greeting : greetings) {
            if (greeting.getProperty("user") == null) {
                %>
                <p>An anonymous person wrote:</p>
                <%
            } else {
                %>
                <p><b><%= ((User) greeting.getProperty("user")).getNickname() %></b> wrote:</p>
                <%
            }
            %>
            <blockquote><%= greeting.getProperty("content") %></blockquote>
            <%
        }
    }
%>


    <form action="/sign" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Post Greeting" /></div>
      <input type="hidden" name="guestbookName" value="<%= guestbookName %>"/>
    </form>


    <form action="/guestbook.jsp" method="get">
      <div><input type="text" name="guestbookName" value="<%= guestbookName %>"/></div>
      <div><input type="submit" value="Switch Guestbook" /></div>
    </form>


  </body>
</html>

La pàgina comença fent la importació de les classes del Google App Engine que farà servir després.

<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%@ page import="com.google.appengine.api.datastore.DatastoreServiceFactory" %>
<%@ page import="com.google.appengine.api.datastore.DatastoreService" %>
<%@ page import="com.google.appengine.api.datastore.Query" %>
<%@ page import="com.google.appengine.api.datastore.Entity" %>
<%@ page import="com.google.appengine.api.datastore.FetchOptions" %>
<%@ page import="com.google.appengine.api.datastore.Key" %>
<%@ page import="com.google.appengine.api.datastore.KeyFactory" %>


A continuació, determina si l'usuari està logat amb  userService.getCurrentUser(); per a poder presentar l'opció de logar-se (userService.createLoginURL(request.getRequestURI())) o deslogar-se (userService.createLogoutURL(request.getRequestURI())) .

Segueix amb la preparació de la "base de dades": el DataStore. Per defecte el llibre de visites és el "default". Es podran crear altres llibres de visites amb el paràmetre guestbookName. Aquest paràmetre es podrà modificar amb el formulari que presenta la pàgina.

Amb Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName); es crea el llibre de visites (o s'obre per accedir-hi si ja existia) i se n'obté la clau de referència (compte que els DataStore NO són bases de dades relacionals!).

A continuació obté la llista dels cinc darrers missatges del llibre de visites ordenats per data, des del més recent:

Query query = new Query("Greeting", guestbookKey).addSort("date", Query.SortDirection.DESCENDING);
List<Entity> greetings = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(5))


Si no hi han missatges, n'informa:

if (greetings.isEmpty()) {
        %>
        <p>Guestbook '<%= guestbookName %>' has no messages.</p>
        <%


I si n'hi han, itera per la llista presentant-ne l'autor i el text del missatge:

} else {
        %>
        <p>Messages in Guestbook '<%= guestbookName %>'.</p>
        <%
        for (Entity greeting : greetings) {
            if (greeting.getProperty("user") == null) {
                %>
                <p>An anonymous person wrote:</p>
                <%
            } else {
                %>
                <p><b><%= ((User) greeting.getProperty("user")).getNickname() %></b> wrote:</p>
                <%
            }
            %>
            <blockquote><%= greeting.getProperty("content") %></blockquote>
            <%
        }
    }


Finalment, la pàgina presenta els camps en el que es pot introduir el missatge i seleccionar el llibre de visites en el que es vol desar.
Internament, els dos camps s'organitzen en dos formularis HTML, el primer invoca al servlet /sign i li passa el text del missatge i el llibre de visites en el que ha d'inserir-lo. El segon formulari auto-invoca la mateixa pàgina guestbook.jsp i permet modificar el paràmetre guestbookName (per defecte,  default), es dir, permet crear un llibre de vistes nou, o seleccionar-ne un d'existent.


<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Post Greeting" /></div>
<input type="hidden" name="guestbookName" value="<%= guestbookName %>"/>
</form>


<form action="/guestbook.jsp" method="get">
<div><input type="text" name="guestbookName" value="<%= guestbookName %>"/></div>
<div><input type="submit" value="Switch Guestbook" /></div>
</form>


La pàgina guestbook.jsp fa molta de l'operativa del llibre de visites tota sola, però cal alguna cosa més. a  a la carpeta appengine-java-sdk/demos/guestbook/src també trobo el codi font del parell de servlets que completen l'aplicació (més un parell de classes auxiliars):

Els servlets són invocats pels formularis anteriors. A web.xml (a la carpeta appengine-java-sdk/demos/guestbook/war/WEB-INF/web.xml) veig que


<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">

<servlet>

<servlet-name>guestbook</servlet-name>

<servlet-class>guestbook.GuestbookServlet</servlet-class>
</servlet>

<servlet-mapping>

<servlet-name>guestbook</servlet-name>

<url-pattern>/guestbook</url-pattern>
</servlet-mapping>

<servlet>

<servlet-name>sign</servlet-name>

<servlet-class>guestbook.SignGuestbookServlet</servlet-class>
</servlet>

<servlet-mapping>

<servlet-name>sign</servlet-name>

<url-pattern>/sign</url-pattern>
</servlet-mapping>

<welcome-file-list>

<welcome-file>guestbook.jsp</welcome-file>
</welcome-file-list>
</web-app>


Per tant  /sign es refereix a SignGuestbookServlet.

package guestbook;


import java.io.IOException;
import java.util.Date;
import java.util.logging.Logger;
import javax.jdo.PersistenceManager;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;


import guestbook.Greeting;
import guestbook.PMF;


public class SignGuestbookServlet extends HttpServlet {
    private static final Logger log = Logger.getLogger(SignGuestbookServlet.class.getName());


    public void doPost(HttpServletRequest req, HttpServletResponse resp)
                throws IOException {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();


        String content = req.getParameter("content");
        Date date = new Date();
        Greeting greeting = new Greeting(user, content, date);


        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(greeting);
        } finally {
            pm.close();
        }


        resp.sendRedirect("/guestbook.jsp");
    }
} 

Hi veig la importació de les classes de l'AppEngine que permeten fer l'autenticació (User, UserService i UserServiceFactory). També fa la importació de les classes auxiliars Greeting i PMF.

La persistència de dades s'aconsegueix amb JDO. Greeting correspon al model. i PMF és el PersistenceManagerFactory.

Greeting:


package guestbook;


import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.users.User;


@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Greeting {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;


    @Persistent
    private User author;


    @Persistent
    private String content;


    @Persistent
    private Date date;


    public Greeting(User author, String content, Date date) {
        this.author = author;
        this.content = content;
        this.date = date;
    }


    public Long getId() {
        return id;
    }


    public User getAuthor() {
        return author;
    }


    public String getContent() {
        return content;
    }


    public Date getDate() {
        return date;
    }


    public void setAuthor(User author) {
        this.author = author;
    }


    public void setContent(String content) {
        this.content = content;
    }


    public void setDate(Date date) {
        this.date = date;
    }
}

i PMF:


package guestbook;


import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;


public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");


    private PMF() {}


    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}



A appengine-java-sdk/demos/guestbook/war/WEB-INF/classes/META-INF trobarem el fitxer de configuració de JDO: el jdoconfig.xml ,on es diu que es farà servir el appengine per a la persistència.


<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

<persistence-manager-factory name="transactions-optional">

<property name="javax.jdo.PersistenceManagerFactoryClass"value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>

<property name="javax.jdo.option.ConnectionURL" value="appengine"/>

<property name="javax.jdo.option.NontransactionalRead" value="true"/>

<property name="javax.jdo.option.NontransactionalWrite" value="true"/>

<property name="javax.jdo.option.RetainValues" value="true"/>

<property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
</persistence-manager-factory>
</jdoconfig>


Amb JDO, gravar la informació és tan senzill com instanciar el model (Greeting greeting = new Greeting(user, content, date);) i fer-lo persistent (pm.makePersistent(greeting);)

L'altre servlet és GuestbookServlet


package guestbook;


import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;


public class GuestbookServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
              throws IOException {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();


        if (user != null) {
            resp.setContentType("text/plain");
            resp.getWriter().println("Hello, " + user.getNickname());
        } else {
            resp.sendRedirect(userService.createLoginURL(req.getRequestURI()));
        }
    }
}

Aquest segon servlet no s'invoca directament en el codi. Es tracta d'un servlet que ens mostra com utilitzar la Users API per a, depenent de si l'usuari està logat o no, fer una acció o altre. Per a invocar-lo directament faig 

http://localhost:8080/guestbook. 

Suposant que no m'he logat abans (fent click a sign in), El resultat és que es mostra el següent:



En l'entorn amb el que faig les proves, la invocació a  createLoginURL porta a una pantalla com l'anterior. El que fa el servlet és que si l'usuari no estàlogat, redirecciona a aquesta pantalla de login. Si estiguéssim a l'entorn "de  producció" de Google, aleshores ens dirigiríem a la pàgina de login de Google.  

Tornant a l'entorn de prova, si en canvi hagués estat logat  obtindria:



Una aplicació ben senzilla. Tampoc hi ha gaire cosa a explicar del codi.

Un cop ja tinc l'aplicació en funcionament, ve el moment de desplegar-la en producció. He de fer "el deploy".

El desplegament és també molt senzill. Primer de tot cal identificar-se al Google App Engine. Accedeixo a la web http://code.google.com/intl/ca-ES/appengine/ i faig click a Sign Up.



I creo una aplicació. Click a Create Application.


Em demana un número de telèfon per verificar la creació   



Des de Barcelona, el codi de regió és +34. Rebo el codi i l'he de posar a la següent pantalla.



Ara he d'emplenar un formulari de registre de l'aplicació. Poso el títol  de l'aplicació, i l'identificador. Fixem-nos que aquí es defineix la URL d'accés de l'aplicació. En aquest cas serà

http://stsoftlliure-proves.appspot.com



Accepto les condicions del servei i, finalment, l'aplicació queda registrada.

 

Ara que ja tinc preparat l'espai on ubicar l'aplicació, faig el deploy des de la línia de comandes, dent servir l'aplicació appcfg.h que es proporciona amb l'SDK de l'App Engine. L'aplicació ens demanarà el correu gmail del compte i el password. Es connectarà a Internet i desplegarà l'aplicació. Fixem-nos que no cal crear un fitxer .war.


albert@apolo:~/appengine-java-sdk/bin$ appcfg.sh -A stsoftlliure-proves update /home/albert/appengine-java-sdk/demos/guestbook/war
Reading application configuration data...
19/02/2012 13:16:45 com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml
INFO: Successfully processed /home/albert/appengine-java-sdk/demos/guestbook/war/WEB-INF/appengine-web.xml
2012-02-19 13:16:45.947:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
19/02/2012 13:16:46 com.google.apphosting.utils.config.AbstractConfigXmlReader readConfigXml
INFO: Successfully processed /home/albert/appengine-java-sdk/demos/guestbook/war/WEB-INF/web.xml
19/02/2012 13:16:46 com.google.apphosting.utils.config.IndexesXmlReader readConfigXml
INFO: Successfully processed /home/albert/appengine-java-sdk/demos/guestbook/war/WEB-INF/appengine-generated/datastore-indexes-auto.xml
Beginning server interaction for stsoftlliure-proves...
0% Created staging directory at: '/tmp/appcfg2668004339756750410.tmp'
5% Scanning for jsp files.
8% Compiling jsp files.
19/02/2012 13:16:55 com.google.apphosting.utils.config.AbstractConfigXmlReader readConfigXml
INFO: Successfully processed /tmp/appcfg2668004339756750410.tmp/WEB-INF/web.xml
20% Scanning files on local disk.
25% Initiating update.
28% Cloning 1 static files.
31% Cloning 27 application files.
40% Uploading 1 files.
52% Uploaded 1 files.
61% Initializing precompilation...
68% Sending batch containing 1 file(s) totaling 1KB.
90% Deploying new version.
95% Will check again in 1 seconds.
98% Will check again in 2 seconds.
99% Will check again in 4 seconds.
99% Closing update: new version is ready to start serving.
99% Uploading index definitions.


Update completed successfully.
Success.
Cleaning up temporary files...
albert@apolo:~/appengine-java-sdk/bin$ 

Verifico que la nova aplicació ja és visible al Dashboard...

 

...I ja puc fer servir la meva nova aplicació instal·lada al Google App Engine.

Accedeixo a l'adreça http://stsoftlliure-proves.appspot.com i puc provar l'aplicació:

diumenge, 11 de desembre del 2011

Lubuntu a un Hercules eCafe 800

En un post anterior vam instal·lar Ubuntu de la darrera versió Netbook Remix en un petit Hercules eCafe 800.La maquineta ha funcionat prou bé però ha estat evident, des del primer dia, que Gnome li anava gran.

La solució ha estat posar una distribució més lleugera. En aquest cas la Lubuntu. Lubuntu és una distribució que fa servir LXDE com a entorn d'escriptori i OpenBox com a gestor de finestres, amb la intenció de minimitzar el consum de recursos que calen. Vegeu també http://ca.wikipedia.org/wiki/Lubuntu. Per cert, està disponible en català.

La instal·lació no ha tingut complicacions: he descarregat la iso de http://cdimages.ubuntu.com/lubuntu/releases/11.10/release/ i he creat un pendrive d'instal·lació.

Per a fer la instal·lació m'ha estat útil connectar un monitor extern, degut a que la petita pantalla de l'eCafe no era suficient per als continguts que s'estaven mostrant. Aquest problema ja el vaig trobar en l'anterior instal·lació de Ubuntu (Gnome), i es resol igual: un cop feta la instal·lació cal afegir el fitxer xorg.conf a /etc/X11.

Un cop feta la instal·lació i fet l'ajust de pantalla, ja es pot treballar amb el netbook. Ens adonarem d'alguns canvis, per exemple en el programari instal·lat. Per mi el més cridaner ha estat trobar Chromium, el navegador open-source que ha servit de base per a Chrome, en comptes de Firefox. I també trobar Synaptic, en comptes del "Centre de Programari de l'Ubuntu".

La millora en la reproducció de vídeos i música és evident. També en la reproducció de vídeos online.

Pegues, de moment, poques. Li he instal·lat Thunderbird, LibreOffice, Skype... i el netbook els mou de forma que són útils. No està gens malament per a aquesta petita maquina.

Potser encara posaré Lubuntu a l'ordinador de sobretaula.

dimarts, 6 de desembre del 2011

Codis QR amb Java i ZXing

Cada cop és més comú trobar arreu codis QR. Els codis QR apareixen en promocions i en publicitat de tots tipus. Sens dubte que les aplicacions per a telèfons mòbils que permeten descodificar aquests  codis de barres han contribuït a popularitzar los i, a la vegada, són un estímul més per a fer-se amb un smartphone que permeti gaudir-ne d'aquesta capacitat. Es tracta de dues tecnologies que es reforcen l'una a l'altre.

Vet aquí el que en diu la Viquipèdia dels codis QR:

"El codi QR (en anglès QR Code) és un codi de barres en 2 dimensions (codi matriu) que pot emmagatzemar fins a 7089 caràcters numèrics, 4296 caràcters alfanumèrics (contràriament al codi de barres "tradicional" que només pot emmagatzemar de 10 a 13 caràcters) o 2953 octets .

Per accedir a la informació continguda o encriptada en un qr-code, és necessari un dispositiu digital de captura d'imatges ( Ex: càmera de fotos d'un mòbil, webcam...) i un software específic lector de qr-codes.

Té l'avantatge de poder emmagatzemar moltes informacions tot i ser petit i ràpid d'escanejar. Així, les sigles «QR» deriven de «Quick Response» ja que el contingut pot ser desxifrat ràpidament.
La informació oculta en el codi pot estar en forma de:

TEXT
URL (ens permetrà visitar una pàgina web concreta amb un sol clic)
SMS ( podrem enviar un sms predeterminat a un número de predeterminat amb un sol clic)
VCARD (targes de presentació)

Les possibilitats i aplicacions d'aquests codis en gestió i màrqueting (especialment en opt-in màrqueting i promocions) són novedoses i interessants ja que mitjançant els codis qr es poden unir dos suports històricament aïllats: paper i internet o tinta i bits.

El codi QR ha estat creat per l'empresa japonesa Denso-Wave el 1994. El codi QR és molt emprat al Japó, on actualment és el codi de dues dimensions més popular.

L'estàndard japonès per als codis QR, JIS X 0510, ha estat publicat el 1999, i la norma ISO corresponent, ISO/IEC 18004, ha estat aprovada el juny de 2000.

Un detall important sobre el codi QR, seria que es tracta de codi obert i els seus drets de patent (propietat de Denso Wave) no són exercits."

Que els codis QR siguin d'alguna manera codi open source tampoc deu ser aliè al seu èxit. 

Però, i com es poden crear codis QQ? i com descodificar-los? Més concretament, com crear o llegir codis QR amb Java ( o amb JVM)? Una cerca ràpida amb Google ens mostra diverses llibreries. Em crida l'atenció la llibreria ZXing.

ZXing és una llibreria per al processat d'imatges de codis de barres de 1D/2D per a clients Android i Java. 

Dit i fet, descarrego la llibreria (versió 1.7) d'aquí: http://code.google.com/p/zxing/downloads/detail?name=ZXing-1.7.zip, i la descomprimeixo.

El paquet té la següent estructura:


albert@athena:~/zxing$ ls
actionscript         androidtest  build.properties  COPYING  csharp  javase  README   zxing.appspot.com
android              AUTHORS      build.xml         core     iphone  jruby   rim      zxingorg
android-integration  bug          CHANGES           cpp      javame  objc    symbian
albert@athena:~/zxing$ 

Observem que no hi han jars. Cal compilar-los. És tan senzill com, en un terminal, moure's primrer a la carpeta core i executar ant, i després a la carpeta javase i tornar a excutar ant. Cal, evidentment, tenir ant instal·lat.

De moment, seguiré les instruccions que posen al lloc web de ZXing. 

albert@athena:~/zxing$ cd core
albert@athena:~/zxing/core$ ls
build  build.xml  lib  pom.xml  src  test
albert@athena:~/zxing/core$ ant
Buildfile: /home/albert/zxing/core/build.xml

clean:
   [delete] Deleting directory /home/albert/zxing/core/build

build:

init:

compile:
    [mkdir] Created dir: /home/albert/zxing/core/build
    [javac] Compiling 177 source files to /home/albert/zxing/core/build
    [javac] warning: [options] bootstrap class path not set in conjunction with -source 1.3
    [javac] 1 warning
      [jar] Building jar: /home/albert/zxing/core/core.jar

BUILD SUCCESSFUL
Total time: 34 seconds
albert@athena:~/zxing/core$ cd ../javase
albert@athena:~/zxing/javase$ ls
build  build.xml  pom.xml  src
albert@athena:~/zxing/javase$ ant
Buildfile: /home/albert/zxing/javase/build.xml

init:

build:
      [jar] Building jar: /home/albert/zxing/javase/javase.jar

BUILD SUCCESSFUL
Total time: 6 seconds
albert@athena:~/zxing/javase$ 

Si examinem de nou les carpetes core i javase trobarem core.jar i javase.jar a les carpetes de mateix nom:

albert@athena:~/zxing/javase$ ls
build  build.xml  javase.jar  pom.xml  src
albert@athena:~/zxing/javase$ cd ../core
albert@athena:~/zxing/core$ ls
build  build.xml  core.jar  lib  pom.xml  src  test
albert@athena:~/zxing/core$ 

En aquest moment es pot fer una prova. Faré servir la següent  imatge:


I ara, fem la prova executant la classe CommandLineRunner que permet descodificar una imatge:
albert@athena:~/zxing$ java -cp javase/javase.jar:core/core.jar com.google.zxing.client.j2se.CommandLineRunner ./sticipl.png
file:/home/albert/zxing/./sticipl.png (format: QR_CODE, type: TEXT):
Raw result:
Serveis TIC i Programari Lliure
Parsed result:
Serveis TIC i Programari Lliure
Found 4 result points.
  Point 0: (14.0,102.0)
  Point 1: (14.0,14.0)
  Point 2: (102.0,14.0)
  Point 3: (90.0,90.0)
albert@athena:~/zxing$ 

Pel que fa a la documentació, el javadoc de ZXing el podem trobar a http://zxing.org/w/docs/javadoc/index.html

També ens pot ajudar la pàgina de les DeveloperNotes (http://code.google.com/p/zxing/wiki/DeveloperNotes)

El que faré serà un parell de programes d'exemple, un per a crear codis de barra  QR i un altre per a descodificar-los. Un coder i un decoder d'exemple.

Primer de tot, el decoder. L'aplicaré al següent QR:


Vet aquí el codi:


package com.sticipl.prova.zxing;


import com.google.zxing.Reader;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.LuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.ResultPoint;
import com.google.zxing.Result;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;






public class ProvaZxing {
    public ProvaZxing() {
    try {
        Reader reader = new QRCodeReader();    
     
        BufferedImage myImage = ImageIO.read(
                                    new File("/home/albert/Workspace/wk-java/prova-zxing/img/albert.png")
                                );
        LuminanceSource source = new BufferedImageLuminanceSource(myImage);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    
        Result result = reader.decode(bitmap);


        String text = result.getText();
        System.out.println("Text llegit: " + text);
              
        byte[] rawBytes = result.getRawBytes();
        for (byte byteElem : rawBytes) { 
            System.out.println("Byte: " + byteElem);
        }
        
        BarcodeFormat format = result.getBarcodeFormat();
        System.out.println("Format del codi: " + format);
        
        ResultPoint[] punts = result.getResultPoints();    
        for (ResultPoint punt : punts) { 
            System.out.println("Punt.x: " + punt.getX() + "; Punt.y: " + punt.getY());
        }
      } catch(Exception e) {
        System.out.println("Error: " + e.toString());      
      }  
    }

  public static void main(String args[]) {
        new ProvaZxing();  
    }
}


El programa és directe: instancia un reader del tipus QRCodeReader, crea una BufferedImage a partir de la imatge del  QR. El reader s'aplica al QRReder i, senzillament, el descodifica. El resultat s'obté en diferents formats.

L'aplicació del programa sobre la darrera imatge proporciona la sortida:


Text llegit: Albert Baranguer Codina
Byte: 65
Byte: 116
Byte: 22
Byte: -58
Byte: 38
Byte: 87
Byte: 39
Byte: 66
Byte: 4
Byte: 38
Byte: 23
Byte: 38
Byte: 22
Byte: -26
Byte: 119
Byte: 86
Byte: 87
Byte: 34
Byte: 4
Byte: 54
Byte: -10
Byte: 70
Byte: -106
Byte: -26
Byte: 16
Byte: -20
Byte: 17
Byte: -20
Format del codi: QR_CODE
Punt.x: 14.0; Punt.y: 86.0
Punt.x: 14.0; Punt.y: 14.0
Punt.x: 86.0; Punt.y: 14.0
Punt.x: 74.0; Punt.y: 74.0

La creació de codis QR no té dificultat i segueix un procediment similar al de la descodificació: primer de tot, cal instanciar un QRCodeWriter i amb aquesta instància, directament es codifica la informació. El resultat és una matriu de bits que es fa servir per a crear una BufferedImage. Vet aquí el mètode codifica(String sText), que rep com a argument el text a codificar


    // genera un codi QR per al text passat
    public void codifica(String sCadena) {
      try {      
        Writer writer = new QRCodeWriter();
        BitMatrix bitmat = writer.encode(sCadena, BarcodeFormat.QR_CODE, 200, 200);
      
        // genera la imatge. 
        // Obté ample i alt
        int width = bitmat.getWidth(); 
        int height = bitmat.getHeight(); 
      
        // crea BufferedImage
        BufferedImage imatge = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);


        // estableix els bits de la imatge (true, negre; false, blanc)
        for (int y = 0; y < height; y++) { 
          for (int x = 0; x < width; x++) { 
   boolean boolPunt = bitmat.get(x,y); 
   imatge.setRGB(x, y, (boolPunt ? 0 : 0xFFFFFF));
 }
        }

        // genera el fitxer d'imatge a partir de la BufferedImage
        File file = new File("/home/albert/Workspace/wk-java/prova-zxing/img/generat.png");
        FileOutputStream fos = new FileOutputStream(file);
        ImageIO.write(imatge, "png", fos);
        fos.flush();
        fos.close(); 
      } catch(Exception e) {
System.out.println("Error: " + e.toString());      
      }       
    }


Aplicant el mètode amb els argument mostrats:


    public static void main(String args[]) {
        ProvaZxing prova = new ProvaZxing();
        // prova.decode();  
        prova.codifica("Generat amb Zxing - Serveis TIC i Programari Lliure - Albert Baranguer Codina - accents: àèò éíóú ç ñ ...");
    }


obtinc la imatge:


Per acabar, una altre imatge generada amb el mètode codifica(). Endevineu quin és el text que amaga?