diumenge, 20 de novembre del 2011

Groovy i HSQLDB

Groovy aporta classes per al treball amb bases de dades que permeten un increment de la productivitat en el desenvolupament de codi d'aplicacions que en facin us.

Dit d'una altre forma: Groovy permet simplificar el codi de les aplicacions de bases de dades.  Res millor que un exemple.

En l'exercici que es proposa faré us de la base de dades HSQLDB, que és el motor de base de dades que es fa servir, per exemple,  a les suites ofimàtiques OpenOffice.org i LibreOffice.

Per a poder utilitzar HSQLDB des de Groovy cal afegir el hsqldb.jar a les llibreries que es carreguen a l'inici. En el cas que m'ocupa, un Groovy sobre Linux Ubuntu, cal revisar el fitxer $GROOVY_HOME/conf/groovy-starter.conf en el que es pot trobar el següent:

    # load user specific libraries
    load !{user.home}/.groovy/lib/*.jar

Tinc l'HSQLDB instal·lat a una carpeta apart. Per tant, per evitar duplicitats, en comptes de posar l'hsqldb.jar a $HOME/.groovy/lib, el que faig és posar en aquesta carpeta un enllaç simbòlic al jar que hi ha a la carpeta on està desplegat el motor de la base de dades.

A continuació, creo una base de dades del tipus fitxer i standalone. Cal dir que HSQLDB pot funcionar en "mode servidor" o  bé en "mode standalone". A més, també pot funcionar o bé mantenint les dades en un fitxer, o bé "mantenint" les dades en memòria (evidentment, només es "mantenen" mentre el procés és viu).

Per a crear la taula es pot fer servir, per exemple, l'OpenOffice.org/LibreOffice Base. En aquest cas caldria afegir hsqldb.jar al classpath de l'OOo/LO (eines - opcions - java)


i camí a les classes:



i, a continuació, crearia una base de dades del tipus Jdbc.



En el meu cas, la base de dades la fico a Documentes/databases/hsqldb/prova.db, per tant la URL JDBC que he de fer servir és:


jdbc:hsqldb:/home/albert/Documents/databases/hsqldb/prova.db

i el driver:

org.hsqldb.jdbc.JDBCDriver


(canviar aquesta imatge)

Això em permetrà fer servir l'assistent de l'OOo/LO per a definir la taula i els tipus de dades. Creo una taula diccionari amb tres columnes: id (enter), nom (varchar de 100) i valor (varchar de 100).


I  un cop creada la taula, la informo amb algunes dades de prova:



Probablement la classe més útil de Groovy per al treball amb bases de dades sigui groovy.sql.Sql.  Aquesta classe és una mena de navalla suïssa que proporciona una varietat de mètodes per a fer gairebé totes les tasques comuns amb taules.

La pàgina de la documentació de groovy.sql.Sql ens explica les possibilitats d'aquesta navalla suïssa.

Per a utilitzar-la  cal importar-la

import groovy.sql.Sql


A continuació puc configurar la connexió a la base de dades que he creat fa un moment:

def ds = [url:'jdbc:hsqldb:/home/albert/Documents/databases/hsqldb/prova.db', 
          user:'sa', 
          password:'', 
          driver:'org.hsqldb.jdbc.JDBCDriver']
def sql = Sql.newInstance(ds.url, ds.user, ds.password, ds.driver)


Consulto la taula que he creat amb el OOo/LO amb el potent mètode eachRow, adonem-nos com la fila en curs és accessible amb 'it':

sql.eachRow("select * from public.\"diccionari\" where \"id\" >= 3") {
    println "id = ${it.id}; nom = ${it.nom}; valor = ${it.valor}"
}


i el resultat és

albert@athena:~/Workspace/wk-groovy/prova-jdbc$ groovy provabd.groovy 
id = 3; nom = nom3; valor = valor3
id = 4; nom = nom4; valor = valor3
id = 5; nom = nom5; valor = valor5


Creo una nova taula i la informo amb dades. Cal tenir en compte algunes particularitats de l'SQL de  l'HSQLDB: els noms de les taules han d'anar precedides de l'esquema; els noms de taules, columnes i  d'altres objectes van entre cometes dobles; els literals van entre comentes simples.

sql.execute '''
create table public.\"traductor\" (
   \"id\" integer not null,
   \"catala\" varchar(100),
   \"castella\" varchar(100),
)
'''


Faig insert de dades amb diferents mètodes:

- fent servir la sintaxi amb '''
sql.execute '''
insert into public.\"traductor\"(\"id\",\"catala\",\"castella\") values (1, 'catala 1','castella 1') 
'''
println "inserta fila 1"

-  fent servir la sintaxi de preparedStatement de JDBC
def params = [2, 'catala 2', 'castella 2']
sql.execute("insert into public.\"traductor\" (\"id\", \"catala\", \"castella\") values (?, ?, ?)", params)
println "inserta fila 2"


params = [3, 'catala 3', 'castella 3']
sql.execute("insert into public.\"traductor\" (\"id\", \"catala\", \"castella\") values (?, ?, ?)", params)
println "inserta fila 3"

- fent servir la sintaxi GString
def map = [id:4, catala:'català 4', castella:'castellà 4']
sql.execute("insert into public.\"traductor\" (\"id\", \"catala\", \"castella\") values ($map.id, $map.catala, $map.castella)")
println "inserta fila 4"


Visualitzo els resultats amb eachRow, ara fent servir el paràmetre fila a la closure, en comptes d'it.

sql.eachRow("select * from public.\"traductor\"") {fila ->
    println "id = $fila.id; català = $fila.catala; castellà = $fila.castella"
}

Després d'aquestes accions, tinc les dues taules amb dades, com puc veure amb l'OOo/LO




He fet inserts, evidentment també es poden fer update, delete... fent servir execute, o millor  executeUpdate, que em retorna una llista de les files afectades.

eachRow admet paginació. He afegit algunes dades més a la taula traductor i la visualitzo, primer tota i després mostrant només quatre files a partir de la fila tres:

println "mostra la taula \"traductor\""
sql.eachRow("select * from public.\"traductor\"") {fila ->
    println "id = $fila.id; català = $fila.catala; castellà = $fila.castella"
}


println "mostra 4 files de la taula \"traductor\" a partir de la fila 3"
sql.eachRow("select * from public.\"traductor\"", 3, 4 ) {fila ->
    println "id = $fila.id; català = $fila.catala; castellà = $fila.castella"
}


El resultat és:
mostra la taula "traductor"

id = 1; català = catala 1; castellà = castella 1
id = 2; català = catala 2; castellà = castella 2
id = 3; català = catala 3; castellà = castella 3
id = 4; català = català 4; castellà = castellà 4
id = 5; català = catala 5; castellà = castella 5
id = 6; català = catala 6; castellà = castella 6
id = 7; català = catala 7; castellà = castella 7
id = 8; català = català 8; castellà = castellà 8
mostra 4 files de la taula "traductor" a partir de la fila 3
id = 3; català = catala 3; castellà = castella 3
id = 4; català = català 4; castellà = castellà 4
id = 5; català = catala 5; castellà = castella 5
id = 6; català = catala 6; castellà = castella 6

La classe groovy.sql.Sql té altres mètodes que també permeten augmentar la productivitat del desenvolupament, que s'afegeixen al fet que els scripts en Groovy no requereixen compilació. 

groovy.sql.Sql també té mètodes que retornen ResultSet de jdbc, permetent el tractament típic de Java.

El package groovy.sql també inclou la classe  DataSet que deriva de groovy.sql.Sql i que permet fer queries a la base de dades fent servir operadors i noms de camps de Groovy en comptes de crides a API JDBC i noms de taules i columnes. Un petit exemple:

Primer de tot, cal importar DataSet
import groovy.sql.DataSet

i ja el puc fer servir:

primer mostro el contingut de la taula amb eachRow d'Sql i després fa la query amb DataSet

println "mostra la taula \"traductor\""
sql.eachRow("select * from public.\"traductor\"") {fila ->
    println "id = $fila.id; català = $fila.catala; castellà = $fila.castella"
}

println "mostra valors amb id entre 3 i 7 fent servir la classe DataSet"
def traduccions = sql.dataSet("public.\"traductor\"")
def filtrat = traduccions.findAll {it."\"id\"" >= 3 && it."\"id\"" <= 7 }
filtrat.each { fila ->
    println "id = $fila.id; català = $fila.catala; castellà = $fila.castella"
}

El resultat és el següent

mostra la taula "traductor"
id = 1; català = catala 1; castellà = castella 1
id = 2; català = catala 2; castellà = castella 2
id = 3; català = catala 3; castellà = castella 3
id = 4; català = català 4; castellà = castellà 4
id = 5; català = catala 5; castellà = castella 5
id = 6; català = catala 6; castellà = castella 6
id = 7; català = catala 7; castellà = castella 7
id = 8; català = català 8; castellà = castellà 8

mostra valors amb id entre 3 i 7 fent servir la classe DataSet
id = 3; català = catala 3; castellà = castella 3
id = 4; català = català 4; castellà = castellà 4
id = 5; català = catala 5; castellà = castella 5
id = 6; català = catala 6; castellà = castella 6
id = 7; català = catala 7; castellà = castella 7

En aquest exemple les particularitats de HSQLDB pel que fa als noms de taules i columnes entre cometes  no permeten aprofitar la simplificació de fer servir els noms de les columnes com si fossin noms de camps Groovy de l'objecte it. Però, si més no, es permet veure l'ús de l'operador && per a construir el "where" del Dataset

I finalment, per acabar...

// tanca la connexió
sql.close()




dimarts, 1 de novembre del 2011

Un "Piulador" amb Twitter4J amb gestió de l'autenticació

En el post anterior vaig fer un "piulador" personal que em permet enviar tweets (o piulades) al compte associat a l'Access Token.

Per obtenir l'Access Token i l'Access Token Secret va caldre demanar-los a la web de Twitter. Els valors que em va proporcionar Twitter  els vaig fer servir posant-los a l'objecte de la classe ConfigurationBuilder que passava com argument al constructor de l'objecte de la classe Twitter que és el que permet l'operació.

Un cop obtinguts l'Access Token i l'Access Token Secretm aquests dos valors els podrà fer servir el Piulador indefinidament. L'usuari pot, però, forçar que se'n generin de nous des de la web de Twitter. 

Per a que el "Piulador" el pugui fer servir tothom que tingui un compte a Twitter cal que sigui el mateix piulador qui el demani i en gestioni l'obtenció. Això és el que faré avui.

El codi del partida és el Piulador del post anterior:

package com.sticipl.proves;

import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.conf.ConfigurationBuilder;

public final class ProvaTwitter {
    /**
     * Usage: java twitter4j.examples.tweets.UpdateStatus [text]
     *
     * @param args message
     */
    public static void main(String[] args) {
        try {
         ConfigurationBuilder cb = new ConfigurationBuilder();
         cb.setDebugEnabled(true)
             .setOAuthConsumerKey("[El consumer Key obtingut]")
             .setOAuthConsumerSecret("[el consumer secret obtingut]")
             .setOAuthAccessToken("[l'access token obtingut]")
             .setOAuthAccessTokenSecret("[l'access token secret obtingut]");
         TwitterFactory tf = new TwitterFactory(cb.build());
         Twitter twitter = tf.getInstance();
            
            Status status = twitter.updateStatus("Aquest és el missatge enviat des de l'aplicació #Java de prova fent us de #Twitter4J");
            System.out.println("Actualitzat status a [" + status.getText() + "].");
            System.exit(0);
        } catch (TwitterException te) {
            te.printStackTrace();
            System.out.println("Excepció de Twitter: " + te.getMessage());
            System.exit(-1);
        }
    }
}

El que cal fer és canviar el bloc de codi del ConfigurationBuilder per un bloc de codi que en gestioni l'obtenció del parell Access Token / Access Token Secret.

Al post anterior vaig dir que el Consumer ha de demanar l'autorització de l'usuari amb un Request Token i que aquest Request Token es bescanvia per l'Access Token.  Com es fa això? A la secció dels exemples de codi de la web Twitter4J expliquen com:

    With OAuth authorization scheme, an application can access the user account without userid/password combination given. You need to register your application athttp://twitter.com/oauth_clients/new to acquire consumer key, and consumer secret in advance. key / secret pair can be set via Twitter#setOAuthConsumer(), or following system properties:

    -Dtwitter4j.oauth.consumerKey=[consumer key]
    -Dtwitter4j.oauth.consumerSecret=[consumer secret]

    Initially, you don't have a permission to access the user's account and need to acquire access token by redirecting the user to an authorization URL as follows:

      public static void main(String args[]) thrwos Exception{
        // The factory instance is re-useable and thread safe.
        Twitter twitter = new TwitterFactory().getInstance();
        twitter.setOAuthConsumer("[consumer key]", "[consumer secret]");
        RequestToken requestToken = twitter.getOAuthRequestToken();
        AccessToken accessToken = null;
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while (null == accessToken) {
          System.out.println("Open the following URL and grant access to your account:");
          System.out.println(requestToken.getAuthorizationURL());
          System.out.print("Enter the PIN(if aviailable) or just hit enter.[PIN]:");
          String pin = br.readLine();
          try{
             if(pin.length() > 0){
               accessToken = twitter.getOAuthAccessToken(requestToken, pin);
             }else{
               accessToken = twitter.getOAuthAccessToken();
             }
          } catch (TwitterException te) {
            if(401 == te.getStatusCode()){
              System.out.println("Unable to get the access token.");
            }else{
              te.printStackTrace();
            }
          }
        }
        //persist to the accessToken for future reference.
        storeAccessToken(twitter.verifyCredentials().getId() , accessToken);
        Status status = twitter.updateStatus(args[0]);
        System.out.println("Successfully updated the status to [" + status.getText() + "].");
        System.exit(0);
      }
      private static void storeAccessToken(int useId, AccessToken accessToken){
        //store accessToken.getToken()
        //store accessToken.getTokenSecret()
      }

El que faig és adaptar aquest codi al meu piulador:


package com.sticipl.proves;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;

import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;

public final class ProvaTwitter {
private Twitter twitter;
private BufferedReader br;
    /**
     * Usage: java twitter4j.examples.tweets.UpdateStatus [text]
     *
     * @param args message
     */
    public static void main(String[] args) {
        new ProvaTwitter();
    }
    
public ProvaTwitter() {
try {
            twitter = new TwitterFactory().getInstance();
            AutenticaConsumer();
            Status status = twitter.updateStatus("Aquest és el missatge enviat des de l'aplicació #Java de prova fent us de #Twitter4J. Versió 2");
            System.out.println("Actualitzat status a [" + status.getText() + "].");
            System.exit(0);
        } catch (TwitterException te) {
            te.printStackTrace();
            System.out.println("Excepció de Twitter: " + te.getMessage());
            System.exit(-1);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Excepció general: " + e.getMessage());
            System.exit(-2);        
        }
}
private void AutenticaConsumer() throws Exception{
twitter.setOAuthConsumer("[consumer key]", "[consumer key secret]");
RequestToken requestToken = twitter.getOAuthRequestToken();
AccessToken accessToken = null;
br = new BufferedReader(new InputStreamReader(System.in));
while (null == accessToken) {
System.out.print("Entra el nom d'aquest perfil d'accés a Twitter:");
String sNomPerfil = br.readLine();
System.out.println("Obre la següent URL a un navegador, identifica't amb el teu compte de twitter i autoritza al piulador:");
System.out.println(requestToken.getAuthorizationURL());
System.out.print("Entra el PIN(si està disponible) i prem enter:");
String pin = br.readLine();
try {
if(pin.length() > 0) {
accessToken = twitter.getOAuthAccessToken(requestToken, pin);
} else {
accessToken = twitter.getOAuthAccessToken();
}
    
//persist to the accessToken for future reference.
storeAccessToken(sNomPerfil, twitter.verifyCredentials().getId() , accessToken);
    
} catch (TwitterException te) {
if (401 == te.getStatusCode()) {
System.out.println("No ha pogut obtenir l'Access Token.");
} else {
te.printStackTrace();
}
}
}
}
    
    private  void storeAccessToken(String sNomPerfil, long l, AccessToken accessToken){
// guarda un fitxer amb:
    // nom del perfil a guardar
    // twitter.verifyCredentials().getId()
// guarda accessToken.getToken()
// guarda accessToken.getTokenSecret()
FileWriter fitxer = null;
PrintWriter pw = null;
try {
fitxer = new FileWriter("./twitter-access-token.txt");
pw = new PrintWriter(fitxer);
pw.println("Nom perfil: " + sNomPerfil );
pw.println("twitter.verifyCredentials().getId(): " + l );
pw.println("Access Token: " + accessToken.getToken() );
pw.println("Access Token Secret: " + accessToken.getTokenSecret() );
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != fitxer)
fitxer.close();
    } catch (Exception e2) {
     e2.printStackTrace();
    }
}      
    }    
}

I l'executo:

Des del main s'invoca al constructor , el qual, primer de tot, va al mètode AutenticaConsumer, que és el que fa la negociació OAuth. 

A AutenticaConsumer,  s'estableixen el Consumer Key i el Cosumer Key Secret i, a continuació s'obté un Request Token. 

Entra el nom d'aquest perfil d'accés a Twitter:sticipl

A continuació demana un nom de perfil d'accés al twitter per a identificar-lo en futures connexions. La idea és que guarda a un fitxer el nom del perfil i l'Access Token i l'Access Token Secret, de forma que per a futures connexions amb aquest perfil,  podria fer servir directament aquests valors emmagatzemats localment, en comptes d'haver de tornar a demanar accés.  En aquesta versió genera el fitxer, però no està implementada la  recuperació per perfil de l'Acces Token. No costaria gaire fer-ho, però potser millor amb una bd HSQLDB que amb un fitxer pla.

Amb el Request Token obté la URL d'autorització. L'usuari ha d'anar a aquesta URL, identificar-se amb el compte de Twitter amb el que vol fer servir el piulador, i autoritzar al Piulador a poder enviar missatges. 

Obre la següent URL a un navegador, identifica't amb el teu compte de twitter i autoritza al piulador:

http://api.twitter.com/oauth/authorize?oauth_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


Dit i fet, copio la URL i l'enganxo a un navegador. Vaig a la web d'Autorització de Twitter. Com que ja estava logat a Twitter amb el compte de sticipl, aquesta és el compte per al que s'autoritzarà el Piulador. Però podria haver sortit i haver-me logat de nou amb un compte diferent :





Faig click a Authorize App i...



La pàgina de Twitter em proporciona un PIN. El piulador estava esperant a que li indiqués aquest PIN:

Entra el PIN(si està disponible) i prem enter:6865240
 
Un cop autoritzat, el mètode AutenticaConsumer invoca al mètode storeAccessToken que genera el fitxer amb l'Access Token i l'Access Token Key per a usos futurs. El fitxer generat és una cosa com aquesta:

Nom perfil: sticipl
twitter.verifyCredentials().getId(): 177894606
Access Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Access Token Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

AutenticaConsumer acaba i torna al constructor, i el constructor fa la següent piulada:

Actualitzat status a [Aquest és el missatge enviat des de l'aplicació #Java de prova fent us de #Twitter4J. Versió 2]. 

Vet aquí el resultat:



Vet aquí, doncs, una versió del piulador amb autenticació OAuth gestionada des de la mateixa aplicació.

La web de Twitter4J ens ens proporciona codi d'exemple per a desenvolupar altres funcions del Twitter que aniré explorant en propers posts.