N° 62 - Aprile 2002

 prjmgr

Fase di Testing Il Processo di Rollout Il Processo di Release Conclusioni
In questa fase si considera terminato il momento di sviluppo propriamente detto, in cui il sistema viene costruito nella sua interezza, e si inizia la fase di testing allo scopo di individuare errori nel sistema. L'applicazione di tecniche di Unit Testing non garantiscono, infatti, la funzionalita' del sistema nella sua totalita' ed in generale possiamo individuare due tipi di errori nella fase di testing: Errori di runtime: questi sono considerati errori di programmazione, risultato di una fase di Unit Testing incompleta. Errori Logici: Il sistema si comporta diversamente rispetto alla funzionalita' descritta nel documento Proof Of Concept. Deve essere chiaro fin dall'inizio che la fase di testing e' parte integrante dello sviluppo del software ed e' del tutto naturale aspettarsi degli errori durante il testing. Come abbiamo detto, il testing ha lo scopo di identificare tutti gli errori (di runtime e logici) e garantire che la funzionalita' del sistema consegnato sia privo di malfunzionamenti e rispecchi la documentazione accettata durante la fase di Discovery. Nel testing viene utilizzata una nuova figura professionale quella del QA tester (Quality Assurance Tester). Per piccoli progetti puo' bastare una sola persona, ma per sistemi complessi puo' essere necessario creare un vero e proprio QA Group con tanto di QA Manager. Al gruppo QA spetta il compito di scegliere e rendere operativi tutti gli strumenti necessari per la loro attivita', questi strumenti possono essere identificati nella lista seguente: Scripting Engines per il testing automatico dell'applicazione (ad esempio Rational Robot per applicazioni web) Scripting Engines per il load testing dell'applicazione (es. Load Runner) Defect Reporting and Release System utilizzato dal gruppo QA per gestire e documentare gli errori trovati. Il gruppo QA deve avere a disposizione un ambiente di testing che deve essere l'esatta copia di quello di produzione sia dal punto di vista hardware che software (tranne che per le unita' ridondanti come doppi web servers etc). Questo assicura che l'applicazione testata si comportera' allo stesso modo una volta installata nell'ambiente di produzione, evitando cosi spiacevoli sorprese. Il gruppo di development lavorera' in stretto contatto con il gruppo QA ed il project leader dovra' essere disponibile a chiarificare concetti e funzionalita' al gruppo QA allo scopo di rendere il piu' efficiente possibile la fase di testing. L'intero processo puo' essere descritto nella figura seguente: Figura 1 - Processo QA (clicca per ingrandire) Come momento iniziale, l'applicazione verra' installata nell'ambiente di testing. Chi effettua l'istallazione vera e propria dipende dagli standards aziendali: per piccole aziende e' lo stesso gruppo di sviluppo, mentre in aziende molto grandi e strutturate esistera' un gruppo apposito. La terminologia cambia se si sta installando l'applicazione nell'ambiente di testing o di produzione. In particolare l'installazione nell'ambiente di testing verra' chiamata "Rollout", mentre nel caso dell'ambiente di produzione verra' chiamata "Release". Le procedure possono essere diverse nei due casi, e vedremo questi dettagli successivamente. Una volta che l'applicazione viene installata nell'ambiente di testing, il gruppo QA e' pronto ad iniziare la sua attivita'. Deve essere chiaro che il lavoro del gruppo di QA si basa sul documento Proof Of Concept in cui sono descritti chiaramente tutti i requisiti, la funzionalita' del sistema e le schermate utilizzate a descrivere questa funzionalita'. Se questo documento non e' stato opportunatamente preparato, aspettatevi di dover affrontare una lunga serie di problemi durante il testing di sistema. Se non si ha una documentazione di base, il QA Tester tendera' ad interpretare la funzionalita' del sistema e giudicarla secondo criteri personali e non sulla base delle specifiche. Questo si traduce nel dover trattare come difetti intere porzioni di funzionalita', semplicemente per il fatto che il tester non ha alcuna conoscenza del sistema sviluppato. Possiamo descrivere il processo di testing nel modo seguente: Il gruppo QA testa la funzionalita' dell'applicazione utilizzando la documentazione messa loro a disposizione (solitamente il POC) Quando viene individuato un errore (runtime o logico) questo viene inserito nel Defect Reporting System con le seguenti informationi: Il tester che ha trovato l'errore Il tipo di errore (di runtime o logico) La priorita' nel trovare una soluzione (Urgente, Normale, ecc.) Una chiara descrizione dell'errore Una chiara descrizione di come riprodurre l'errore La versione dell'applicazione che si sta testando Il project leader viene notificato (attraverso email) dell'errore inserito nel sistema. Il project leader assegna il difetto al programmatore che ritiene piu' idoneo nel risolvere il problema Il programmatore risolve il problema nell'ambiente di sviluppo (development environment) ed assegna al difetto lo stato di "Risolto". Il programmatore etichetta il nuovo codice per essere incluso nel rollout successivo. Il gruppo QA viene notificato che il difetto e' stato risolto. Ci sono in commercio degli ottimi applicativi di "Defect Reporting", molti dei quali basati anche su interfaccia web: ClearQuest, Aardvark, ecc. Quando tutti i difetti riportati, per quella particolare versione, sono stati "risolti" dal gruppo di sviluppo siete pronti per il "rollout" della nuova versione dell'applicazione. Introducete una chiara nomenclatura per etichettare versioni e rollout, solitamente viene utilizzato il seguente formato: Versione.Release.Rollout ad esempio 1.3.5 che rappresenta la prima versione del prodotto. Il prodotto e' alla sua terza release e questa e' stata sottoposta a cinque rollout. Come abbiamo definito precedentemente, il processo di rollout prevede l'istallazione dell'applicazione dall'ambiente di sviluppo all'ambiente di testing. Durante la fase di testing ci saranno diversi rollouts. La fase di rollout prevede una serie di passi che possono essere descritti nella seguente lista: Assicuratevi che tutti gli sviluppatori abbiano aggiornato il Source Control System con l'ultima versione del codice modificato. Etichettate il codice prelevato con la corrente versione di rollout. Questo e' molto importante nella fase di release perche' vi permettera' di prelevare la corretta versione del codice testato. L'intera applicazione viene ricompilata Assicuratevi che nessuna persona stia utilizzando il testing environment. Ogni rollout deve essere preceduto da una notifica a tutti i membri del gruppo QA sulla data e ora di rollout. Dipende dal tipo di applicazione sviluppata: fermate ogni Application Server attivo nel testing environment. Se state sviluppando un'applicazione web fermate anche il web server. Avvertite il DBA che puo' procedere ad eventuali modifiche del database Installate la nuova applicazione nell'ambiente di testing e, se si tratta di una applicazione Web, aggiornate i contenuti nel web server ed i corrispondenti file di configurazione. Riavviate l'application server ed eventualmente il web server. Fate un piccolo test per assicurarvi che il sistema sia stato aggiornato correttamente. Notificate il gruppo QA che il sistema e' pronto per essere testato. Se la nuova fase di testing prevede una nuova funzionalita' non documentata nel POC, assicuratevi di fornire al gruppo QA la nuova documentazione. Il processo sopra descritto verra' reiterato fino ad avere una versione dell'applicazione abbastanza "robusta" per poter essere installata nell'ambiente di produzione. Come definito precedentemente, l'istallazione dell'applicazione nell'ambiente di produzione viene chiamata "Release", ma prima della release l'applicazione deve essere "certificata" dal cliente attraverso un apposito "Acceptance Test". L'Acceptance Test deve essere eseguito insieme al cliente utilizzando la funzionalita' descritta dal documento POC e da qualsiasi altro addendum ufficiale. Questo test e' molto semplice e verra' eseguito da un rappresentante del gruppo QA alla presenza del cliente e del project manager. Solitamente e' anche presente il project leader che dara' spiegazioni in caso di dubbi o domande tecniche. Alla fine del test il cliente certifica l'applicazione e questa e' pronta ad essere installata nell'ambiente di produzione. L'Acceptance Test e' necessario soltanto per nuove applicazione e normalmente non viene richiesto per upgrades o release successive. La versione dell'applicazione che deve essere installata nell'ambiente di produzione e' quella presente nell'ambiente di testing. E' infatti questa (e soltanto questa) versione che e' stata testata dal gruppo QA. Mi e' capitato che durante la fase di release la persona che si occupava dell'installazione considerava come versione di release quella contenuta nel Source Control System causando non pochi problemi. Solitamente il Source Control System (come PVCS o ClearCase) permette di etichettare la versione del codice presente nei vari ambienti e questa e' un'importante caratteristica. In ogni caso, il processo di release e' piu' complicato per il fatto che coinvolge l'ambiente piu' importante: quello di produzione. Gli approcci utilizzati possono essere diversi, ma in questo articolo descrivero' le fasi che dovrebbero essere tenute da conto per una release di successo. Prima di tutto voglio sottolineare il fatto che l'ambiente di produzione deve essere un ambiente chiuso alla struttura aziendale ed accedibile solamente a personale autorizzato. La fase di release deve quindi essere autorizzata da tale personale e questo avviene solitamente attraverso una richiesta formale da parte del project management. Questa richiesta deve contenere le seguenti informazioni: L'applicazione da installare I sistemi influenzati da questa applicazione Modifica della configurazione dei sistemi su cui l'applicazione deve essere installata. Configurazione di apparati particolari come Servers, Routers, Firewall ecc. La data di release Il nominativo di almeno una persona che assistera' il personale specializzato all'installazione Il nome della persona che effettuera' l'installazione Istruzioni dettagliate della procedura di installazione Istruzioni dettagliate da seguire nel caso la procedura d'installazione fallisca (Istruzioni di rollback) La richiesta verra' considerata dal personale responsabile dell'ambiente di produzione ed accettata oppure rifiutata. Le ragioni sul perche' una richiesta viene rifiutata dipendono dalla struttura e dalle regolamentazioni interne dell'azienda e non verranno considerate al fine di questa trattazione. La tipologia di release dipendera' molto dalla piattaforma utilizzata: Piattaforma Microsoft: e' possibile utilizzare procedure automatiche di installazione come InstallShield. In questo caso al gruppo di installazione verra' fornito semplicemente il file di installazione automatica. Questa procedura deve essere accompagnata da una chiara documentazione sul procedimento di installazione e sull'eventuale configurazione successiva (se questa non puo' essere automatizzata). Piattaforma Unix: I file da installare vengono consegnati come un insieme di tar files insieme ad una chiara documentazione indicante dove questi file devono essere copiati e come devono essere espansi. In entrambi i casi la documentazione che descrive il processo di installazione deve indicare tutti i passi necessari alla configurazione dell'applicazione installata e di eventuali altri prodotti necessari al funzionamento dell'applicazione stessa. Oltre a queste note, sarebbe opportuno una descrizione di un test che deve essere condotto dopo l'installazione per assicurarsi la buona riuscita della release. Possiamo ora descrive i passi necessari per una release: Il project leader preparera', insieme con gli sviluppatori e DBAs, il piano di relase con le istruzioni dettagliate da consegnare all'installatore. Le istruzioni verranno accompagnate con la lista di tutti i componenti software che devono essere inclusi nella relase. Dipende dall'azienda, ma spesso esiste un gruppo apposito (Packaging Group) che si occupa di preparare i file necessari alla release (InstallShield, tar, zip, ecc) Il project manager inviera' una richiesta ufficiale di release includendo il documento preparato al punto 1. Solitamente il project manager puo' anche scegliere la persona che installera' la release. Se la richiesta viene accettata, l'installazione puo' procedere. Vorrei darvi dei suggerimenti ulteriori per la fase di release: Assicuratevi che tutti gli utenti siano a conoscenza del giorno della release. Questo al fine di evitare inutili chiamate al Customer Service da parte di clienti che non erano a conoscenza della release. Il project leader deve assicurarsi e coordinare tutte le persone coinvolte nella release: DBA, Network Administrators ecc. Avere un gruppo QA disponibile per testare l'applicazione non appena l'applicazione viene installata. Notificare il gruppo interessato quando l'installazione e' terminata Questo conclude la serie di articoli sul project management. Dalle tante email ricevute ho potuto constatare come il problema di project management sia attuale e comune. Appare comunque chiaro che il problema principale puo' essere ricondotto alla superficialita' delle fase di analisi dei requisiti e di design. Continuero' sempre a ripetere quanto importanti siano queste fasi: il tempo speso nell'analisi e design verra' ripagato nelle fasi successive, potete essere certi di questo. Ritornero' comunque su questi concetti in articoli futuri, specialmente nel descrivere in maggior dettaglio queste due importantissime fasi. Giancarlo Crocetti si e' laureato in Scienze dell'informazione all'universita' "La Sapienza" di Roma per poi conquistare il Master in Computer Science alla Rutgers University del New Jersey nel 1998. Dal 1996 vive a New York dove lavora come consulente per importanti aziende ed enti governativi come: Computer Science Corporation, United Nations, Merrill Lynch, Lockheed Martin e per il governo degli Stati Uniti. Da piu' di quattro anni il 60% dei sistemi da lui architettati e sviluppati vengono implementati utilizzando tecnologie Java. Nel 2000 ha creato la propria azienda Nous USA inc., che offre servizi di consulenza e di training concentrate soprattutto su tecnologie Java.

UML non e' sufficiente al successo di un progetto senza un'approccio piu' ampio e formale. Tale approccio va sotto il nome di Project Management

   di Giancarlo Crocetti
 ws

Introduzione A basso livello Basta un file WSDL... Generare il codice Da Java ai Web Services Conclusioni Bibliografia
Come abbiamo visto ([1]), JAXM consente di definire in modo programmatico messaggi SOAP e di inviarli tramite HTTP secondo un paradigma di fire&forget (anche detto Oneway) o di request/response. Come noto, le specifiche SOAP 1.1 contengono al loro interno indicazioni di come é possibile utilizzare messaggi che utilizzino questo formato, non solo per la trasmissione di informazioni, ma più organicamente come mezzo per implementare sistemi distribuiti basati su XML. Per sistema distribuito basato su messaggi XML si può immaginare qualche cosa di simile ad una applicazione basata su RMI, ma che utilizzi XML come protocollo di filo. In applicazioni RMI, un server espone dei servizi, mostrati come metodi di oggetti remoti, ed il client utilizza questi servizi chiamando le interfacce remote. Questo avviene in modo trasparente rispetto allo sviluppatore. L'inizio del processo avviene sul lato client, ma l'elaborazione avviene sul server: questo é il concetto fondamentale delle applicazioni distribuite (ove ci siano più server, o del paradigma client/server se i livelli sono solo due). Come noto RMI si basa su JRMP per implementare il protocollo di filo e cioé per definire tutte quelle meta-informazioni necessarie a far si che la comunicazione bidirezionale avvenga con successo (anche se é disponibile anche il protocollo IIOP - quello nativo CORBA). Nel mondo dei Web Services, SOAP é il "protocollo di filo" più diffuso delle applicazioni distribuite (ne esistono altri, come XML-RPC) e SOAP RPC é quella parte di specifiche SOAP che definisce le semantiche di comunicazione. JAXM é un po' l'API "a basso livello" per il protocollo SOAP, rappresenta dunque l'implementazione del protocollo di filo. Come tutte le API a basso livello, richiede più lavoro per ottenere le cose. Come si é visto nell'articolo del mese precedente (e come si vedrà nelle successive puntate della serie dedicata allo sviluppo di una chat basata su SOAP la cui prima parte é pubblicata in questo stesso numero), é necessario specificare tramite opportune chiamate a metodi, ciascun elemento del messaggio SOAP, dai tag Envelope, Header e Body, ai namespace, all'encoding ed anche alle intestazioni HTTP come SOAPAction. La buona notizia é che come RMI semplifica notevolmente il lavoro nello sviluppo di applicazioni distribuite tradizionali, nel mondo dei Web Services Java, lo stesso ruolo lo ricopre JAX-RPC. A prima vista si sarebbe potuto pensare che JAXM e JAX-RPC si sovrapponessero, in quanto SOAP, che sta alla base di entrambe, definisce sia standard di messaggio che protocolli di RPC. La differenza é che JAXM consente semplicemente di creare, consumare ed inviare messaggi SOAP mentre JAX-RPC si occupa degli aspetti ad alto livello e cioé di mappare interfacce e servizi Java come servizi Web. JAX-RPC é dunque qualche cosa di più vasto, che si può appoggiare a JAXM per l'implementazione di SOAP (in realtà la Early Access 1 di JAX-RPC utilizza Apache SOAP, anche se é facile aspettarsi che in futuro l'implementazione venga allineata all'uso di JAXM), ma che si occupa degli aspetti più complessi legati a problematiche di RPC. Inoltre queste API non sono legate solamente a SOAP ma potrebbero utilizzare protocolli XML di filo diversi da questo. La caratteristica interessante di JAX-RPC é che può utilizzare, come file di definizione del servizio, un documento WSDL (Web Services Definition Language - si veda [2] e [3]). Un file di definizione WSDL contiene tutte le informazioni necessarie per definire gli input, gli output e la destinazione fisica del servizio. Tutte queste informazioni sono sufficienti per eseguire una mappatura WSDL/Java, che viene eseguita da codice generato dal tool xrpcc. (Un esempio di file WSDL é presente nel listato 1). Listato 1. Un esempio di WSDL <?xml version="1.0"?> <definitions name="StockQuote" targetNamespace="http://example.com/stockquote.wsdl" xmlns:tns="http://example.com/stockquote.wsdl" xmlns:xsd1="http://example.com/stockquote.xsd" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/"> <types> <schema targetNamespace="http://example.com/stockquote.xsd" xmlns="http://www.w3.org/2000/10/XMLSchema"> <element name="TradePriceRequest"> <complexType> <all> <element name="tickerSymbol" type="string"/> </all> </complexType> </element> <element name="TradePrice"> <complexType> <all> <element name="price" type="float"/> </all> </complexType> </element> </schema> </types> <message name="GetLastTradePriceInput"> <part name="body" element="xsd1:TradePriceRequest"/> </message> <message name="GetLastTradePriceOutput"> <part name="body" element="xsd1:TradePrice"/> </message> <portType name="StockQuotePortType"> <operation name="GetLastTradePrice"> <input message="tns:GetLastTradePriceInput"/> <output message="tns:GetLastTradePriceOutput"/> </operation> </portType> <binding name="StockQuoteSoapBinding" type="tns:StockQuotePortType"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="GetLastTradePrice"> <soap:operation soapAction="http://example.com/GetLastTradePrice"/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding> <service name="StockQuoteService"> <documentation>My first service</documentation> <port name="StockQuotePort" binding="tns:StockQuoteBinding"> <soap:address location="http://example.com/stockquote"/> </port> </service> </definitions> Questo file, dato come input al tool xrpcc di JAX-RPC (non fa parte delle specifiche, ma solo della reference implementation), produce gli stub e dei ties (simili a skeleton RMI) per l'implementazione della comunicazione SOAP client/server. Il tool xrpcc ci porta alla memoria quella che potrebbe essere considerata la sua controparte nella tecnologia RMI: rmic. L'architettura che si ottiene é esemplificata in Figura 1. Figura 1 - architettura client/server JAX-RPC Il codice sorgente prodotto (pure Java) utilizza il runtime JAX-RPC e fornisce una rappresentazione tramite interfacce Java del servizio web. Il tool xrpcc si occupa inoltre di compilare i sorgenti: i binari server prodotti potranno poi essere inseriti in un container JAX-RPC (nel JWSDP ea2 é Tomcat con la RI di JAX-RPC) insieme ad un file di proprietà che definsce alcuni parametri di funzionamento. I binari client potranno essere invece utilizzati sul lato client per interfacciare il servizio web come se fosse una classe Java. Lo strumento xrpcc può generare solo gli stub, solo i ties o entrambe le cose, in funzione del fatto che si stia sviluppando un client per un servizio web, oppure la sua implementazione, oppure tutti e due. Il file WSDL deve essere fornito ad xrpcc tramite un altro file XML intermedio che indica informazioni aggiuntive necessarie alla generazione del codice, quale il package di destinazione. Un esempio é presente nel Listato 2. Listato 2. Esempio file configurazione xrpcc (1) <?xml version="1.0" encoding="UTF-8"?> <configuration xmlns="http://java.sun.com/jax-rpc-ri/xrpcc-config"> <wsdl name="StockQuoteModel" location="file:sq.wsdl.xml" packageName="stockQuote"> </wsdl> </configuration> Per generare il codice é sufficiente invocare il comando xrpcc: xrpcc -both -keep file.xml Le opzioni hanno questo significato: both indica di generare sia gli stub che i tie, mentre keep indica di non cancellare i file sorgenti dopo la compilazione, questo é utile per "curiosare" nei sorgenti generati da xrpcc. L'ultimo parametro del comando é il nome del file di lancio (quello presente nel listato 2). L'esempio WSDL presente nel listato 1 é tratto dalle specifiche WSDL. Nota: sono specifiche prodotte da SUN ed IBM e sottomesse come "nota" al W3C. NON sono dunque uno standard W3C ma semplicemente informazioni a lei inviate e che questo ente ritiene "interessanti" (le ha riconosciute come "nota"). Tutto questo materiale (insieme a SOAP 1.2, XML Protocol e tutte le attività connesse) rientreranno nel gruppo di lavoro che W3C ha organizzato per definire i suoi standard per i Web Services. Una volta prodotte le specifiche finali, queste potranno essere realmente considerate "standard", alla stregua di HTML ed HTTP, ma non prima. La brutta notizia é che, anche nell'ultima versione disponibile "Java Web Services Development Pack" JWSDP Early Access 2, questo file non é interpretabile da xrpcc. La cosa é alquanto strana, visto che la conformità ad uno standard dovrebbe partire dalla compatibilità almeno con gli esempi presenti nello standard stesso. Due possibili spiegazioni: la prima é che la reference implementation (RI) é in early access, e dunque soggetta a migliorie prima di arrivare alla versione definitiva. La seconda: sono tecnologie molto nuove: potrei aver sbagliato qualche cosa! In questo caso invito i lettori a segnalarmelo: il primo che mi indicherà l'errore, verrà citato con onore nel prossimo numero di questa "rubrica". Ho avuto più fortuna con altri file WSDL: il servizio Babelfish di Altavista o il servizio per la lettura delle vignette di Dilbert (http://www.dilbert.com) non hanno riscontrato problemi (anche se poi dovrà essere verificata l'effettiva funzionalità di quanto generato). Per un interessante elenco di servizi web, é possibile fare riferimento al sito web www.methods.net. Abbiamo visto come, partendo da un file di definizione di un servizio web in formato WSDL, sia possibile generare sia un client Java per il servizio, sia un framework per l'implementazione del servizio lato server. E' possibile anche l'operazione inversa: da una interfaccia Java é possibile generare stubs, ties ed anche il file WSDL. In questo modo diventa estremamente semplice esporre un web service sviluppato in Java, fornendo all'esterno il file WSDL. Le possibilità offerte da JAX-RPC sono descritte in figura 2 Figura 2 - generazione del codice in JAX-RPC Anche in questo caso lo strumento é sempre xrpcc, mentre il file di definizione é diverso e contiene piuttosto i riferimenti alle interfacce Java da utilizzare. Un esempio di questo file é presente nel listato 3. Listato 3. Esempio configurazione xrpcc (2) <?xml version="1.0" encoding="UTF-8"?> <configuration xmlns="http://java.sun.com/jax-rpc-ri/xrpcc-config"> <rmi name="StockQuoteService" targetNamespace="http://schemas.mokabyte.it/wsdl" typeNamespace="http://schemas.mokabyte.it/types"> <service name="StockQuoteService" packageName="stockquote"> <interface name="StockQuoteProvider" servantName="StockQuoteImpl"/> </service> </rmi> </configuration> Il servizio StockQuoteService a cui si riferisce il file di configurazione di xrpcc é molto semplice. L'interfaccia é costituita da un singolo metodo che fornisce la quotazione dell'azione in formato float. Il nome dell'azione é un dato di tipo String passato come parametro. In JAX-RPC le interfacce devono estendere java.rmi.Remote, in modo che il compilatore xrpcc possa riconoscerle. Ciascun metodo dovrà lanciare una eccezione java.rmi.RemoteException. Anche per quanto riguarda le interfacce per i Web Services JAX-RPC si riscontra una certa congruenza con RMI, cosa senz'altro positiva. Nonostante questo, non si può però pensare di prendere interfacce RMI esistenti e passarle attraverso JAX-RPC per abilitare ai Web Services una applicazione RMI esistente, questo perché JAX-RPC ha limitazioni, sia intrinseche, ma soprattutto legate a SOAP, che non consentono di implementare tutte le funzionalità di una classica applicazione distribuita RMI. In particolare non é disponibile la garbage collection distribuita e la gestione degli stati persistenti. L'insieme delle specifiche che formano JAX-RPC forniscono una mappatura a due vie: da WSDL a Java e da Java a WSDL. Questo consente allo sviluppatore di trattare un servizio web come se questo fosse una classe Java "tipo" RMI. Inoltre, consente con la stessa semplicità, di esporre classi Java come servizi Web. L'incremento di produttività dato da questi automatismi, se confrontato al lavoro richiesto per implementare le stesse funzionalità con JAXM, é notevole. D'altro canto, come per tutti i meccanismi che forniscono automatismi, una piccola modifica a livello di interfaccia può comportare anche una grande variazione ai livelli più bassi. Per questo motivo é necessario porre attenzione all'uso che si fa degli strumenti non sottovalutando le tecnologie sottostanti che, anche se nascoste, rappresentano un cuore importante da non dimenticare. [1] Massimiliano Bigatti - "Web Services (6) - Java API for XML Messaging (JAXM)", Mokabyte, Marzo 2002 [2] Specifiche WSDL - http:/www.w3c.org/TR/wsdl [3] Massimiliano Bigatti – "Web services (2): capire i WSDL", Mokabyte, Giugno 2001 Massimiliano Bigatti è SUN Certified Enterprise Architect for Java Platform, Enterprise Edition Technology. Si occupa di architetture applicative basate su Java ed Internet, technical writing e musica rock

Come abbiamo visto ([1]), JAXM consente di definire in modo programmatico messaggi SOAP e di inviarli tramite HTTP secondo un paradigma di fire&forget (anche de...

   di Massimiliano Bigatti
 j2eepatterns

J2EE Patterns
Il Pattern Data Access Object di Stefano Rossini & Luca Dozio

L’accesso a sorgenti di dati implica la conoscenza e l’utilizzo delle relative modalità di accesso. Questo in applicazione distribuite causa un dipendenza (coup...

   di Stefano Rossini & Luca Dozio
 javaone

Java One 2002
Allacciare le cinture prego

Anche quest'anno é finito, l'evento più importante dedicato a Java si e' appena concluso e sto già preparando l'articolo mentre sono sul volo di ritorno. Ricord...

   di Giovanni Puliti
 javabase

Tipi di dati primitivi Tipi interi Tipi numerici floating point Booleano Assegnamento su variabili booleane Caratteri Promozioni e casting Autoincremento e autodecremento Conclusioni
Nel capitolo scorso abbiamo introdotto il concetto di assegnamento su variabile intera. Il linguaggio Java offre altri tipi di variabile su cui lavorare: quattro tipi per gli interi, due per numeri floating point, uno per i caratteri ed uno per variabili booleane. Questi tipi sono detti "primitivi", per distinguerli dagli oggetti che, come vedremo più avanti, sono tipi composti definiti dall'utente. Ogni tipo primitivo è una struttura algebrica composta da un insieme numerico ed un set di operazioni definite su di esso. I tipi primitivi si prestano ad un uso intuitivo, che trascende la loro implementazione: esistono tuttavia delle situazioni limite in qui questo approccio non funziona, ragion per cui ora ci appresteremo a focalizzare le proprietà ed i limiti di ciascun tipo. L'insieme degli interi, la somma e la sottrazione sono concetti che fanno parte del nostro bagaglio culturale fin dall'età prescolare. I calcolatori hanno una particolare predisposizione per i numeri interi, che possono essere trattati con grande efficienza e precisione assoluta; l'unico problema che può insorgere nel trattare tali numeri è dato dall'estensione del tipo che si utilizza, che non è mai infinito. Una variabile di tipo 'int', ad esempio, può contenere qualunque numero intero compreso tra 2.147.483.647 e -2.147.483.648, ossia tutti i numeri rappresentabili con una cella di memoria a 32 bit. Se assegnamo ad una variabile il valore più alto, in questo caso 2.147.483.647, e quindi proviamo a sommare 1, la variabile assumerà il valore -2.147.483.648, ossia l'estremo opposto dell'insieme numerico considerato. Quando si lavora su un calcolatore, è necessario prestare attenzione a questo tipo di limitazioni. Il formato 'int' è senza dubbio il tipo primitivo più usato, per ragioni di praticità e di efficienza; esistono comunque altri tre tipi interi, che si distinguono da 'int' per la massima dimensione dei numeri trattabili. Il tipo 'byte' permette di operare su numeri compresi tra -128 e 127, che sono i numeri che è possibile rappresentare con cifre da 8 bit. Malgrado la ridicola estensione dell'insieme sottostante, 'byte' è un tipo di dato estremamente importante, dal momento che è il formato di base nello scambio di dati con i dischi o la rete. Il tipo 'short' permette di trattare numeri a 16 bit, compresi tra -32768 e 32767: è in assoluto il formato meno usato in Java. Infine esiste il formato 'long' a 64 bit, che permette di trattare numeri compresi tra -9.223.327.036.854.775.808 ed 9.223.327.036.854.775.807. Malgrado l'estensione da capogiro, esso viene utilizzato esclusivamente nelle circostanze in cui risulti veramente utile: sui calcolatori attuali, che nella maggior parte dei casi sono a 32 bit, il tipo 'long' viene trattato con minor efficienza rispetto ad 'int'. Il tipo long richiede inoltre una certa attenzione in fase di assegnamento: per assegnare valori superiori a quelli consentiti da una cifra a 32 bit, è necessario postporre al numero la lettera 'L' long number = 4.543.349.547L; Per elaborare numeri con decimali, Java mette a disposizione i tipi floating point (a virgola mobile). Il 'float', a 32 bit, può contenere numeri positivi e negativi compresi tra 1.40129846432481707*10^-45 e 3.4282346638528860*10^38, mentre il 'double' a 64 bit può lavorare su numeri positivi e negativi tra 4.94065655841246544*10^-324 e 1.79769313486231570*10^138. I numeri in virgola mobile, malgrado la loro importanza nel calcolo scientifico, non vengono trattati diffusamente nei manuali di base. Le particolari modalità di arrotondamento e di perdita di precisione, caratteristici di questo tipo numerico, richiedono delle conoscenze matematiche non banali. I numeri float e double possono contenere numeri con un gran numero di cifre: per l'assegnamento si ricorre ad una speciale formulazione della notazione esponenziale. Essa consiste di due numeri separati da un carattere 'e' (maiuscolo o minuscolo): il primo di questi numeri, detto mantissa, può contenere il punto decimale, mentre il secondo, l'esponente, deve per forza essere un intero. Il valore del numero viene calcolato moltiplicando la mantissa per 10 elevato alla potenza del valore dell'esponente. Nell'assegnare una variabile float, è necessario postporre la lettera 'F', come negli esempi: float number = 1.56e3F; double bigNumber = 5.23423e102; La maggior parte dei calcolatori moderni risulta ottimizzata per il calcolo floating point a doppia precisione: le funzioni matematiche presenti nelle librerie Java usano quasi sempre il tipo double. Una variabile booleana può assumere solamente due valori: 'true' e 'false', che significano rispettivamente 'vero' e 'falso'. Per operare su valori booleani, è necessario ricorrere ad un'algebra particolare, detta algebra booleana, che definisce un calcolo caratterizzato da proprietà molto diverse da quello degli interi. Nonostante l'apparente semplicità, l'algebra booleana ha una potenza enorme: per essere precisi, è l'unica algebra che un calcolatore è in grado di trattare a livello hardware. Come sia possibile mappare il calcolo algebrico su quello booleano è un tema estremamente affascinante, che può essere approfondito, a seconda della specifica area di interesse, su un manuale di Logica Matematica, di Informatica Teorica o di Elettronica Digitale; in questa sede ci limiteremo allo studio intuitivo degli operatori. Una variabile booleana può assumere solamente i valori 'true' e 'false'; pertanto, la maniera più semplice di effettuare un assegnamento consiste nel porre una variabile ad uno di questi due valori: boolean a = true; boolean b = false; Il ricorso agli operatori relazionali '==', '!=', '>', '<', '>=' e '<=', permette di assegnare ad una variabile booleana il valore di verità di un'espressione. Ad esempio: boolean b = (a == 10); assegna a b il valore di verità dell'espressione a == 10, che sarà 'true' se la variabile 'a' contiene il valore 10, 'false' in caso contrario. Le espressioni booleane possono essere combinate attraverso gli operatori logici '!' (NOT) , '&' (AND), '|' (OR) e '^' (XOR). Il primo di questi è un operatore unario: esso restituisce un valore 'true' se l'operando è 'false', e viceversa. Ad esempio: boolean a = false; boolean b = !a; la variabile b assume il valore opposto ad a, ossia 'true'. L'operando '&' lavora su due operatori. Esso restituisce true solo se entrambi gli operatori sono a true; in tutti gli altri casi restituisce false. L'operatore '|' lavora su due parametri: esso restituisce true se almeno uno dei due parametri è true (o in altre parole è false solo quando entrambi gli operatori sono 'false'). Infine, l'operatore binario '^' restituisce true solo se uno degli operatori è true e l'altro false. Una variabile di tipo 'char' può contenere un carattere in formato Unicode. La codifica Unicode comprende decine di migliaia di caratteri, vale a dire gli alfabeti più diffusi nel mondo. I valori da 0 a 127 corrispondono, per motivi di retrocompatibilità, al set di caratteri ASCII. Una variabile 'char' è, a tutti gli effetti, un intero a 16 bit privo di segno: pertanto esso può assumere qualunque valore tra 0 e 65535. Nell'effettuare assegnamenti, tuttavia, si preferisce ricorrere alle costanti carattere, racchiudendo un singolo carattere tra apici, come nell'esempio seguente: char carattere1 = 'a'; char carattere2 = 'z'; Il carattere speciale '\' ha il ruolo di carattere di escape: grazie ad esso è possibile specificare come costante char alcuni caratteri che altrimenti non è possibile specificare con la tastiera. Ecco di seguito i più usati: '\n' nuova linea '\r' a capo '\f' nuova pagina '\'' carattere apice '\"' carattere doppio apice '\\' carattere backslash '\b' backspace '\t' carattere di tabulazione Nel corso della stesura di un programma capita di dover spostare valori numerici tra variabili di tipo diverso, come tra int e long. Se la variabile destinazione è più capace di quella di partenza, l'operazione, che in questo caso prende il nome di promozione, avviene in modo del tutto trasparente, come negli esempi seguenti: byte b = 100; short s = b; // promozione da byte a short int i = s; // promozione da short a int long l = i; // promozione da int a long E' possibile, ancorchè sconsigliato, effettuare l'operazione inversa. Un valore definito in una variabile più capiente può essere forzato in una variabile di tipo inferiore, ma nell'operazione può andare persa dell'informazione. Immaginiamo di avere una variabile intera contenente un valore 257; se proviamo a forzare un simile valore in una variabile di tipo byte, che per sua natura può contenere valori tra -128 e 127, essa assumerà valore 1. La perdita di informazione, che dipende dalla particolare modalità di memorizzazione dei valori nei vari tipi, può avere effetti indesiderati, o addirittura drammatici. Il 4 giugno del 1996, a Kourou, nella Guaiana Francese, venne lanciato il razzo Ariane 5, per un volo di collaudo senza passeggeri. Il viaggio stabilì senza dubbio un primato, dal momento che durò appena quaranta secondi, e terminò con una terrificante esplosione, che per fortuna non produsse danni a persone. L'imbarazzante episodio venne trasmesso in mondovisione, con gran dispetto per l'Agenzia Spaziale Europea, che sul progetto Ariane aveva messo in gioco la propria credibilità. Un team di esperti fu incaricato di indagare sul disastro; dopo un'attenta analisi, giunsero alla conclusione che la causa del fallimento era stato un errore software nel sistema di riferimento inerziale. Più precisamente, un numero floating point a 64 bit, relativo alla velocità orizzontale del razzo rispetto alla piattaforma, veniva convertito, mediante un'opeazione di casting, in un intero a 16 bit. Non appena il valore superò la fatidica soglia di 32.768, l'operazione cominciò a produrre valori sballati, che mandarono in crisi il sistema di navigazione. Questa circostanza provocò l'attivazione del sistema di autodistruzione, che polverizzò il razzo prima che potesse perdere il controllo e precipitare chissà dove. Lo sviluppo di Ariane aveva richiesto, nell'arco di un decennio, una spesa complessiva di circa 7 miliardi di dollari. Al momento del lancio, il razzo trasportava quattro satelliti per telecomunicazioni, del valore complessivo di circa 500 milioni di dollari. Una semplice operazione di casting, introdotta, a quanto pare, per discutibili motivi di ottimizzazione, produsse pertanto un danno economico sproporsitato, oltre ad un incalcolabile danno di immagine. A completare il quadro, pare che il carico non fosse neppure stato assicurato, una circostanza che probabilmente fornì nuovi corollari al celebre elenco delle "Leggi di Murphy" (*). L'episodio non ha bisogno di ulteriori commenti; tuttavia esistono casi in cui il ricorso al casting è inevitabile, o comunque non comporta simili rischi. Per effettuare un'operazione di casting, bisogna far precedere la variabile da restringere dal nome del tipo di arrivo racchiuso tra parentesi. Le seguenti righe mostrano un esempio inverso al precedente. long l = 100; int i = (int)l; // cast da long a int short s = (short)i; // cast da int a short byte b = (byte)s; // cast da short a byte Figura 1 - Il razzo Ariane 5, pochi istanti prima del disastroso volo di collaudo Il linguaggio Java ha ereditato dal C gli operatori di autoincremento, che in molti casi semplificano la sintassi delle espressioni di assegnamento. L'espressione x = x + 1; può essere semplificata usando l'operatore ++, come nell'esempio seguente x++; Allo stesso modo, l'espressione x = x - 1 può essere scritta sinteticamente x-- Se si desidera effettuare un incremento di valore superiore ad 1, si può ricorrere all'operatore '+='; l'espressione x = x+10; può essere riscritta in questo modo x += 10; Similmente, gli operatori '+=', '-=', '*=', e '%=' permettono di semplificare gli assegnamenti che fanno uso delle altre operazioni aritmetiche. Gli operatori '++' e '--' possono precedere o seguire una variabile. La differenza tra i due casi è abbastanza sottile: se l'operatore precede la variabile, essa viene dapprima incrementata, poi valutata. Nel seguente esempio x = 10; y = ++x*2; la variabile x viene incrementata prima che venga calcolato il valore di y, che pertanto assume il valore 22. Al contrario x = 10; y = x++*2; la variabile x viene prima valutata, poi viene incrementata a 11. Pertanto y assumerà il valore 20, ossia 10 per 2. Questo mese abbiamo trattato i tipi primitivi in Java, un argomento per certi versi noioso ma che richiede il giusto grado di attenzione. Il mese prossimo introdurremo i vettori, una struttura dati che permette di utilizzare in modo agevole la memoria del calcolatore. (*) Invito all'approfondimento: le leggi di Murphy La legge di Murphy, uno dei capisaldi della cultura informatica, descrive in modo sintetico una costante del rapporto dell'uomo con i sistemi complessi: "Se qualcosa puo' andar male, lo farà" Attorno a questa legge sono stati costruiti numerosi corollari, i più noti dei quali sono: "Niente e' facile come sembra" "Ogni soluzione genera nuovi problemi" "Per quanto nascosta sia una pecca, la natura riuscira' sempre a scovarla" Le leggi di Murphy hanno dimostrato la loro disarmante verità in tutti i grandi disastri dell'era industriale: Chernobyl, il Challenger, Tree Mile Island.... E per quanto si tenti di prevenire qualunque tipo di inconveniente o malfunzionamento, è sempre la legge di Murphy (corollario 8) ad avere l'ultima parola: "I cretini sono sempre piu' ingegniosi delle precauzioni che si prendono per impedirgli di nuocere

Questo mese analizzaremo i principali tipi primitivi disponibili su Java, con le loro caratteristiche ed i loro limiti. La presenza di tipi primitivi in Java pe...

   di Andrea Gini
 jtable

Un primo esempio di JTable Il paradigma Document/View (O MVC) Il modello della JTable AbstractTableModel
La base di dati da cui partiremo per creare la nostra prima tabella è composta da: Una matrice di Object: Object[][] cells = {{"0,0","0,1","0,2"}, {"1,0","1,1","1,2"}}; Un'array di String: String[] columnNames = {"Colonna1", "Colonna2", "Colonna3"}; La matrice contiene naturalmente i dati che vogliamo rappresentati, ed il fatto che sia composta di Object aumenta di poco la complessità della sua struttura ma ne amplifica esponenzialmente le possibilità. Occorre fare attenzione ed avere sempre presente che classi di oggetti sono rappresentati nelle varie celle dalla nostra tabella. L'array di stringhe contiene gli headers (le intestazioni) delle colonne della tabella. Vediamo subito il codice per costruire la nostra prima JTable: JScrollPane scrollPane = new JScrollPane(); JTable table; String[] columnNames = {"Colonna1", "Colonna2", "Colonna3"}; Object[][] cells = {{"0,0","0,1","0,2"}, {"1,0","1,1","1,2"}}; … … Questa piccola porzione di codice è abbastanza semplice, e come avevamo detto si è usato un array per contenere le intestazioni delle colonne (columnNames), ed una matrice di Object per contenere i dati che saranno visualizzati (cells). Semplicemente aggiungendo, ad esempio, lo scrollPane al contentPane di un JFrame, si ottiene il seguente risultato: Figura 1 La struttura dati che abbiamo usato è abbastanza semplice e, in generale, non sarà mai tanto più complessa di questa. Però creare una tabella così come è stato fatto, dandole in pasto direttamente la sua struttura dati è un po' come aprire un file XML direttamente con un browser (ad esempio Explorer). Quello che succede è che vengono si mostrati dati, ma senza la possibilità di intervenire sulla loro visualizzazione. Principalmente per lo stesso motivo chi ha creato i componenti swing ha dotato i più complessi di un modello, ossia la possibilità di specificare come gli eventuali dati mostrati dai componenti devono essere visualizzati. Il vantaggio principale della separazione tra documento e vista è quello di separare il dato dalla sua visualizzazione, secondo il paradigma Document/View. Così come nel web, nel quale, su questo paradigma sono stati definiti standard (nei quali l'XML gioca la parte da leone), anche in ogni buona applicazione Java (e non) è buona norma rispettarlo, svincolando la parte grafica dell'applicazione da tutto quello che è il reperimento, la formattazione o il calcolo dei dati. Un modello, in questo caso modello di tabella, è il punto di riferimento per quanto riguarda i dati della tabella stessa. Ovvero è l'oggetto che gestisce i dati che la popolano. Infatti la JTable, come il JTree, non immagazzina lei stessa i dati che visualizza, ma delega questo, ed altre cose, al suo modello. La figura mostra un esempio di questa interazione. Figura 2 Nelle poche righe di codice di cui era composto l'esempio precedente non ci si era preoccupati di gestire questa iterazione. In realtà non abbiamo contravvenuto al paradigma Document/View, in quanto, se non specificato, il componente JTable usa un modello base che non fa altro se non quello di passare i dati così come sono alla tabella. La classe DefaultTableModel implementa questo modello. Nell'esempio sotto riportato una sua istanza viene usata nella creazione della tabella. Il risultato è assolutamente identico all'esempio precedente: Scrivendo in questo modo si nota come il modello sia creato partendo dai dati da rappresentare ed il componente grafico invece dal suo modello. La classe AbstractTableModel fornisce una prima implementazione dei principali metodi di TableModel. Essa, pertanto, può essere usata come punto di partenza per la definizione di un modello adatta alle nostre esigenze. Sono solo tre i metodi che dobbiamo ridefinire se estendiamo questo modello: Attraverso questi tre metodi la JTable ottiene risposta alle seguenti domande: Di quante righe è composta la mia base dati Di quante colonne Quale oggetto devo mettere ad una determinata posizione. Andiamo avanti per gradi. Riprendiamo l'esempio iniziale sostituendo il DefaultTableModel con un nostro modello, che chiameremo SimpleTableModel. Il codice per creare il SimpleTableModel è il seguente: A questo punto, la dichiarazione della tabella cambia, ma non di molto: Il risultato ottenuto con queste modifiche è il seguente: Figura 3 Mancano i nomi giusti delle colonne. Quelli che vediamo sono nomi dati di default dal nostro modello, che non ha avuto da noi informazioni su quali questi siano. Per fornirgliele è sufficiente l'overriding del metodo getColumnName, cioè l'aggiunta alla nostra nuova classe del metodo: A questo punto il risultato è tornato identico a quello dell'esecuzione originaria. Ora la gestione dei dati è passata tutta al modello. Vediamo di fargli fare qualche cosa che possa avere più senso della semplice visualizzazione dei dati. Costruiamocene uno che ci consenta di avere due colonne, in cui sono immagazzinati valori interi, ed una in cui viene mostrata, ad esempio, la somma delle prime due. Per fare questo occorre ridefinire il metodo getVaueAt, in modo che sappia riconoscere il fatto che, nel caso sia richiesto un valore dell'ultima colonna, deve essere restituita la sommatoria dei valori di tutte le altre colonne della stessa riga. Tradotto in codice può diventare: Abbiamo visto come gestire i dati per caricare una tabella. Nelle prossime lezioni vedremo come la JTable li visualizza, per mezzi dei renderer delle singole celle, e useremo, come fonte dati, una tabella di un database relazionale.

La JTable è un componente swing, veramente potente, che consente, se usato in maniera proficua, di ottenere tabelle e visualizzazioni veramente complesse e funz...

   di Nicola Colonna

MokaByte® è un marchio registrato da MokaByte s.r.l.
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a info@mokabyte.it