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.