Programarea dispozitivelor cu resurse limitate
1. Scopul lucrării
Scopul acestei lucrări este insuşirea tehnicilor de programare folosind Java 2 Micro Edition pentru dispozitive cu resurse limitate. Se va urmări înţelegearea modului de construire a unei aplicaţii j2ME pentru configuratia CLDC.
2. Noţiuni preliminare
Tehnologia Java 2 Micro Edition reprezintă un subset al Java 2 Standard Edition, şi este destinată programării dispozitivelor cu resurse limitate cum ar fi telefoane mobiel, PDA-uri. J2ME este format dintr-o maşină virtuală construită special pentru a rula pe dispozitive cu resurse limitate, şi un set de librării (Java API) ce oferă facilităţile necesare construirii de aplicaţii java.
OBSERVATIE: Programatorul de dispozitive nu trebuie să se ocupe de instalarea unei maşini virtuale pe dispozitiviul mobil, de obicei acestea venind cu o maşină virtuală preinstaltă.
În figura 1 sunt prezentate cele trei componente ale J2ME.
Figura 1. Structura J2ME.
O configuraţie este formată dintr-o maşină virtuală java (special concepută pentru a putea rula pe anumite dispozitive) şi un set de clase de bază optimizate pentru a rula pe un set de dispozitive cu anumite caracteristici.
În prezent sunt definite două configuraţii:
- Conected Device Configuration – este destinată dispozitivelor cu resurse importante, având nevoie de minim 512K ROM, 256K RAM şi care suportă o implementare completă a JVM.
- Connected, Limited Device Configuration – este destinată dispozitivelor cu resurse mai limitate : 128k – 512k ROM, capacităţi de alimentare cu energie limitate, conectivitate continuă sau intermitentă la anumite tipuri de reţele, interfaţă grafică cu capacităţi limitate ( sau fără interfaţă grafică).
Sun a dezvoltat o maşină virtuală nouă pentru a rula aplicaţii J2ME pe dispozitive cu microprocesoare pe 16 şi 32 de biţi. Acestă maşină virtuală poartă numele KVM şi este destinată în special pentru configuraţia CLDC având nevoie de numai 40K memorie, putând de asemenea fi ajustată în funcţie de restricţiile dispozitivului pe care urmează să ruleze.
Un profil reprezintă o colecţie de clase (Java API) pe care dezvoltatorul le poate folosi pentru a construi aplicaţii.
În prezent sunt definite două profile:
- Foundation Profile – acest profil necesită configuraţia CDC
- MID Profile – acest profil necesită configuraţia CLDC
Pachetele opţionale reprezintă librării de clase ne standard care oferă acces la anumite resurse specifice ale dispozitivului, şi pe care dezvoltatorul trebuie sa le instaleze separat.
Figura 2. Tehnologiile Java
În mod tradiţional un dispozitiv vine cu o configuraţie şi un profil preinstalat.
Procesul de construire a unei aplicaţii J2ME cuprinde următoarele etape: construire arhitectură aplicaţie, codare, compilare, preverificare, împachetare, testare şi instalare.
Instalare J2ME
Înainte de a începe dezvoltarea de aplicaţii J2ME (denumite şi MIDlet-uri), va trebui instalată, pe calculatorul pe care urmează să se dezvolte aplicaţia, unealta J2ME Wireless Toolkit 2.2.
Observatie: Pe maşina de dezvoltarea va trebui să fie instalat J2SE 1.4.2 sau mai mare.
Observaţie: Dacă se doreşte dezvoltarea de aplicaţii J2ME direct din mediul Eclipse se poate instala plug-inul Eclipse ME. Plug-inul se găseşte aici sau poate fi downloadat de pe site-ul oficial http://eclipseme.org.
3. Construirea unei aplicaţii MIDlet
Aplicaţiile MIDlet sunt diferite de aplicaţiile standard java. Aplicaţiile MIDlet trebuie să extindă clasa abstractă MIDlet din pachetul javax.microedition.midlet (în mod similar cu crearea applet-urilor, pentru crearea cărora trebuie extinsă clasa Applet sau JApplet).
În listing-ul următor este prezentată structura minimală a unei aplicaţii de tip MIDlet.
package lab.scd.j2me;
import java.util.Date;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
public class DateTimeApp extends MIDlet {
Alert timeAlert;
public DateTimeApp() {
timeAlert = new Alert("Alarm!");
timeAlert.setString(new Date().toString());
}
public void startApp() {
Display.getDisplay(this).setCurrent(timeAlert);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Editaţi codul anterior în cadrul unui fişier cu numele DateTimeApp.java şi salvaţi fişierul într-un director a cărui structură are forma ...\lab\scd\j2me (de exempli: d:\temp\lab\scd\j2me\DateTimeApp.java).
Compilarea unei aplicaţii MIDlet se face în mod similar cu o aplicaţie obisnuită folosindu-se acelaşi utilitar javac.exe (pentru mediul windows), doar că va trebui specificat parametrul –bootclasspath care va indica calea către librăriile J2ME ce vor fi folosite pentru compilare (daca nu se specifică acest parametru aplicaţia va fi compilată în mod J2SE)
d:\temp
>javac-bootclasspath ..\lib\cldcapi11.jar;..\lib\midpapi20.jar com\j2me\part1\DateTimeApp.java
Preverificarea este o etapă ce trebuie realizată înainte de instalarea unei aplicaţii MIDlet pe un dispozitiv mobil. În cadrul acestei etape este verificată integritatea şi corectitudinea aplicaţiei MIDlet şi respectăspecificaţiile maşinii virtuale pe care va fi rulată. Această verificare este realizată pentru toate tipurile de aplicaţii java, dar pentru că maşina virtuală pentru dispozitive mobile ar resurse limitate sarcina verificării revine programatorului, şi aceasta se realizează înainte de instalarea pe dispozitiv.
Wireless Toolkit conţine în directorul \bin utilitarul preverify.exe ce este utilizat pentru realizarea acestei etape. Comanda ce trebuie executată pentru a verifica un MIDlet este:
d:\temp
>preverify.exe -classpath ..\lib\cldcapi11.jar; ..\lib\midpapi20.jar com.j2me.part1.DateTimeApp
În urma executării comenzii se va genera o versiunea „preverificată” a DateTimeApp.class într-un director numit output.
Împachetarea aplicaţiei şi pregătirea acesteia pentru a fi instalată presupune parcurgerea următorilor paşi:
Se construieşte fişierului Manifest.mf cu următorul conţinut:
MIDlet-Name: DateTimeApp
MIDlet-Version: 1.0.0
MIDlet-Vendor: Your Name
Acest fişier va trebui construit în directorul .../output.
Se construieşte arhiva jar ce conţine aplicaţia şi fişierul manifest:
d:\temp\output>jar cvfm DateTimeApp.jar Manifest.mf .\com
Se construieşte un fişier DateTimeApp.jad cu următorul conţinut:
MIDlet-1: DateTimeApp, , com.j2me.part1.DateTimeApp
MIDlet-Name: DateTimeApp
MIDlet-Version: 1.0.0
MIDlet-Vendor: Your Name
MIDlet-Jar-URL: DateTimeApp.jar
MIDlet-Jar-Size:
MicroEdition-Profile: MIDP-2.0
MicroEdition-Configuration: CLDC-1.1
Se salvează fişierul DateTimeApp.jad în acelaşi director cu arhiva jar (în directorul ...\output). După cum se observă atributul MIDlet-Jar-Size nu este completat. Valoarea acestui câm trebuie să fie identică cu dimensiunea fişierului cu extensia .jar. Aceasta poate diferi de la o maşină la alta, astfel încât în momentul parcurgerii acestor paşi va trebui să citiţi valoarea exactă a dimensiunii fişirului jar şi sa completaţi acest atribut cu valoare corespunzătoare (în octeţi).
De exemplu pe maşina pe care a fost compilată şi verificată aplicaţia dimensiunea fişierului .jar este de 1487 octeţi.
În acest moment este finalizată etapa de împachetare a aplicaţiei, aceasta fiind pregătită pentru a fi rulată.
Testarea aplicaţiei MIDlet se face înainte de încărcarea acesteia pe dispozitivul mobil. Testarea se realizează pe un emulator ce simulează funcţionalităţile dispozitivului pe care urmează să fie încărcată aplicaţia. Acest emulator face parte din Wireless Toolkit. Pentru lansarea în execuţie a emulatorului şi testarea aplicaţie se execută comanda:
D:\temp\output>emulator.exe -Xdescriptor DateTimeApp.jad
După testarea aplicaţiei pe emulator se poate trece la ultimul pas.
Instalarea aplicaţiei MIDlet pe dispozitivul mobil se poate face în două moduri, fie printr-o conexiune directă între dispozitiv şi calculator utilizând unul dintre mecanismele de comunicaţie disponibile (bluetooth, USB, COM, etc...) fie prin Internet (aceasta presupune că dispozitivul mobil are acces la Internet).
4. Construirea unei aplicaţii MIDlet folosind Wireless Toolkit
Unealta Wireless Toolkit automatizează paşii prezentaţi anterior pentru construirea unei aplicaţii MIDlet, oferind programatorului o interfaţă grafică prin intermediul cărei poate să construiască aplicaţii de tip MIDlet putând efectua toţi paşii prezentaţi în capitolul anterior.
Lansarea în execuţie a interfeţei Wireless Toolkit se face executând fişierul ktoolbar.exe din directorul bin (din directorul de instalare al Wireless Toolkit). În urma lansării va fi afişată fereastra:
În continuare v-om crea acelaşi proiect ca şi în capitolul anterior pentru a vede modul în care unealta Wireless Toolkit ajută dezvoltatorul în procesul de construire a unei aplicaţii.
Se selecează opţiunea New Project... şi în fereastra care apare se introduce numele proiectului: DateTimeApp şi MIDlet Class Name: lab.scd.j2me.DateTimeApp.
În următoarea fereastră se pot seta parametrii MIDlet-ului, printre care şi platforma de destinaţie pentru care va fi construit acesta.
Se apasă butonul OK şi proiectul va fi generat.
Având în vedere ca se va construi aceiaşi aplicaţie ca şi în capitolul anterior, se va refolosi codul sursă DateTimeApp.java şi va fi copiat acest fişier în directorul proiectului în ...\DateTimeApp\src\lab\scd\j2me\.
În acest moment aplicaţia MIDlet este finalizată. Prin apăsarea butonului Run, din cadrul ferestrei principale a Wireless Toolkit se vor executa automat paşii de compilare, preverificare şi rulare în cadrul emulatorului.
Dacă aplicaţia a rulat corect pe emulator se poate realiza ultimul pas de inpachetare şi construire a fişierului jad. Pentru aceasta se selecteaza opţiunea Create Package.
În acest moment aplicaţia MIDlet este pregătită pentru a fi încărcată pe dispozitivul mobil.
5. Ciclul de viaţă al aplicaţiilor MIDlet
Ciclul de viaţă al MIDlet-urilor este gestionat de către Aplication Management System (AMS) – acesta fiind o aplicaţie aflată pe dispozitivul mobil care gestionează proceslee de download, instalare, rulare şi ştergere a MIDlet-urilor pe dispozitivul mobil.
În momentul în care utilizatorul selectează un MIDlet pentru a fi executat, AMS îl lansează în execuţie şi îl pune în starea Paused. În continuare AMS mută MIDlet-ul din starea Paused în starea Active şi îl notifică prin apelarea metodei startApp(). În mod similar sunt invocate metodele pauseApp() şi destroyApp() în momentul în care aplicaţia este dusă în starea de pauză sau distrusă.
Folosind diagramele de Masini cu Stări Finite reprezentarea stărilor unui MIDlet şi tranziţiile posibile sunt reprezentate în figura următoare:
Stările unui MIDlet reprezentate utilizând FSM.
O atenţie deosebită trebuie acordată tranziţiei stărilor pentru un MIDlet. De exemplu în condiţiile rulării aplicaţiei pe un telefon mobil, în momentul recepţionării unui apel, aplicaţia MIDlet este automat trecută în starea Paused. Aplicaţia va trebui să îşi salveze starea şi să îşi poată continua execuţia din punctul în care a rămas în momentul în care va reveni în starea Active.
6. Interfeţe grafice
Acest capitol prezintă modul în care pot fi construite interfeţe grafice în cadrul aplicaţiilor de tip midlet pentru dispozitive mobile folosind MIDP 2.0.
Clasele pentru construirea interfeţelor grafice sunt localizate în MIDP 2.0 în cadrul pachetelor: javax.microedition.lcdui şi javax.microedition.lcdui.game. Clasele esenţiale sunt listate în figura x şi figura y:
Figura x.
Figura y.
Utilizarea clasei Alert
Obiectele de tip Alert se recomandă a fi folosite pentru pentru afişarea de informaţii sau mesaje de eroare ce rămân pe ecran o perioadă scurtă de timp.
Setarea titlului alertei se face în cadrul constructorului.
Mesajul afişat de alertă este setat folosind metoda setString() sau prin intermediul constructorului.
Tipurile de alerte ce pot fi afişate sunt:
ALARM
, CONFIRMATION
, ERROR
, INFO
, şi WARNING
.
Metoda setTimeout(int time) poate fi folosită pentru a seta timpul cât alerta este vizibilă pe ecran. Dacă este setată ca valoare de timp constanta definită Alert.FOREVER atunci fereastra de tip alertă devine modală şi permanentă pe ecran.
Asocierea unei imagini la alerta se face folosind metoda setImage(Image img).
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class mymidlet extends MIDlet {
public mymidlet() {
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
// TODO Auto-generated method stub
Alert a = new Alert("message displayed by java midlet");
a.setTimeout(Alert.FOREVER);
Display.getDisplay(this).setCurrent(a);
}
}
Utilizarea clasei List
Listele modelează componente grafice ce permit selectarea uneia sau mai multor opţiuni dintr-o listă. Optiunile pot fi de tip „radio button” sau de tip „check box”.
Programul următor exemplifică modul în cre pot fi construite şi utilizate obiectele de tip List.
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
public class ListExample extends MIDlet {
List fruitList1;
List fruitList2;
public ListExample() {
fruitList1 = new List("Select the fruits you like",
Choice.MULTIPLE);
fruitList1.append("Orange ", null);
fruitList1.append("Apple", null);
fruitList1.insert(1, "Mango", null);
// inserts between Orange and Apple
String fruits[] = {"Guava", "Berry ", "Kiwifruit"};
fruitList2 =
new List(
"Select the fruits you like - List 2",
Choice.IMPLICIT,
fruits,
null);
}
public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(fruitList1);
try{
Thread.currentThread().sleep(3000);
} catch(Exception e) {}
display.setCurrent(fruitList2);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Utilizarea clasei TextBox
Clasa TextBox reprezintă componente grafice prin intermediul cărora poate fi preluat text de la utilizator. Prin intermediul metodelor definite în cadrul clsei pot fi aplicate diferite constrângeri asupra obiectelor de tip TextBox.
Programul următor exemplifică utilizarea clasei TextBox.
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
public class TextBoxExample extends MIDlet {
private TextBox txtBox1;
private TextBox txtBox2;
public TextBoxExample() {
txtBox1 = new TextBox(
"Your Name?", "", 50, TextField.ANY);
txtBox2 = new TextBox(
"Your PIN?",
"",
4,
TextField.NUMERIC | TextField.PASSWORD);
}
public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(txtBox1);
try{
Thread.currentThread()Sleep(5000);
} catch(Exception e) {}
txtBox1.setString("Bertice Boman");
try{
Thread.currentThread()Sleep(3000);
} catch(Exception e) {}
// inserts 'w' at the 10th index to make the
// name Bertice Bowman
txtBox1.insert("w", 10);
try{
Thread.currentThread()Sleep(3000);
} catch(Exception e) {}
display.setCurrent(txtBox2);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Utilizarea clasei Form
Clasa Form oferă posibilitaea de grupare a mai multor elemente grafice de tip Item. Adăugarea de obiecte Item se face folosind metoda add(index i, Item i).
Item-urile care pot fi adăugate în cadrul unui Form sunt: StringItem, DateItem, TextField, ChoiceGroup, Spacer, Gauge, ImageItem, CustomItem.
Programul următor exemplifică folosirea Item-urilor de mai sus cu excepţia Item-ului CustomItem.
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Spacer;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.DateField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.ChoiceGroup;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
public class FormExample extends MIDlet {
private Form form;
private Gauge gauge;
private Spacer spacer;
private ImageItem imageItem;
private TextField txtField;
private DateField dateField;
private StringItem stringItem;
private ChoiceGroup choiceGroup;
public FormExample() {
form = new Form("Your Details");
// a StringItem is not editable
stringItem = new StringItem("Your Id: ", "WXP-890");
form.append(stringItem);
// you can accept Date, Time or DateTime formats
dateField = new DateField("Your DOB: ", DateField.DATE);
form.append(dateField);
// similar to using a TextBox
txtField = new TextField(
"Your Name: ", "", 50, TextField.ANY);
form.append(txtField);
// similar to using a List
choiceGroup = new ChoiceGroup(
"Your meals: ",
Choice.EXCLUSIVE,
new String[] {"Veg", "Non-Veg"},
null);
form.append(choiceGroup);
// put some space between the items to segregate
spacer = new Spacer(20, 20);
form.append(spacer);
// a gauge is used to show progress
gauge = new Gauge("Step 1 of 3", false, 3, 1);
form.append(gauge);
// an image may not be found,
// therefore the Exception must be handled
// or ignored
try {
imageItem = new ImageItem(
"Developed By: ",
Image.createImage("/duke.gif"),
ImageItem.LAYOUT_DEFAULT,
"DuKe");
form.append(imageItem);
} catch(Exception e) {}
}
public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(form);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Interacţiunea cu utilizatorul
Aplicaţiile de tip Midlet interacţionează cu utilizatorul prin intermediul comenzilor. O comandă este echivalentă unui buton sau a unei opţiuni de meniu dintr-o aplicaţie obişnuită. Comenzile sunt încapsulate în cadrul obiectelor de tip Command. Comenzile pot fi ataşate componentelor grafice din cadrul midlet folosind metoda addCommand(Command c). Un element de interfaţă grafică poate avea asociat mai multe comenzi.
În momentul construirii obiectelor de tip Command pot fi specificaţi următorii parametri: nume scurt al comenzii, nume extins al comenzii (opţional), tipul comenzii şi prioritatea. Prin asocierea la o comandă a unui tip, dispozitivul ce rulează aplicaţia va putea mapa la comenzi anumite taste funcţionale. De exemplu o comandă cu tipul Command.OK va fi mapată la tasta OK. Restul tipurilor care pot fi asociate unei comenzi sunt: BACK, EXIT, HELP, ITEM, SCREEN şi STOP. Prioritatea unei comenzi specifică gradul de importanţă al comenzii respective şi in funcţie de aceasta dispozitivul va decide modul de afişare a comenzilor.
Tratarea evenimentelor generate ca urmare a acţionării unei comenzi este realizată de către implementatori ai interfeţei CommandListener ce conţine metoda commandAction(Command c, Displayable d). Înregistrarea unui ascultător de evenimente se face folosind comanda addComandListener(CommandListener l).
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Spacer;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.DateField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.ChoiceGroup;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Command;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.CommandListener;
public class FormExample
extends MIDlet
implements CommandListener {
private Form form;
private Gauge gauge;
private Spacer spacer;
private ImageItem imageItem;
private TextField txtField;
private DateField dateField;
private StringItem stringItem;
private ChoiceGroup choiceGroup;
public FormExample() {
form = new Form("Your Details");
// a StringItem is not editable
stringItem = new StringItem("Your Id: ", "WXP-890");
form.append(stringItem);
// you can accept Date, Time or DateTime formats
dateField =
new DateField("Your DOB: ", DateField.DATE);
form.append(dateField);
// similar to using a TextBox
txtField = new TextField(
"Your Name: ", "", 50, TextField.ANY);
form.append(txtField);
// similar to using a List
choiceGroup = new ChoiceGroup(
"Your meals: ",
Choice.EXCLUSIVE,
new String[] {"Veg", "Non-Veg"},
null);
form.append(choiceGroup);
// put some space between the items
spacer = new Spacer(20, 20);
form.append(spacer);
// a gauge is used to show progress
gauge = new Gauge("Step 1 of 3", false, 3, 1);
form.append(gauge);
// an image may not be found,
// therefore the Exception must be handled
// or ignored
try {
imageItem = new ImageItem(
"Developed By: ",
Image.createImage("/duke.gif"),
ImageItem.LAYOUT_DEFAULT,
"Duke");
form.append(imageItem);
} catch(Exception e) {}
// create some commands and add them
// to this form
form.addCommand(
new Command("EXIT", Command.EXIT, 2));
form.addCommand(
new Command("HELP", Command.HELP, 2));
form.addCommand(
new Command("OK", Command.OK, 1));
// set itself as the command listener
form.setCommandListener(this);
}
// handle commands
public void commandAction(
Command com, Displayable dis) {
String label = com.getLabel();
if("EXIT".equals(label))
notifyDestroyed();
else if("HELP".equals(label))
displayHelp();
else if("OK".equals(label))
processForm();
}
public void displayHelp() {
// show help
}
public void processForm() {
// process Form
}
public void startApp() {
Display display = Display.getDisplay(this);
display.setCurrent(form);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
7. Comunicarea folosind protocoale de comunicaţie în reţea
În cadrul aceste secţiuni este exemplificat modul în care poate fi folosit J2ME pentru a implementa aplicaţii Midlet ce rulează pe telefoane mobile şi comunică cu servere de internet prin intermediul protocolului de comunicaţie TCP\IP.
La fel ca şi în cazul aplicaţiilor standar, în cazul aplicaţiilor mobile se foloseşte conceptul de socket ce reprezintă un capăt de conexiune prin intermediul căruia datele sunt transmise şi recepţionate de către servere aflate la distanţă.
Paşii necesari pentru a realiza comunicarea prin socket-uri în cadrul aplicaţiilor mobile J2ME sunt următorii:
- Deschiderea conexiunii prin apelarea metodei statice Connector.connect(„socket://123.23.2.12:888”). Această metodă returnează un obiect de tip OutputConnection (ce permite doar transmiterea de date), StreamConnection (ce permite comunicaţie bidirecţională) sau InputConnection (ce permite doar operaţii de recepţionare de date).
- Construirea fluxurilor de intrare ieşire de tip InputStream şi OutputStream.
- Transmiterea şi recepţionarea datelor prin intermediul fluxurilor construite.
- Închiderea fluxurilor după terminarea comunicaţiei.
import javax.microedition.io.*;
import java.io.*;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.StringItem;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class SocketTest extends MIDlet {
String connectString = "socket://127.0.0.1:888";
StreamConnection streamConnection = null;
DataInputStream dis = null;
DataOutputStream dos = null;
Display deviceDisplay;
Form iniScreen;
Form responseScreen;
StringItem responseFiled;
public SocketTest() {
deviceDisplay = Display.getDisplay(this);
responseScreen = new Form("Response from server:");
iniScreen = new Form("Networking App");
iniScreen.append(new StringItem(null,"Connecting to:"+connectString));
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
protected void pauseApp() {
}
protected void startApp() throws MIDletStateChangeException {
try{
deviceDisplay.setCurrent(iniScreen);
//connect to server
streamConnection =
(StreamConnection) Connector.open(connectString);
//create IO streams
dos = streamConnection.openDataOutputStream();
dis = streamConnection.openDataInputStream();
iniScreen.append(new StringItem(null,"Connection to server ok."));
//send data
iniScreen.append(new StringItem(null,"Send data..."));
dos.writeChars("Hello\n\r");
dos.flush();
iniScreen.append(new StringItem(null,"Receive data..."));
//receive data
StringBuffer sb = new StringBuffer();
int inputChar;
while ( (inputChar = dis.read()) != -1) {
sb.append((char) inputChar);
}
//display the results on the screen
responseFiled = new StringItem(null, sb.toString());
responseScreen.append(responseFiled);
deviceDisplay.setCurrent(responseScreen);
}catch(Exception e){
System.err.println("App error:" + e);
iniScreen.append(new StringItem(null,"Error connecting:"+e.getMessage()));
}finally{
try {
if(dis!=null )dis.close();
if(dos!=null) dos.close();
if(streamConnection!=null) streamConnection.close();
} catch (Exception e) {
System.err.println("App error while clossing conenctions:" + e);
iniScreen.append(new StringItem(null,"Error clossing connection:"+e.getMessage()));
}
}
}
}
Obiecte distribuite (RMI)
1. Obiectivul lucrării
Scopul lucrării de faţă este de a prezenta arhitectura tehnologie Remote Server Invocation şi modul de utilizare pentru a implementa aplicaţii distribuite.
2. Noţiuni teoretice
Tehnologia RMI (Remote Method Invocation) este una dintre componentele fundamentale ale java, fiind introdusă începând cu versiunea jdk 1.1 . Cu ajutorul RMI părţi ale unui program pot exista pe maşini diferite , acest lucru fiind transparent din puct de vedere al programului. RMI este una dintre componentele fundamentale care stă şi la baza Enterprise Java Beans.
Programarea distribuită este benefică în foarte multe situaţii. Pe măsură ce aplicaţiile cresc în dimensiuni, complexitate şi resurse necesare, programarea într-un mediu distribuit duce la creşterea performanţelor şi posibilităţilor de administrare a acestor aplicaţii.
O aplicaţie RMI poate fi privită ca fiind compusă din două programe : un program client şi un program server. Programul server crează unul sau mai multe obiecte distribuite, şi aşteaptă programele client care vor invoca metodele acestor obiecte distribuite.
Aplicaţiile ce folosesc RMI mai sunt numite şi aplicaţii cu obiecte distribuite. Într-o astfel de aplicaţie pot fi identificate următoarele activităţi ce au loc:
- Localizarea obiectelor distribuite – aplicaţia foloseşte diverse mecanisme pentru a localiza obiectele distribuite şi pentru a obţine referinţe către acestea.
- Comunicarea cu obiectele distribuite – detaliile de comunicare cu obiectele distribuite sunt gestionate de mecanismele interne ale RMI.
- Încărcarea definiţii claselor – deoarece tehnologia RMI permite transmiterea obiectelor prin reţea, se oferă mecanisme de încărcare a claselor şi de transmitere a obiectelor prin reţea.
In figura de mai jos este prezentată o diagramă simplă ce ilustrează relaţia dintre client şi server într-o aplicaţie distribuita RMI.
Figura 1. Relatia client server
Principiul de funcţionare al mecanismului RMI este următorul: obiectele distribuite (accesibile de la distanţă) vor trebui să implementeze interfaţa Remote. Fiecărui obiect distribuit i se va asocia un nume şi va fi înregistrat de către rmirgistry. Clientul (care trebuie să cunoască numele cu care a fost înregistrat obiectul în rmiregistry) va primi o referinţă către obiectul distribuit, după care, va lucra cu acesta ca şi cum acesta ar fi un obiect local. Toate detaliile despre ceea ce se întâmplă de fapt în momentul în care se apelează o metoda a obiectelor distribuite, sunt invizibile pentru client şi pentru server, si sunt înglobate în cadrul claselor Stub şi Skeleton.
Figura 2. Aplicaţie RMI.
În figura 2 sunt reprezentate toate componentele implicate în cadrul unei aplicaţii distribuite ce foloseşte tehnologia RMI.
3. Paşii pentru implementarea în java a obiectelor distribuite
Aplicaţia server
Se defineşte interfaţa pe care o implementează obiectele distribuite. Această interfaţă va conţine metodele ce vor putea fi apelate de la distanţă prin intermediul mecanismelor RMI. Interfaţa va trebui să extindă interfaţa RemoteInterface.Meotdele definite în cadrul aceste interfeţe vor trebui sa aibă adăugată clauza throws RemoteException.
Construirea clasei ce implementează interfaţa definită la pasul anterior. Această clasă trebuie să extindă clasa UnicastRemoteObject.
Pe baza clasei definite la pasul anterior pot fi construite instanţe de obiecte ce vor putea fi accesate de la distanţa. Pentru a putea face accesibil de la distanţă un obiect prin intermediul mecanismului RMI trebuiesc realizaţi următorii paşi suplimentari:
a) Instalarea în cadrul aplicaţiei ce urmează a construi şi înregistra obiecte distribuite a unui manager de securitate folosind clasa java.rmi.RMISecurityManager.
b) Lansarea în execuţie fie din consolă, fie direct din cadrul aplicaţiei a utilitarului rmiregistry. Lansarea de la consolă se face executând comanda rmiregistry port (unde port reprezintă portul pe care utilitarul va aştepta conexiuni – sau cereri de accesare a obiectelor distribuite). Lansarea din cadrul aplicaţiei se face cu instrucţiunea LocalRegistry.createRegistry(port) (unde port are aceiaşi semnificaţie ca şi în cazul lansării de la consolă).
Paşii pentru a construi şi iniţializa un obiect distribuit sunt următorii:
a) Construirea obiectului pe baza clasei ce implementează interfaţa RemoteInterface
b) Înainte de a putea instanţia si a face vizibile în reţea obiecte distribuite, pe baza clasei definite la pasul anterior trebuiesc generate clasele Stub şi Skeleton folosind utilitarul rmic.exe. Cele două clase vor fi generate de către utilitarul rmic.exe pe baza clasei ce defineşte structura obiectelor distribuite. Aceste două clase implementează mecanismele de transmitere la distanţa a apelurilor de metode şi a răspunsurilor metodelor şi vor trebui să se regăsească şi în classpath-ul aplicaţiei client ce încearcă să apeleze un obiect distribuit.
c) Înregistrarea obiectului în cadrul rmiregistry pentru a puea fi accesat de la distanţă – Naming.rebid(„//numecalc:port/numeodiec); - unde numecalc reprezintă numele calculatorului unde se afla startat rmiregistry, port reprezintă portul pe care este lansat în exectuţie rmiregistri şi numeobiect reprezintă numele prin intermediul căruia obiectul este recunoscut în cadrul rmiregistry şi va putea fi invocat de la distanţă.
Aplicaţia client
Pentru ca o aplicaţie client să obţină o referinţă către un obiect distribuit trebuie să instaleze un manager de securitate de tip java.rmi.RMISecurityManager.
După instalarea managerului de securitate clientul poate obţine o referinţă către un obiect distribuit folosind o instrucţiune de froma:
ObjectInterface obj = Naming.lookup(„//host:port/name”);
Unde host reprezintă numele calculatorului pe care obiectul distribuit este înregistrat, port reprezintă portul pe care utilitarul rmiregistry este lansat in execuţie pe maşina server şi name reprezintă numele sub care obiectul a fost înregistrat în cadrul serverului.
După obţinerea referinţei către obiectul distribuit, aplicaţia client va putea manipula respectivul obiect exact în acelaşi mod ca şi obiectele locale, fiind transparent faptul că apelul metodelor din obiectul distribuit sunt de fapt transmise la distanţă prin intermediul mecanismelor RMI.
Mecanismele interne care se ocupă cu apelarea la distanţă a metodelor distribuite şi întoarcerea rezultatelor metodelor apelate sunt implementate în cadrul claselor Stub şi Skeleton ce sunt generate de către compilatorul rmic.exe. Datorită acestui fapt cele două clase trebuie să fie accesibile în cadrul aplicaţiei client ce încearcă să apeleze un obiect distribuit.
4. Exemplu aplicaţie RMI
Aplicaţia ilustrează modul în care tehnologia RMI poate fi folosită pentru a implementa un sistem care să permită controlarea de la distanţă a unor resurse. In cazul de faţă sa implementat o aplicaţie ce permite modificarea de la distanţă a algoritmilor de control pentru un set de controlere. In figura 3 este descrisă arhitectura generală a aplicaţiei.
Figura 3. Arhitectura generala aplicaţie RMI
Aplicaţia este formată din două componente: componenta Control local care acţionează ca un server şi componenta Control la distanţă care acţionează ca şi un client.
Componenta server permite iniţializarea şi instalarea a unuia sau mai multor controlere. Prin intermediul mecanismului RMI aplicaţia server permite instalarea în cadrul controlerelor de la distanţă, de către aplicaţia client a algoritmilor de control pentru fiecare dintre controlerele instalate.
Componenta client defineşte unul sau mai mulţi algoritmi de control pe care îi poate instala în cadrul controlerelor aflate la distanţă prin intermediul mecanismului RMI.
Aplicaţia exemplifică modul în care mecanismul RMI permite transmiterea la distanţă de cod executabil.
In figura 4 este prezentată diagrama UML a claselor pentru aplicaţia server.
Figura 4. Diagrama UML a claselor pentru aplicaţia server
In cadrul aplicaţiei server clasa ControlersServer este responsabilă cu iniţializarea mecanismelor RMI, pentru a putea înregistra obiecte distribuite (figura y).
if(System.getSecurityManager()==null){
System.setSecurityManager (new RMISecurityManager ());
}
Registry reg = LocateRegistry.createRegistry (rmiport);
Figura y. Secvenţa de cod pentru initializare security manager şi rmiregistry
De asemenea clasa ControlersServer defineşte metoda registreControler (ControlerEngine ctr) prin intermediul căreia un controler este înregistrat în sistem (folosind metoda Naming.rebind(...)) şi poate fi accesat de la distanţă, şi metodaunregistreControler(String ctrlName) prin intermediul căreia un controler poate fi şters din sistem (folosind metoda Naming.unbind(...)).
Clasa ControlEngine defineşte structura obiectelor ce vor putea fi apelate de la distanţă, ea implementând interfaţa Controler în cadrul căreia sunt declarate metodele accesibile de la distanţă. Această clasă este de tip fir de execuţie şi permite rularea în cadrul firului a unui algoritm de control. Setarea algoritmului de control se face prin intermediul metodei setAlgorithm(ControlAlgorithm alg) care poate fi apelată de la distanţă de către clienţi distribuiţi. Clientul are posibilitatea de a defini proprii algoritmi de control, şi de a-i instala în cadrul controlerului, el trebuind să apeleze la distanţă metoda setAlgorithm(ControlAlgorithm alg) transmiţând ca parametru al metodei un obiect ce implementează interfaţa ControlAlgorithm.
In figura 5 este prezentată structura metodei run() ce se execută în cadrul firelor de tip ControlerEngine.
public void run(){
nextState = RUNNING;
while(active){
synchronized(look){
algorithm.executeStep();
if(nextState == PAUSED)
try {
look.notify();
look.wait();
nextState = RUNNING;
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
try{Thread.sleep(1000);}catch(Exception e){e.printStackTrace();}
}
}
Figura 5. Metoda run() din cadrul clasei ControlEngine.
In figura 6 este prezentă metoda setAlgorithm(ControlAlgorithm alg) apelabilă de la distanţă de către clienţi distribuiţi prin intermediul mecanismului RMI.
public void setAlgorithm(ControlAlgorithm alg) {
if(controlerThread==null){
controlerThread = new Thread(this);
controlerThread.start();
}
else{
nextState = PAUSED;
synchronized(look){
try {
look.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}//.else
synchronized(look){
this.algorithm = alg;
nextState = RUNNING;
look.notify();
}
}
Figura 6. Metoda setAlgorithm(ControlAlgorithm alg) din cadrul clasei ControlEngine, apelabilă de la distanţă.
In figura 7 este prezentată diagrama UML a claselor aplicaţiei client.
Figura 7. Diagrama UML a claselor pentru aplicaţia client.
Aplicaţia client implementează unul sau mai mulţi algoritmi de control pe care îi poate încărca în cadrul controlerelor ce rulează în alte locaţii. Comunicaţia între client şi controler se face prin intermediul mecanismului RMI, aplicaţia client putând obţine o referinţă a unui controler, pentru care apoi să seteze un alt algoritm de control prin apelarea metodei setAlogirthm(ControlAlgorithm).
In figura 8 este prezentate secvenţele de instrucţiuni necesare pentru un client pentru a seta un algoritm pe n controler distribuit.
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
Controler ctrl = (Controler)Naming.lookup(remoteObjectName);
ctrl.setAlgorithm(new SimpleControlAlgorithm(args[1]));
Figura 8. Secvenţă de instrucţiuni pentru setarea unui algoritm de control.
Exerciţii
Importaţi în mediul Eclipse proiectul ce exemplifică noţiunile prezentate în acest laborator (link proiect). Aplicaţia pentru acest proiect este formată din două proiecte: un proiect pentru aplicaţia server RMI şi un proiect pentru aplicaţia client RMI.
Compilati si executati aplicatiile server si client RMI.
Compilare aplicatie server
- Compilati aplicatia server (direct din mediul Eclipse sau din linia de comanda).
- Generarea fisierului stub folosind utilitarul rmic. Din directorul radacina al proiectului laborator4_rmi_server se executa comanda:
rmic –v1.2 –classpath . lab.scd.rmi.server.RouteFinderEngine
Compilare aplicatie client
- Compilati aplicatie client (direct din mediul Eclipse sau din linia de comanda).
Lansare in executie aplicatie server.
- Adaugati in directorul radacina al proiectului server fisierul cu numele java.policy cu urmatorul continut:
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.io.FilePermission
"c:\\java\\eclipse\\workspace\\laborator4_rmi_client\\-", "read";
permission java.io.FilePermission
"c:\\java\\eclipse\\workspace\\laborator4_rmi_server\\-", "read";
};
- Adaugati in directorul radacina al proiectului fisierul startserver.bat cu urmatorul continut:
java -cp . -Djava.rmi.server.codebase=file:/c:\java\eclipse\workspace\laborator4_rmi_server/ -Djava.rmi.server.hostname=localhost -Djava.security.policy=java.policy lab.scd.rmi.server.RouteFinderEngine
- Lansati in executie aplicatia server executand startserver.bat
Lansare in executie aplicatie client.
- Adaugati in directorul radacina al proiectului client fisierul java.policy cu urmatorul continut
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.io.FilePermission
"c:\\java\\eclipse\\workspace\\laborator4_rmi_client\\-", "read";
permission java.io.FilePermission
"c:\\java\\eclipse\\workspace\\laborator4_rmi_server\\-", "read";
};
- Adaugati in directorul radacina al proiectului fisierul startclient.bat cu urmatorul continut:
java -cp . -Djava.rmi.server.codebase=file:/c:\java\eclipse\workspace\laborator4_rmi_server/ -Djava.security.policy=java.policy lab.scd.rmi.client.RmiClient localhost
- Lansati in executie aplicatia client executand startclient.bat
Exrciţiul 2
Construiţi o aplicaţie RMI pe baza descrierii aplicaţiei date în cadrul paragrafului Exemplu aplicaţie RMI.
Manageri de securitate
1. Obiectivul lucrării
Scopul lucrării este de presenta mecanismul de securitate java folosit pentru controlul drepturilor aplicaţiilor.
2. Noţiuni teoretice
Securitatea este o problemă importantă atunci când programele interacţionează în cadrul unei reţele. Unul dintre mecanismele pe care java le pune la dispoziţie pentru creşterea securităţii aplicaţiilor îl reprezintă managerii de securitate. Un manager de securitate implementează şi impune restricţii de securitate pentru aplicaţia în cadrul căreia este instalat.
Un manager de securitate este un obiect integrat în cadrul aplicaţiei, care poate bloca anumite operaţii care nu sunt permise pentru respectiva aplicaţie.
Observaţie: Pentru o aplicaţie, un manager de securitate poate fi instalat doar o singură dată. Daca se încearcă instalarea pentru a doua oara a unui manager de securitate va rezulta excepţia SecurityException.
Pentru a obţine managerul de securitate pentru o aplicaţie se utilizează metoda getSecurityManager() din cadrul clasei System.
SecurityManager sm = System.getSecurityManager();
Daca o aplicaţie nu are instalat nici un manager de securitate atunci metoda getSecurityManager() returnează null. In mod implicit o aplicaţie nu are nici un manager de securitate instalat.
Clasa SecurityManager conţine un set de instrucţiuni checkxxx() ( checkAccess(), checkExit()m etc. ). Aceste metode sunt apelate automat de către diverse metode din cadrul librăriilor java înainte de a efectua anumite operaţii. Aceste metode de verificare a permisiunilor, în momentul când sunt apelate, se termină normal daca operaţia este permisă, sau aruncă o excepţie SecurityException, daca operaţia respectivă nu este permisă.
Pentru a scrie un manager de securitate trebuie extinsă clasa SecurityManager. Această clasă va rescrie o parte din metode checkxxx() pentru a restricţiona operaţiile pe care aplicaţia le poate efectua.
3. Scrierea unui manager de securitate
In această secţiune este prezentat modul în care se poate construi un manager de securitate.
Pentru a construi un manager pentru securitate trebuie extinsă clasa SecurityManager.
Class MySecurityManager extends SecurityManager
{
…
}
Implementarea standard oferită de clasa SecurityManager pentru metodele checkxxx() aruncă excepţia SecurityException. Aceasta înseamnă că daca nici una dintre metode nu este rescrisă toate operaţiile, care sunt verificate de către managerul de securitate, sunt interzise.
In continuare se vor rescrie metodele din cadrul MySecurityManager care oferă permisiune pentru scrierea în fişier.
public void checkWrite(String f){}
public void checkWrite(FileDescriptor f){}
In aceste condiţii o aplicaţie care va instala managerul MySecurityManager va avea numai drept de scriere în fişier, restul de operaţii verificate de către managerul de securitate ne fiind permise întrucât restul metodelor checkxxx() nu au fost rescrise.
Odată construit un manager de securitate acesta trebuie instalat în cadrul aplicaţiei. Pentru instalare se utilizează metoda setSecurityManager().
try {
System.setSecurityManager(new MySecurityManager());
} catch (SecurityException se) {
System.out.println("SecurityManager already set!");
}
4. Alegere metodelor ce trebuiesc rescrise
In momentul când se doreşte crearea unui manager de securitate partea cea mai dificilă este alegerea metodelor checkxxx() care trebuie rescrise. In această secţiune sunt prezentate metodele care trebuie rescrise pentru a bloca operaţiile verificate de managerul de securitate.
Operatii asupra Aprobate de
sockets
checkAccept(String host, int port)
checkConnect(String host, int port)
checkConnect(String host, int port, Object executionContext)
checkListen(int port)
threads
checkAccess(Thread thread)
checkAccess(ThreadGroup threadgroup)
class loader
checkCreateClassLoader()
file system
checkDelete(String filename)
checkLink(String library)
checkRead(FileDescriptor filedescriptor)
checkRead(String filename)
checkRead(String filename, Object executionContext)
checkWrite(FileDescriptor filedescriptor)
checkWrite(String filename)
system commands
checkExec(String command)
interpreter
checkExit(int status)
package
checkPackageAccess(String packageName)
checkPackageDefinition(String packageName)
properties
checkPropertiesAccess()
checkPropertyAccess(String key)
checkPropertyAccess(String key, String def)
networking
checkSetFactory()
windows
checkTopLevelWindow(Object window)
Exerciţii
Importaţi în mediul Eclipse proiectul ce exemplifică noţiunile prezentate în acest laborator (link proiect). Testaţi aplicaţia din cadrul acestui pachet.
Baze de date (JDBC)
1. Scopul lucrării
Scopul aceste lucrări este de a prezenta tehnologia JDBC pentru lucrul cu baze de date.
2. Consideraţii teoretice
O bază de date reprezintă o modalitate de stocare persistentă a informaţiilor pe un suport fizic cu posibilitate de regăsire a acestora ulterior. Cel mai cunoscut model de baze de date este cel relaţional în care datele sunt memorate sub formă de tabele. Bazele de date relaţionale mai conţin pe lângă tabele funţii şi proceduri, mecanisme de gestionare a utilizatorilor, tipuri de date, etc.
Printre cei mai cunoscuţi producători de baze de date se numără: Oracle, Microsoft, Sybase, IBM.
Pentru lucrul cu baze de date Sun a dezvoltat Java Database Connection API. JDBC a fost creat astfel încât să simplifice lucrul cu baze de date în java. JDBC este o interfaţă API (Application Programming Interface) care permite unui programator java accesul la un SGBD (Sistem de Gestiune a Bazelor de Date). Arhitectura JDBC este prezentată în cadrul figurii 1.
Figura 1. Arhitectura JDBC.
Una dintre problemele care apare atunci când se lucrează cu baze de date este reprezentată de incompatibilităţile dintre diverşi producători. Deşi există limbaj standard (SQL – 92), totuşi utilizatorul trebuie să ştie cu ce tip de bază de date lucrează. JDBC a fost proiectat astfel încât să fie independent de tipul de bază de date cu care se lucrează.
Accesarea unei baze de date folosind JDBC este simplă şi implică următorii paşi:
- Obţinerea unui obiect de tip Connection ce încapsulează conexiunea la baza de date (în acest pas se realizează deci conexiunea la baza de date).
- Obţinerea unui obiect Statement dintr-un obiect de tip Connection. Acest obiect este folosit pentru a transmite spre execuţie comenzi SQL către baza de date. Prin intermediul acestui obiect sunt efectuate operaţii de interogare şi modificare a bazei de date.
- Obţinerea unui obiect ResultSet dintr-un obiect Statement. Obiectul ResultSet încapsuleaz rezultatele operaţiilor de interogare.
- Procesarea rezultatelor încapsulate în obiectul ResultSet.
Folosind tehnologia JDBC pot fi structurate modele de aplicaţii software pe două şi trei nivele.
Modelul pe două nivele implică comunicarea directă între aplicaţia java şi baza de date. Aplicaţia utilizator trimite direct către baza de date secvenţe de instrucţiuni SQL ce sunt executate, iar rezultatele sunt întoarse către client. Baza de date poate fi localizată pe acelaşi calculator cu aplicaţia client sau pe un calculator aflat la distanţă, comunicarea realizându-se prin reţea (în acest caz aplicaţia poate fi considerată o aplicaţie de tip client server).
Figura 2. Arhitectura pe două nivele.
În cadrul modelului pe trei nivele comenzile sunt trimise către un nivel de mijloc (intermediar), care va trimite comenzile către baza de date. Baza de date procesează comenzile şi transmite rezultatele către nivelul intermediar care mai apoi le va trimite către client. Avantajele acestui model sunt: controlul mai bun al accesului, se simplifică instalarea aplicaţie (la nivelul clientului nu este nevoie să se instaleze o aplicaţie complexă ce consumă resurse, de cele mai multe ori accesul realizându-se prin intermediul unei aplicaţii de tip WEB), performanţe mai bune, mentenanţă mai uşoară, felexibilitate.
Figura 3. Arhitectura pe 3 nivele.
3. Conectarea la o bază de date
In continuare sunt prezentaţi principalii paşi care trebuie realizaţi pentru a realiza o conexiune la o bază de date din java.
Aplicaţiile din cadrul acestui laborator vor utiliza o bază de date MySQL, iar driverul JDBC corespunzător poate fi descărcat de aici.
Pentru a testa aplicaţiile se recomandă instalarea aplicaţiei UniServer 3.2 ce conţine un server MySQL şi un server Apache.
Incarcarea driverului
Primul pas necesar pentru a putea lucra cu o bază de date este încărcarea driverului bazei de date la care urmează să se realizeze conexiunea. În funcţie de tipul bazei de date va trebui selectat driverul JDBC corespunzător acesteia. În mod uzual pe site-ul oficial al producătorilor de sisteme de baze de date se regăseşte si driverul JDBC pentru conectarea din java la acea baza de date.
Incarcarea driverului este foarte simplă şi implică o singură instrucţiune. Presupunând că se utilizează driverul JDBC MySQL pentru conectarea la o bază de date MySQL atunci codul care realizează încărcarea driverului este următorul:
Class.forName("com.mysql.jdbc.Driver");
Driverul JDBC încărcat are rolul de a converti comenzile SQL într-un anumit protocol specific unui anumit SGBD.
Şirul de caractere dat ca argument la metoda forName reprezintă clasa de tip driver. Acest şir îl regăsiţi de obicei în documentaţia driverului. După încărcarea driverului se poate realiza conexiunea la baza de date.
Realizarea conexiunii
Pentru realizarea conexiunii cel mai important parametru este URL-ul bazei de date. URL-ul identifică baza de date la care urmează să se realizeze conexiunea.
Exemplu:
jdbc:mysql://localhost:3306/contacts/
jdbc:odbc:people
URL-ul conţine următoarele componente:
- Componenta care specifică utilizarea driverului jdbc.
- Componenta care specifică mecanismul de conectare la baza de date.
- Identificatorul bazei de date. Acesta reprezintă un identificator – un nume logic – care este mapat de către softul de administrare al bazei de date la baza de date fizică.
Următorul cod realizează conexiunea cu baza de date:
Connection con = DriverManager.getConnection(url,"login", "password");
public class TestConectare {
public static void main(String[] args) {
try {
//incarcare driver petru baza de date
Class.forName("com.mysql.jdbc.Driver");
//conectare la baza de date
Connection conn = DriverManager.getConnection("jdbc:mysql://10.3.4.1/persoane?user=student&password=pas123”);
System.out.println("Conexiune la baza de date realizata.");
//inchide cnexiune la baza de date
conn.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
4. Operatii asupra unei baze de date
Pentru a putea efectua operaţii asupra unei baze de date la care s-a realizat conexiunea se lucrează cu un obiect Statement. Acest obiect se obţine din cadrul obiectului Connection astfel:
Statement stat = con.createStatement();
Din acest moment obiectul ‘stat’ va fi utilizat pentru a efectua operaţii asupra bazei de date.
Creare tabel
Se utilizează metoda executeUpade() care va primi ca parametru un String care reprezintă o comandă SQL validă pentru crearea unui tabel.
stat.executeUpdate(“CREATE TABLE PERS (NUME VARCHAR(32), VARSTA INTEGER)”);
Introducere date în tabel
Pentru adăugarea de înregistrări în cadrul unui tabel se utilizează aceiaşi metodă executeUpdate().
stat.executeUpdate(“INSERT INTO PERS VALUES(‘ADI’, 15)”);
Citirea conţinutului unui tabel
Pentru citirea conţinutului unui tabel se utilizeză metoda executeQuery() a clasei Statement.
ResultSet result = stat.executeQuery("SELECT PROD,PRET FROM STOC");
Rezultatul interogării bazei de date va fi returnat într-un obiec de tip ResultSet. Pentru a parcurge , linie cu linie, a rezultatelor interogării se realizează utilizând metoda next() din cadrul obiectului ResultSet.
In cadrul programului următor este prezentat exemplu care realizează operaţiile de creare de tabel, inserare de înregistrări şi interogare a unui tabel.
import java.sql.*;
public class JdbcTest {
public static void main(String[] args) {
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:db1");
Statement stat = con.createStatement();
stat.executeUpdate("CREATE TABLE STOC (PROD VARCHAR(32), PRET INTEGER)");
stat.executeUpdate("INSERT INTO STOC VALUES ('PROD1' , 2500)");
stat.executeUpdate("INSERT INTO STOC VALUES ('PROD2' , 7900)");
ResultSet result = stat.executeQuery("SELECT PROD,PRET FROM STOC");
while(result.next()){
String prod = result.getString(1);
int pret = result.getInt(2);
System.out.println("Produs = "+prod+" "+"Pret = "+pret);
}
}
catch(Exception e){e.printStackTrace();}
}
}
5. Alte operaţii cu baze de date
Utilizarea clasei PreparedStatement
Clasa PreparedStatement extinde clasa Statement. Spre deosebire de Statement, un obiect PreparedStatemtn primeşte o comandă SQL în momentul în care este creat. Un obiect PreparedStatement reprezintă o comandă SQL precompilată. Această clasă se utilizează in momentul în care se doreşte executarea unei comenzi de mai multe ori, intrucât cresc performanţele (viteza de execuţie) , deoarece comanda SQL este precompilată în momentul creerii obiectului PreparedStatement.
PreparedStatement ps = con.prepareStatement("INSERT INTO STOC VALUES(?,?)");
ps.setString(1,"PROD 3");ps.setInt(2,6000);
ps.executeUpdate();
ps.setString(1,"PROD 4");ps.setInt(2,9000);
ps.executeUpdate();
Utilizarea procedurilor stocate
Procedurile stocate reprezintă un set de comenzi SQL (comenzi de updatare şi / sau interogare) care se vor executa împreună. Procedurile stocate sunt suportate de majoritatea DBMS-urilor dar pot apărea variaţii în ceea ce priveşte sintaxa.
In continuare este prezentat modul în care se creeză o procedură stocată utlizând JDBC.
String pstoc="CREATE PROCEDURE DISP_TABLE"+
"AS "+
"SELECT PROD, PRET FROM STOC";
Statement st = con.createStatement();
st.executeUpdate(pstoc);
In acest moment procedura DISP_TABLE va fi compilată şi stocată în cadrul bazei de date, ea putând fi apelată ori de câte ori este nevoie.
Executarea unei proceduri stocate se realizează astfel:
CallableStatement cs = con.prepareCall("{call DISP_TABLE}");
ResultSet result = cs.executeQuery();
6. Facilităţi introduse de JDBC 2.0
Deplasarea cursorului în cadrul unui obiect ResultSet
Pachetul java.sql introdus în cadrul JDK 1.2 introduce o serie de noi facilităţi, o parte dintre aceste facilităţi fiind prezentate în cadrul acestei secţiuni.
Dacă în cadrul JDBC 1.0 deplasarea cursorului în cadrul unui obiect ResultSet se putea face doar înainte, utilizând metoda next(), JDBC 2.0 oferă noi facilităţi de deplasare în cadrul ResultSet.
Liniile următoare arată modul de crearea a unui ResultSet în cadrul căruia cursorul se poate deplasa atât înainte cât şi înapoi.
Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
Metoda createStatement primeşte doi parametri. Primul parametru poate lua valorile
TYPE_FORWARD_ONLY
, TYPE_SCROLL_INSENSITIVE
şi TYPE_SCROLL_SENSITIVE. Cel de al doilea parametru poate lua valorile CONCUR_READ_ONLY
şi CONCUR_UPDATABLE.
Odată obţinut obiectul ResultSet se pot folosi următoarele metode pentru deplasarea cursorulu : absolute(), afterLast(), beforeFirst(), first(), next(), previous(), relative(row).
Modificarea conţinutului unui obiect ResultSet
O nouă facilitate introdusă de JDBC 2.0 este abilitatea de a modifica liniile din cadrul unui obiect ResultSet. O dată realizate modificările , baza de date va putea fi reîmprospătată conform acestor modificări.
rs.absolute(2);
rs.updateString(1,"AltProdus");
rs.updateInt(2,6700);
rs.updateRow();
Secvenţa anterioară prezintă modul în care se poate realiza reînprospătarea unei linii în cadrul unui ResulSet. Se observă că după ce linia a fost modificată, pentru ca aceste modifcări să se realizeza şi în cadru bazei de date, se apelează metoda updateRow().
Inserarea unei linii în cadrul bazei de date, utilizând JDBC 1.0 se realiza astfel:
stat.executeUpdate(”INSERT INTO STOC VALUES(”PRODX”,9800)”)
Aceiaşi operaţie se poate realiza utlizând JDBC 2.0 astfel:
rs.moveToInsertRow();
rs.updateString("PROD", "PRODX");
rs.updateInt(2, 9800);
rs.insertRow();
Pentru a modifica o înregistrare se pot folosi atât numele coloanelor cât şi numărul acestora. Numărul coloanei din cadrul ResultSet nu are nici o legătură cu numărul coloanei din cadrul bazei de date.
In momentul în care se execută moveToInsertRow(), obiectul ResultSet memorează linia pe care se afală cursorul, după care adaugă o nouă linie şi mută cursorul pe linia respectivă. In aceste condiţii după ce s-a adăugat o linie , execuţia metodei moveToCurrentRow(), determină mutarea cursorului pe linia la care se afla înainte de inserarea noii linii.
Stergerea unei linii se realizează astfel:
rs.absolute(4);
rs.deleteRow();
Pentru a vedea cea mai recentă valoare a unei linii din cadrul bazei de date, se utilizează metoda refreshRow().
import java.sql.*;
public class DBWork{
public static void main(String[] args) {
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con = DriverManager.getConnection("jdbc:odbc:db1");
Statement stat = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stat.executeQuery("SELECT * FROM STOC");
//modificarea liniei 2 din cadrul rs
rs.absolute(2);
rs.updateString(1,"AltProdus");
rs.updateInt(2,6700);
rs.updateRow();
//adauga linie
rs.moveToInsertRow();
rs.updateString(1, "PRODX");
rs.updateInt(2, 9800);
rs.insertRow();
//sterge linie
rs.absolute(4);
rs.deleteRow();
rs.afterLast();
while(rs.previous()){
System.out.println(rs.getString(1)+" "+rs.getInt(2));
}
rs.close();
}
catch(Exception e){e.printStackTrace();}
}
}
Exerciţii
Importaţi în mediul Eclipse proiectul ce exemplifică noţiunile prezentate în acest laborator (link proiect).
Pachetul lab.scd.db.jdbc10 conţine clase ce exemplifică operaţiile principale ce pot fi realizate asupra unei baze de date folosind clasele JDBC API.
Pachetul lab.scd.db.jdbc20 conţine clasele ce exemplifică functionalitatile adaugate in JDBC 2.0 API pentru lucrul cu baze de date.
Pachetul lab.scd.db.tableview demonstrează modul în care se poate construi o aplicaţie ce afiseaza intr-un tabel grafic continutul unui tabel din cadrul unei baze de date.
Pachetul lab.scd.db.movecursor demonstrează modul în care se poate construi o aplicaţie ce permite parcurgerea înregistrărilor dintr-un tabel, modificarea acestora si adăugarea de noi înregistrări. Aplicaţia conţine de asemenea o interfaţă grafica.
Niciun comentariu:
Trimiteți un comentariu