Come devo strutturare la mia applicazione se voglio interfacciare una base di dati con il web? In particolare, cosa devo fare per permettere ad una applet di leggere i dati contenuti in tale database?
Eccoci arrivati all'ultimo articolo dedicato all'interrogazione di database via web. Nel primo articolo abbiamo posto le basi teoriche per implementare un'architettura di comunicazione client-server, nel secondo abbiamo implementato la struttura di base per il server. In quest'ultimo articolo vedremo invece come creare un applet di interrogazione, cioè il client.
Le informazioni fornite nei tre articoli dovrebbero essere sufficienti per poter estendere le caratteristiche a seconda delle proprie necessità. In realtà, come già accennato, la serie di articoli è stata concepita in modo da mostrare una metodologia aperta, nel nostro caso applicata ad un sistema di interrogazione di database, ma virtualmente adattabile ad un qualsiasi sistema client-server.
L'applet
Il nostro semplicissimo applet sarà composto da un campo di testo per l'inserimento dei comandi da inviare al server, un bottone per confermare l'invio del comando e una TextArea per la visualizzazione dei dati di ritorno. Ci siamo quindi limitati al minimo indispensabile per avere un applet utilizzabile, semplice ma allo stesso tempo implementando il tutto in modo che sia estremamente facile estenderne le funzionalità a proprio piacimento o necessità.
Per rendere la lettura dell'articolo il più scorrevole possibile, il codice viene commentato pezzo per pezzo. Come vedrete il tutto è molto semplice e comunque risulta perfettamente speculare al codice scritto per la parte server. Infatti tutta la parte di comunicazione è esattamente la stessa. In realtà, l'unica differenza sta nel fatto che il client ha necessità di visualizzazione che il server può invece trascurare.
Si parte dagli import:
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
Applet è il tipo di applicativo che si sta scrivendo, awt. e awt.event. servono per la gestione dell'interfaccia grafica e delle sue interazioni, net. per la comunicazione con il server e io. per l'input/output.
A questo punto viene creata la classe, che oltre ad essere un applet implementa le interfacce ActionListener, per poter rispondere agli eventi generati dall'interfaccia grafica, e Runnable, per poter essere trattata come thread.
public class JDBCClient extends Applet implements ActionListener, Runnable {
TextField textField;
TextArea textArea;
Button bottone4;
String newline;
Color bgcolor = new Color(0xc2d3f1);
private Thread selfThread = null;
String hostname;
Socket theSocket;
BufferedReader in;
PrintWriter out;
int port = 3000;
boolean oktorun = true;
}
Il metodo `init()`
Il metodo init(), proprio dell'oggetto Applet, è il metodo richiamato all'inizializzazione dell'applet stesso. Per funzionare, il nostro applet ha necessità di sapere l'indirizzo del server al quale connettersi, anche se sappiamo che per ragioni di sicurezza, per default, un applet può solo connettersi al server dal quale sia stato scaricato.
Il resto del codice del metodo init() si occupa di costruire l'interfaccia grafica. L'articolo ne approfitta per inserire del codice didattico ai fini della costruzione di una interfaccia grafica, per quanto spartana, utilizzando GridBagLayout e GridBagConstraints. Di tutti i layout, il GridBag è forse il più complesso e potente. Per chi non sia abituato a lavorare con i layout, all'inizio può sembrare più una complicazione che una potenzialità, ma quando si prova ad allargare o restringere la finestra di lavoro e si vede l'auto-resizing in azione, ci si ricrede immediatamente.
public void init() {
hostname = getParameter("HOST");
textField = new TextField(20);
textArea = new TextArea(5, 20);
bottone4 = new Button("Invia testo");
textArea.setEditable(false);
Font fnt = new Font("Arial", Font.PLAIN, 12);
textArea.setFont(fnt);
textField.setFont(fnt);
GridBagLayout grid = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(grid);
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.weighty = 1;
c.gridwidth = 1;
add(textField, c);
c.gridwidth = GridBagConstraints.REMAINDER;
add(bottone4, c);
bottone4.addActionListener(this);
c.weightx = 1.0;
c.gridwidth = GridBagConstraints.REMAINDER;
c.weighty = 15.0;
textArea.setBackground(bgcolor);
add(textArea, c);
textField.addActionListener(this);
newline = System.getProperty("line.separator");
}
`start()`, `stop()` e `run()`
Il metodo start(), così come stop() e run(), appartiene invece all'interfaccia Runnable, e sono quindi legati all'oggetto Thread.
In particolare, start() nel nostro caso si occupa di creare il socket di connessione al server, aprire gli stream di input/output con il server stesso e di creare e lanciare un thread dell'applet stesso.
public void start() {
try {
theSocket = new Socket(hostname, port);
in = new BufferedReader(new InputStreamReader(theSocket.getInputStream()));
out = new PrintWriter(theSocket.getOutputStream(), true);
} catch (IOException e) {
System.err.println("ERRORE!" + e);
}
selfThread = new Thread(JDBCClient.this);
oktorun = true;
selfThread.start();
}
Il metodo stop() viene eseguito se per qualche motivo viene stoppato il thread, e si occupa di chiudere il socket:
public void stop() {
oktorun = false;
try {
theSocket.close();
} catch (IOException ee) {
}
}
Il metodo run() è il cuore dell'applet, in quanto si occupa di stare in ascolto sul socket creato precedentemente e di interpretare i dati che arrivano dal server. Questo è il punto in cui si nota di più la somiglianza con il codice del server; infatti la tecnica utilizzata è la stessa.
Il formato di dati accettato è comando:dati. Nella stringa ricevuta dal server si cerca il separatore :, ponendo nella variabile what il comando e in data i dati. Nel caso mostrato nell'articolo è riconosciuto solo il comando getString, che segnala la richiesta del valore di un campo dal ResultSet creato in base alla query fatta in precedenza.
public void run() {
String inputLine, what, data;
while (true && oktorun) {
try {
inputLine = in.readLine();
int p = inputLine.indexOf(":");
if (p > 0) {
what = inputLine.substring(0, p);
data = inputLine.substring(p + 1, inputLine.length());
if (what.equals("getString")) {
textArea.append(data + "\n");
}
}
} catch (IOException e) {
System.err.println("Error reading input");
}
}
}
Avendo implementato un solo comando riconosciuto si è utilizzato un semplice if. Naturalmente, avendo un ventaglio di comandi più ampio sarà meglio utilizzare una struttura switch.
Gestione degli eventi
Il metodo actionPerformed() deve essere implementato in quanto nella creazione della classe è stata specificata come clausola implements ActionListener. Questo metodo viene richiamato in seguito al verificarsi di eventi da parte di quegli oggetti per i quali sia stato definito ActionListener l'applet, nel nostro caso bottone4 e textField.
Nel caso mostrato l'if iniziale è quasi superfluo, ma è stato inserito in previsione di espansioni future che riguardino il lancio di eventi da parte di altri oggetti all'interno dell'applet.
public void actionPerformed(ActionEvent e) {
if (e.getSource().equals(bottone4) || e.getSource().equals(textField)) {
String text = textField.getText();
out.println(text + "\n");
out.flush();
}
}
Anche qui, nota l'autore, se il numero di oggetti dovesse diventare alto sarebbe meglio sostituire l'if con uno switch.
Conclusioni
Dalla lettura di questo articolo e di quelli precedenti dovrebbe essere possibile ricavare una visione abbastanza chiara di uno dei metodi più semplici per la scrittura di applicativi client-server via applet. I mezzi utilizzati sono quelli standard di Java 1.1, quindi non ci dovrebbero essere problemi di compatibilità con i vari browser in circolazione.
Prendendo come spunto il codice mostrato, è possibile ampliare a dismisura le capacità del progetto. Tanto per citare qualcosa, sarebbe possibile implementare:
- meccanismi di
connection pooling per l'ottimizzazione delle connessioni a database;
- passaggio di
ResultSet, interi o paginati, da server a client invece che un campo alla volta, prestando attenzione alla mole di dati trasferita sulla rete;
- un'interfaccia di gestione del database, usando i metadata;
- ulteriori comandi lato server e ulteriori controlli lato client.