lunedì 31 marzo 2008

Leopard & Samba: alias da riparare

Leopard non sembra proprio un sistema cooperativo: oltre ai problemi trovati con Netatalk (vedi miei precedenti post) ci sono anche dei problemi con Samba. Nonostante le opzioni per seguire i link simbolici di Samba, Leopard si rifiuta di seguire i link in una share Samba reclamando che l'alias è danneggiato e deve essere riparato.

Una possibile soluzione al problema consiste nel disabilitare le estensioni unix in Samba (unix extension = no). Penso che il problema sia che con tali estensioni abilitate il server Samba comunica al Mac OSX che il file in analisi è un link (QUERY_FILE_UNIX_LINK), e che quindi OSX lo interpreti come un alias senza riuscire a risolvere la destinazione (e quindi senza fidarsi di dove Samba lo rimanda). Disabilitando tali estensioni, il Mac OSX è costretto a fidarsi di Samba e a seguire il symlink nel path appropriato.

Si ricomincia?

Ci riprovo!
Riprovo a tirare con l'arco, la voglia in questo momento l'ho, speriamo che mi duri e che i già molti impegni non mi tolgano il tempo necessario a qualche allenamento settimanale.

Sabato scorso ho ricominciato a tirare all'aperto. Dopo quasi 3 anni di inattività le prime sensazioni sono state buone. Più volte avevo provato a rimettere in movimento le spalle allenandomi nel garage di casa, ma tirare all'aria aperta è un'altra cosa, e sicuramente è più stimolante e al tempo stesso rilassante che chiudersi in garage.

Oggi, dopo due giorni, ancora ho dei forti dolori alla spalle e alla mano della corda (o meglio alle dita), ma non voglio fermarmi. Infatti sono ormai 3 anni che vado in piscina regolarmente, quindi penso di avere i muscoli sufficientemente allenati, solo non sono più abituato a fare determinati movimenti, e ovviamente non ho più i calli sulle dita, percui per il momento le proteggo con del cerotto. Ho fatto circa 70-80 frecce, ma già dopo 50 iniziavo ad accusare qualche dolore. La rosota è stata abbastanza buona, sia a 18 mt. che a 30 (la massima distanza alla quale ho tirato sabato, sul bersaglio hunter da 60 cm). L'arco rispondeva bene, guizzando via abbastanza pulito ad ogni tiro. Ancora avverto qualche tremolio durante la mira, un problema che avevo (anche se molto più accentuato) anche negli ultimi anni di attività. Purtroppo ho anche rotto una freccia, inforcandola con un'altra a 18 mt., cosa che renderebbe fiero qualsiasi arciere se non sapessi che nel mio caso si trattava, ovviamente, di fortuna e se non avesse minato la mia già scaduta attrezzatura.

Ho apportato due variazioni significative al mio equipaggiamento:
  1. ho montato dei flettenti Hoyt (quelli larghi) Gold Medalist da 70" 34#, così da tirare più scarico almeno fino a quando non sarò in grado di montare i più nervosi e veloci Sky/Beiter da 70" 36#.
  2. ho montato l'asta lunga del mio mirino Las Vegas (da compound), quando solitamente usavo quella corta (a 5 buchi). Devo dire che sia per il peso leggermente più in avanti che per il mirino più distante, la sensazione di mira è stata più confortevole di quanto mi ricordassi.
La stabilizzazione è ancora la stessa ACE che usavo ai tempi d'oro, e in effetti sento che è piuttosto consumata e incapace ormai di assorbire le vibrazioni. La pattella è la stessa Cavalier in pelle, che ho opportunamento smontato, pulito e ingrassato.

Attualmente sto cercando di impostare il mio tiro tenendo presente pochi punti di riferimento:
  • bacino dritto e testa ferma
  • braccio dell'arco stabile, spalla bassa
  • buona presa di corda
  • osservazione del clicker (una cosa che non ho mai fatto)
  • tiro veloce
Fino a quando non avrò riacquisito un po' di fluidità nel movimento non penso valga la pena distrarsi con altri dettagli.

Per quello che riguarda il braccio dell'arco direi che la situazione è abbastanza buona. Ho ancora la mano dell'arco molto verticale, ma questo è sempre stato un mio difetto. Oltretutto sono dispiaciuto per non trovare più la mia impugnatura bassa, che sicuramente mi permetterebbe di tenere maggiormente fermo il braccio dell'arco.
Al rilascio l'arco si muoveva in modo abbastanza pulito, anche se la mano dell'arco rivela qualche rigidità muscolare. Sono curioso di provare con un po' di cerotto sull'impugnatura se riesco a posizionare meglio la mano dell'arco e a togliere tensione.





















Da questa foto noto diversi problemucci da mettere a posto quanto prima. Anzitutto la testa è troppo sollevata (si nota dal fatto che la corda non raggiunge bene il naso nella prima fase di ancoraggio). Questo è sempre stato un mio problema, devo riuscire in qualche modo a trovare l'altezza giusta della testa per un ancoraggio preciso e corretto al primo colpo.
In secondo luogo il gomito dell'arco è un po' alto, altro mio vecchio problema sul quale spero di riuscire a lavorare appena riacquisita un po' di flessibilità nel gesto.
Infine, anche se non ne sono sicuro, mi sembra di essere un po' in sovrallungo, cosa verificata anche dall'osservazione del clicker durante il tiro (mi resta da tirare tutta la punta, una L-5). A questo si dovrebbe rimediare facilmente intervenendo sul clicker stesso. In effetti guardando il tiro da un'altra angolazione non sembra ci sia la presenza di sovrallungo, ad ogni modo proverò a spostare il clicker in avanti visto che comunque ho ancora tutta la punta da tirare.


Circa la presa della corda, direi che sia buona, anche se ho sempre il problema di non riuscire a fare una presa salda con tutte le dita. In particolare l'anulare risulta quello più debole e che quindi si apre di più durante il tiro stesso. Questa è una cosa sulla quale ho sempre provato a lavorare senza ottenere mai dei buoni risultati.


Ecco il link a due video ripresi durante l'allenamento:
video 1
video 2

mercoledì 26 marzo 2008

Registrazione del driver JDBC di PostgreSQL

Solitamente i driver JDBC vengono caricati in una applicazione Java mediante reflection (tipicamente con Class.forName(..)) e successivamente devono essere registrati con il DriverManager, un repository di driver disponibili per i differenti URL (e quindi database) disponibili.

Il driver JDBC di PostgreSQL non richiede il passaggio di registrazione presso il DriverManager, e infatti effettua automaticamente la registrazione di se stesso presso il driver manager, come facilmente visibile dal codice sorgente di org.postgresql.Driver:

   static
{
try
{
// moved the registerDriver from the constructor to here
// because some clients call the driver themselves (I know, as
// my early jdbc work did - and that was based on other examples).
// Placing it here, means that the driver is registered once only.
java.sql.DriverManager.registerDriver(new Driver());
}
catch (SQLException e)
{
e.printStackTrace();
}
}

Come si nota il driver registra presso il driver manager una nuova istanza di se stesso (un'istanza sempre diversa, anche da quella che l'applicazione userà). Come si intuisce dai commenti, il fatto che la registrazione del driver sia effettuata a livello statico (ossia al caricamento della classe) implica che la registrazione venga fatta in automatico una sola volta, la prima in cui la classe viene messa in funzione (tipicamente con Class.forName(..) o più tardi nel caso si sia utilizzato un class loader). Non è quindi richiesto al codice utente di registrare il driver presso il driver manager.

Creazione automatica di account NIS da un file CSV

Quello presentato di seguito è uno script Perl utilizzato per la generazione automatica di account per gli studenti su un sistema Linux+NIS. L'idea è quella di partire da un file CSV contentente diverse informazioni (nome utente, nome account, informazioni circa l'anno di studio, ecc.).

L'idea alla base dello script è quella di costruire una collezione di hash, uno per ogni utente letto dal file CSV, nel quale vengono memorizzate le informazioni realmente necessarie alla creazione del relativo account (come ad esempio username, password, shell, etc.). Il file viene letto una riga alla volta dalla funzione parseFile, la quale passa il controllo a parseLine che costruisce l'hash dai valori di una singola linea; l'hash viene poi aggiunto ad un array che contiene la lista di tutti gli hash estratti dal file sorgente. Durante la creazione di un hash utente viene anche generata una password casuale (funzione generatePassword), che verrà poi salvata in chiaro in un file di log.

Una volta ottenuti tutti gli hash per tutti gli utenti, si passa il controllo alla funzione addUser che si occupa di lanciare i comandi Unix per la creazione e il setup dell'account utente sulla macchina locale. Si noti che è possibile creare più directory home per ogni utente, come specificato nell'array @homeDirs; questo nel caso serva esportare (tramite NFS) diversi file system a seconda di differenti architetture client.

Una volta terminato il ciclo di aggiunta di ogni utente, vengono lanciati i comandi per la ripopolazione del NIS e l'aggiornamento dei client collegati al server stesso.
#!/usr/bin/perl



# esempio di una riga del file di account
#Luca;Ferrari;20202;599;II;luca.ferrari37094@unimo.it;37094;si;0;37094;U4L5R;;;;;;;;;Sì;C;Windows



# * Copyright (C) Luca Ferrari 2003-2008
# *
# * This program is free software: you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation, either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program. If not, see .





# se definito questo flag lo script non esegue i comandi, li simula solo
$DEBUG_MODE="true";


# separatore di campo nel file degli account
$FIELD_SEPARATOR=';';
# il carattere da anteporre ad ogni username ricavato dal file
$USERNAME_START="n";
# gruppo di default
$DEFAULT_GROUP="users";
# lunghezza di default delle eventuali password generate automaticamente
$DEFAULT_PASSWORD_LENGTH=8;


# array con le home da creare per ogni utente
@homeDirs = ("/export/Linux-i686/home/",
"/export/Linux-sparc64/home/",
"/export/SunOS-sun4u/home/",
"/export/SunOS5.8-sun4u/home/",
"/export/shared/home/",
);


# indicatori dei vari campi (loro posizioni) nel file di account
$ACCOUNT_POSITION=6;
$PASSWORD_POSITION=10; # impostare ad un valore negativo per avere la generazione automatica delle password
$NAME_POSITION=1;
$SURNAME_POSITION=0;
$OTHER_INFO_POSITION=5;


# chiavi di ongi singolo hash di informazioni
$USERNAME_KEY='username';
$PASSWOD_KEY='password';
$INFO_KEY='info';
$CYPHERED_PASSWORD_KEY='password_cifrata';
$GROUP_KEY='gruppo';



# file di log
$_REPORT = "accounts.log.txt";



# Funzione per fare il log di una stringa
sub history($)
{
my ($_stringa) = @_;

# se il log non e' inizializzato lo apro ora
if( not defined($__log__) ){
open(LF,">$_REPORT") || die("\nNon posso aprire il file di log $_REPORT:\n$!\n\n");
$__log__="true";
}

print LF $_stringa,"\n";
}








# Funzione per fare il parsing di tutto il file
# La funzione accetta il nome del file di cui fare il parsing e ritorna
# un vettore con dei riferimenti ad ogni hash di account.
sub parseFile
{
# prelevo i parametri
my ($_file) = @_;
my $_success=0;
my $currentHash;
my @accounts;

if( not defined($_file) || length($_file)==0 || $_file eq "" ){
return undef();
}

# apertura del file in lettura
open(FILE,"<".$_file) || die("\nNon riesco ad aprire $_file:\n$!");

# leggo ogni riga e la passo alla funzione di parsing
while( $line = ){

$currentHash = parseLine($line);

# inserisco l'account nell'array di account
if( defined($currentHash) ){
$accounts[++$#accounts] = $currentHash;
undef($currentHash);
}
}


return @accounts;

}






# Funzione per fare il parse di una singola linea di un file di account.
# Argoemnti: la linea di input.
# La funzione restituisce un riferimento ad un hash con i dati dell'account.
sub parseLine($)
{
# prelevo i parametri
my ($_line) = @_;

# controllo
if( not defined($_line) || length($_line) == 0 || $_line eq "" ){
return undef();
}

# faccio il parsing della linea
my @_parts = split(/$FIELD_SEPARATOR/,$_line);


if( not defined(@_parts) || $#_parts<0 ){
return undef();
}

# creo l'hash
my $_hash = {
$USERNAME_KEY => $USERNAME_START.$_parts[$ACCOUNT_POSITION],
$PASSWORD_KEY => $_parts[$PASSWORD_POSITION],
$INFO_KEY => "$_parts[$SURNAME_POSITION] $_parts[$NAME_POSITION] - $_parts[$OTHER_INFO_POSITION]",
};


# ATTENZIONE: se la password e' nulla oppure non e' definito il campo PASSWORD_POSITION genero io una
# password.
if( (not defined($PASSWORD_POSITION)) || $PASSWORD_POSITION<0>{$PASSWORD_KEY}) ||
length($_hash->{$PASSWORD_KEY}) == 0 || $_hash->{$PASSWORD_KEY} eq "" ){
my $_psw = generatePassword($DEFAULT_PASSWORD_LENGTH);
$_hash->{$PASSWORD_KEY} = $_psw;
}


# Pulisco ogni campo da spazi bianchi all'inizio e alla fine.
foreach $_i ( keys(%$_hash) ){
$_tmp = $_hash->{$_i};
$_tmp =~ s/^\s+|\s+$//g;
$_tmp =~ s/\'//g;
$_hash->{$_i} = $_tmp;
}


# restituisco l'hash (il suo riferimento)
return $_hash;
}







# FUnzione per generare una password.
# La funzione si appoggia a rand e map per generare una password totalmente casuale.
sub generatePassword($)
{
my ($_size) = @_;
my $_password;
# lista dei caratteri validi nelle password
my @chars = ('a'..'z','A'..'Z',0..9,'_','+','-','(',')' );

for($_i=0;$_i<=$_size;$_i++){
$_password .= join('',$chars[rand($#chars)]);
}

return $_password;
}











# Funzione di debug per stampare il contenuto di uno degli hash utente
sub dumphash($)
{
my ($h) = @_;

print "Hash costuito:\n";
print $h->{$USERNAME_KEY},"<",length($h->{$USERNAME_KEY}),"> - ",$h->{$PASSWORD_KEY}," - ",$h->{$INFO_KEY};
print " - ",$h->{$CYPHERED_PASSWORD_KEY};
print "\n----------------------------------------------\n";
}




# Funzione per aggiungere un singolo utente dato il suo hash di account.
# La funzione ritorna undef in caso di errore.
sub addUser($)
{
my ($hash) = @_;

if( not defined($hash) || not defined($hash->{$USERNAME_KEY}) || not defined($hash->{$PASSWORD_KEY}) ){
return undef();
}

# devo ottenere la password cifrata
my $cyphered = crypt($hash->{$PASSWORD_KEY},1);
$hash->{$CYPHERED_PASSWORD_KEY} = $cyphered;


# primo passo: invocazione di useradd
# ATTENZIONE: se non e' definito un gruppo uso quello di default
if( not defined($has->{$GROUP_KEY}) ){ $hash->{$GROUP_KEY} = $DEFAULT_GROUP; }
$useradd = " useradd -g ".$hash->{$GROUP_KEY}. " -s /bin/bash -p ". $hash->{$CYPHERED_PASSWORD_KEY}. " ";
$useradd .= " -c \"".$hash->{$INFO_KEY}."\" ".$hash->{$USERNAME_KEY}." ";

# secondo passo: creazione delle home
$createhome="";

foreach $__home (@homeDirs){
if( not -d $__home ){
if( mkdir($__home) ){
history("Creata directory $__home");
}
}
$currentHome = $__home;
$currentHome .= $hash->{$USERNAME_KEY};

$createhome .= " mkdir $currentHome; ";
if( defined($hash->{$GROUP_KEY}) ){
$createhome .= " chown ".$hash->{$USERNAME_KEY}.".".$hash->{$GROUP_KEY}." $currentHome; ";
}else{
$createhome .= " chown ".$hash->{$USERNAME_KEY}." $currentHome; ";
}
$createhome .= " chmod 755 $currentHome; ";
}






# log dei comandi
history("-------------- UTENTE ".$hash->{$USERNAME_KEY}."----------------------------");
history("password in chiaro => ".$hash->{$PASSWORD_KEY});
history($useradd);
history($createhome);
history("------------------------------------------------------------");


# esecuzione dei comandi
if(defined($DEBUG_MODE) ){ return "true"; }
return (`$useradd` && `$createhome`);
}


# Funzione per la ricostruzione della mappa del nis.
sub reNIS
{
my $nis = "cd /var/yp; ";
$nis .= "make";

history(">>>>>>>>>>>>>>>>>>>> RICOSTRUZIONE DEL NIS <<<<<<<<<<<<<<<<<<<<<<<");
history($nis);

if(defined($DEBUG_MODE) ){ return "true"; }
return `$nis`;
}
















#############################################################
#############################################################
#############################################################
#############################################################
#############################################################

# controllo parametri
if($#ARGV<0){
print "Utilizzo:\n";
print "$0 \n";
print "oppure:\n";
print "$0 username password_in_chiaro gruppo info";
exit(1);
}
else{

print "\nEsecuzione di $0....log impostato su $_REPORT\n";


if($#ARGV==0){
# un solo parametro: deve essere il file degli account
$ACCOUNT_FILE=$ARGV[0];
chomp($ACCOUNT_FILE);

# faccio il parsing del file
print "\nparsing del file di account $ACCOUNT_FILE...";
if( @acc = parseFile($ACCOUNT_FILE) ){
print "\nRilevati $#acc account da inserire, procedo...";

foreach $u (@acc){
# $u => riferimento all'hash dell'account
print "\nAggiunta dell'utente <",$u->{$USERNAME_KEY},">, [",$u->{$INFO_KEY} ,"]....";
if( addUser($u) ){
print "fatto!";
}
else{
print "NON riuscito!\n";
}
}

}
}
elsif($#ARGV==3){
# inserimento di un solo account
$_usr = $ARGV[0];
chomp($_usr);
$_pwd = $ARGV[1];
chomp($_pwd);
$_grp = $ARGV[2];
chomp($_grp);
$_info = $ARGV[3];
chomp($_info);

$_crypt = crypt($_pwd,1);

$singleAccount = {
$USERNAME_KEY => $_usr,
$PASSWORD_KEY => $_pwd,
$GROUP_KEY => $_grp,
$INFO_KEY => $_info,
$CYPHERED_KEY => $_crypt,
};

# aggiungo l'utente
print "\nAggiunta dell'utente <",$singleAccount->{$USERNAME_KEY},">...\n";
if( addUser($singleAccount) ){
print "fatto!";
}
else{
print "NON riuscito";
}
}


# ora che ho aggiunto gli utenti ripopolo il nis
print "\n\nRipopolazione del NIS...";
reNIS();
print "fatto\n\n";

}

Alcune considerazioni sul caricamento delle classi

Java mette a disposizione dell'utente/programmatore due metodi principali per il caricamento di una classe:
  • l'utilizzo dei metodi statici definiti in java.lang.Class
  • l'utilizzo di un ClassLoader
In entrambi i casi è necessario conoscere il nome della classe che si vuole caricare dinamicamente. Le differenze fra l'utilizzo di Class.forName(..) e ClassLoader.loadClass(..) sono sostanzialmente le seguenti:
  • Class.forName(..) richiama tutti gli inizializzatori statici della classe caricata, mentre ClassLoader.loadClass(..) ritarda gli inizializzatori statici fino al momento in cui la classe non viene concretamente utilizzata;
  • l'utilizzo di un class loader consente una personalizzazione maggiore nel processo di caricamento delle classi;
  • l'utilizzo di ClassLoader.loadClass(..) memorizza la classe caricata nella cache del class loader che effettivamente carica la classe (che potrebbe essere un parent loader), mentre Class.forName(..) memorizza la classe nella cache del class loader corrente.
In generale, se occorre avere il controllo sul processo di risoluzione/caricamento di una classe, è necessario implementare tale controllo all'interno di uno specifico class loader.

Si tenga presente che molti application server (es. Tomcat, JBoss, ecc.) utilizzano un proprio schema di classloader al fine di fornire spazi protetti alle classi dell'utente e per consentire un hot-deployment delle stesse. Infatti, all'interno di una JVM una classe è univocamente identificata dal suo nome e dal class loader che l'ha caricata, ne consegue che fornendo un class loader per ogni applicazione all'interno di un container si può consentire l'esecuzione di più classi con lo stesso nome e comportamento differente. Inoltre, grazie all'uso di classloader separati (quindi con cache separati) è possibile caricare immediatamente la nuova versione di una applicazione senza bisogno di riavviare il container stesso.

Connessione di unità di rete automatiche mediante file batch: createSambaBat.pl

Risulta molto comodo poter collegare unità di rete Windows con share Samba. Avendo una lista degli utenti e delle unità da collegare, è possibile utilizzare un semplice script Perl per la realizzazione di file batch che colleghino automaticamente all'avvio l'unità di rete prescelta. Lo script mostrato di seguito, denominato createSambaBat.pl processa un file in formato csv nel quale devono essere inserite le informazioni relative a username, password (in chiaro), server a cui collegarsi, unità di rete sulla quale montare la share e la share a cui collegarsi. Il programma crea un file .bat per ogni utente/server, nella forma username_server.bat, che può contenere anche più share/unità per lo stesso utente (nel qual caso più righe per lo stesso utente devono essere specificate nel file CSV).

#!/usr/bin/perl

# $ARGV[0] = csv file for reading user credentials: it must have the following columns
# username;password;drive;server;share
# $ARGV[1] = directory where the bat files will be stored


# * Copyright (C) Luca Ferrari 2008
# *
# * This program is free software: you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation, either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program. If not, see .

# open the input file
open(INPUT_FILE, "<$ARGV[0]") || die("\nCannot open input file $ARGV[0]\n$!\n"); $written = 0; # read each file from the file READLINE: while( $line = ){
@parts = split(";", $line);

$username = $parts[0];
$password = $parts[1];
$drive = $parts[2];
$server = $parts[3];
$share = $parts[4];

$filename = $ARGV[1] . "/" . $username . "_" . $server . ".bat";

chomp($username);
chomp($password);
chomp($drive);
chomp($server);
chomp($share);

# create a file for the username
if( open(BAT_FILE, ">>$filename") ){

# store the information on the bat file
print BAT_FILE "NET USE $drive \\\\$server\\$share /USER:$username \"$password\"\n";
close(BAT_FILE);
$written++;
}
else{
warn("\nCannot write ouptu file $filename\n$!\n");
next READLINE;
}

} # end of while


print "\nWritten $written files\n";

Se il file di ingresso contiene le seguenti tuple:

luca.ferrari;LucFer;U:;myServer;myShare

e viene invocato come

createSambaBat.pl file.csv .

allora viene generato un file di nome luca.ferrari_mySever.bat che contiene la seguente linea:

NET USE U: \\myServer\myShare /USER:luca.ferrari "LucFer"

che consente a Windows di collegare \\myServer\myShare come unità di rete U:.

A questo punto è sufficiente copiare il file batch nel computer di destinazione, magari direttamente sotto la cartella esecuzione automatica del menù Start per ottenere il collegamento dell'unità di rete all'avvio del computer. E' possibile rendere il file non modificabile, ma non va assolutamente messo come nascosto, altrimenti non verrà nemmeno eseguito.

Rimuovere file esterni al database: creare comandi shell da una query

Capita a volte di aver memorizzato dei record nel database contenenti percorsi di file sul filesystem (ad esempio immagini) e di voler cancellare i record e i relativi file. Ci sono varie soluzioni a questo problema, una delle quali consiste nell'effettuare una query SQL che produca una serie di comandi shell per la rimozione di ogni singolo file, dopodiché si procede all'esecuzione di tali comandi e alla rimozione dei record.
In sostanza:
  • occorre redirigere l'output della query non formattato su file, quindi da occorre dare i comandi \a \t \o comandi.sh
  • si effettua la query concatenando i comandi da eseguire. Ad esempio:
select 'rm path/*/' || '/' || id_elemento || '.png'

from elementi where upload_name like'path/%';


Come si può notare questa query produce una serie di tuple in uscita che effettuano un semplice rm del file con nome id_elemento.png nel percorso path.
  • eseguire il file comandi.sh da shell
  • cancellare le tuple selezionate nel database

venerdì 21 marzo 2008

Samba & Leopard: nomi di file

Mac OS X è dotato di un filesystem (HFS) di tipo case insensitive (quindi che non distingue fra nomi di file maiuscoli e minuscoli) ma case preserving (quindi che tiene traccia di come il file è stato nominato). Questo produce notevoli problemi quando si inseriscono gli stessi nomi di file in maiuscolo/minuscolo.

La mia personale esperienza ha portato a diversi problemi nell'interoperabilità con Samba, soprattutto ove sia attiva l'opzione

preserve case = no
default case = lower

che consente di registrare i file senza tenere conto di maiuscole/minuscole, convertendo i nomi sempre in minuscolo (lower). Purtroppo una simile opzione fa si che spostando un file dal Finder ad una cartella Samba, questo sparisca se contiene lettere maiuscole. La ragione di ciò sta nel fatto che il file, una volta spostato su Samba, viene rinominato in minuscolo, e il finder non lo riconosce più, facendolo sparire. La risoluzione del problema è abbastanza semplice: basta abilitare l'opzione preserve case, così da fare in modo che file registrati con lettere maiuscole/minuscole non siano rinominati.

KMail: indici della posta corrotti

A volte capita che lo spazio a disposizione sul disco sul quale risiede la posta di KMail finisca, e che KMail quindi improvvisamente faccia sparire la posta contenuta in una cartella. Il problema risiede nel fatto che, alla saturazione dello spazio disco, l'indice per quella cartella di posta viene corrotto. Rimediare è abbastanza semplice:
  1. chiudere l'applicazione KMail
  2. entrare nella cartella mail ove risiede la sotto-cartella di posta colpita (tipicamente ~/.kde/share/apps/kmail/mail)
  3. eliminare il file nascosto .nome_cartella.index
  4. riavviare KMail
Purtroppo al riavvio potrebbero riapparire come da leggere alcune e-mail che erano già state lette, ma dopotutto si tratta di una cosa trascurabile rispetto all'improvissa impossibilità di accedere alla posta di quella cartella!

mercoledì 19 marzo 2008

Una veloce introduzione alle rules di PostgreSQL

PostgreSQL fornisce un potentissimo strumento per il query rewriting: le rules. Una rule è una regola che specifica come una particolare query DML debba essere riscritta al volo. L'utilizzo più comune delle rules si ha nelle viste: di fatto le viste sono delle informazioni su come riscrivere una query SELECT per ottenere le informazioni ricercate.
Ma altri utilizzi delle rules sono possibili: ad esempio è possibile intercettare un INSERT ed eseguire, contestualmente, un altro INSERT (es. log dei dati in inserimento) o addirittura ridirezionare l'inserimento su altre tabelle, o inibirlo totalmente. La stessa cosa si può fare per una query di UPDATE o DELETE. Va tenuto presente che le rules, seppur possano avere utilizzi molto simili a quelli di un trigger, hanno un notevole valore aggiunto rispetto a questi ultimi: esse sono infatti analizzate prima della effettiva esecuzione della query, e quindi intervengono prima che i dati siano effettivamenti letti/scritti sul database.

Come semplice esempio didattico, si consideri di avere una tabella con articoli e relativi prezzi:

CREATE TABLE articoli_con_prezzo
(
pk serial NOT NULL,
descrizione character varying,
prezzo double precision,
CONSTRAINT articolo_con_prezzo_pk PRIMARY KEY (pk)
)


sulle quali si andrà ad agire con query di inserimento simili alla seguente:

insert into articoli_con_prezzo(descrizione,prezzo) values('articolo001', 10.16);



Si supponga ora di voler rendere più modulare il design della struttura dati relativa agli articoli, separando i dati anagrafici da quelli relativi al prezzo:

CREATE TABLE articoli
(
articolipk serial NOT NULL,
descrizione text,
CONSTRAINT articoli_pkey PRIMARY KEY (articolipk)
)


CREATE TABLE prezzo
(
pk serial NOT NULL,
articoli_pk integer,
prezzo double precision,
CONSTRAINT prezzo_pk PRIMARY KEY (pk),
CONSTRAINT articoli_prezzi_fk FOREIGN KEY (articoli_pk)
REFERENCES articoli (articolipk) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)



A questo punto la tabella articoli_con_prezzo non ha più validità, e quindi ogni query eseguita su di essa deve essere riscritta per considerare il nuovo schema a due tabelle. Ovviamente cambiare tutte le query in tutti i possibili client applicativi potrebbe essere non fattibile (anche se consigliato per correttezza), quindi si può ovviare utilizzando delle rules che riscrivano al volo le query per tenere conto delle due tabelle.

Si consideri il caso della query di inserimento: la regola deve separare i dati anagrafici da quelli di prezzo e inserire i primi nella tabella articoli, e i secondi nella tabella prezzo:

CREATE OR REPLACE RULE articoli_con_prezzo_insert_rule AS
ON INSERT TO articoli_con_prezzo DO INSTEAD
(
INSERT INTO articoli (descrizione) VALUES( NEW.descrizione );
INSERT INTO prezzo( articoli_pk, prezzo ) SELECT articolipk,NEW.prezzo FROM articoli WHERE descrizione=NEW.descrizione;
)


Come si può notare, la regola si applica ad un evento di tipo INSERT e inserisce la descrizione (NEW.descrizione) nella tabella articoli e il prezzo (NEW.prezzo) nella tabella prezzo. Come è facile intuire, la pseudo-relazione NEW referenzia i dati passati alla query effettiva, e quindi con una query:


insert into articoli_con_prezzo(descrizione,prezzo) values('articolo001', 10.16);

si avrà che:

NEW.descrizione = 'articolo001'
NEW.prezzo=10.16


Si noti che la descrizione di un articolo potrebbe non essere univoca, e quindi la tecnica usata per reperire la primary key del record inserito nella tabella articoli non è particolarmente elegante e precisa, ma è sufficiente per i fini didattici di questo post.

Avendo cambiato la query di inserimento, è possibile modificare anche la query di selezione. Purtroppo è necessario creare una tabella con la stessa struttura di quella originale ed effettuare le query su di essa, avendo cura di creare una regola apposita:

CREATE TABLE articoli_con_prezzo_tmp(pk integer, descrizione varchar, prezzo float);

CREATE RULE "_RETURN" AS ON SELECT TO articoli_con_prezzo_tmp
DO INSTEAD
SELECT a.articolipk as pk, a.descrizione::varchar, p.prezzo
FROM articoli a, prezzo p
WHERE a.articolipk = p.articoli_pk

La tabella articoli_con_prezzo_tmp non deve essere riempita con i dati, serve solo come entry nel catalogo di sistema per associare ad essa la rule. Si noti che il nome della rule per il caso SELECT deve essere obbligatoriamente _RETURN (solo una rule per SELECT è possibile su ogni tabella) e che i campi in uscita devono avere lo stesso layout, nome e tipo di quelli della tabella alla quale la rule è applicata.

Mac OS X & Samba: codice di errore -36

Mi è capitato di dover configurare un computer con Mac OS X 10.4 per accedere ad un normale server Samba (3.0.28). Purtroppo il Mac continuava a chiedere l'autenticazione prima di mostrare la lista delle share e il processo di autenticazione terminava con un errore -36 (errore di I/O confermato dai log di samba).

Il problema è legato alla cifratura delle password: OS X si aspetta di inviare password cifrate, mentre il server samba si aspettava password in chiaro. La soluzione proposta qui di far mandare al Mac OS X le password in chiaro non ha avuto effetti positivi, anzi il risultato è stato quello di far rifiutare al Mac qualsiasi connessione con un messaggio di errore relativo ad un URL sbagliato.

La soluzione è quindi quella di abilitare le password cifrate sul server (encrypt password = yes). In questo modo da subito il Mac OS X presenta la lista delle share prima dell'autenticazione, e questa volta ci si riesce a collegare alle share.

Baaad vista

I've signed today the campaign of the Free Software Foundation about the problems of the Microsoft Vista Operating System. I believe that, aside the problems of requiring more powerful and modern computers, this Operating System has the big problem, and maybe the big mistake, of trying to control the users' activities in order to prevent an inappropriate use of multimedia files and programs. I don't believe that limiting and monitoring users' activities is the right way to avoid illegality in the multimedia context. I believe that users' should be able to do what they want with their software and computer. After all, it can be trivial, but when a user gets a new car, she can do whatever she want with it: she can change the engine, can change the colour, can adapt the seats and so on. In other words, she can adapt the car to her needings. Why this should not be possible with software? Why am I supposed to be a bad person only because I'm storing a multimedia file on my hard disk?

There must be rules and laws for preventing people doing illegal actions with multimedia files, but the stree Vista is running on is not the right one, in my opinion.

Qualche veloce considerazione sui pattern

Molto spesso c'è confusione circa alcuni concetti relativi ai desgin pattern; in questo brevissimo post tento di fare chiarezza su alcuni punti fondamentali.

  1. Il concetto di "design-pattern" nasce con il famoso libro di Gamma et. al (GoF). FALSO! I design pattern esistevano già da tempo, ed erano applicati anche a scienze differenti dalla computer-science, ad esempio nell'urbanistica. Il libro della GoF ha messo in luce diversi design-pattern applicati alla computer-science.
  2. Il libro della GoF è la fonte più autorevole sui design-pattern per la computer science. FALSO! Nonostante sia universalmente riconosciuto come uno dei testi più autorevoli sui design-patterns, il libro della GoF non è l'unico testo in circolazione. Ad esempio esistono cinque testi sul pattern-oriented software architecture (chiamati POSA books) che trattano design patterns sotto diversi punti di vista. I vari testi sui design pattern si differenziano per l'analisi che ne fanno, per l'esame delle forze in gioco (contesto iniziale) e del contesto risultante.
  3. Essendo i design pattern corretti, usare un design pattern nel proprio codice significa scrivere codice migliore. FALSO! I desgin pattern sono soluzioni a problemi noti, non vi è quindi nessun motivo per applicare un design pattern al proprio codice se non vi è traccia di un problema risolvibile tramite un pattern.
  4. I pattern sono innovazione. FALSO! I pattern descrivono soluzioni usate di frequente in determinati contesti, e rappresentano per tanto delle good-practice piuttosto che innovazioni. Introdurre patterns nel proprio codice non significa innovarlo, quanto renderlo maggiormente documentato e manutenibile.
  5. I pattern sono una forma di documentazione. VERO! I pattern consentono di spiegare e comprendere meglio come un determinato software faccia fronte a contesti, situazioni e problemi specifici.
  6. I pattern possono essere introdotti nel proprio codice in forma automatica. VERO! Esistono diversi tool che consentono di introdurre alcuni pattern nel proprio codice in maniera automatica (o semi-automatica).
  7. I pattern sono sempre automatizzabili. FALSO! Un pattern è una cosa ben differente da un template di codice. Seppur alcuni pattern possano essere introdotti nel proprio codice in modo automatico, questo non è sempre il caso generale. In effetti, se si cercano pezzi di codice per implementare un pattern si sta guardando il problema da una angolazione sbagliata e si rischia anzi di introdurre codice errato.
  8. I pattern rendono il codice maggiormente manutenibile. VERO! Essendo una forma di documentazione, ed essendo generalmente noti, i pattern consentono di migliorare la leggibilità e la comprensibilità del codice, nonché a migliorarne in molti casi la modularità. Tutte caratteristiche queste che migliorano la manutenibilità del codice stesso.
  9. I pattern sono svincolati dal linguaggio di programmazione. VERO! I pattern rappresentano concetti e soluzioni che possono essere implementati in diversi modi e diversi linguaggi. Lo stesso pattern può essere implementato in Java come in C#.
  10. I pattern sono legati alla programmazione OOP. FALSO! I pattern si applicano a qualsiasi paradigma di programmazione. Oggi giorno è molto facile trovare pattern (e relative implementazioni) legate all'OOP o all'AOP, ma solo perché tali paradigmi sono fra i più diffusi e moderni disponibili.

martedì 18 marzo 2008

The Aglets GUI

At the end of the last year I finished committing a set of changes to the Aglets GUI (to be precise the Tahiti GUI) so that it is now more reach, supports Swing (the original 2.0.2 was written in AWT). The followings are a few screenshots I took on my laptop.

The dialog for the login is now more funny!


The main window provides now more information to the user, like the number of active threads in the pool and the memory situation. The list of the agent now appears different depending on the state of the agent (running, active, suspended, ...).



Just a refactoring of the create dialog and of the security one....


Other windows and widget have been adapted similarly. Some of them have been enhanced, while others have been simplified removing no more necessary options (for instance the settings of the preferences dialog). I hope the new GUI will be merged in the trunk as soon as possible.

The Aglets MessageQueue

Each agent stores incoming messages into a message queue (com.ibm.aglets.MessageQueue); the message insertion into the message queue is transparently done by the delivering of a message.

The idea is that a message manager creates its own message queue and uses it to store message before it has activate a thread to process it. The use of a message queue allows the uncoupling of agent threads. In fact when an agent sends a message to another agent, the sending thread thru several method calls inserts the message in the message queue, and thus the sender thread returns. Thus the sender thread is involved until the message has been inserted in the message queue. Once this is done, it is the addressee thread in charge of processing the message itself, and this is done by the message manager that gets a thread and pops a message from the message queue.

The following is the implementation of the MessageQueue in the current development tree. As readers can see, the queue exploits a simple List to store messages that must be processed. All the methods implements the appropriate logic (e.g., priority) working around such list. Please note that the message queue is parametrizable thru Java Generics.

public class MessageQueue {


/**
* The messages will be stored into a linked list.
*/
private List messages = new LinkedList();



/**
* Appends a message, that is places the message at the end of the queue.
* @param msg the message to append
*/
public synchronized void append(MSG msg){
// check params
if( msg == null )
return;

// append the message at the tail
this.messages.add(msg);
}

/**
* Inserts a new message in the queue. The alghoritm is the following:
* each message is extracted from the queue and analyzed; at the first message with
* a priority less than the one we want to insert, the message is inserted. If no one
* message is found, than the message is placed at the tail of the queue.
* @param msg the message to insert
*/
public synchronized void insert(MSG msg){
// check params
if( msg == null )
return;


Iterator iter = this.messages.iterator();

while (iter != null && iter.hasNext()) {
MSG currentMessage = (MSG) iter.next();
if( currentMessage.getPriority() < msg.getPriority() ){
int index = this.messages.indexOf(currentMessage);
this.messages.add(index, msg);
return;
}
}
// if here the message must be placed at the tail of the queue
this.messages.add(msg);
}

/**
* Inserts the message at the top of the queue. Please note that this method will
* overtake the priority mechanism, thus it is possible to insert at the top a message
* with a low priority.
* @param top the message to place at the head of the list
*/
public synchronized void insertAtTop(MSG top){
if( top == null ) return;
// place the message at the head of the queue
this.messages.add(0, top);
}

public synchronized void insertAtTop(MessageQueue queue) {
if( queue == null || queue.isEmpty() )
return;
else{
this.messages.addAll(0, queue.messages);
}
}


public int size(){
return this.messages.size();
}

public boolean isEmpty(){
return this.messages.isEmpty();
}

/**
* Gets (but don't remove) the message at the top of the queue.
* @return the message at the top.
*/
public synchronized MSG peek(){
if( ! this.messages.isEmpty() )
return this.messages.get(0);
else
return null;
}


/**
* Extract (i.e., remove) the message at the top of the queue.
* @return the message at the top or null
*/
public synchronized MSG pop(){
if( this.messages.isEmpty() )
return null;
else
return this.messages.remove(0);
}



/**
* Removes a message from the queue.
* @param msg the message to remove
* @return true if the message has been removed, false otherwise
*/
public synchronized boolean remove(MSG msg){
if( this.messages.contains(msg) ){
this.messages.remove(msg);
return true;
}
else
return false;
}

/**
* Removes all the messages from the queue, that is after this the queue will be
* empty.
*
*/
public synchronized void removeAll(){
if( ! this.messages.isEmpty() )
this.messages.clear();
}


public String toString() {
StringBuffer buffer = new StringBuffer(50 * this.messages.size() );

buffer.append("The message queue contains " + this.messages.size() + " messages");
for(int i=0; i< this.messages.size(); i++){
buffer.append("\n");
for(int j=i; j>0; j--) // make a few indentation spaces
buffer.append(" ");

buffer.append("message " + i +")" + this.messages.get(i));
}

return buffer.toString();
}
}

The Aglets threading scheme

The Aglets platform is a multi-threaded platform where a thread can serve one agent depending on the actions it must perform during its life. The thread used by Aglets is implemented in the AgletThread class of the com.ibm.aglets.thread package.

Please note that each thread is activated depending on the delivery of one (or more) messages to an agent. Once a message is delivered, the addressee agent must process it and so a thread is woken up and used to handle the message thru the addressee agent.


Once an agent must receive/process a message, a thread is activated and used to process such message. In other words, the handleMessage() method of an aglet is run on top of one AgletThread.
Please note that the thread management involves the message manager and the message itself, since the steps are the following:
1) a message is delivered from a sender to the destination agent. The addressee agent stores the message into a message queue, hold from the message manager.
2) the message manager, once one or more thread have been stored in the queue, pops a thread and starts processing such threads one by one. In particular:
a) the message is passed to the thread;
b) the thread invokes the handle() method on the message itself
c) the message invokes the handleMessage() on the aglet, passing itself as argument
d) the thread exits the monitor, that is informs the message manager that the above message has been delivered. The message manager processes another message or leaves the thread. In the first case there is a chain that produces that until the message queue is empty the thread is hold to process messages. In the second case the thread is free, and the message manager waits until a new message comes.

In the 2.0.2 schema each agent has its own threadSpool, that is a stack of threads used to manage only messages related to the owner agent. Once the thread has delivered the message, it is pushed back into such stack (that is contained in the message manager).

In the 2.1.0 under development the schema is different: there is a thread pool that, globally, provides threads for the whole messaging system. Thus the message manager does not handle any more a private stack of threads but requires them to the pool. Once the thread has delivered the message and no more messages must be processed for this agent, the message manager pushes it back in the thread pool This allows a thread to be used for different agents at different times. Please note that this implies that a thread must know not only the message it is going to process, but also the message manager that oredered that, for coherence.
The thread has also two ways of locking depending on its state:
_ processing = it is processing a message, thus it cannot receive changes about the message itself or the message manager;
_ changing = it is changing either the message manager or the message to process and thus cannot process it.

Please note that the message manager will push and pop the thread again when it process a next message, this can bring to situations where the next message is processed by a different thread and, in general, wastes a little resources. Maybe this will be fixed in the future.

It is important to note that when a thread is woken up to handle a message, it is assigned to a specific MessageManager, that is an handler that owns messages for a specific agent (thru a message queue), as well as an agent reference. So when the thread re-start its execution, it knows exactly the message manager from which it can obtain the message and the agent. The message is already directly available to the agent, so that the thread can directly process the message. Processing the message means that the handle(..) method of the MessageImpl object is invoked, that will call consequently the handleMessage(..) method on the agent itself.

More in detail with regard to the MessageManager: the message manager is the decoupling point between a sender thread and a receiver one. In fact, when an agent sends a message to another agent, it comes up to the message manager of the addressee agent and enters the postMessage method. Such method is quite complex, but briefly stores the message into the addressee message queue and then notifies the message manager itself that are at least one new pending message. The addressee message manager then pops a thread from the thread pool and then processes the message at the top of the queue (and all the following ones) until the queue is empty. After that the message managers waits for other messages to come.

Once the thread has processed the message, it searches to process a new message (pushThreadAndExitMonitorIfOwner(..)). Please note that the MessageManager implementation (MessageManagerImpl) has the concept of owner: a message that has just been picked from the message queue or that has been just processed by a thread. So, if the thread was processing the message owner (i.e., the message for which the thread has been waked up) a new message to process is searched. If no new (or remaining) messages are present in the message queue, the thread is forced to suspend itself (and to return to the thread pool).
In the special case of re-entrant message (a message that issued a new message to process, as for instance in a request-response protocol) a new message is popped from the message queue and processed; in the case no remaining messages are available (this should not be the case of a re-entrant message) the thread suspends itself as above.
public void run() {
// if the loop of handing messages is already started return, so thus
// no more than one run call can be done.
if (loop_started) {
// to assure that aglet cannot call run on this thread.
return;
}

// set this thread as "started to handle messages"
loop_started = true;
start = false;

// get the reference of the agent behind the message manager
if( this.messageManager == null )
return; // the message manager is not valid!

try {
while (valid) {
try {

logger.debug("AgletThread is starting processing");
this.setReentrant(false); // if the process is here and is re-entrant now I'm processing
// a re-entrant message, thus after this I have to suspend myself.
this.setProcessing(true);
// get the right reference to the aglet behind the current
// message manager. This must be done each time in the cycle because
// the thread could be suspended or the message manager could be changed
// if the thread has passed thru the pool.
MessageManagerImpl manager = this.getMessageManager();
logger.debug("The message manager is " + manager + ", the message is " + message);
LocalAgletRef ref = manager.getAgletRef();
message.handle(ref); // handle the message
this.messageHandled++; // increment the number of messages handled by this thread

synchronized(this){
if( ! this.isReentrant() ){
message = null; // invalidate the message so to not repeat the handling
logger.debug("AgletThread has invalidate the message just processed (no reentrant find!)");
}
}

this.setProcessing(false);
logger.debug("AgletThread finished processing a message");

} catch (RuntimeException ex) {
logger.error("Exception caught while processing a message", ex);
valid = false;
throw ex;
} catch (Error ex) {
logger.error("Error caught while processing a message");
valid = false;
throw ex;
} catch (InvalidAgletException ex) {
logger.error("Exception caught while processing a message", ex);
valid = false;
start = true;
} finally {

// if the thread is valid, i.e., it has not been stopped
// then invoke special methods on the message manager to process
// another message (thus once the thread has been activated all messages are processed)
// or to process another message (if present) and to push back the thread in the pool.
if (valid && (! this.isReentrant())) {
// push the thread back into the pool...
logger.debug("The thread is going to be pushed back in the pool...");
messageManager.pushThreadAndExitMonitorIfOwner(this);
} else {
// process one more message...
messageManager.exitMonitorIfOwner();
}
}

// here the message has been processed, thus I can suspend myself
// waiting for a new message to process
synchronized (this) {

while (valid && this.message == null && (! this.isReentrant())) {
try {
logger.debug("Thread suspending waiting for a next message...");
this.wait();
} catch (InterruptedException ex) {
logger.error("Exception caught while waiting for an incoming message", ex);
}
}

}

}
}
finally {
message = null;
}
}
}


The AgletsTranslator: considerations about the adoption of a ResourceBundle

The Java library provides the java.util.ResourceBundle class that can be used to localize within a Java run-time. Localizing means that each kind of resource can be adapted to the execution environment, in order to present to the users a more comprehensive resource. The simplest example is that of strings embedded in a program, that can be translated into the native language of the final users.

Since mobile agents can run on very different hosts, it is important that they can provide to the final users a localized version of messages and other resources (e.g., icons, images). In this brief post I present my implementation of the org.aglets.util.AgletTranslator class, that can be used both from agents and the platform to translate resources (mainly string messages).

The AgletsTranslator is a wrapper around the ResourceBundle that provides simplified services for the localization. Let's start considering the class implementation base:

public class AgletsTranslator implements Cloneable {

/**
* The resource bundle used to handle locale content.
*/
private transient ResourceBundle bundle = null;
public String translate(String text){
// be sure there is something to translate and I have a bundle to
// ask for translation
if( text != null && text.length() > 0
&& this.bundle != null
&& this.bundle.containsKey(text)){
String translated = null;
translated = this.bundle.getString(text);
logger.translation(text, translated);
return translated;
}
else
// nothing to do, return the string passed as argument
return text;
}

...

}
As readers can see, the class wraps an instance of a ResourceBundle, and then provides the translate(String) method that translates the text. The latter method works as follows: if the resource bundle contains the key (i.e., an identifier of the text that must be translated), then the returned text is provided by the resource bundle (and thus is a translated text). If the bundle does not contain the key (i.e., cannot provide a translation for the specified text), then the untraslated text is provided. This means that, if the key passed as a string has not been included in the translation configuration, then this text will be returned unmodified. This allows, in any moment, an agent or a developer can try to translate a string without worring too much about its availability in the translation system. This also means that a developer can use always the translate(..) method without worrying about the translation dictionary, that can become available even later the end of the development. So, the adoption of the translate(..) method for issuing strings is a good practice.

The AgletsTranslator is constructed knowing a Locale (i.e., an object that represents the configuration of the hosting platform) and a basename. A basename represent a configuration point for a resource bundle. The basename can be either a class name (fully qualified name) that provides a translation by its own, or a file name (e.g., a property file) that is within the classpath. In the case of the AgletsTranslator, the platform uses a property file called tahiti.properties and placed within the lib directory. The file contains a dictionary with entries like the followings:

com.ibm.aglets.tahiti.LoginDialog.usernameLabel = Username:
com.ibm.aglets.tahiti.LoginDialog.passwordLabel = Password:
com.ibm.aglets.tahiti.LoginDialog.okButton = Log in
com.ibm.aglets.tahiti.LoginDialog.okButton.tooltip = Performs the log-in into Aglets
com.ibm.aglets.tahiti.LoginDialog.okButton.icon = img/ok.png


It is possible to see the key (e.g., com.ibm.aglets.tahiti.LoginDialog.usernameLabel) and the appropriate translation (e.g., Username:). Please note that a resource bundle can be used also for icons and images, not only for text, as already stated before.
The convention of using the fully qualified class name as part of the key is just a good practice that allows a quick individuation of where a key is used in the application itself.

Having obtained an AgletsTranslator its use is quite simple:

AgletsTranslator translator = ...;
String translated = translator.translate("com.ibm.aglets.tahiti.LoginDialog.usernameLabel");
Icon icon = new ImageIcon( translator.translate("com.ibm.aglets.tahiti.LoginDialog.okButton.icon") );




How can a translator be obtained? The AgletsTranslator class provides a factory method to obtain a new instance:

public static AgletsTranslator getInstance(String localeBaseName, Locale currentLocale) {
String key = localeBaseName + currentLocale.toString();

// search first in the cache
if( translators.containsKey(key) )
return translators.get(key);
else{
AgletsTranslator translator = new AgletsTranslator(localeBaseName, currentLocale);
translators.put(key, translator);
return translator;
}

}

private static HashMap translators = new HashMap();




As readers can see the factory method searches first for an already constructed translated for the specified basename and locale. The adoption of such caching technique provides the same translator for the same code base, keeping the creation of translators under control and a lower memory footprint. In case the translator has not been created, it is created and a reference to it is stored in the translator map for caching.

lunedì 17 marzo 2008

Analizzare le annotazioni a runtime

Una cosa non sempre chiara è che in Java, le annotazioni vengono implementate a run-time mediante delle classi particolari che implementano l'interfaccia di annotazione. Questo significa che, a run-time, non esiste una classe del tipo specifico dell'annotazione, bensì una classe (tipicamente un proxy) costruito ad hoc per funzionare come una annotazione, ma che di fatto non è un tipo annotazione.

Questo significa che a run-time non si può operare direttamente sulla Class di una Annotation, ma occorre utilizzare il metodo getAnnotationType() della Annotation stessa. Tale metodo ritorna l'oggetto Class della annotazione così come è stata definita nel codice, e ciò consente anche di accedere alle annotazioni applicate alle annotazioni stesse.

venerdì 14 marzo 2008

Gestione di sequenze alfanumeriche

Quando si lavora con applicativi interfacciati a database relazionali si ha spesso la necessità di far generare all'applicativo o al database stesso dei codici sequenziali. Molti RDBMS consentono di creare sequenze automatiche tramite delle sequence (tabelle speciali il cui valore cambia ad ogni lettura) o campi auto-increment. Il problema è che le sequenze così generate sono numeriche, mentre spesso sono necessarie sequenze alfanumeriche (ad esempio articolo001, articolo002, articolo003,...). Il problema non è sempre risolvivibile direttamente dal lato database, e quindi deve essere affrontato molto spesso dal lato applicativo.

In questo articolo viene descritta una possibile soluzione Java, capace di adattarsi a diversi database e contesti applicativi. Successivamente, viene illustrata una possibile soluzione lato database implementata su PostgreSQL.
Entrambe le soluzioni si appoggiano ad una sequenza lato database per la generazione della parte numerica del codice. Il codice qui illustrato è a scopo puramente didattico.

Soluzione Java

L'idea è quella di costruire una serie di classi che consentano di ottenere il prossimo valore alfanumerico di una sequenza. Per fare questo occorre anzitutto predisporre un tipo Sequence che possa essere usato per gestire i vari tipi di sequenza che, lato applicativo, si vogliono gestire (es. alfanumerici, numerici, ecc.):

public interface Sequence { }
Come si può notare l'interfaccia Sequence non definisce nessun metodo in particolare, lasciando alle sue concrete implementazioni la definizione dei tipi di dato da gestire. In altre parole, l'interfaccia Sequence è un semplice segnaposto (tag) per gestire una serie di tipi concreti. Una possibile estensione della Sequence è quella che consente di gestire le sequenze alfanumeriche:

public interface StringSequence extends Sequence {

/**
* Returns the next value of the sequence. This value could come from a database sequence (in such case
* this method will simply query the database) or can be calculated by the program.
* @return the sequence string.
*/
public String nextValue();


/**
* Sets up this sequence. Setting up a sequence means initialize it to run the nextValue() method.
* @param sequencePrefix a prefix to place in the beginning of the string
* @param sequenceName the name of the sequence in the database (if supported) or null if no sequence must
* be queried (or the database does not support it).
*/
public void setUp(String sequencePrefix, String sequenceName);
}

L'interfaccia StringSequence prevede due metodi fondamentali: setUp(..) che serve ad inizializzare il generatore di codice, e nextValue() che fornisce il prossimo valore dalla sequenza. In particolare, il metodo setUp(..) accetta come parametri un prefisso da appendere alle stringhe generate ad ogni chiamata a nextValue() e il nome della sequenza numerica (lato database) da usare.

Una possibile implementazione concreta del generatore di stringhe può essere la seguente:

public class PostgresqlSequence implements StringSequence, SerialSequence {

/**
* Each sequence generated by this genertor will have this prefix.
*/
protected String sequencePrefix = "articolo-";


/**
* The table to query to get the next sequence value.
*/
protected String sequenceTable = "";


/**
* Builds a sequence generator with the specified sequence table and the specified prefix.
* @param sequenceName the name of the sequence on the database to query
* @param sequencePrefix the prefix of the sequence generated
*/
protected PostgresqlSequence(String sequenceName, String sequencePrefix){
super();
this.setUp(sequencePrefix, sequenceName);
}


/**
* Sets up the sequence generator.
*/
public void setUp(String prefix, String sequenceName){
// avoid to store a null sequence name
if( prefix == null )
this.sequencePrefix = "";
else
this.sequencePrefix = this.sequencePrefix;

// store the sequence name
this.sequenceTable = sequenceName;
}


/**
* Returns the sequence key string as union of the string fixed in this class and the number that comes
* from the database sequence.
*/
public String nextValue() {
String ret = null;
Statement st = null;
ResultSet rs = null;

try{
/* connect to the database with your own code */
Connection connection = // database connection
st = connection.createStatement();
rs = st.executeQuery("SELECT nextval('" + this.sequenceTable + "')");
if( rs!= null && rs.next() ){
int seqVal = rs.getInt(1);
ret = this.sequencePrefix + seqVal;
}
else
ret = null;

// close database resources
rs.close();
st.close();

}catch(SQLException e){
// handle errors
return null;
}

return ret;

}
}


Come si può notare, il costruttore della classe richiama immediatamente il metodo setUp(..) fornendo come dati il nome della sequenza da interrogare e quello del prefisso da usare nella restituzione dei dati. Una volta inizializzata, l'istanza del generatore di codici provvede ad interrogare il database ad ogni chiamata di nextValue() e a restituire una stringa formata dalla composizione del valore restituito dalla sequenza del database e dal codice da usarsi come prefisso.

Un esempio di utilizzo del generatore di codici è il seguente:

StringSequence sequence = new PostgresqlSequence("test_pk_seq","articoli_");
String code = sequence.nextValue(); // produce qualche cosa del tipo articoli_763

Per rendere l'architettura sopra descritta maggiormente portabile, è possibile utilizzare una SequenceFactory che fornisca un riferimento ad un oggetto Sequence (in particolare StringSequence) in modo trasparente e a seconda della configurazione del sistema.


Soluzione PostgreSQL

L'idea è quella di creare una funzione che interroghi la sequenza opportuna e restituisca la concatenazione della stringa di prefisso con il valore della sequenza stessa.
Un primo modo può essere quello di creare una funzione specifica per ogni sequenza che debba essere interroga. Supponendo di avere una sequenza test_pk_seq è possibile utilizzare la seguente funzione plpgsql:



/**
* Esempio di uso:
* select next_test_alphanumeric_value('articolo-test');
* che ritorna un valore simile a
* articolo-test27
*/
CREATE OR REPLACE FUNCTION next_test_alphanumeric_value(prefix character varying)
RETURNS character varying
AS $BODY$
DECLARE
/* it will contain the next computed value */
next_key character varying;
BEGIN
raise debug 'Querying the test_pk_seq sequence';
SELECT prefix || nextval('test_pk_seq') INTO next_key;
return next_key;
END;


Come si può notare la funzione è molto semplice: viene accettato come parametro unico la stringa da usare come prefisso nella generazione del codice alfanumerico, e questo viene concatenato all'interrogazione della sequenza test_pk_seq. Questo metodo, seppur semplice, ha il forte svantaggio di non essere adattabile a sequenze differenti: occorrerà implementare una funzione specifica per ogni sequenza da interrogare.


Una soluzione migliore consente di specificare, oltre alla stringa di prefisso, anche il nome della sequenza da interrogare. In questo modo, con una sola funzione, è possibile ottenere i valori da più sequenze. L'idea è simile a quella vista in precedenza: si concatena la stringa di prefisso al valore ottenuto dalla sequenza. Essendo però questa volta la sequenza non nota a priori, è necessario costruire la query SQL dinamicamente:

/**
* Esempio di uso:
* select next_alphanumeric_value('articolo-','test_pk_seq');
* che ritorna un risultato simile a
* articolo-34
*/
CREATE OR REPLACE FUNCTION next_alphanumeric_value(prefix character varying, sequence_name character varying)
RETURNS character varying AS
$BODY$

DECLARE
/* it will contain the next computed value */
next_key character varying;

/* the query string dynamically built */
query_string character varying;

BEGIN
raise debug 'Sequence used %', sequence_name;
query_string := 'SELECT ' || quote_literal(prefix) || '|| nextval(' || quote_literal(sequence_name) || ');';
raise debug 'Dynamic query %', query_string;
execute query_string into next_key;
return next_key;
END;

$BODY$
LANGUAGE plpgsql VOLATILE;
ALTER FUNCTION next_alphanumeric_value(character varying, character varying) OWNER TO luca;
Come si può notare, viene anzitutto costruita una stringa che rappresenta la query da eseguire, ossia uno statement SELECT con la concatenazione della stringa di prefisso e del nextval della sequenza. Questa query viene poi fatta eseguire tramite il comando execute e il risultato viene memorizzato nella variabile ritornata dalla funzione. Si noti che, siccome la funzione nextval(..) si aspetta un argomento stringa, il nome della sequenza passato come parametro deve essere racchiuso da apici, e quindi si utilizza la funzione quote_literal(..) per ottenere la relativa stringa SQL.

JTable e ResultSet: visualizzazione dei risultati di una query in una tabella

Nella scrittura di un programma Java capita molto spesso di dover visualizzare i risultati di una query in una tabella. Questo breve articolo illustra un modo semplice e rapido per visualizzare i dati estratti da un database SQL in un oggetto JTable, dando la possibilità di aggiornare tali dati qualora la query cambi nel tempo.

L'idea fondamentale è quella di creare un modello di tabella, che analizzerà i risultati della query SQL (sotto forma di ResultSet) memorizzandoli internamente in una struttura di tipo Vector. Terminato ciò il modello disporrà di una immagine in memoria dei dati estratti dal database, che quindi potranno essere visualizzati dalla relativa JTable.

Di seguito il codice:

public class GenericTableModel extends DefaultTableModel implements TableModelListener{

public GenericTableModel(){
super();
this.addTableModelListener(this);
}

public GenericTableModel(ResultSet rs){
this();
// parse the result set if possible
if( rs != null )
this.parseResultSet(rs);
}


public synchronized final Object[] getRow(int rowIndex){
if( rowIndex < 0) return null;
else{
Object ret[] = new Object[ this.getColumnCount() ];
for(int i=0; i< ret.length; i++){
ret[i] = ((Vector)this.dataVector.get(rowIndex)).get(i);
}

return ret;
}

}


public synchronized void parseResultSet(ResultSet rs){
if( rs == null ){
// empty the vectors!
this.dataVector = new Vector();
this.columnIdentifiers = new Vector();
return;
}

try{

// table headers and other information
ResultSetMetaData metaData = rs.getMetaData();
int columns = metaData.getColumnCount();

Vector tmpHeaders = new Vector(columns);
//this.headers = new Vector();
for(int i=1; i<=columns; i++)
tmpHeaders.add( new String(metaData.getColumnLabel(i)));

// extract the data as String and put it into the vector
Vector tmpData = new Vector(20,5);
Vector row = null;

while( rs.next() ){
row = new Vector();

for(int i=1; i<=columns; i++)
row.add( rs.getObject(i) );

tmpData.add(row);
}
}

// notify changes to the table
this.setDataVector(tmpData, tmpHeaders);


}catch(SQLException e){
// .....
}

}



public void tableChanged(TableModelEvent e) {
int row = e.getFirstRow();
int column = e.getColumn();

// check to have a good selection, thus to avoid exception while changing the table
if( row < 0 || column < 0 )
return;

TableModel model = (TableModel)e.getSource();

String columnName = model.getColumnName(column);

Object data = model.getValueAt(row, column);

// replace the value of the data at such row and column
((Vector)this.dataVector.get(row)).set(column, data);
}

}

Come si intuisce il metodo fondamentale è parseResultSet(..), che accetta un ResultSet in ingresso e lo analizza, estrando i dati e riempendo due Vector, uno per le intestazioni della tabella e uno per le righe effettive. Una volta terminato il riempimento dei Vector, è necessario informare il modello (DefaultTableModel) di utilizzare i dati in essi contenuti per la visualizzazione; questo è ottenuto con la chiamata al metodo setDataVector(..).
Si noti che il modello consente anche di gestire le modifiche apportate ai dati attraverso la tabella stessa: il metodo tableChanged(..) dell'interfaccia implementata TableModelListener consente di catturare la riga e la colonna (cella) modificata e il suo nuovo valore. Da tenere presente che il vettore dataVector è un Vector protetto nella superclasse DefaultTableModel, e quindi accessibile. Attenzione: la modifica al modello, come si può notare, non implica automaticamente la scrittura dei nuovi dati nel database! E' necessario implementare una qualche logica di gestione della serializzazione dei dati verso il database (ad esempio usando dei ResultSet aggiornabili).

L'utilizzo del modello sopra illustrato nei propri programmi risulta piuttosto semplice:

GenericTableModel model = new GenericTableModel();
JTable table = new JTable( model );
...
model.parseResultSet( rs );

martedì 11 marzo 2008

Leopard e Netatalk: problema di permessi

Ho perso diverso tempo cercando di capire come mai un file/cartella creato da un Apple OS X Leopard su una share Netatalk (2.0.3) venisse creato con i permessi

rwx--S---

indipendentemente dalla umask impostata per quell'utente.
Non ho trovato nessuna opzione di configurazione di Leopard per la gestione dei permessi usati nella creazione di un file su una share afp, ma sono riuscito ad arginare il problema utilizzando l'opzione upriv di Netatalk.
Questa opzione forza la creazione dei file con gli stessi permessi Unix del server sul quale Netatalk è ospitato, e quindi con l'umask relativa all'utente con il quale il Mac OS X si è autenticato.
E' sufficiente impostare quindi l'opzione per ogni share nel modo seguente:

/mnt/data/share "MyShare" options:nostat allow:myUser rwlist:myUser options:upriv

Va precisato che, qualora si fossero usati permessi di tipo g+s sulle cartelle al fine di forzare uno specifico gruppo condiviso, è necessario rimuovere tale permesso e aggiustare i gruppi opportunamente.

lunedì 10 marzo 2008

R4R @ CTS 2008

I'm happy to announce that a paper I wrote has been accepted at the The 2008 International Symposium on Collaborative Technologies and Systems (CTS 2008).
The paper titled Binding Agent Roles to Environments: the R4R approach describes an approach based on Aspect Oriented Programming that focuses on the role deployment. The idea is that, at the time of the role deployment to a specific host/platform, roles must be integrated in the environment rules and policies. This means, for example, that some role actions must be executed accordingly to the above rules.

Such rules are specified thru a set of annotations that express which actions must be performed before and/or after a role action. In this way, the host administrator can define a set of rules that will be transparently applied to the roles and to the agents that are going to execute them.

The integrative actions executed before and/or after a role action are called preconditions and postconditions respectively. To ease the adoption of pre and post conditions, R4R exploits the concept of resource, an entity tied to the environment/context where the role is exploited, that provides a set of actions used as integration points. Only the resource actions (that are a subset of the role one) can be extended with the pre and post conditions, while normal role actions cannot. This choice allows role developers to be sure that there actions that will never be re-adapted by an environmental rule, while others could be integrated and adapted with such rules. Please note that, thanks to R4R, it is possible to implement reactions to role actions, something similar to reactive tuple spaces.

I suggest everyone interested in this approach to read the paper in the CTS 2008 prooceedings.

martedì 4 marzo 2008

atoi o strtol?

Nel corso di Sistemi Operativi dell'Università di Modena e Reggio Emilia gli studenti devono spesso realizzare programmi C (per Unix) che accettino argomenti da riga di comando. Spesso tali argomenti devono essere convertiti in numeri, e per questo agli studenti viene illustrato il funzionamento della funzione atoi(3). Il problema è che la funzione atoi(3), come da pagina di manuale non rileva errori di conversione.
Sarebbe molto più corretto spingere gli studenti ad usare l'alternativa e più sicura strtol(3), che se non altro rileva gli errori.


Quindi, in un programma C, supponendo di voler convertire il secondo parametro passato sulla linea di comando, si avrebbe che:

val = atoi( argv[2] );

deve essere sostituito da:
errno = 0;
val = strtol( argv[2], (char**) NULL, 10 );
if( errno > 0 && val == 0 )
perror("\nErrore di conversione numerica\n");



Certo, il blocco di codice riportato qui sopra risulta di ben lunga più lungo che l'uso della semplice atoi(3), ma consente anche un controllo migliore e più raffinato. Inoltre abitua gli studenti a lavorare con gli strumenti corretti da subito.