marți, 30 iunie 2009

Servicii web cu jax-ws: Partea 2

Acest post vine ca o continuare pentru prima parte despre serviciile web cu jax-ws. O problema care apare des in serviciile web este aceea a datelor ce pot fi returnate. Un exemplu posibil este urmatorul: Am un EJB care modeleaza o persoana. Cum pot sa il returnez? O abordare naiva ar putea fi aceea de a specifica return type-ul ca fiind clasa EJB-ului. De ce nu este corecta aceasta abodare? In momentul in care clientul primeste obiectul si incearca de exemplu sa acceseze un element dintr-o colectie de tip OneToMany nu va putea face acest lucru(session closed). Ce putem face? Putem scrie un obiect wrapper care sa fie serializabil. In metoda serviciului web construim obiectul wrapper pe baza ejb-ului incarcat. Avem grija sa nu folosim tipuri de colectii de genul List, Set, etc... Vom folosi in schimb vectori normali []. Codul de mai jos exemplifca acest lucru.

@Entity
public class Persoana {
@Id
private long cnp;

@OneToMany(mappedBy="parinte")
private List rude;


//metode getter/setter
}


public class PersoanaWrapper implements Serializable {
private long cnp;

private PersoanaWrapper[] rude;

//metode getter/setter
}


Metoda din webservice care doreste sa returneze o persoana ar putea fi implementat de maniera urmatoare:

public PersoanaWrapper getPersoana(long cnp) {
//cod prin care incarc ejb-ul persoanei dorite. => Persoana persEJB;
PersoanaWrapper pers = new PersoanaWrapper();
int nrRude = persEJB.getRude().size();

pers.setCNP(persEJB.getCNP());
pers.setRude(new PersoanaWrapper[nrRude]);

for(int i = 0; i < nrRude; i++) {
PersoanaWrapper tmp = new PersoanaWrapper();
tmp.setCNP(persEJB.getRude().get(i).getCNP());
pers.getRude()[i] = tmp;
}

return pers;
}

O imbunatatire vizibila a codului de mai sus este reprezentata de adaugarea unei metode/constructor de copiere: "valueOf". Utilizand clase wrapper, puteti sa returnati orice tip de date doriti.
S-a mentionat intr-un post anterior ca in momentul in care un serviciu web este deployat in container el va fi expus si ca Session bean. Este recomandat totusi sa accesati serviciul ori ca session bean ori ca web service(de preferat web service) pentru a fi sigur ca toate metodele sunt functionale.

Cum pot injecta resursa in serviciul web?

De cele mai multe ori este nevoie sa interactionam intr-un fel cu un sistem backend(o baza de date, un fisier txt, etc..) Pentru acest lucru, in Java EE exista PersistenceContext. Contextul de persistenta poate fi injectat usor utilizand urmatoarele linii de cod:

@PersistenContext(unitName="myPersistence")
private EntityManager em;

Contextul de persistenta nu este singura resursa care poate fi injectata intr-un serviciu web. Alte resurse pot fi de asemenea injectat. De exemplu un mail session sau un datasource(pentru bd). Presupun ca avem un DataSource mapat ca: java:jdbc/MyDS. Pentru a-l face disponibil in cod este suficient sa folosesc urmatoarea constructie:

@Resource(mappedName="java:jdbc/MyDS")
private DataSource ds;

Se procedeaza analog pentru orice fel de resursa doriti sa injectati. Intr-un post urmator voi arata cateva strategii pentru securizarea unui serviciu web.

luni, 29 iunie 2009

Servicii web cu jax-ws: Partea 1

In unul din proiectele recente in care am fost implicat am avut nevoie sa implementez o arhitectura SOA(service oriented application). Partea de business era expusa prin intermediul serviciilor web. Aceasta solutie a luat nastere datorita faptului ca una din componente era scrisa in C# in timp ce restul componentelor utilizau JSF/Java EE. Container-ul ales a fost JBoss 5 pentru stratus entreprise(servicii web, ejb) si Tomcat 6 pentru front-end(aplicatia web). Prima problema majora a fost cauza de versiune de JBoss 5 downloadata.

Obs: !!!!!!!!!Daca utilizati jre/jdk 6 downloadati versiune de jboss compilata pentru java 6. Astfel nu veti putea accesa serviciile web, in mare parte datorita unui conflict din saaj.jar din jboss si saaj.jar din jdk 6.

Acum vom presupune ca aveti versiunea corecta de jboss pe calculatorul dumneavoastra. Java EE/jax-ws usureaza foarte mult dezvoltarea de servicii web.

@WebService
@SOAPBinding(style=Style.RPC)
@Stateless
public interface ServiciuTest {
@WebMethod
public String sayHello(@WebParameter(name="message")String msg);
}

@Stateless
@WebService(endpointInterface="ServiciuTest")
public class ServiciuTestBean {
public String sayHello(String msg) {
return msg;
}
}
Asta e tot ce trebuie sa faceti pentru a avea un serviciu web functional. In acest moment serviciul poate fi deployat pe un container care suporta jax-ws. In Java EE serviciul de mai sus va fi expus atat ca serviciu web cat si ca un session bean. Interfata reprezinta de fapt o specificatie pentru serviciul web(endpoint). Retineti faptul ca nu veti putea returna obiecte care nu sunt serializabile.

In continuare voi arata cum se poate implementa un serviciu web care transmite un fisier.

@WebService
@SOAPBinding(style=Style.RPC)
@Stateless
public interface ServiciuFilesTest {
@WebMethod
public byte[] getFile(@WebParam(name="file_name")String fName);
}

@Stateless
@WebService(endpointInterface="ServiciuFilesTest")
public class ServiciuFilesTestBean {
public byte[] getFile(String fName) {
   byte[] deRet = new byte[(int)(new File(fName)).length()];
try {
FileInputStream file = new FileInputStream(fName);
file.read(deRet);
file.close();
}
catch(IOException ioe) {
ioe.printStackTrace();
return null;
}
return deRet;
}
}

Asta e tot. In acest fel putem trimite fisiere de la webservice catre un client. De obicei este recomandat sa encodam continutul trimis in Base64 pentru a garanta faptul ca nu vor aparea probleme.
Scenariul in care am folosit abordarea de mai sus a fost urmatorul: clientul web face o cerere pentru un document(dupa id ci nu dupa cale absoluta). Serviciul web interogheaza baza de date, citeste continutul si il returneaza catre clientul web. Acesta salveaza continutul local si il afiseaza in interfata.
Intr-un post ulterior voi arata cum putem returna date complexe printr-un serviciu web si cum putem folosi injectarea resurselor(specific pentru Java EE).

joi, 4 iunie 2009

java.util.logging si o clasa de logare

In acest post arat cum se poate implementa o clasa logger care poate loga trei tipuri de mesaje:
  • info
  • warning
  • error
Aceste mesaje sunt salvate intr-un fisier de forma: err-"datacurenta". Acest stil de logger este inspirat din logger-ele traditionale din unix/linux. Clasa ce urmeaza sa fie listata mai jos utilizeaza java.util.logging si ar trebui sa fie suficienta pentru majoritatea aplicatiilor. Clasa este un singleton intrucat am nevoie sa o instantiez o singura data.

package ro.example;

import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.logging.*;

public class LoggerSingleton {
private static Logger logger;
private static LoggerSingleton selfInst = null;
private String msgFile = "err%s.log";

private LoggerSingleton() throws Exception {
logger = Logger.getLogger("MyApplication");

this.switchStdErr(this.msgFile);
}

/**
* Metoda folosita pentru a schimba stderr(standard error stream)
* @param fName
* @throws Exception
*/
private void switchStdErr(String fName) throws Exception {
Calendar cal = GregorianCalendar.getInstance();
String file = String.format(fName, cal.get(Calendar.YEAR) + "-" + cal.get(Calendar.MONTH) + "-" + cal.get(Calendar.DAY_OF_MONTH));

PrintStream err = new PrintStream(new FileOutputStream(file, true));

System.setErr(err);
}

public static LoggerSingleton getInstance() throws Exception {
if(selfInst == null)
selfInst = new LoggerSingleton();

return selfInst;
}

/**
* Metoda folosita pentru a scrie un mesaj de tip info
* @param msg
*/
public void writeInfo(String msg) {
try {
logger.log(Level.INFO, msg);
}
catch(Exception ex) {
ex.printStackTrace();
}
}


/**
* Metoda folosita pentru a scrie un warning
* @param msg
*/
public void writeWarning(String msg) {
try {
logger.log(Level.WARNING, msg);
}
catch(Exception ex) {
ex.printStackTrace();
}
}

public void writeWarning(Exception excp) {
try {
logger.log(Level.WARNING, excp.getMessage(), excp);
}
catch(Exception ex) {
ex.printStackTrace();
}
}


/**
* Metoda folosita pentru a loga un mesaj de eroare.
* @param msg
*/
public void writeError(String msg) {
try {
logger.log(Level.SEVERE, msg);
}
catch(Exception ex) {
ex.printStackTrace();
}
}

public void writeError(Exception excp) {
try {
logger.log(Level.SEVERE, excp.getMessage(), excp);
}
catch(Exception ex) {
ex.printStackTrace();
}
}
}

Obs: Metoda log din class Logger scrie mesajele in System.err. Din acest motiv primul lucru pe care il fac este sa redirectionez System.err catre fisierul dorit.

In continuare arat un posibil fisier de test care utilizeaza toate metodele disponibile.

package ro.example;

/**
* @author Radu Viorel Cosnita
* Doar testez logarea fiecarui tip de mesaj definit in logger singleton.
*/
public class TestLoggerSingleton {
public static void main(String[] args) throws Exception {
LoggerSingleton log = LoggerSingleton.getInstance();

log.writeInfo("Doar ca sa ma amuz");
log.writeWarning("Doar un warning");
log.writeError("Doar un mesaj de eroare");

try {
throw new Exception("O eroare draguta");
}
catch(Exception ex) {
log.writeWarning(ex);
log.writeError(ex);
}
}
}

Rezultatul obtinut a fost generarea unui fisier numit: err2009-5-4.log cu urmatorul continut:

Jun 4, 2009 3:27:01 PM ro.example.LoggerSingleton writeInfo
INFO: Doar ca sa ma amuz
Jun 4, 2009 3:27:01 PM ro.example.LoggerSingleton writeWarning
WARNING: Doar un warning
Jun 4, 2009 3:27:01 PM ro.example.LoggerSingleton writeError
SEVERE: Doar un mesaj de eroare
Jun 4, 2009 3:27:01 PM ro.example.LoggerSingleton writeWarning
WARNING: O eroare draguta
java.lang.Exception: O eroare draguta
at ro.example.TestLoggerSingleton.main(TestLoggerSingleton.java:16)
Jun 4, 2009 3:27:01 PM ro.example.LoggerSingleton writeError
SEVERE: O eroare draguta
java.lang.Exception: O eroare draguta
at ro.example.TestLoggerSingleton.main(TestLoggerSingleton.java:16)

In fiecare zi in care se executa/invoca metode din clasa TestLoggerSingleton va fi generat un log separat. Aceasta abordare permite urmarirea evolutiei aplicatie intr-o maniera extrem de simpla. Intr-un post viitor voi arata cum se poate genera un log in format xml in log de plaintext.

luni, 1 iunie 2009

Hibernate session and web application

În acest articol prezint modul în care se poate evita în totalitate folosirea fetch type-ului EAGER în aplicații web. Există un mod recomandat pentru a deschide o sesiune hibernate în aplicații web: acela este de a folosi un filtru care să deschidă sesiunea și să o facă disponibilă pe request. În acest fel orice entitate are nevoie de o sesiune de hibernate validă o folosește pe cea de pe request. De asemenea, în cazul instanțierilor LAZY pentru colecții, în momentul în care accesați un element al colecției nu veți primi eroare session closed. Primul cod prezentat este cel pentru filtrul care deschide o sesiune hibernate.

public class ManageConexiune implements Filter {

private FilterConfig filterConfig;

public void destroy() {}

public void init(FilterConfig fConfig) {

this.filterConfig = fConfig;

}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {

Object obj = request.getAttribute("OPENED_CONNECTION");

Session ses = null;

if(obj != null) {

ses = (Session)obj;

try {

ses.close();

}

catch(HibernateException ex) {

ex.printStackTrace(); //just print the stack trace into log files

}

}

ses = HibernateSingleton.getInstance().openSession();

request.setAttribute("OPENED_CONNECTION", ses);

chain.doFilter(request, response);

}

}


Obs: Clasa HibernateSingleton pune la dispoziție două metode statice: una din ele este getInstance care returneaza un SessionFactory. Cea de a doua metodă statică nu face altceva decât să returneze sesiunea deschisă de pe request.


public class HibernateSingleton {

  private static SessionFactory sessionFactory;

   

  static

  {

  sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();

  }

   

  public static SessionFactory getInstance()

  {

  return sessionFactory;

  }

  public static Session getRequestSession() {

  return (Session)Contexts.getRequest().getAttribute("OPENED_CONNECTION");

  }

}


Astfel, ori de câte ori aveți nevoie de o sesiune validă invocați HibernateSingleton.getRequestSession. Utilizând maniera deschisă aici veți evita multe erori de genul Can't open connection sau Session closed. De asemenea, veți simți o îmbunătățire a performanțelor site-ului și veți obține timpi de acces mai buni.