marți, 1 decembrie 2009

Cum ne configuram mediul de development?

In acest post incerc sa descriu o configurare posibila a mediului de development care se apropie foarte mult de mediul de productie. Desi este putin mai mult de lucru beneficiile sunt enorme: sansele de a obține un comportament diferit pe mediul de producție(mi s-a întâmplat de multe ori asta). Pentru tehnologiile open source precum java, python și php această metodă este eficientă.

Primul pas este să instalați un server apache pe mediul de development și să îl configurați să suporte virtual host-uri (așa sunt configurate toate mediile de producție). Vom presupune că aplicația la care lucrăm se numește: myexample.com (va fi accesată în producție prin www.myexample.com).

Simulare DNS:

În fișierul hosts adăugăm următoare intrare:

127.0.0.1 localhost.myexample.com

În funcție de sistemul de operare folosit fișierul hosts poate fi găsit în:
  • Windows: %WINDOWS%/system/drivers/etc/hosts
  • Linux / Unix / OS X: /etc/hosts
Soluție pentru aplicație java

Plasăm liniile de mai jos într-un fișier în interiorul directorului de configurare apache. O să îl numim virtualhosts.conf.

NameVirtualHost *:80

<VirtualHost *:80>
ServerAdmin rcosnita@myexample.com
ServerName localhost.myexample.com

ProxyRequests Off
ProxyPreserveHost On

ProxyPass / http://localhost:8080/myexample.com/
ProxyPassReverse / http://localhost:8080/myexample.com/
</VirtualHost>

În httpd.conf, la sfârșitul fișierului, adăugam Include "virtualhosts.conf".

Soluție pentru aplicație php:

Procedăm la fel ca și în cazul aplicațiilor java. VirtualHost-ul arată un pic diferit:

ServerAdmin rcosnita@myexample.com
ServerName localhost.myexample.com
DocumentRoot /Users/raduviorelcosnita/Proiecte/myexample.com

DirectoryIndex index.php

AllowOverride All
Options +Indexes +FollowSymLinks
Order allow,deny
Allow from all

Partea bună este că reușim să testăm și partea de rescriere url utilizând fișierul .htaccess. Este la fel ca în mediul de producție.

Soluție pentru aplicație python:

Pentru partea de python, lucrurile sunt un pic mai complicate deoarece există multe moduri de rulare în producție. Întrucât eu folosesc django și wsgi module o să descriu această modalitate de deploy:

ServerAdmin rcosnita@myexample.com
ServerName localhost.myexample.com
DocumentRoot /Users/raduviorelcosnita/Proiecte/myexample.com

Order allow,deny
Allow from all

WSGIDaemonProcess myexample.com processes=2 threads=15 display-name=%{GROUP}
WSGIProcessGroup myexample.com
WSGIScriptAlias / /Users/raduviorelcosnita/Proiecte/myexample.com/src/mysite/apache/django.wsgi

Order allow,deny
Allow from all

Aici sunt multe detalii de povestit, dar ceea ce facem este să mapăm handler-ul de fișiere python pentru aplicația noastră django. Pentru a face refresh la aplicație, trebuie să executăm următoare instrucțiune pe linux: touch django.wsgi

Din câte știu, este singura modalitate de deploy a unei aplicații python care nu necesită restartarea server-ului apache.

Încheiere

Indiferent de tehnologia folosită pentru a dezvolta aplicația, tot ce va trebui să faceți în browser este să introduceți url-ul: http://localhost.myexample.com/. Cu siguranță această metodă de acces a aplicației este mult mai aproape de mediul de producție decât varianta clasică de acces direct pe server-ul dedicat: tomcat / jboss pentru java sau server-ul propriu django pentru python.

miercuri, 2 septembrie 2009

Python Decorators

In unele privinte decorator-urile in python sunt asemanatoare cu annotarile din java. Totusi, ele ofera o flexibilitate sporita si o usurinta remarcabila in implementare. Modul de implementare al unui decorator este valabil pentru Python 2.6+. Exemplul de mai jos este luat din documentatia python: implementarea pattern-ului Singleton.

class singleton:
def __init__(self, aClass):
self.__instance = None
self.__aClass = aClass
def __call__(self, *args):
if not self.__instance:
self.__instance = self.__aClass(*args)
return self.__instance

@singleton
class TestSingleton:
def __init__(self):
print("Instantiez")

objTest = TestSingleton()
objTest = TestSingleton()

Pornind de la acest exemplu, voi explica bazele unui decorator. Un decorator este orice clasa care are metoda __init__ cu un parametru si metoda __call__. Parametrul de la metoda __init__ reprezinta o clasa sau o functie(depinde de scop: decorator de clasa sau de functie). De obicei in metoda __init__ se va referinta la clasa iar in metoda __call__ vine logica efectiva. Referitor la logica codului de mai sus, daca va uitati in __dict__ clasei veti putea observa _singleton__instance. In principiu toate atributele unui decorator sunt salvate in dictionarul clasei si urmeaza conventia de name mangling pentru atributele "private".

In practica am folosit decorator pentru a implementa mecanisme de cache pentru o clasa. Exista multe aplicatii pe care vi le puteti imagina. De obicei, utilizarea decorators sporeste lizibilitatea codului.

luni, 24 august 2009

Richfaces and JQuery

Richfaces este un framework bazat pe JSF care ofera o gama larga de componente "ajax-enabled". Componentele din acest framework utilizeaza libraria javascript prototype. Din acest motiv trebuie sa aveti grija in momentul in care doriti sa folositi JQuery. In acest weekend, am avut nevoie sa folosesc componenta dataScroller din richfaces precum si galeria foto lightbox(plugin pentru jquery). Nu mica mi-a fost mirarea in momentul in care componenta dataScroller nu mergea asa cum era de asteptat(linkurile paginilor nu mergeau si apareau erori javascript). Dupa ce am cautat pe internet si am citit si in referinta oficiala am aflat ca trebuie sa importam jquery intr-un mod specific richfaces:

<a4j:loadScript src="resource://jquery.js" >

In functiile javascript, daca aveti nevoie de functionalitati JQuery, folositi functia jQuery(.....) in loc de $(...). In aceasta maniera orice conflict dintre jQuery si prototype este rezolvat.

miercuri, 19 august 2009

Python SQLAlchemy ORM

Zilele trecute a trebuit sa implementez niste entitati care sa mapeze tabele dintr-o baza de date la o logica obiectuala. In Java, acest lucru il fac utilizand Hibernate. In Python am ales sa folosesc SQLAlchemy. La inceput sintaxa a fost un pic derutanta dar dupa ce m-am obisnuit cu ea a fost extrem de usor sa implementez clasele de care aveam nevoie. Ca backend am folosit o baza de date Oracle XE. In continuare voi arata cum se poate implementa o relatie one-to-many si cum se pot implementa query-uri utilizand SQLAlchemy. Pentru a face lucrurile un pic mai interesante voi utiliza o cheie primara compozita. Voi presupune ca implementam entitatile in pachetul da.

1. In fisierul __init__.py am ales sa instantiez dependentele sqlalchemy ce vor fi folosite in fiecare entitate.

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

BaseEntity = declarative_base()
strDbURL = "oracle://db_tes:xxx@xe"
objDbEngine = create_engine(strDbURL, echo=False)

Session = sessionmaker(bind=objDbEngine)
Session.configure(bind=objDbEngine)

def getConnection():
return Session()

In acest moment putem incepe sa implementam entitatile. Fiecare entitate va extinde BaseEntity. De fiecare data cand vrem sa obtinem o conexiune la baza de date utilizam metoda getConnection(). O sessiune SQLAlchemy este asemanatoare cu o sesiune Hibernate.

2. Definim o clasa utilizator care are ca si cheie primara(username si parola --- nu faceti asa in practica: puteti alege cnp-ul ca metoda de identificare). Aici vreau doar sa demonstrez utilizarea cheilor compozite.

from da import BaseEntity
from sqlalchemy import Column,Integer,String,Numeric,DateTime,ForeignKey,Sequence
from sqlalchemy.orm import relation, backref

class User(BaseEntity):
username = Column("username", String, primary_key=True)
password = Column("password", String, primary_key=True)
descriere = Column("descriere", String)

def __init__(self, strUsername, strPassword, strDesc):
self.username = strUsername
self.password = strPassword
self.descriere = strDesc

3. In continuare implementam clasa adresa.

from da import BaseEntity
from sqlalchemy import Column,Integer,String,Numeric,DateTime,ForeignKey,Sequence
from sqlalchemy.orm import relation, backref

class Adress(BaseEntity):
id = Column("id", Integer, Sequence("seq_xxx_id"), primary_key=True)
fk_username = Column("fk_username", String, ForeignKey("utilizatori.username"))
fk_password = Column("fk_password", String, ForeignKey("utilizatori.password"))
street = Column("street", String)
number = Column("number", String)

user = relation("User",
foreign_keys=[fk_username, fk_password],
primaryjoin="User.username == Address.fk_username and "
"User.password == Address.fk_password",
backref="adresses")

def __init__(self, strUser, strPasswd, strStreet, strNumber):
self.fk_username = strUser
self.fk_password = strPassword
self.street = strStreet
self.number = strNumber

Observatii: in acest exemplu incerc sa demonstrez puterea si flexibilitatea pe care o ofera SQLAlchemy. In primul rand, in orice entitate SQLAlchemy vom defini structura tabelei mapata la atribute ale entitatii. Utilizarea functiei relation permite implementarea legaturilor de tip one-to-one, one-to-many si many-to-many. De asemenea, utilizarea argumentului keyword backref indica frameworkului sa adauge automat un atribut la capatul celalalt al relatiei.

4. Dupa ce am implementat toate entitatile trebuie sa le si folosesc. In exemplul de mai jos arata o succesiune de linii de cod care demonstreaza functionalitatea entitatilor.

import da
from da import User, Address

# query pentru a selecta toti utilizatorii din bd
objSes = da.getConnection()
for objUsr, in objSes.query(User).all():
print(objUsr.addresses)

# query pentru a conditiona returnarea unui utilizator care are o anumita adresa
for objUsr in objSes.query(User, Address).filter(User.username == Address.fk_username and User.password == Address.fk_password).filter(Address.id == 1).all():
print(objUsr.username)

In concluzie, SQLAlchemy faciliteaza maparea tabelelor in logica obiect(la fel ca orice ORM) dar nu necesita un fisier de configurare separat sau anotari suplimentare(precum Hibernate). De asemenea, scrierea de query-uri este extrem de usoara. Ceea ce nu s-a aratat dar este intuitiv este adaugare de noi entitati in bd. Pentru aceasta folositi objSes.add(objInstance). Folositi add chiar daca vreti sa faceti update(SQLAlchemy va sti ce doriti sa faceti in functie de atributele setate in instanta).

vineri, 7 august 2009

Java si mocking - PowerMock

In unele posturi precedente am vorbit despre cum puteti testa cod python prin eliminarea dependentelor. Acelasi lucru il puteti face si in java desi este un pic mai complicat. Dupa ce am studiat mai multe librarii pentru mock am ajuns la concluzia ca PowerMock se apropie cel mai mult de libraria mocker pentru python. Aceasta librarie aduce imbunatatiri pentru EasyMock iar in ultimele versiuni vine cu suport si pentru Mockito. Printre principalele functionalitati ale PowerMock mentionez:
  • mock pentru functii statice
  • mock pentru functii private
Aceste doua elemente sunt extrem de importante si greu de realizat in alte framework-uri. Pentru a intelege mai bine cum se utilizeaza PowerMock prezint urmatorul exemplu:

class Calculator {
public static int add(int a1, int a2) {
return a1 + a2;
}
}

class Operatii {
public int operatieComplexa(int a1, a2) {
int a = Calculator.add(a1, a2);
return ++a;
}
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({Calculator.class})
class TestOperatii {
@Test
public void testOperatieComplexa() {
PowerMock.mockStatic(Calculator.class);
EasyMock.expect(Calculator.add(1, 2).andReturn(3);

PowerMock.replayAll();

Operatii obj = new Operatii();
int ret = obj.operatieComplexa(1, 2);

PowerMock.verifyAll();

Assert.assertEquals(4, ret);
}
}

Asta e tot. In momentul in care se executa testul in loc sa se foloseasca metoda statica add din clasa Calculator se foloseste o metoda injectata. Cateva elemente interesante sunt enumerate mai jos:
  • @RunWith(PowerMockRunner.class). Indica JUnit sa foloseasca clasa PowerMockRunner pentru rularea testelor(nu merge fara asta datorita faptului ca PowerMock implementeaza un class loader custom).
  • @PrepareForTest({Calculator.class}). In aceasta anotare se enumera clasele pentru care se va face mockStatic.
In concluzie, PowerMock este un framework puternic de testare care implementeaza fazele record/replay/verify asa cum face si mocker pentru python. Pentru mai multe detalli vizitati: http://code.google.com/p/powermock/w/list

miercuri, 29 iulie 2009

Python si servicii web

Recent a trebuit sa apelez din Python un serviciu web. In momentul de fata, "consumarea" serviciilor web din Python este greoaie. O solutie relativ usor de implementat este aceea de construire a unei cereri SOAP. In exemplul urmator voi arata cum poate fi invocata o metoda a serviciului web de pe site-ul infovalutar.ro.


------------------------------------------------------------------------------------------------
import httplib

HOST = "www.infovalutar.ro"
URL = "/curs.asmx"

def SOAP_GetMoneda(strMoneda):
strReq = """<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<getlatestvalue xmlns="http://www.infovalutar.ro/">
<Moneda>%s</Moneda>
</getlatestvalue>
</soap12:Body>
</soap12:Envelope>"""

return strReq % strMoneda

if __name__ == "__main__":
data = SOAP_GetMoneda("USD")

dctHeaders = {"Host" : HOST,
"Content-Type" : "text/xml; charset=utf-8",
"Content-Length" : len(data),
"SOAPAction" : "http://www.infovalutar.ro/getlatestvalue"}

con = httplib.HTTPConnection(HOST)

con.request("POST", URL, data, dctHeaders)
resp = con.getresponse()
print(resp.read())

con.close()
------------------------------------------------------------------------------------------------

Ceea ce nu se arata in acest exemplu este modul in care se parseaza raspunsul. Apelul resp.read() returneaza un string care reprezinta un document xml. Parsarea raspunsului nu este prezentata aici intrucat nu acesta este scopul articolului. Acelasi rezultat poate fi obtinut si prin utilizarea bibliotecilor urllib si urllib2, dar in momentul de fata httplib este libraria recomandata.

luni, 27 iulie 2009

JSF si localizarea

Localizarea este procesul prin care o aplicatie(web sau desktop) poate prezenta continutul in mai multe limbi. Este un subiect de actualitate si nu exista inca un standard despre cum ar trebui implementat. In Java/JSF se poate folosi clasa ResourceBundle din pachetul java.util. Pasii care trebuie urmati sunt prezentati printr-un exemplu.

1. Se creeaza doua fisiere de resurse(limba engleza si limba romana)

In fiecare dintre ele se adauga o cheie lbWelcome=........ cu textul in engleza, respectiv romana.

2. se modifica fisierul faces-config.xml pentru a indica limbile suportate pentru localizare.

<application>
<locale-config>
<default-locale>ro< /default-locale>
<supported-locale>ro< /supported-locale>
<supported-locale>en< /supported-locale>
< / locale-config>
< /application>

3. Se creeaza o pagina jsp pentru pentru prezentarea continutului.

<%@page pageEncoding="UTF-8" contentType="text/html; charset=ISO-8859-2" %>
<%@taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>


<
h:form>
<h:commandLink action="#{languageBean.changeLanguage}" value="Romana">
<f:param name="lang" value="ro">
< /h:commandLink>
|
<h:commandLink action="#{languageBean.changeLanguage}" value="English">
<f:param name="lang" value="en">
< /h:commandLink><>

<h:outputText value="#{msg.lbWelcome}">
</h:form>
</f:view>

4. Se creeaza un backbean in care implementez metoda de schimbare limba.

package ro.testlocalization.traduceri;

import java.util.Locale;
import java.util.Map;

import javax.faces.application.Application;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

public class LanguageBean {
public void changeLanguage() {
Map req = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
String lang = req.get("lang").toString();

Locale newLocale = new Locale(lang);
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().setLocale(newLocale);
}
}

5. Se mapeaza backbean-ul in faces-config.xml

6. Asta e tot. In acest moment ar trebui sa puteti rula aplicatia si sa puteti schimba limba de afisare a mesajului.

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.

miercuri, 20 mai 2009

Python mocking __new__

In acest tutorial voi prezenta o facilitate python extrem de interesanta: inlocuirea metodei speciale __new__. Acest lucru mi-a fost util in momentul in care am scris o suita de teste pentru o clasa. Problema era ca aceasta clasa era Singleton si in concluzie era dificil sa ma asigur ca resursele injectate sunt corecte de la un test la altul. Solutia de compromis a fost sa elimin comportamentul Singleton al clasei doar pentru teste.

class TestedClass(float):
def mockedNew(cls, *args, **kwds):
print("Mocked new")
self = float.__new__(*args, **kwds)
return self

def deleteNew(cls):
cls.__new__ = cls.mockedNew

def __new__(cls, arg=0.0):
"Convert from inch to meter"
print("Normal new")
return float.__new__(cls, arg*0.0254)

def __init__(self, arg=0.0):
print("Not working")

deleteNew = classmethod(deleteNew)
mockedNew = classmethod(mockedNew)

if __name__ == "__main__":
TestedClass.deleteNew()
print(TestedClass(12))

In mod normal, in exemplul de mai sus, daca nu as apela deleteNew rezultatul ar fi:
  • Normal new
  • Not working
  • 0.3048
Apeland deleteNew in main obtinem rezultatul:
  • Mocked new
  • Not working
  • 12
In aceasta maniera am reusit sa modific comportamentul metodei speciale __new__ in mod dinamic.

marți, 19 mai 2009

Python si Mocker

In acest tutorial voi prezenta un mod de testare care va ajuta sa eliminati dependentele(de scriere pe disk, de query-uri catre baze de date, etc...) si sa urmariti rularea propriu-zisa a algoritmului. Pentru a putea face acest lucru, trebuie sa permiteti functiei/clasei dumneavoastra sa "injecteze" obiecte in algoritm. In exemplul urmator vom arata cum se poate elimina dependenta de sistemul de fisiere.

def algoritm_complex(objOpen=open):
#aici vine rularea algoritmului

#aici vine partea de dependenta de hdd
f = objOpen("fisier.txt", "w")
f.write("testare simpla")
f.close()

Observam ca in functia de mai sus am furnizat un parametru cu valoare default buitin-ul open. In aceasta maniera in momentul in care functia va fi apelata intr-un mediu de productie nu va fi furnizat nici un parametru in timp ce in momentul in care se va dori testarea algoritmului se fa furniza o functie simulata. In continuare prezint partea de "mocking":

def test_AlgoritmComplex():
controller = Mocker()
objOpen = controller.mock()
fMocked = controller.mock()

objOpen.open("fisier.txt", "w")
controller.result(fMocked)

fMocked.write("testare simpla")

fMocked.close()

controller.replay()

algoritm_complex(objOpen.open)

Asta e tot ce aveti nevoie. Daca functia ar fi returnat un rezultat puteati face un assert pe valoarea returnata doar ca sa va asigurati ca algoritmul a functionat asa cum ati dorit. Pentru mai multe informatii despre acest mod de testare, vizitati site-ul http://labix.org/mocker.

joi, 7 mai 2009

Python si metod statice

Tinand cont ca am fost nevoit sa invat Python la noul loc de munca, am descoperit cateva elemente care se implementeaza un pic mai ciudat decat in Java. Unul din aceste exemple ar fi metodele statice.

In Java, o metoda statica se implementeaza in felul urmator:

public static void helloWorld() { System.out.println("Salutari"); }

In python lucrurile sunt mai complicate si depinde si de versiune folosita. De exemplu, daca folosim o versiune de python anterioara 2.4 atunci vom defini o metoda statica in felul urmator:

def helloWorld(cls):
print("Salutari")

helloWorld = classmethod(helloWorld)

Daca folosim o versiune mai noua de python lucrurile se simplifica deoarece putem folosi un decorator.
@staticmethod
def helloWorld(cls):
print("Salutari")

In ambele cazuri lucrurile sunt mai putin lizibile decat in Java. Oricum, asta este modul de a defini/folosi functii/metode statice in Python.

marți, 21 aprilie 2009

Richfaces panelMenu

Richfaces reprezinta un framework bazat pe JSF extrem de puternic. Pune la dispozitie multe componente si in plus are ajax pentru toate aceste componente. In acest post voi arata cum se poate crea un meniu flotant.

Pasul 1: Se creaza o solutie eclipse de tip Dynamic Web Project versiunea 2.5 si se activeaza JSF 1.2. De asemenea,trebuie sa faceti disponibil framework-ul richfaces asa cum este descris la adresa: http://www.jboss.org/file-access/default/members/jbossrichfaces/freezone/docs/devguide/en/html_single/index.html.

Pasul 2: Se creaza un fisier .jsp si se adauga urmatorul cod:

<%@page pageEncoding="UTF8" contentType="text/html; charset=ISO-8859-2" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://richfaces.org/a4j" prefix="a4j" %>
<%@ taglib uri="http://richfaces.org/rich" prefix="rich" %>

<f:view>
<h:form>

<rich:panelmenu event="onclick" mode="ajax" width="300">
<rich:panelmenugroup label="Meniu test">
<rich:panelmenuitem label="Optiune 1" onclick="window.location.href='test.jsf';">
<rich:panelmenuitem label="Optiune 2" onclick="window.location.href='test.jsf';">
<rich:panelmenuitem label="Optiune 3" onclick="window.location.href='test.jsf';">
</rich:panelMenuGroup>
</rich:panelMenu>

</h:form>
</f:view>


Observatii:

In acest momentant ati obtinut un meniu flotant dar fara nici un fel de functionalitate alta decat fold. De cele mai multe ori veti avea nevoie sa faceti redirectare catre o alta pagina. Cel mai usor este sa folositi: onclick attribute si cod javascript pentru redirectionare. Acestea fiind lamurite ar mai fi de mentionat ce reprezinta: event="onclick" mode="ajax".
Onclick indica framework-ului richfaces sa genereze un meniu flotant care sa faca fold doar in momentul in care se executa click. O alta varianta ar putea fi onmouseover. A doua obtiune tine de modul in care se face submit. Modul predefinit este "server" ceea ce inseamna ca la fiecare click se va face refresh la pagina si se va reface arborele JSF. De asemenea, ca orice componenta din JSF se poate face bind pentru a se face legatura cu un backbean. Tipul de date este de forma: "HtmlPaneMenuXXXXX" unde xxx este: {"", "Group", "Item"}. Pachetul pentru acest tip de date este: org.richfaces.component.html. In acest pachet veti gasi majoritatea componentelor din RichFaces.

luni, 6 aprilie 2009

Configurare subversion + apache2 + ldap

In acest post, descriu cum se poate crea autentificarea pentru un repo de subversion utilizand mod_dav_svn si ldap. Inainte de a descrie in detaliu toti pasii voi presupune ca avem un repo virtual creat cu path-ul: /svn/repos/repo_virtual.

Pe parte de ldap, vom presupune ca avem urmatoarea structura:

dc=example,dc=com
ou=Groups,dc=example,dc=com
cn=group_svn_repo,ou=Groups,dc=example,dc=com
ou=Users,dc=example,dc=com
cn=user1,ou=Users,dc=example,dc=com
cn=user2,ou=Users,dc=example,dc=com

In mod normal asta e tot ce aveti nevoie inainte de configurare apache. Pe scurt, logica este sa facem autentificare pe nodul de users dupa care verificam daca utilizatorul este in grupul specificat. Aici se va descrie configurarea in virtual host, intrucat este cea mai raspandita.


ServerName svn1.example.com
ServerAdmin rcosnita@example.com
ErrorLog /var/log/apache2/error_svn.log
CustomLog /var/log/apache2/access_svn.log combined


DAV svn
SVNPath /svn/repos/repo_virtual/
SVNListParentPath on

AuthBasicProvider ldap

AuthType Basic
AuthName "Example server"
AuthzLDAPAuthoritative off

AuthLDAPURL ldap://svn1.example.com:389/OU=Users,DC=informatix,DC=ro?cn?sub
AuthLDAPBindDN CN=root,DC=example,DC=com
AuthLDAPBindPassword parolamea

AuthLDAPGroupAttribute member
AuthLDAPGroupAttributeIsDN on

Require group cn=group_svn_repo,ou=Groups,dc=informatix,dc=ro



Asta e tot ce trebuie sa faceti. Dupa ce ati terminat dati un restart la serverul de apache si totul ar trebui sa functioneze.

Este important sa tineti minte ca serverul apache face cache in momentul in care se conecteaza la ldap. Asta inseamna ca orice intrare care se adauga in ldap dupa ce apache este conectat nu va fi vazuta pana la restart-ul unuia dintre servere.

Dintr-un motiv care inca mi-e neclar, cand am folosit apache + openldap solutia de mai sus nu a functionat. Din acest motiv, am "trisat" si am adaugat fiecarui utilizator un atribut din schema ldap care sa semnifice grupul. In cazul meu acest atribut a fost labeledURI.

Dupa aceasta am inlocuit: Require group cn=group_svn_repo,ou=Groups,dc=informatix,dc=ro
cu: Require ldap-attribute labeledURI=cn=group_svn_repo,ou=Groups,dc=informatix,dc=ro.

Dupa aceasta modificare totul a functionat fara probleme.

Sper sa gasiti acest post interesant.
Cosnita Radu Viorel!

luni, 9 martie 2009

Configurare Apache2 si Tomcat 6 prin mod_proxy

Această variantă de conectare a celor două servere este una extrem de utilă întrucât folosește doar componente standard fără să fie nevoie să instalați mod_jk. De asemenea, puteți folosi url_rewriting din apache. Acest articol va prezenta in câțiva pași simpli modul în care se poate realiza acest lucru.

1. Configurăm un virtual host în apache

ServerAdmin admin@domeniumeu.ro
ServerName www.domeniumeu.ro
DocumentRoot /calea/catre/site

        
           ProxyRequests off
           ProxyPass / http://server:8081/domeniumeu.ro/
           ProxyPassReverse / http://server:8081/domeniumeu.ro/        

În principiu, în acest moment trebuie doar să restartați serverul de apache și totul este ok. Remarcați faptul că apare portul 8081 în loc de 8080. Acest lucru este datorat faptului că se activează un listener de tomcat pe acel port. Acest lucru este descris în pasul următor al tutorialului. Pe scurt, ceea ce am făcut în acest pas a fost să indicăm serverului web apache să trimită toate requesturile pentru domeniul www.domeniumeu.ro către tomcat.

2. Configurăm un listener în tomcat

      proxyName="www.domeniumeu.ro" 
      proxyPort="80" />

Această configurare se află în secțiunea de din cadrul fișierului $CATALINA_HOME/conf/server.xml. În acest moment tot ce aveți de făcut este să restartați server-ul de tomcat și să accesați din browser: www.domeniumeu.ro. După cum se poate observa, este mult mai ușor să configurați apache 2 + tomcat cu mod_proxy decât prin mod_jk. Singura problema ar fi că prin această metodă se deschid mai multe porturi(câte un port pentru fiecare site găzduit). Această problemă se rezolvă relativ simplu, prin blocare portului din firewall. Este recomandat să procedăm așa întrucât apache-ul va putea să acceseze tomcat-ul întrucât va trimite  un request local.

Sper că acest articol vi se pare interesant,
Coșniță Radu Viorel!

joi, 22 ianuarie 2009

Configurare birt in mediul virtual hosts

In acest post descriu modul in care se poate seta birt-viewer sa functioneze pentru mai multe site-uri gazduite pe acelasi server. Acest lucru este util intrucat administratorul poate administra o singura instanta de birt, si poate face upgrade mult mai usor la mediul de rapoarte. Primul pas ar fi sa instalam runtime-ul pentru birt care poate fi downloadat de pe site-ul: http://download.eclipse.org/birt/downloads/. Dupa ce extragem arhiva downloadata se copiaza folder-ul birt-viewer in $CATALINA_HOME/webapps. In acest moment se poate testa runtime-ul la adresa: http://localhost:8080/birt-viewer. Pentru a putea folosi acest runtime pentru mai multe host-uri virtuale cel mai usor este sa creeam in $CATALINA_HOME/conf/Catalina/<> context.xml.default. Aici se adauga o intrare context asemanatoare cu cea de mai jos:

In acest moment puteti folosi acest context. Asta e tot ce aveti de facut. Intr-un post viitor voi arata cum se pot creea virtual host-uri pentru tomcat.

Cosnita Radu Viorel!