venerdì 29 agosto 2008

Gwt-Log

Una delle cose più noiose nel logging con applicazioni GWT è che, lato client, non si riesce ad usare nativamente la console di loggin, ne quella di GWT in Hosted mode, nè quella del server. Il che diventa noioso, poichè spesso si ha la necessita di avere un minimo di tracciabilità delle attività svolte dal codice, e si vuole subito avere un feddback con il loggin, ma con gwt, almeno lato client, sono sempre stato obbligare ad usare i fastidiosi "windows alert".
Certo, da una parte hanno la loro utilità, come quella di bloccare, di fatto, l'esecuzione dell'applicazione e questo può avere un suo aspetto positivo... ma sono troppo invadenti... e soprattutto se ce si li dimentica nel codice, passando poi il prodotto al cliente, si posson avere situazioni imbarazzanti.

Ho trovato, in questi giorni, una libreria davvero interessante per il loggin con GWT


Che gestisce in maniera del tutto trasparente il loggin su più livelli... infatti, lasciando fare tutto a lui (ci sono comunque dei setting da poter usare per disabilitare/abilitare i vari tipi di logger), si può avere il log lato server (anche gli errori lato client possono essere spediti in amniera del tutto trasparente al server con rpc), con la normale console errore del server (errori lato server, come con il normale System.out o System.err), sia nella console dell'Hosted Browser di GWT (come con il classico GWT.log)(client e server), sia un log da leggere con al console di FireBug (un ottima estensione di Fiorefox), sia con un interessante "finestrella div" che compare all'interno delal applicazione stessa, un div semitrasparente draggabile...
Insomma, si può fare veramente di tutto e con in pratica nessuna riga di codice particolare... si importa la libreria nel progetto, si inseriscono alcune righe nel file gwt.xml... e basta usare Log(stringa) e via che si logga dappertutto.
Stupendo.


Aggiornamento: Strani Errori
Ieri sembrava funzionare tutto perfettamente, mentre oggi, aggiornando il progetto a GWT 1.5, cambiando quindi librerie eccetera, ho forse commesso qualche errore... e non si compilava più il modulo gwt... continuava a lamentarsi che non trovava la libreria di gwt-log, in particolare che non trovava il file di confiugurazione gwt.xml del progetto gwt-log
L'errore era:
[echo] Module: com.fdlservizi.sse.HelpDeskGWT
[java] Loading module 'com.fdlservizi.sse.HelpDeskGWT'
[java] Loading inherited module 'com.allen_sauer.gwt.log.gwt-log'
[java] [ERROR] Unable to find 'com/allen_sauer/gwt/log/gwt-log.gwt.xml' on your classpath; could be a typo, or maybe you forgot to include a classpath entry for source?
[java] [ERROR] Line 7: Unexpected exception while processing element 'inherits'
[java] com.google.gwt.core.ext.UnableToCompleteException: (see previous log entries)
[java] at com.google.gwt.dev.cfg.ModuleDefLoader.nestedLoad(ModuleDefLoader.java:225)
[java] at com.google.gwt.dev.cfg.ModuleDefSchema$BodySchema.__inherits_begin(ModuleDefSchema.java:194)
[...]

Non so per quale motivo, ma ignorava totalmente la presenza della libreria, che invece avevo normalmente selezionato tar le librerie esterne in IntelliJ Idea.
Poi, guardando bene il forum di grails (in particolare questa discussione), non ho capito bene perchè, ho scoperto che dovevo inserire tale libreria nella directory /lib/gwt.
Cosa mai fatta, anche perchè eventualmente le librerie le avrei messe in /lib, dentro al quale la directory gwt non esisteva nemmeno.
Ora invece, con l file messo lì dentro, funziona senza problemi.... misteri di grails.

venerdì 22 agosto 2008

Alcuni problemi con gli id composti e i valori di default con Hibernate

Stamattina, dopo aver passato due giorni a studiarmi il "paradigma di programmazione" per le GUI usato in sap (potrebbe tornarci utili, è uno interessante punto di riferimento a cui potremmo ispirarci), sono tornato a fare qualche esperimento con Hibernate.
In particolare ho continuato a giocare con le select e gli insert con le nostre classi POJO create per ORM.

Ho generato una pagina gsp di esperimento, con alcuni tasti gwt che usano una rpc, che "stimola" sue servizi lato server, uno per la ricerca delle classi (sempre "chiamata") e una per salvarla.
Nessun problema nella ricerca di chiamata, sia con il findall, il list, il find, i criteria, eccetera...
Bhe, in realtà ci sono dei problemi quando nel database ci sono dei null di troppo... ma è un problema correlato a quello che descvrivo qui in seguito.

Infatti nel momento dell'inserimento abbiamo trovati i primi problemi. Essendo il nostro database costruito con delle doppie chiavi primarie (ogni tabella ha un id, nemmeno numerico autoincrementale, come sarebbe meglio fosse, ma come stringa, correlato sempre ad un altro campo presente in ogni tabella "az" che fa riferimento ad una tabella, az), ogni volta che noi abbiamo una chiave esterna, questa chiave esterna è anch'essa costruita con due campi... e anche al tabella a cui facciamo riferimento ha un id primario composto. Questo comporta che nel ricavare i dati di uno di queste colonne, devo fare un doppio passaggio. Nella colonna ricavo l'id delal classe associata, nella quale ricavo poi l'id, che è quello che viee di fatto immagazzinato nella colonna stessa.
Ma all'atto dell'inserimento questo doppio passaggio non funziona. In pratica non riesco a memorizzare i campi se sono chiavi secondarie esterne... e questo ovviamente è un grosso problema. Il fatto è che non tutto il database è costruito in maniera perfetta, per esempio la tabella chiamata non ha riferimenti di chiave secondarie che puntano ad azienda, altre colonne sì... quindi il toll per il reverse enginering è andato un po' in pappa e non riesce a gestire in maniera completa queste doppie chiavi.
Il tutto dovrebbe risolversi attuando una modifica al database che già volevamo attuare, cioè eliminare totalmente tutte le chiavi primarie composte con quel campo az, che di fatto non serve a nulla. Era stato introdotto tempo addietro quando il programma doveva essere multiazienda, cosa che è stata in seguito abbandonata.
Non toglieremo nella tabelle il campo az (presente in tutte) e nemmeno la tabella az (per retrocompatibilità, alcune query presenti qua e là nel programma fanno riferimento a questa colonna), ma modificheremo tutte le chiavi primarie, facendole diventare uniche e non più composte (composte cioè dal solo campo id) e elimineremo ogni chiave secondaria esterna, in ogni tabella, che faccia riferimento ad az.

Stamattina mi sono accorto però di un nuovo problema... la generazione automatica delle classi di dominio POJO, usate da Hibernate, non ha tenuto conto dei valori di default impostati nel database... essendo in sql server sono impostate con delle costraint di questo tipo
ALTER TABLE [dbo].[chiamata] ADD DEFAULT ('N') FOR [libretto]
in questo caso la colonna libretto della tabella chiamata è impostata con un valore di default = N (tra l'alro la colonna ha come proprietà il poter accettare valori null).
In hibernate di fatto non esistono modi per specificare nel file di mapping delle classi i valori di default... esisterebbe ma non è portabile sui vari databasi, prchè bisognerebbe specificare il formato di colonna, diverso per ogni tipologia di database, qundi da evitare.
Di fatto, in una situazione del genere, se si vuole memorizzare nel database una classe di dominio per la quale non si specificano valori per un determinato fields, se questo accetta nel mapping valori null, hibernate lo inizializza automaticamente con null... infatti hibernate, quando deve memorizzare una classe sul database, nella query sql insert, specifica tutti i campi della classe stessa che nella proprietà del mapping abbiano insert=true
Ovviamente, se questa proprietà non viene specificata, hibernate automaticamente le popola con il valore null se la proprietà stessa l'accetta, o con al string avuota nel caso sia specificata not null
Questo significa che di fatto vengono ignorati i valori di default specificati nel database, perchè di fatto al database arriva una query con quei campi specificati... quindi inserisce quelli e non i valori che lui ha come default.
Se per esempio voglio forzare il database ad inserire i valori di dafault, dovrei fare in modo che hibernate non gli passi proprio quel campo... cosa possibile da fare, basta specificare insert=false



facendo così hibernate ignora il campo libretto, e il database, non ricevendolo nella query insert, lo inserisce con il valore di default
Così facendo però non è possibile inserire alcun valore... quindi se io volessi, all'atto della insert, specificare un valore per quella propietà, di fatto questa viene ignorata.

Nel nostro caso, in effetti, c'è un modo molto semplice per risolvere il problema... basta mettere nel costruttore della classe di dominio il valore di default, o nel mio caso, dato che per le classi di dominio che vengono utilizzate anche lato client gwt, devo costruire una determinata classe che viene poi serializzata ed usata nel passaggio dati nella rpc, dichiaro quel dato field con il valore di default, così facendo, anche se lato client non viene specificato alcun valore, di fatto hibernate lo riceve con il valore preimpostato e lo passa al database.

public class DatiChiamata implements IsSerializable {
public String id;
public String apertaDa;
public String tipoChiamata;
public String gradoDisservizio;
[...]
public String noteAssegnazione;
public String libretto = "N";
[...]
}

giovedì 7 agosto 2008

JAVA DAO, ORM, POJO

Abbiamo deciso di approfondire lo studio di Hibernate, effettivamente potrebbe esserci molto di aiuto, sia per la normale interrogazione di database (sia in selezione che in salvataggio) sia perché potrebbe evitarci i problemi di compatibilità con i due database che vogliamo supportare: Oracle e SQL Server.

Abbiamo deciso di non affidarci totalmente a GORM (Grails Object Relational Mapping) perchè groovy si rileva meno performante in alcune condizioni, e abbiamo paura che sia limitante nel futuro.

Quindi la scelta ricade sulla creazione di un ORM tramite JAVA DAO (Java Data Access Object) create con delle classi java POJO (Plain Old Java OBject).
Questo significa mappare tutto il nostro database con delle semplici classi java che devvano rispecchiare determinate carrateristiche.
In particolare Hibernate non ha richieste eccessive, ma secondo la logica del convention over configuration, è sempre meglio cercare di rispettare più richieste possibili, tra le quali:

  • Ogni classe dovrebbe avere un suo identificare univoco, chiamato Id (e qui per noi è un problema... perchè in pratica ogni tabella del nostro database ha un id composto da due campi stringa... decisamente lontano dagli standard che prevedono un id numerico autoincrementale... lo so lo so)
  • Ogni classe dovrebbe prevedere un costruttore a zero argomenti, che sarà utilizzato da Hibernate per istanziare la classe
  • Tutti i campi della tabella dovrebbero essere trasformari in campi della classe. Quindi ogni classe dovrebbe avere una serie di campi da considerare persistenti (quindi direttamente mappabilis ul database), preferibilmente da settare come privati, e quindi definire i getter e i setter per ognuno di questi campi. In particolare ogni campo dopvra rispettare le classiche convenzioni ORM, quindi nel caso di un nome campo (o tabella) composto d apiù nomi uniti con l'underscore, si dovrà converire con nome univoco con le lettere maiuscole per ogni parola che lo compone... esempio il campo data_di_nascita sarà convertito in dataDiNascita. Inoltre per convenzione Hibernate si aspetta di trovare i getter e i setter così composti getDataDiNascita e setDataDiNascita.
  • Le classi non dovrebbero essere dichiarate final, o in tal caso caso, dovrebbero implementare un interfaccia che dichiara tutti i metodi pubblici.
  • Nel caso di id composti conviene dichiarare l'id della classe come istanza di un'altra classe, convenzionamente chiamata nomeclasseId, nella quale vengono specificati i singoli campi delal chiave primaria composta.

Ci siamo affidati, per la stesura di queste classid i dominio, ad Hibernate Tool, un plugin per Eclipse appositamente studiato dallo staff di hibernate.
Seguendo i consigli di questa pagina, abbiamo proceduto con questi passi
  • dopo aver installato il plugin per eclipse (semplicemente scompattato lo zip preso dalla pagina di cui supra), ho creato un progetto grails, vuoto, con la classica procedura (nuova directory, grails create-app)
  • Ho messo nella cartella lib del progetto il jar con il driver jdbs del database sqlserver (jtds-1.2.2.jar preso su sourceforge). Avviato eclipse ho importato il progetto appena creato.
  • tasto destro sul progetto, ho creato un nuovo file di configurazione per hibernate: File -> New -> Other -> Hibernate -> Hibernate Configuration File.
    Il file di configurazione l'ho salvato nella directory grails-app/config/hibernate
    Come dialect ho scelto SQL Server, che poi sarà salvato nel file come org.hibernate.dialect.SQLServerDialect
    Come classe del driver ho scelto net.sourceforge.jtds.jdbc.Driver
    Come connection url jdbc:jtds:sqlserver://localhost/Sacci_Gabry;instance=SQLEXPRESS (ho instalato il database in locale)
    Il resto ho lasciato in bianco
  • ancora tasto destro e quindi ho settato al configuarazione della console di Hibernate (File -> New -> Other -> Hibernate -> Hibernate Console Configuration)
    Ho lasciato tutto come proposto, inserendo però nel classpath la lib inserita prima nella directory lib del progetto (il driver jdbc)
  • ancora tasto destro e quindi ho creato un file per il reverse enginering (di fatto quello che poi creerà i file) [File -> New -> Other -> Hibernate -> Hibernate Reverse Engineering File(reveng.xml) ]
    Il file l'ho inserito come prima in grails-app/config/hibernate
    dopo aver selezionato la configurazione e aspettato che il database venisse letto, ho selezionato solo le tabelle del dbo
  • Fatto questo ho fatto partire la compilazione (Lunch -> Hibernate Code Generation -> Open Hibernate Code Generation Dialog)
    Come outpout folder ho scelto src\java, e come nome di package gorm
    Come exporter ho selezionato le domain code, hibernate xml mappings, hibernate xml configuration, e lo Schema Documentation (decisamente opzionale, ma carino, genera della documentazione html interessante e facile da consultae)
    La generazione del codice impiega un po' ad eseguirsi.
  • A questo punto bisogna spostare qualche file per seguire le convenzioni hibernate/grails
    Il file hibernate.cfg.xml appena generato, diverso dal precedente in quanto conteiene tutti i riferimenti ai file di maping, va spostato nella directori config/hibernate, sovrascrivendo quindi il precedente
    Tutti i file hbl.xml vanno spostati nella stessa directorty grails-app/config/hibernate
A questo punto tutto è pronto per lavorare... si possono visualizzare e quindi utilizzare le view di eclipse predisposte per hibernate, consultare il database, la session factory, fare query HQL ecc..