You are on page 1of 74

Università degli studi di Torino

Facoltà di Scienze Matematiche, Fisiche e Naturali
Corso di laurea in Informatica
Anno accademico 2007/2008

Google App Engine: una soluzione
alternativa per la creazione di web
application

Relatore:
Prof. Matteo Baldoni

Candidato:
Davide Ferrero
Indice

1 App Engine SDK 1
1.1 Funzionamento del kit . . . . . . . . . . . . . . . . . . . . . . 2

2 Servizi offerti da App Engine 3
2.1 Google Account . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2 Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.3 Mail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.4 Memcache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.5 Datastore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.6 Admin Console . . . . . . . . . . . . . . . . . . . . . . . . . . 9

3 Il cloud computing 11
3.1 Salesforce.com . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2 Amazon Web Services . . . . . . . . . . . . . . . . . . . . . . 14

4 Il DBMS di Google: Bigtable 15
4.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.2 Modello di rappresentazione dei dati . . . . . . . . . . . . . . 16
4.2.1 Righe . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.2.2 Famiglie di Colonne . . . . . . . . . . . . . . . . . . . . 16
4.2.3 Timestamp . . . . . . . . . . . . . . . . . . . . . . . . 17
4.3 Struttura interna . . . . . . . . . . . . . . . . . . . . . . . . . 17

5 Un caso di studio: Sellbook 19
5.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.2 Specifica dei requisiti . . . . . . . . . . . . . . . . . . . . . . . 19
5.3 Sviluppo dell’applicazione . . . . . . . . . . . . . . . . . . . . 20
5.4 Ricerca dei casi d’uso . . . . . . . . . . . . . . . . . . . . . . . 21
5.5 Specifiche dei casi d’uso . . . . . . . . . . . . . . . . . . . . . 22
5.6 Interazione utente/applicazione . . . . . . . . . . . . . . . . . 26
5.6.1 Homepage . . . . . . . . . . . . . . . . . . . . . . . . . 26

I
INDICE II

5.6.2 Libri . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
5.6.3 Stanze . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.7 Schema della base dati . . . . . . . . . . . . . . . . . . . . . . 32
5.8 Presentazione degli algoritmi utilizzati . . . . . . . . . . . . . 34
5.8.1 Ricerca . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.8.2 Estrazione dei tag da un titolo . . . . . . . . . . . . . . 34
5.8.3 Ricerca dei tag correlati . . . . . . . . . . . . . . . . . 35
5.8.4 Visualizzazione delle stanze . . . . . . . . . . . . . . . 35
5.9 Considerazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

Appendice A 37
Il file main.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Il file libri.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Il file stanze.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Codice javascript per Google Maps . . . . . . . . . . . . . . . . . . 67
Il file di configurazione app.yaml . . . . . . . . . . . . . . . . . . . 68

Bibliografia e sitografia 70
Capitolo 1

App Engine SDK

Da sempre la Google Inc. cerca di rendere disponibile ai suoi utenti, tutte le
nuove tecnologie che si affacciano al “mondo” dell’informatica.
In questi ultimi anni si è aperta una nuova frontiera sul web: il cloud com-
puting.
Il team di App Engine, fornisce un Software Development Kit (in breve SDK)
per creare applicazioni web, sfruttando le sole potenzialità del linguaggio Py-
thon1 e un semplice framework che consente alle classi software di interagire
con le pagine html. Con App Engine l’azienda californiana di Mountain
View, punta a fornire un valido strumento, in alternativa a strumenti simili,
già messi a disposizione da altre aziende americane leaders nel settore infor-
matico. Questo kit permette a coloro i quali ne usufruiscono, di utilizzare i
servers dell’azienda, e sfruttare, come detto in precedenza, le caratteristiche
del cloud computing, le peculiarità di big table (Il database ad oggetti creato
proprio dagli ingegneri dell’azienda americana). e la moltitudine di servizi
sviluppati da Google Labs, già a disposizione di tutti i possessori di un google
account.
Sono state messe a disposizione le API per il servizio di mail, per quello di
manipolazione delle immagini, per la gestione degli account con Google, e
per l’itegrazione di altri servizi molto comuni agli utenti della rete dei giorni
nostri. È ad esempio possibile integrare i servizi di youtube, calendar, di
docs, contacts, base e picasa web album, oltre naturalmente a tutti quelli già
disponibili in precedenza.
Al primo impatto con questo strumento, si potrebbe erroneamente pensa-
re che non vi sia nessuna novità in questo strumento, e che la creazione di
web application era già possibile numerosi anni addietro. Una delle vere
novità nell’utilizzare questo SDK è quella del poter sfruttare un linguaggio
1
Al momento è il solo linguaggio supportato anche se pare, in futuro, sia prevista
l’apertura ad altri linguaggi.

1
CAPITOLO 1. APP ENGINE SDK 2

come python, senza che questo venga abbinato ad altri linguaggi, per creare
e interagire con pagine web. Tutto ciò abbinato al poter sfruttare i server
google per le proprie applicazioni utilizzando sistemi distribuiti, ma trattan-
doli come sistemi standard e tralasciando gli oneri delle varie configurazioni
e precauzioni, di cui un creatore di siti web deve prendersi carico (progetta-
zione e creazione della base dati in primis).
Con questo kit google rende molto più semplice, intuitiva e veloce la scrittura
di applicazioni web, permette alta scalabilità e offre strumenti di ammini-
strazione utili e funzionali grazie al pannello di controllo disponibile per ogni
applicazione.

1.1 Funzionamento del kit
Il primo passo da compiere per iniziare a creare un’applicazione con questo
kit, è quello di scrivere il file di configurazione. Ogni applicazione, per poter
funzionare, richiede un file denominato app.yaml (Vedere appendice A, file
di configurazione app.yaml, per avere un’esempio concreto).
Questo documento testuale contiene le informazioni base dell’applicazione
(Nome, versione, runtime python necessarie, ecc ecc. . . ),i percorsi delle car-
telle e dei file statici utilizzati dall’applicazione e le corrispondenze tra i path
del sito web che possono essere digitati con i file contenenti le classi python
che catturano la richiesta ed eseguono le operazioni necessarie (Non ultimo
il redirect al documento html appropriato alla pagina richiesta). Fatto ciò si
potrà iniziare a scrivere il primo file python, il quale corrisponderà al percorso
“\”ne file di configurazione appena creato.
Capitolo 2

Servizi offerti da App Engine

2.1 Google Account
All’interno del kit di sviluppo, sono state messe a disposizione delle librerie
per consentire al programmatore di non doversi preoccupare della gestione
del login alla propria web application da parte dei suoi utilizzatori. Con una
semplice funzione si può controllare se l’utente in questione è loggato con
google oppure deve effettuare il login o la registrazione. Per quest’ultimo
caso esiste una funzione (sempre integrata nell’SDK) che crea un link alla
pagina di Google Account, dedicata alla propria applicazione, che una volta
eseguito il login, consente di ritornare ad una determinata pagina voluta. In
questo modo gli utenti non necessitano di creare nuovi account, ma sfruttano
lo stesso utilizzato per gmail, o più in generale per i servizi di Google.
Tutto ciò elimina una buona parte di lavoro al programmatore, il quale non
dovrà preoccuparsi di tutti i problemi relativi al login (sql injection, per fare
un esempio su tutti) e sarà utile all’utente finale perchè non dovrà creare un
nuovo account, e di conseguenza ricordare nuove credenziali d’accesso.
Le applicazioni possono così accedere all’indirizzo email dell’utente, al suo
nickname e stabilire se l’utente loggato può accedere ad aree riservate agli
amministratori per una determinata applicazione o meno.
Con poche e brevi istruzioni si può facilmente riassumere il funzionamento
di google account:

user = users.get_current_user()
if user:
print "Benvenuto, "+user.email()+"!"
print "<a href=\"users.create_logout_url("/")\">Esci</a>"
if users.is_current_user_admin():
print "<a href=\"/admin/\">Admin area</a>"

3
CAPITOLO 2. SERVIZI OFFERTI DA APP ENGINE 4

Figura 2.1: Esempi delle funzioni di manipolazione delle immagini.

else:
print "<a href=\"%s\">Accedi o registrati</a>"
%users.create_login_url("/")

2.2 Images
Molte applicazioni necessitano, per diversi motivi, di manipolare delle imma-
gini. App Engine mette a disposizione le stesse funzionalità usate per Picasa,
il web album di Google.
È infatti possibile ridimensionare, ruotare, ritagliare, riflettere (orizzontal-
mente e verticalmente), selezionare un riquadro più ristretto dell’immagine
e addirittura usare la funzione di miglioramente automatico. Quest’ultima
aggiusta colore, luminosità e saturazione dell’immagine.

2.3 Mail
In alcuni casi risulta utile poter contattare un utente della propria web appli-
cation, che sia per comunicazioni generali o per mettere in comunicazione due
o più utenti tra loro. All’interno dell’SDK vi è la funzione mail.send_mail()
che serve proprio a questi scopi. La funzione riceve come parametri in ingres-
so il mittente, il destinatario, l’oggetto e il corpo del messaggio da inviare.
È anche possibile aggiungere allegati al messaggio di posta elettronica da
CAPITOLO 2. SERVIZI OFFERTI DA APP ENGINE 5

inviare, basterà passare come parametri della funzione, i file che si voglio-
no allegare, e questi verrano inclusi nel messaggio e spediti. Bisogna però
rispettare un elenco di tipi file includibili:
MIME Type Estensione del file
image/x-ms-bmp bmp
text/css css
text/comma-separated-values csv
image/gif gif
text/html htm html
image/jpeg jpeg jpg jpe
application/pdf pdf
image/png png
application/rss+xml rss
text/plain text txt asc diff pot
image/tiff tiff tif
image/vnd.wap.wbmp wbmp

2.4 Memcache
La maggior parte delle applicazioni web che mirano ad avere alte prestazioni
e a gestire grandi quantità di dati, spesso ricorrono all’utilizzo di un servizio
di memorizzazione degli stessi all’interno della cache di un server, in modo
tale da servirli con assoluta immediatezza al momento della loro richiesta e,
soprattutto, senza dover effettuare ogni volta una nuova lettura dal database.
Una cosa importante di cui prendere atto è che questo servizio perde la sua
utilità nel momento in cui i dati sul database cambiano. In questo caso al-
la successiva richiesta, bisognerà provvedere a rieseguire la query e salvarli
nuovamente nella cache del server.
Il servizio di memcache può anche essere usato per caricare valori temporanei
e non accetta necessariamente solo dati letti dal db. Si può usare tranquilla-
mente in alternativa alle variabili di sessione, col vantaggio di non rischiare
che chiunque possa accendere a contenuti riservati, al contrario di quanto
può succedere servendosi delle variabili di sessione. Inoltre queste ultime, nel
caso l’utente non abbia i cookies attivati, non funzionerebbero, al contrario
della memcache che può essere utilizzata indipendentemente dall’attivazione
o meno di questi.
Inoltre il servizio di salvataggio in cache e di reperimento delle chiavi salvate,
può essere utilizzato contemporaneamente da più istanze di una applicazio-
ne, e le chiavi salvate possono essere cancellate da codice, dall’interfaccia di
amministrazione dell’applicazione o possono avere una scadenza settata al
CAPITOLO 2. SERVIZI OFFERTI DA APP ENGINE 6

momento dell’aggiunta in cache del valore. È altresì possibile salvare chiavi
multivalore (ad esempio con un prefisso nel nome uguale, cambiando solo il
suffisso, o viceversa) oppure creare variabili-contatori e incrementarle o de-
crementarle senza dover cancellarne il valore e reinserirlo manualmente. In
aggiunta a tutto ciò viene fornita anche una funziona per ottenere le statisti-
che di utilizzo della cache comprensive di percentuali di successi e insuccessi
delle richieste, numero di oggetti salvati, tempo di permanenza dei vari og-
getti nella cache e numero di byte salvati. Un possibile utilizzo di questo
servizio è rappresentato dal seguente codice python:

greetings = memcache.get("greetings")
if greetings is not None:
return greetings
else:
greetings = self.render_greetings()
if not memcache.add("greetings", greetings, 10):
logging.error("Memcache set failed.")
return greetings

2.5 Datastore
Il datastore di App Engine fornisce un’interprete di query e una memorizza-
zione dei dati tramite transazioni. Il tutto lavora sempre nei sistemi scalabili
di Google. L’interfaccia python fornisce anche uno strumento di modelliz-
zazione dei dati e un linguaggio sql-like chiamato gql1 . Questo datastore
consente di effettuare qualsiasi tipo di operazione su oggetti di dati, i quali
rappresentano le comuni entità. Un’entità possiede una o più proprietà, le
quali rappresentano i valori delle entity e possono appartenere ad una vasta
tipologia, a scelta tra i tipi supportati.
1
L’acronimo sta per Generic Query Language
CAPITOLO 2. SERVIZI OFFERTI DA APP ENGINE 7

Proprietà Valore Ordinamento
StringProperty str Unicode (ASCII)
ByteStringProperty ByteString byte
BooleanProperty bool False < True
IntegerProperty int Numerico
long
FloatProperty float Numerico
DateTimeProperty
DateProperty datetime.datetime Chronological
TimeProperty
ListProperty Se ascendente, dal <
StringListProperty lista(tipi supportati) Se discendente, dal >
ReferenceProperty
SelfReferenceProperty db.Key (tipo, ID o nome, . . . )
UserProperty users.User Per e-mail(Unicode)
BlobProperty db.Blob nessuno
TextProperty db.Text nessuno
CategoryProperty db.Category Unicode
LinkProperty db.Link Unicode
EmailProperty db.Email Unicode
GeoPtProperty db.GeoPt Latitudine, longitudine
IMProperty db.IM Unicode
PhoneNumberProperty db.PhoneNumber Unicode
PostalAddressProperty db.PostalAddress Unicode
RatingProperty db.Rating Numerico
Una proprietà può essere referenziata ad una entità, permettendo la creazio-
ne di relazioni con cardinalità uno-a-molti o molti-a-molti. Può inoltre avere
dei vincoli di validità.
Il datastore può eseguire più istruzioni in una singola transazione ed eseguir-
ne il rollback nel caso in cui qualche operazione non dovesse andare a buon
fine. Questa caratteristica risulta particolarmente utile per le applicazioni
web distribuite, in cui può accadere che più utenti possano manipolare lo
stesso oggetto nello stesso istante. La scrittura sulla base dati è atomica. Le
operazioni di put(scrittura) e di delete(cancellazione) possono andare a buon
fine oppure fallire (per varie ragioni), ma qualunque sia la motivazione, la
modifica viene persa e il contenuto del datastore non cambia.
Per rendere attuabile questo processo basta semplicemente scrivere una fun-
zione contenente le operazioni che si vogliono includere nella stessa transa-
zione, come nell’esempio sottostante:
from google.appengine.ext import db
CAPITOLO 2. SERVIZI OFFERTI DA APP ENGINE 8

class Accumulator(db.Model):
counter = db.IntegerProperty()

def increment_counter(key, amount):
obj = db.get(key)
obj.counter += amount
obj.put()

q = db.GqlQuery("SELECT * FROM Accumulator")
acc = q.get()

db.run_in_transaction(increment_counter, acc.key(), 5)
In una singola transazione, un’applicazione può creare o modificare un’entità
al massimo una sola volta, e non può eseguire query standard o query gql.
Per ottenere un’istanza di una entity si può solamente utilizzare la funzione
get, la quale prende come parametro di input la chiave dell’oggetto deside-
rato. Come detto, ad un’entità viene associata una chiave, che la identifica
univocamente. Questa key può essere recuperata mediante l’apposita funzio-
ne implicita, presente in ogni oggetto letto da db2 .
Un esempio del codice da scrivere per modellare un’entità è il seguente:
class Book(db.Model):
title=db.StringProperty()
author=db.StringProperty()
version=db.IntegerProperty()
year=db.IntegerProperty()
img=db.BlobProperty(required=False)

class SellBook(db.Model):
book=db.ReferenceProperty(Book)
user=db.UserProperty()
price=db.FloatProperty()
dataSell=db.DateTimeProperty(auto_now_add=True)
location=db.GeoPtProperty(required=False)
È inoltre possibile definire classi-entità gerarchiche tramite le Polymorphic
Models API, e quindi avere una o più sottoclassi di una superclasse. Tutto
ciò ovviamente supportato dal fatto che le query supportano e riconoscono le
2
La funzione a cui ci si riferisce è entity.key(), dove con entity si vuole indicare il nome
della variabile rappresentante l’istanza di una determinata entità.
CAPITOLO 2. SERVIZI OFFERTI DA APP ENGINE 9

classi gerarchiche, e possono restituire tutta la superclasse comprensiva dei
dati contenuti nella sottoclasse oppure solamente i dati di una sottoclasse
desiderata.
Il datastore supporta naturalmente anche gli indici. Questi ultimi possono
essere definiti manualmente dallo sviluppatore dell’applicazione tramite un
apposito file di configurazione (denominato index.yaml ). In aggiunta il siste-
ma, riconosce le query complesse all’interno dell’applicazione, e nel caso ve
ne sia la necessità, crea in automatico l’indice necessario. Questa procedura
automatica entra in funzione quando il sistema riconosce query che utiliz-
zano solamente operatori di confronto tutti positivi (o l’operatore sql IN ) o
tutti negativi, oppure query con un solo ordinamento su una proprietà, indi-
pendentemente che esso sia ascendente o discendente. È inoltre consigliabile
creare indici quando nell’applicazione esistono:
• query con molteplici ordinamenti.
• query con ordinamenti decrescenti sulla chiave uno.
• query con più filtri di inuguaglianza su una proprietà e uno o più filtri
di uguaglianza su altre proprietà.
• nel caso di tabelle che sfruttano l’ereditarietà, query con filtri di inu-
guaglianza e filtri su entità-parenti.
In aggiunta, il server utilizzato localmente per sviluppare l’applicazione, crea
gli indici necessari, nel caso in cui le query dovessero fallire e non fossero già
stati creati precedentemente.

2.6 Admin Console
Una volta caricata l’applicazione on-line è a disposizione di tutti gli svilup-
patori una funzionale console di amministrazione.
Essa mette a disposizione una dashboard per verificare l’utilizzo dell’appli-
cazione, con grafici che mostrano le richieste, gli errori, i byte trasmessi e
ricevuti, i secondi di cpu utilizzati e gli eccessi di quota tutti lungo una linea
temporale(in secondi o millisecondi). Inoltre sempre nella stessa pagina vi è
un riassunto generale delle percentuali di quota raggiunte nelle 24 ore pre-
cendenti.
Tramite il menu della console si può accedere alla seconda pagina, ovvero
quella dei riepiloghi sulle varie quote, in particolare vengono forniti ampi
dettagli su:
• Richieste
CAPITOLO 2. SERVIZI OFFERTI DA APP ENGINE 10

• Datastore

• Mail

• Url Fetch

• Manipolazione di immagini (Images)

• Memcache

• Funzionamento dell’applicazione

Nell’ultima pagina del gruppo delle funzioni di riepilogo, si possono tenere
sotto controllo i logs dell’applicazione, ovvero la cronologia di tutte le richie-
ste, gli errori, i debug e gli errori critici in cui è incappata l’applicazione.
Il secondo gruppo di funzioni della console, riguarda il datastore, ed in par-
ticolare è possibile prendere visione degli indici relativi alle varie tabelle e
amministrare le entity del database, visualizzando tutte le istanze presenti
per ogni entità e con la possibilità di eseguire query o cancellare tupla per
tupla di ogni entity.
Il terzo ed ultimo gruppo di funzioni, oltre a permettere di settare il titolo
dell’applicazione, consente di amministrare gli sviluppatori autorizzati per
l’applicazione e di invitarne di nuovi, di gestire le varie versioni della web
app e decidere quale sia quella di default e, in ultimo, consente di prendere
visione dei log della console stessa, ovvero tutte le azioni che gli amministra-
tori dell’applicazione svolgono (ordinati per data e ora). È inoltre possibile
settare un dominio aggiuntivo per l’applicazione nel qual caso essa ne avesse
uno dedicato.
Capitolo 3

Il cloud computing

Il cloud computing è una tecnologia che consente di avere a disposizione ri-
sorse distribuite, (cpu o database ad esempio) le quali vengono viste come
un’unica risorsa. Il termine cloud deriva dall’inglese, significa nuvola e rap-
presenta una implementazione delle risorse, definite in modo sommario, con
caratteristiche sconosciute al reale utilizzatore e viste come un servizio for-
nito su richiesta.
L’architettura prevede server distribuiti che collaborano e vengono utilizzati
come se fossero un unico sistema. I vantaggi sono quelli di avere a disposio-
ne risorse più prestanti o di capacità notevolmente superiori e di garantire
maggiore scalabilità all’applicazione, con pochi oneri da parte del program-
matore. Inoltre un sistema “cloud” possiede una struttura notevolmente più
complessa ma, all’utilizzatore, appare come un sistema normale, che non ne-
cessita di particolari precauzioni.
Il cloud computer si è sviluppato di pari passo con altri concetti, come i Soft-
ware as a Service 1 e il web 2.0. Un esempio molto conosciuto di SaaS sono
i servizi forniti dall’azienda americana Salesforce.com ([19]).
Uno strumento analogo ad App Engine invece, è stato creato da Amazon.com,
con gli Amazon Web Services ([20], i quali vengono visti come un System as
a Service, ovvero sistemi che vengono forniti come se fossero un servizio per
l’utente.

3.1 Salesforce.com
Saleforce è un’azienda americana leader nel settore dei CRM (Customer Rela-
tionship Management), ovvero nei servizi di fidelizzazione del cliente. Questa
società mette a disposizione diversi servizi per le aziende che vogliono crescere
1
Software visti come servizio, o comunemente detti in gergo SaaS.

11
CAPITOLO 3. IL CLOUD COMPUTING 12

Figura 3.1: Schema stilizzato del cloud computing.

nel mercato. Il CRM le aiuta nella gestione dei propri clienti, nella gestione
dei rapporti con essi e nell’acquisizione di nuovi potenziali acquirenti.
Salesforce.com è stata fondata nel 1999 e nel 2008 è stata riconosciuta come
la 43esima azienda più grande al mondo tra le compagnie di sofware. Conta
filiali in diverse parti del mondo, come Dublino, Singapore, Tokyo, Toronto,
New York, Londra, Sydney oltre alla sede principale di San Mateo in Cali-
fornia.
Nel giugno 2004 è stata quotata in borsa alla New York stock exchange (NY-
SE).
Questa società punta a fornire strumenti online che sostiuiscano i vari soft-
ware di cui un’azienda ha bisogno e in particolare, fornisce servizi che consen-
tono di automatizzare i processi di business che prevedono il contatto diretto
con il cliente, migliorare la conoscenza del cliente attraverso l’estrazione dei
dati dal processo precedente e utilizzare tecnologie integrate per la comuni-
cazione con il cliente (telefono, fax e e-mail). Tutto ciò viene fornito senza
l’installazione di software nelle aziende che si affidano a loro, bensì garanten-
do servizi online, che sfruttano il cloud computing, e permettono alle varie
società di caricare le loro applicazioni sui server di salesforce e utilizzarle
online.
CAPITOLO 3. IL CLOUD COMPUTING 13

Figura 3.2: L’home page italiana di Salesforce.com
CAPITOLO 3. IL CLOUD COMPUTING 14

Figura 3.3: L’home page di Amazon Web Services

3.2 Amazon Web Services
Amazon Web Services (AWS) è un servizio che consente ai programmatori di
scrivere web application, utilizzando strumenti di sviluppo forniti da Ama-
zon, e renderle pubbliche e disponibili, sui server dell’azienda americana.
Amazon mette a disposizione diversi web service per facilitare la creazione
di queste applicazioni ai suoi utilizzatori.
Elastic Compute Cloud, meglio conosciuto come EC2 è il web service che
consente di scrivere le applicazioni e di interfacciarsi con gli altri strumenti
di AWS. Esso sfrutta le potenzialità del linguaggio java per la scrittura del
codice.
Come DBMS, viene fornito uno strumento chiamato Simple DB, dal funzio-
namento del tutto analogo al Datastore di Google App Engine il quale deve
essere combinato al Simple Storage Service (Amazon S3) per consentire al
programmatore di memorizzare i dati utilizzati dalla propria applicazione.
Questo strumento, utilizza in larga scala il cloud computing e, come App En-
gine, consente all’utilizzatore di creare utility semplici, dalle alte prestazioni
e altamente scalabili, senza particolari oneri da parte sua.
Capitolo 4

Il DBMS di Google: Bigtable

4.1 Introduzione
Bigtable ([10]) è un Database Management System (DBMS) distribuito, co-
struito per lavorare con dati strutturati. Esso è nato nel 2004 nei laboratori
di Google ed è stato disegnato per essere altamente scalabile e per supportare
grandi quantità di dati1 .
Più di sessanta progetti di Google salvano i dati tramite questo DBMS, in-
clusi l’indicizzazione del web, Google Earth, Google Finance, Orkut e Google
Docs, per citarne alcuni. Queste applicazioni effettuano numerosi tipi diffe-
renti di richieste, sia in termini di dimensione dei dati (che possono variare
da semplici URLs a intere pagine web fino ad arrivare ad immagini prove-
nienti dai satelliti) sia in termini di elaborazione vera e propria dei dati (dalla
fornitura di dati in real-time alla rielaborazione di una grande mole di dati).
Nonostante questa ampia varietà di esigenze, il sistema ha fornito un’ottima
soluzione, molto flessibile e dalle alte prestazioni per ognuno di questi pro-
dotti di Google. Oltre alle qualità già menzionate, gli sviluppatori dei vari
progetti sopra citati, hanno riscontrato una altissima affidabilità di questo
DBMS. I clusters di Bigtable sono suddivisi in qualche migliaio di servers e
possono immagazzinare diverse centinaia di terabyte di dati. Questo sistema
combina insieme diverse strategie utilizzate nei comuni database. Parallel
databases e main-memory databases sono alcuni esempi di tipologie di data-
base dalle alte prestazioni e che consentono una alta scalabilità, Bigtable però
utilizza solo alcune delle caratteristiche di queste tipologie di DBMS. Esso
non supporta un modello relazionale completo, ma fornisce i dati ai client
seguendo un semplice modello che tiene conto del formato e del layout di ogni
proprietà rappresentata nel db. I dati sono indicizzati utilizzando i nomi di
1
Si parla di un supporto a vari petabytes distribuiti su diversi servers.

15
CAPITOLO 4. IL DBMS DI GOOGLE: BIGTABLE 16

righe e colonne che possono essere stringhe arbitrarie. Bigtable può trattare
anche i dati come stringhe indefinite sebbene spesso i client convertano varie
forme di dati strutturati e semi strutturati in stringhe. I client possono inol-
tre controllare localmente i loro dati prendendosi cura di definirne lo schema
più adatto. Infine esso permette ai client di stabilire dinamicamente se i da-
ti saranno da memorizzare seguendo lo schema in-memory o il tradizionale
salvataggio su disco.

4.2 Modello di rappresentazione dei dati
Bigtable usa una sorted map multidimensionale, sparsa, distribuita e persi-
stente. La mappa è indicizzata per chiave di riga, chiave di colonna e istante
di inserimento.I valori sono rappresentati al suo interno come array di bytes
indefiniti.
(row:string,column:string,time:int64) → string

4.2.1 Righe
Le chiavi di riga in una tabella sono stringhe di dimensioni arbitrarie che
variano tra i 10 e i 100 bytes. Le letture/scritture di una singola di riga
sono atomiche, indipendentemente dal numero di colonna nella quale si vuo-
le compiere l’azione, in modo da evitare qualsiasi problema di concorrenza.
Bigtable mantiene i dati ordinati lessicograficamente per chiave di riga. Ogni
riga è suddivisa in intervalli, singolarmente chiamati tablet, che sono l’unità
di bilanciamento e di carico. Di conseguenza le letture di piccoli insiemi di
tablet sono efficienti e richiedono tipicamente comunicazioni con un numero
ristretto di macchine. I client possono così sfruttare questa proprietà sta-
bilendo le chiavi di riga in modo da ottenere un posizionamento contiguo
per facilitare i futuri accessi ai dati. Un possibile esempio può essere citato
prendendo spunto dall’indicizzazione del web: le url delle pagine di domini
identici vengono raggruppate in righe contigue capovolgendo le componenti
dell’url stessa. Ad esempio l’url della pagina maps.google.com/index.html
verrà salvata come com.google.maps/index.html in modo tale che tutte le
pagine appartenenti a questo dominio verranno salvate in posizioni vicine,
rendendo più efficienti le successive letture.

4.2.2 Famiglie di Colonne
Le chiavi delle colonne sono raggruppate in insiemi chiamati column families
le quali formano l’unità base di accesso. Tutti i dati salvati in una famiglia
CAPITOLO 4. IL DBMS DI GOOGLE: BIGTABLE 17

di colonne sono di solito dello stesso tipo (vengono compresse nella stessa
famiglia). Una famiglia di colonne deve essere creata necessariamente prima
che i dati possano essere salvati e raggruppati al suo interno. Queste famiglie
sono state progettate per raggruppare al loro interno un numero ristretto di
colonne(nell’ordine delle centinaia al massimo) e per cambiare raramente una
volta create, ma non vi sono limiti fisici a queste caratteristiche.
Una chiave di colonna è descritta da un sintassi del tipo family:qualifier.
Il nome della famiglia deve essere formato da caratteri stampabili ma il qua-
lificatore può essere una stringa arbitraria. Una famiglia può inoltre venire
creata come derivata di una famiglia già esistente.

4.2.3 Timestamp
Ogni cella di Bigtable può contenere molteplici versioni dello stesso dato,
proprio per questo motivo è stato creato il campo timestamp. Esso viene
rappresentato come intero a 64bit, e può essere generato in automatico dal
sistema, rappresentando l’instante di tempo real in microsecondi oppure può
venire assegnato dal client che sta scrivendo il dato. Una stessa versione di
un dato verrà ordinato in ordine decrescente in modo tale che il dato più
recente possa essere letto per primo. Si può inoltre specificare il numero di
versioni dello stesso dato che si vogliono mantenere, dato che in Bigtable
esiste una sorta di garbage collector dedicato alla funzione di eliminare i dati
ridondanti che superano il numero di ripetizioni scelte dall’utente.

4.3 Struttura interna
Bigtable usa Google File System (GFS,[11]), il file system distribuito creato
da Google, per salvare i file di log e i file di dati.
Come abbiamo già detto in precedenza, questo DBMS è un sistema distri-
buito; un suo cluster opera generalmente su insieme condiviso di calcolatori,
che ospitano una grande varietà di altre applicazioni distribuite e i processi
di Bigtable condividono sovente queste macchine con processi di altre appli-
cazioni. Bigtable è controllato da un sistema di gestione clusterizzato che
gestisce lo scheduling, la gestione delle risorse sulle macchine condivise, la
gestione dei rapporti d’errore dei calcolatori e il monitoraggio dello stato dei
sistemi. I dati vengono salvati in Bigtable utilizzando un file che rispettano
un formato chiamato SSTable, esso fornisce una mappa persistente, ordina-
ta e immutabile di corrispondenze tra chiavi e valori, dove entrambe sono
stringhe arbitrarie. Al suo interno, ogni SSTable contiene una sequenza di
blocchi, ciascuno delle dimensioni di 64Kb, sebbene la dimensione del blocco
CAPITOLO 4. IL DBMS DI GOOGLE: BIGTABLE 18

sia configurabile. Alla fine di ogni SSTable esiste un indice dei blocchi che
viene usato per localizzarli, questo indice viene caricato in memoria quan-
do l’SSTable viene aperto. Una ricerca può essere effettuata mediante una
sola scansione del disco, infatti prima viene cercato il blocco desiderato effet-
tuando una ricerca binaria nell’indice all’iterno della cosidetta in-memory, e
succesivamente leggendo il blocco desiderato dal disco. In alternativa, si può
configurare il sistema in modo da mappare in memoria un SSTable, il che
permette di effettuare le ricerche in memoria senza dover toccare il disco.
Bigtable prevede anche una gestione del lock distribuita, tramite un servizio
chiamato Chubby. Esso consiste in cinque repliche attive, una delle quali è
nominata master e si occupa attivamente delle richieste. Questo servizio usa
un particolare algoritmo (Denominato paxo) per fare in modo che le repliche
slave si attivino nel caso in cui il master vada in errore. Chubby fornisce
un namespace che consiste in directory e piccoli files, e ognuno di questi può
essere usato come lock. La lettura e la scrittura di questi file è atomica. Per
poter utilizzare questo servizio ogni client mantiene una sessione, e quando
questa scade, se il client non la può rinnovare, perderà ogni diritto di scrit-
tura/lettura sui file.
Bigtable si serve di Chubby per molti compiti delicati quali: avere la certez-
za che ci sia un solo master attivo in ogni istante, scrivere dove si trova il
bootstrap dai dati di Bigtable, scoprire quali sono i server adibiti ai tablet
e per quanto tempo, salvare le informazioni relative allo schema di Bigtable
e salvare le liste di controllo degli accessi. Se Chubby per qualsiasi motivo
dovesse diventare inattivo per un lungo periodo di tempo, allora Bigtable
diventerebbe inutilizzabile2 .

2
Recenti studi condotti dai Google labs hanno misurato la percentuale di inattività del
sistema in 0.0047%. Le cause principali sono stati problemi di reti o periodi di inattività
di Chubby.
Capitolo 5

Un caso di studio: Sellbook

5.1 Introduzione
Uno dei problemi che afflige spesso gli studenti universitari, sono i costi che
si devono sostenere per l’acquisto dei libri. Spesso i testi acquistati vengono
utilizzati per i corsi ad essi adibiti e poi finiscono per cadere nel dimenticatoio
e non essere più sfruttati. Fermo restando che un libro può sempre risultare
utile per svariati motivi, si è pensato di creare uno strumento a disposizione
di tutti, per ammortizzare il costo di acquisto dei tomi universitari. Nasce per
questo l’idea di una semplice web application, che metta in comunicazione
gli studenti in possesso di libri diventati ormai inutili, e quelli che cercano un
determinato testo per il loro corso universitario che si apprestano a seguire.
Sellbook si propone come un semplice portale in cui chiunque può vendere
i volumi in proprio possesso tramite l’inserimento di un semplice annuncio
oppure rispondere ad un annuncio, ricercando il libro che a cui si è interessati.
Questa applicazione nasce per il corso di studi in Informatica dell’università
di Torino, ma può tranquillamente essere esteso ad altri corsi di studi e ad
altre università.

5.2 Specifica dei requisiti
Come detto in precedenza, l’applicazione dovrà consentire di inserire annun-
ci di libri in vendita, ricercare un determinato libro (aiutando l’utente nella
ricerca, fornendo diverse tipologie di ricerca), consentire a due utenti di co-
municare tra di loro.
In particolare durante l’inserimento di un libro si devono poter specificare le
seguenti caratteristiche:
• Titolo

19
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 20

• Autore
• Casa Editrice
• Anno di pubblicazione
• Versione
• Categoria1 di appartenenza
• Prezzo
• Tipologia di vendita (Diretta o on-line)
• Luogo in cui si trova l’oggetto
Dovrà anche essere possibile editare un libro inserito, e eliminarlo. In ag-
giunta alla sezione dei libri, dovrà essere possibile per ogni utente, creare,
editare e rimuovere stanze. Le stanze sono una sorta di categoria, in cui
sono contenuti i libri, e verranno utilizzate per suddividere i testi in base al
corso di appartenenza. Tramite una navigazione all’interno delle stanze si
potranno ricercare i libri a cui si è interessati. Ciò permetterà all’utente che
cerca un determinato volume relativo ad un corso di studi, di trovarlo più ra-
pidamente, escludendo libri a cui sicuramente non è interessato. Scendendo
di livello all’interno delle categorie, la ricerca dei libri, sarà via via sempre
più raffinata.

5.3 Sviluppo dell’applicazione
App Engine al momento supporta solamente python come linguaggio di pro-
grammazione, quindi non vi sono state alternative nella scelta del linguaggio.
Il vantaggio di questo kit di sviluppo è che in esso sono contenuti tutti gli
strumente necessari per creare una web application partendo dal nulla. Big-
table è il DMBS ad oggetti incluso nella SDK, e tutte le librerie utilizzate
sono quelle fornite insieme al tool. Le pagine HTML sono scritte rispettando
le specifiche relative all’XHTML 1.0 Strict.
Si è voluto inoltre cercare di progettare e sviluppare l’applicazione con stru-
menti open source. Nella fase di progettazione, i diagrammi in UML sono
stati realizzati con Umbrello UML Modeler. Per la scrittura del codice python
e html è stato utilizzato GEditor con alcuni utili plugin per la programmazio-
ne. Il sistema operativo utilizzato per lo sviluppo è Ubuntu Linux 8.04-8.10.

1
Altrimenti denominata stanza.
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 21

Figura 5.1: Il banner di SellBook

5.4 Ricerca dei casi d’uso
Durante la fase di progettazione sono emersi due attori per l’applicazione:
l’utente e il sistema. Lo studio dei requisiti dell’applicazione ha portato
all’individuazione dei seguenti casi d’uso (suddivisi per categorie di interesse):
Tag:

• Aggiornamento_Tag

• Nuovo_Tag

Libri:

• Nuovo_Libro

• Editare_Libro

Area riservata:

• Log-in

• Log-out

Stanze:

• Crea_Nuova_Categoria

• Edita_Categoria

• Elimina_Categoria

Ricerca:

• Ricerca_Libro

• Visualizza_Libro

• Ricerca_per_Tag

• Ricerca_per_Categorie
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 22

Figura 5.2: Diagramma dei casi d’uso

• Ricerca_per_Keywords

Comunicazione tra utenti:

• Contattare_proprietario_libro

• Segnalare_richiesta_altro_utente

• Inviare_Mail

5.5 Specifiche dei casi d’uso
Aggiornamento_Tag:
Attori: Sistema
Precondizioni: Viene inserito un nuovo libro, o ne viene eliminato uno già
presente.
Flusso: Se viene inserito un nuovo libro, una volta estratto un tag dal titolo,
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 23

già presente tra i tag registrati nella base dati, il sistema aggiorna il contatore
del tag.
Se viene eliminato un libro, il sistema cerca i tag ad esso associati e li elimina
nel caso abbiano il contatore uguale ad 1, o decrementa il contatore di una
unità in caso contrario.
Nuovo_Tag:
Attori: Sistema
Precondizioni: Viene inserito un nuovo libro.
Flusso: All’inserimento di un nuovo libro, una volta estratto un tag dal titolo,
se questo non è ancora presente tra i tag registrati nella base dati, il sistema
lo aggiunge all’elenco dei tag.
Nuovo_Libro:
Attori: Utente, Sistema
Precondizioni: Un utente vuole inserire un nuovo libro in vendita.
Flusso: Viene presentata la pagina di inserimento di un nuovo libro, e la
mappa di Google Maps per la ricerca della località in cui si trova il libro in
vendita. Una volta premuto il pulsante di inserimento del libro, si attiva il
caso d’uso Nuovo_Libro o Aggiornamento_Tag a seconda della necessità.
Editare_Libro:
Attori: Utente, Sistema
Precondizioni: Si deve modificare un libro precedentemente inserito.
Flusso: Viene eseguita una ricerca del libro da modificare tramite la chiave
associata al libro interessato. Vengono caricati i dati precedentemente inseriti
negli appositi campi della pagina di modifica. Quando l’utente conferma la
modifica dei dati, questi vengono sovrascritti ai precedenti nella base dati.
Log-in:
Attori: Utente
Precondizioni: Si vuole accedere alla propria area personale.
Flusso: L’utente viene reindirizzato alla pagina di Google Accounts. Se è
già in possesso di un account Google, può effettuare l’accesso inserendo sem-
plicemente nome utente e password. Se non è ancora registrato, può farlo e
ottenere un account. Fatto ciò l’utente viene reindirizzato alla pagina da cui
proveniva.
Log-out:
Attori: Utente
Precondizioni: Si vuole uscire dalla propria area personale.
Flusso: L’utente viene reindirizzato alla pagina di Google Accounts. Viene
eseguito il log-out dall’applicazione e in seguito si verrà reindirizzati alla
pagina da cui proveniva la richiesta di log-out.
Crea_Nuova_Categoria:
Attori: Utente, Sistema
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 24

Precondizioni: È necessario aggiungere una nuova stanza. L’utente ha effet-
tuato con successo l’accesso al sistema.
Flusso: Viene presentata una pagina che permette di inserire il nome della
stanza. Quando l’utente conferma l’inserimento, i dati della stanza vengono
registrati nella base dati.
Edita_Categoria:
Attori: Utente, Sistema
Precondizioni: È necessario modificare una stanza. L’utente ha effettuato con
successo l’accesso al sistema. L’utente è il creatore della stanza o possiede
privilegi di amministrazione.
Flusso: Viene presentata una pagina che permette di inserire il nome della
stanza. Quando l’utente conferma l’inserimento, i dati della stanza vengono
registrati nella base dati.
Elimina_Categoria:
Attori: Utente, Sistema
Precondizioni: Si vuole eliminare una stanza. L’utente ha effettuato con
successo l’accesso al sistema. L’utente è il creatore della stanza o possiede
privilegi di amministrazione.
Flusso: Quando un utente clicca sul simbolo “-” posto a fianco di una stanza,
questa viene cancellata dalla base dati.
Ricerca_Libro:
Attori: Utente
Precondizioni: Vi è la necessità di ricercare un determinato libro o un insieme
di libri.
Flusso: L’utente è nella condizione di poter scegliere fra tre tipologie distinte
di ricerca, quella che si addice di più alle sue esigenze.
Visualizza_Libro:
Attori: Utente
Precondizioni: L’utente vuole visionare le informazioni dettagliate relative
ad un libro.
Flusso: Il caso d’uso in questione può venire attivato in seguito ad una ricerca,
o tramite link diretto al libro da visualizzare. Dopo aver selezionato il libro
da visualizzare, vengono presentati i dettagli del libro.
Ricerca_per_Tag:
Attori: Utente
Precondizioni: L’utente vuole ricercare un libro selezionando un tag dall’ap-
posito spazio riservato al tag cloud.
Flusso: L’utente seleziona un tag tra quelli proposti. Viene attivata la ricerca
sul database, di tutti i libri il cui titolo corrisponde alla parola scelta.
Ricerca_per_Categorie:
Attori: Utente
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 25

Precondizioni: L’utente vuole ricercare un libro navigando tra le stanze pre-
senti.
Flusso: Partendo dall’elenco delle stanze, l’utente può ricercare la presunta
stanza interessata. Dalla pagina della descrizione di una stanza si può navi-
gare verso stanze più specifiche (se ne esistono) oppure verso stanze antenate
di quella che si sta visualizzando. Una volta trovata la stanza cercata, se con-
tiene libri, questi ultimi verranno proposti all’utente. Il link dei vari volumi,
rimanderà alla pagina descrittiva del libro.
Ricerca_per_Keywords:
Attori: Utente
Precondizioni: L’utente vuole ricercare un libro immettendo delle parole chia-
ve.
Flusso: Tramite il campo di testo apposito, l’utente può inserire parole chiave
che pensa possano corrispondere al libro che interessa. Avviando la ricerca, il
sistema analizza le parole inserite e le confronta con i vari titoli dei libri. Se
una porzione di testo, inserita nel suddetto campo, corrisponde ad una parola
presente nel titolo di un determinato libro, questo verrà proposto nell’elenco
dei risultati della ricerca.
Contattare_proprietario_libro:
Attori: Utente, Sistema
Precondizioni: L’utente vuole stabilire un contatto con il proprietario di un
libro a cui è interessato.
Flusso: Dalla pagina della descrizione di un libro, un utente può scrivere
un messaggio indirizzato al proprietario del testo in questione. Questo caso
d’uso attiva Inviare_Mail.
Segnalare_richiesta_altro_utente:
Attori: Sistema
Precondizioni: È stato scritto un messaggio da un utente indirizzato al pro-
prietario di un libro.
Flusso: Viene presentato nel menù dall’applicazione, un contatore che rap-
presenta il numero di messaggi di ricevuti dagli altri utenti dell’applicazione.
Inviare_Mail:
Attori: Sistema
Precondizioni: È stato scritto un messaggio da un utente indirizzato al pro-
prietario di un libro.
Flusso: Il sistema inoltra il messaggio scritto da un utente, al proprietario
del libro contattato, tramite e-mail.
I primi due casi d’uso, riguardano la taggatura dei libri. I tag sono pa-
role chiave estratte in automatico dal titolo ogni qualvolta viene inserito un
nuovo libro. Ogni parola presente in un titolo può essere condivisa da più
titoli. Perciò potrebbe risultare utile ricercare mediante i tag, un libro che
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 26

riguarda un determinato argomento, rappresentato da una parola significa-
tiva. Ad esempio, se uno studente è alla ricerca di un volume che tratti il
linguaggio uml, potrà cliccare sulla parola uml tra i tag, e trovare tutti i libri
che all’interno del titolo contengono la suddetta parola.
I due casi d’uso successivi, riguardano l’inserimento, la modifica o la cancel-
lazione dei testi, per gli utenti che vogliono inserire un annuncio di vendita.
Immediatamente succesivi a questi, vi sono i due casi d’uso che riguardano
l’accesso e l’uscita dall’area personale di ogni utente. Grazie a questi, l’uti-
lizzatore potrà inserire o modificare i propri volumi da vendere, controllare
se sono state inviate richieste per un proprio libro da parte di altri utenti e
aggiungere o modificare le stanze presenti nel sistema.
I tre casi d’uso riguardanti le categorie, sono quelli che prendono in analisi la
gestione delle stanze. Come per i libri, anche qui è possibile inserire, modifi-
care o eliminare una stanza. La scelta implementativa fatta in questo caso è
stata quella di permettere a chiunque di poter inserire una stanza per rendere
indipendente il sistema e non renderlo suddito dell’attività di un’amministra-
tore. La modifica o la cancellazione di una stanza sono invece consentite al
solo utente responsabile della creazione della stanza in questione e all’ammi-
nistratore del sistema, che in questo caso può moderare e mantenere pulito,
ordinato e senza ridondanze l’elenco delle stanze presenti.
Immediatamente sotto vengono presentati i casi d’uso relativi alla ricerca di
un libro con le varie tipologie che consentono ad un utente di trovare il titolo
a cui egli è interessato percorrendo tre diverse strade: una ricerca mirata la
quale consente di immenttere le parole da cercare, una ricerca per stanze di
appartenenza dei libri in modo da navigare all’interno delle aree di interesse
ricoperte dal testo che si vuole cercare e una più generica, per affinità di
parole (tag).
In conclusione vi sono i casi d’uso riguardanti la comunicazione tra i cosi-
detti “venditore” e “compratore”. Questa avviene tramite l’invio di messaggi
e-mail da una pagina dell’applicazione.

5.6 Interazione utente/applicazione
5.6.1 Homepage
La pagina principale dell’applicazione è stata pensata per essere semplice e
allo stesso tempo intuitiva.
Essa è suddivisa in più sezioni:

• Banner: contiene un link che rimanda alla home page, questa sezione
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 27

Figura 5.3: L’homepage di SellBook

Figura 5.4: Il form di ricerca di SellBook

sarà presente in tutte le pagine dell’applicazione, per consentire una
buona navigabilità agli utenti.

• Ricerca: è un semplice campo testuale. Una volta che l’utente inserisce
un valore e preme il pulsante “Cerca”, viene avviata la routine di ricerca,
che porterà ad una pagina con una lista dei risultati trovati.

• Tag Cloud: contiene l’insieme delle parole chiave estratte dai titoli dei
volumi presenti nel sistema. La dimensione di ogni parola è determi-
nata dal numero di ripetizioni della parola stessa all’interno di tutti i
titoli dei libri presenti. Ciò aiuterà l’utente a capire quanto ogni pa-
rola è comune, e di conseguenza quanti libri sono accomunati da quel
termine.
Quando si clicca su un tag viene avviata la ricerca, e vengono succesi-
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 28

Figura 5.5: Lo spazio dedicato alla visualizzazione dei tag

Figura 5.6: Interfaccia per accedere o disconnettersi dal sistema

vamente presentati i libri che sono associati al tag selezionato. Sotto i
risultati appare un altro specchietto, del tutto simile a quello del tag
cloud, dove vengono proposti i tag correlati. Questi tag non sono al-
tro che tutte le parole associate ai libri trovati dalla ricerca. Accanto
ad ogni tag correlato, appare tra parentesi un numero, il quale indica
quanti dei libri sopra citati, sono associati alla parola in questione.

• Log-in/Log-out: questa sezione cambia a seconda se l’utente ha già
effettuato l’accesso o meno, nel sistema di Google Account. In caso
affermativo, vengono visualizzate l’email usata per l’accesso, un link
per accedere alla pagina di riepilogo dei libri inseriti o di inserimento di
un nuovo testo e un link per effettuare la disconnessione dal sistema. In
caso contrario viene presentato un semplice link per effettuare l’accesso.

• Ultimi libri inseriti: questo specchietto, consente di tenere traccia del-
l’attività del sistema. Permette di visualizzare gli ultimi 5 libri inseriti,
e fornisce un’alternativa in più all’utente per trovare un libro. Questa
lista è formata infatti da link, che reindirizzano alla pagina contenente
le informazioni dettagliate su un determinato volume.

• Menu: sulla parte destra della pagina si può trovare il menù dell’appli-

Figura 5.7: Specchietto per la visualizzazione degli ultimi 5 libri inseriti
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 29

Figura 5.8: Il menù di SellBook

cazione. Se l’utente ha effettuato il login, qui appariranno 3 diciture,
di cui due link. Il primo per accedere alla pagina dei libri, mentre il se-
condo per accedere alla sezione delle stanze. La terza voce rappresenta
il contatore di contatti ricevuti al proprio indirizzo e-mail, da parte di
utenti interessati ad un proprio volume in vendita su SellBook. Clic-
cando sull’icona a fianco del numero, verrà azzerato questo contatore.
Nel caso un utente non avesse effettuato il login, il menù conterrà so-
lamente il link per poter visualizzare le stanze presenti.
Questa sezione sarà presente in ogni parte del sito, in modo tale da
fornire all’utente la possibilità di raggiungere velocemente le funzioni a
sua disposizione nell’applicazione.

5.6.2 Libri
La pagina di inserimento di un nuovo libro è suddivisa in due sezioni, il form
di inserimento dei dati e l’elenco dei propri libri già inseriti.
Il form presenta alcuni campi testuali, un pulsante per la selezione dell’im-
magine di copertina del libro da inserire, una combo box per la scelta della
stanza a cui il libro dovrà appartenere, uno specchietto di selezione per sta-
bilire il tipo di vendita e una mappa interattiva, tramite la quale l’utente
potrà indicare il luogo esatto in cui si trova il libro. Quest’ultima informa-
zione potrebbe risultare utile per definire il luogo di incontro tra acquirente
e venditore, per la vendita effettiva del testo. L’altra sezione della pagina è
posta nella parte bassa del documento ipertestuale, e rappresenta un riepilo-
go dei libri inseriti dall’utente, sotto forma di lista di titoli. Accanto ad ogni
breve descrizione dei volumi, vi sono due pulsanti, uno per la modifica delle
informazioni sul testo, ed una per eliminare dal sistema il libro.
Cliccando sul pulsante per la modifica delle descrizioni di un determinato li-
bro, vengono riempiti tutti i campi del form soprastante, con le informazioni
relative al libro selezionato. Quando si ricerca un libro e si vogliono visua-
lizzare i suoi dettagli, viene presentata una pagina del tutto simile a quella
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 30

Figura 5.9: Form di inserimento di un nuovo libro

Figura 5.10: La lista di riepilogo dei libri inseriti dall’utente
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 31

Figura 5.11: La lista delle stanze presenti

per l’inserimento, con la differenza che le informazioni e la mappa di google,
sono solamente visualizzabili e non modificabili. In aggiunta vengono anche
visualizzati i tag associati a quel libro. Sotto la mappa è anche presente il
pulsante che permette di contattare il prorietario del libro.

5.6.3 Stanze
La pagina delle stanze presenta un semplice elenco gerarchico. Ogni stanza
principale ha, sotto di se, tutte le sue stanze “figlie”. Le stanze figlie si ri-
conoscono perchè hanno un leggero margine di rientro, rispetto alle stanze
gerarchicamente superiori ad essa. Come si può notare dalla figura [5.11],
ogni voce ha al suo fianco tre pulsanti. Il simbolo “+”serve per aggiungere
una sottostanza a quella indicata. Il simbolo “-”, al contrario, serve per eli-
minare la stanza in questione. L’ultima icona consente invece di modificare
la descrizione della stanza. Le ultime due azioni, come detto in precedenza
sono consentite solamente al creatore della stanza e agli utente con privilegi
di amministrazione, mentra l’aggiunta di una sottostanza o di una stanza
radice, è consentita a chiunque abbia effettuato l’accesso. Se un visitatore,
non ha inserito le sue credenziali d’accesso, potrà solamente vedere l’elenco
delle stanze e i dettagli per ogni stanza.
Per vedere i dettagli di una stanza, basta semplicemente cliccare sul nome
della stessa. Verrà aperta una pagina in cui vengono mostrate, oltre alla
descrizione, il creatore della stanza, il livello a cui essa appartiene, la stan-
za “padre”e le stanze “figlie”. In basso verranno presentati i libri che sono
contenuti nella stanza, se presenti.
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 32

Figura 5.12: La visualizzazione dei dettagli di una stanza

5.7 Schema della base dati
La base dati dell’applicazione presenta 5 entità, descritte dal codice python
sottostante, utilizzato per descrivere lo schema del database in App Engine:

class Rooms(db.Model):
descr=db.StringProperty()
listaFigli=db.ListProperty(db.Key)
father=db.SelfReferenceProperty()
livello=db.IntegerProperty()
owner=db.UserProperty()

Il campo listaFigli è una lista contenente le chiavi di ogni stanza “figlia”di
una data istanza di Rooms. È del tutto simili alla ReferenceProperty2 ma,
siccome le stanze possono avere più sottostanze, non vi era altro modo di
descrivere questa situazione.
Il campo user contiene le informazioni relative ad un utente di Google Ac-
counts.

class Book(db.Model):
title=db.StringProperty()
author=db.StringProperty()
version=db.IntegerProperty()
year=db.IntegerProperty()
room=db.ReferenceProperty(Rooms)
2
Utilizzata ad esempio nel campo father, per descrivere l’istanza padre di quella in
questione.
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 33

casaEd=db.StringProperty()
img=db.BlobProperty(required=False)

Il campo room contiene il riferimento ad un’istanza dell’entità Rooms, men-
tre il campo img contiene l’immagine della copertina di un libro. Viene
specificato tra parentesi che può contenere un valore nullo.

class Tag(db.Model):
descr=db.StringProperty()
nTag=db.IntegerProperty()

Questa tabella tiene traccia delle parole chiave estratte dai titoli. Attraverso
il campo nTag si tiene conto del numero di ripetizioni di una determinata
parola. Questo metodo consente di velocizzare notevolmente la costruzione
dello strumento del Tag cloud.

class TaggedBook(db.Model):
tag=db.ReferenceProperty(Tag)
book=db.ReferenceProperty(Book)

TaggedBook viene utilizzata per mantenere i riferimenti tra un determi-
nato libro e un tag ad esso associato. Tutti e due i campi sono di tipo
ReferenceProperty.

class SellBook(db.Model):
book=db.ReferenceProperty(Book)
user=db.UserProperty()
price=db.FloatProperty()
sellType=db.IntegerProperty()
contact=db.IntegerProperty()
dataSell=db.DateTimeProperty(auto_now_add=True)
location=db.GeoPtProperty(required=False)

L’entity SellBook, risulta utile per immagazzinare le informazioni relative
alla vendita di un libro.
La proprietà book si riferisce all’istanza del libro in vendita, mentre owner
all’utente proprietario del libro in questione.
Il campo dataSell rappresenta la data di creazione dell’istanza nella base dati.
Ciò è consentito infatti dalla clausola auto_now_add=True, che alla scrittura
dei dati sul db, registra in questo campo l’istante di avvenuta scrittura.
L’ultimo campo (location) rappresenta le coordinate geografice restituite da
Google Maps, quando l’utente inserisce il luogo in cui il libro si trova.
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 34

5.8 Presentazione degli algoritmi utilizzati
5.8.1 Ricerca
L’algoritmo di ricerca, per prima cosa esegue una split del testo inserito
dall’utente nel campo di ricerca, usando come carattere di suddivisione uno
spazio. In questo modo si ottiene un’array contenente le parole cercate, chia-
mato keys.
A questo punto vengono letti dalla base dati, tutti i libri presenti nella ta-
bella Book.
Per scorrere l’insieme dei libri vi è un primo ciclo for, subito sotto ce n’è un
secondo per scorrere l’array delle parole, le quali vengono cercate tramite la
funzione find all’interno del titolo del libro. Se la funzione di confronto delle
due parole da esito positivo, e il libro non era ancora nella lista dei risultati,
viene inserito, viceversa, se il libro era già tra i risultati della ricerca, viene
semplicemente incrementato il suo contatore.
Terminata questa procedura di ricerca, prima di restituire la lista di risulta-
ti, questa viene ordinata in base al numero di parole corrispondenti a quelle
cercate, trovate in ogni titolo.
Per questo algoritmo sono stati anche scritti due metodi di confronto. Il
primo metodo (compare) è una ridefinizione del metodo predefinito per con-
frontare due oggetti, di modo da che essi vengano confrontati semplicemente
per titolo. Il secondo invece (comparaNKey) viene passato nel metodo sort
sulla lista, per stabilire il criterio di ordinamento della stessa.

5.8.2 Estrazione dei tag da un titolo
L’idea alla base di questo algoritmo è molto semplice([12]). Si salvano in
una lista tutti i tag già presenti nel db, e si crea un’altra lista con delle stop
words 3 . A questo punto sul titolo del libro viene eseguita un’operazione di
split, ossia viene divisa la frase (usando come separatore lo spazio) in parole,
che andranno poi a riempire una terza lista. Fatto ciò basterà confrontare
le parole della terza lista con le stop words ed eliminarle quando verranno
trovate delle uguaglianze. Dopodichè tutte le altre parole saranno da salvare
come tag. Bisognerà solamente distinguere quali parole sono già presenti nel
db, e in questo caso si dovrà solamente incrementare il contatore della parola
nel db, e quali sono nuovi termini, da inserire come nuovo tag.
L’algoritmo viene avviato ogni qualvolta si inserisce un nuovo testo.
3
Questo termine viene usato per indicare parole che non sono da considerarsi come tag.
Quando vengono trovate all’interno del testo, si devono ignorare.
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 35

5.8.3 Ricerca dei tag correlati
Il metodo cercaCorrelati ([18]) riceve in input una lista di libri. Per ognuno
di essi richiama la funzione tagCorrelati. Quest’ultima trova i tag correlati
per un libro e li aggiunge alla lista tags.
Il primo metodo richiama ciclicamente il secondo passandogli sempre la stessa
lista di tag, in modo tale che essa cresca con lo scorrere della lista di libri
di cui cercare i termini correlati. Il secondo metodo infatti, prima di inserire
un termine nella lista di ritorno, controlla se questo è già presente. In caso
positivo, non lo dovrà aggiungere, ma dovrà solamente incrementare il suo
contatore.

5.8.4 Visualizzazione delle stanze
All’apparenza, questo, sembra un problema insignificante. Purtroppo il si-
stema di programmazione di App Engine rende tutto ciò più complicato. Il
fatto che nelle pagine html non si possano effettuare controlli veri e propri,
ma solo if per controllare se una variabile è nulla o meno e cicli for per scor-
rere liste, combinato alla realizzazione di stanze dinamiche4 che si è deciso di
adottare in SellBook, obbliga a dover scrivere un algoritmo più complesso.
Per elencare le stanze infatti, l’algoritmo legge inizialmente tutte quelle a li-
vello radice. Questo elenco viene passato ad una funzione che, ricorsivamente,
scorre tutte le sottostanze delle rooms radice. Queste funzioni creano così
una stringa contenente codice html che verrà poi semplicemente stampata
nel documento ipertestuale.

5.9 Considerazioni
Il caso d’uso SellBook è stato molto utile per sperimentare App Engine, per-
chè ha permesso di usufruire di diversi servizi, tra quelli messi a disposizione
dal team di Google.
In definitiva, questo strumento mette a disposizione tutto ciò di cui uno svi-
luppatore di siti web ha bisogno, semplificando notevolmente il suo lavoro.
Il processo di creazione dell’applicazione è del tutto analogo a quello che si
compie utilizzando php, jsp o asp. Uno dei più grandi vantaggi è sicuramente
quello di avere il database integrato nel kit. Con le librerie per il datastore in-
fatti, quando si vogliono leggere dati dal db è necessaria la sola istruzione che
invia la query, senza nessuna procedura per la connesione e la disconnessione
al dbms. Tutto ciò risparmia al programmatore alcune linee di codice ogni
4
Ogni stanza può contenere un numero variabile e indipendente di sottostanze
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 36

qualvolta si voglia effettuare una lettura dal database e contribuisce anche a
snellire il codice dell’applicazione. In aggiunta, utilizzando python, il codice
risulta molto comprensibile, e bastano poche linee per eseguire qualsiasi ope-
razione, sia grazie alle peculiarità insite nel linguaggio, sia per merito delle
varie librerie realizzate da Google e presenti all’interno dell’SDK.
Appendice A

Il file main.py
import wsgiref.handlers
import re
import time
import logging

from string import *
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.api import datastore
from google.appengine.api import datastore_types
from google.appengine.ext import webapp
from google.appengine.api import memcache
from google.appengine.ext.webapp \
import template

#da impostare a True se si vuole avere il
#report degli errori a runtime
_DEBUG=True

logging.getLogger().setLevel(logging.DEBUG)

class Rooms(db.Model):
descr=db.StringProperty()
listaFigli=db.ListProperty(db.Key)
father=db.SelfReferenceProperty()
livello=db.IntegerProperty()
owner=db.UserProperty()

class Book(db.Model):

37
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 38

title=db.StringProperty()
author=db.StringProperty()
version=db.IntegerProperty()
year=db.IntegerProperty()
room=db.ReferenceProperty(Rooms)
casaEd=db.StringProperty()
img=db.BlobProperty(required=False)

class Tag(db.Model):
descr=db.StringProperty()
nTag=db.IntegerProperty()

class TaggedBook(db.Model):
tag=db.ReferenceProperty(Tag)
book=db.ReferenceProperty(Book)

class SellBook(db.Model):
book=db.ReferenceProperty(Book)
user=db.UserProperty()
price=db.FloatProperty()
sellType=db.IntegerProperty()
contact=db.IntegerProperty()
dataSell=db.DateTimeProperty(auto_now_add=True)
location=db.GeoPtProperty(required=False)

class MainPage(webapp.RequestHandler):
#gestisce le richieste get
def get(self):
if not cmp(self.request.get("action"),"SearchTag"):
try:
_id=db.get(self.request.get("idtag"))
except:
_id=None
self.redirect(’/’)
else:
q1=db.GqlQuery("SELECT * FROM TaggedBook
WHERE tag= :1",_id)
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 39

if t.contact is not None:
cont+=t.contact
tags=Cloud.cercaCorrelati(self,q1,_id)
link=’tag.html’
valori={
’user’: users.GetCurrentUser(),
’loginUrl’: users.CreateLoginURL(
self.request.uri),
’logoutUrl’: users.CreateLogoutURL(
self.request.uri),
’contacts’: cont,
’books’:q1,
’tags’:tags
}
#reindirizzamento a pagina html
self.response.out.write(template.render(
link,valori))
else:
if not cmp(self.request.get("action"),"Pulisci"):
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
t.contact=None
db.put(t)
self.redirect("/")
else:
tags=Cloud.tagCloud()
link=’index.html’
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
ultimi=memcache.get("lastBooks")
if ultimi is None:
ultimiInseriti=db.GqlQuery("
SELECT * FROM SellBook ORDER BY dataSell DESC LIMIT 0,5")
if ultimiInseriti.count()<=0:
ultimi=None
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 40

else:
ultimi=""
for u in ultimiInseriti:
ultimi+="<a href=\"libro?id="
+str(u.book.key())+"&action=View\"
class=\"no_under\">"+u.book.title+"
&nbsp;("+str(u.price)+"&euro;)&nbsp;
<img src=\"img/icons/edit-find.png\"
alt=\"Richiedi Info\" title=\"Richiedi Info\" /></a><br />"
if not memcache.add("lastBooks",ultimi):
logging.error("MemCache add
error on \"lastBooks\"")
valori={
’user’: users.GetCurrentUser(),
’loginUrl’: users.CreateLoginURL(
self.request.uri),
’logoutUrl’: users.CreateLogoutURL(
self.request.uri),
’contacts’: cont,
’tags’:tags,
’ultimiInseriti’:ultimi
}
#reindirizzamento a pagina html
self.response.out.write(template.render(
link,valori))

#gestisce le richieste post
def post(self):
tStart=time.clock()
query=Search.run(self)
len_query=len(query)
if len_query==0:
len_query=None
tEnd=time.clock()
tmp=round(tEnd-tStart,4)
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 41

result={
’result’: query,
’n_query’: len_query,
’user’: users.GetCurrentUser(),
’loginUrl’: users.CreateLoginURL(
self.request.uri),
’logoutUrl’: users.CreateLogoutURL(
self.request.uri),
’contacts’: cont,
’tempo’: tmp
}
self.response.out.write(template.render(
’search.html’,result))

class Cloud():
descr=’’
dim=’’
key=’’
@staticmethod
def tagCorrelati(self,q2,tags):
for q21 in q2:
flag=0
for t in tags:
if t.descr==q21.tag.descr:
flag=1
tmp=t
if flag==1:
tmp.dim+=1
else:
tmp=Cloud()
tmp.descr=q21.tag.descr
tmp.dim=1
tmp.key=q21.tag.key
tags.append(tmp)
return tags

@staticmethod
def cercaCorrelati(self,q1,_id):
tags=[]
for q in q1:
_id=db.get(q.book.key())
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 42

q2=db.GqlQuery("SELECT * FROM TaggedBook
WHERE book= :1",_id)
tags=Cloud.tagCorrelati(self,q2,tags)
return tags
@staticmethod
def tagCloud():
tags=memcache.get("tagCloud")
if tags is None:
q1=db.GqlQuery("SELECT * FROM Tag ORDER BY descr ASC")
tags=""
for q in q1:
dim=’’
if q.nTag < 2:
dim=’s1’
else:
if q.nTag < 3:
dim=’s2’
else:
if q.nTag < 4:
dim=’s3’
else:
if q.nTag < 5:
dim=’s4’
else:
if q.nTag < 6:
dim=’s5’
else:
if q.nTag < 7:
dim=’s6’
else:
if q.nTag < 8:
dim=’s7’
else:
if q.nTag < 9:
dim=’s8’
else:
dim=’s9’
tags+="<a href=\"tag?idtag="
+str(q.key())+"&action=SearchTag\"
class=\""+dim+"\">"+q.descr+"</a>&nbsp;\n"
if not memcache.add("tagCloud",tags):
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 43

logging.error("MemCache add error on \"tagCloud\"")
return tags

class SearchObj():
book=None
nKey=0

class Search(object):
@staticmethod
def compare(obj1,obj2):
return cmp(obj1.book.title,obj2.book.title)
@staticmethod
def comparaNKey(x,y):
if x.nKey==y.nKey:
return 0
else:
if x.nKey>y.nKey:
return -1
return 1

@staticmethod
def run(self):
p=re.compile(’(\!|\"|\%|\&|\/|\=|\’|\?|\^)’)
key=p.sub(’’,self.request.get("key"))
p=re.compile(’\+’)
key=p.sub(’ ’,key)
keys=split(key,’ ’)
q1=db.GqlQuery("SELECT * FROM Book")
ris=[]
for q in q1:
for k in keys:
if find(lower(q.title),lower(k))!=-1:
tmp=SearchObj()
tmp.book=q
tmp.nKey=1
flag=0
app=None
for r in ris:
if Search.compare(tmp,r)==0:
flag=1
app=r
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 44

if flag==1:
app.nKey+=1
else:
ris.append(tmp)
ris.sort(Search.comparaNKey)
return ris

def main():
app=webapp.WSGIApplication([(r’.*’,MainPage)],debug=_DEBUG)
wsgiref.handlers.CGIHandler().run(app)

if __name__ == ’__main__’:
main()

Il file libri.py
import wsgiref.handlers
import re
import sys

from main import Cloud
from string import *
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.api import datastore
from google.appengine.api import datastore_types
from google.appengine.ext import webapp
from google.appengine.api import memcache
from google.appengine.api import images
from google.appengine.api import mail
from google.appengine.ext.webapp \
import template

#da impostare a True se si vuole avere
#il report degli errori a runtime
_DEBUG=False

class Rooms(db.Model):
descr=db.StringProperty()
listaFigli=db.ListProperty(db.Key)
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 45

father=db.SelfReferenceProperty()
livello=db.IntegerProperty()
owner=db.UserProperty()

class Book(db.Model):
title=db.StringProperty()
author=db.StringProperty()
version=db.IntegerProperty()
year=db.IntegerProperty()
room=db.ReferenceProperty(Rooms)
casaEd=db.StringProperty()
img=db.BlobProperty(required=False)

class Tag(db.Model):
descr=db.StringProperty()
nTag=db.IntegerProperty()

class TaggedBook(db.Model):
tag=db.ReferenceProperty(Tag)
book=db.ReferenceProperty(Book)

class SellBook(db.Model):
book=db.ReferenceProperty(Book)
user=db.UserProperty()
price=db.FloatProperty()
sellType=db.IntegerProperty()
contact=db.IntegerProperty()
dataSell=db.DateTimeProperty(auto_now_add=True)
location=db.GeoPtProperty(required=False)

class Image(webapp.RequestHandler):
def get(self):
book=db.get(self.request.get("img_id"))
if book.img:
self.response.headers[’Content-Type’]="image/png"
self.response.out.write(book.img)
else:
self.response.write("No Image")

class Libri(webapp.RequestHandler):
#gestisce le richieste get
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 46

def get(self):
if not users.get_current_user() and cmp(
self.request.get("action"),"View")!=0:
self.redirect(’/’)
msg=None
link=’libri.html’
b=None
sd=None
imgBook=None
output=None
q1=None
tags=[]
p=re.compile(’(\!|\"|\%|\&|\/|\=|\’|\?|\^)’)
if p.sub(’’,self.request.get("msg"))=="1":
msg="Attenzione, compilare
correttamente tutti i campi"
id_key=p.sub(’’,self.request.get("id"))
if self.request.get("action") == "Elimina":
self.elimina()
self.redirect(’/libri’)
else:
if self.request.get("action") == "View":
link=’libro.html’
if id_key:
try:
b=db.get(id_key)
except:
b=None
self.redirect("/libri")
q3=db.GqlQuery("SELECT * FROM Rooms
WHERE livello= :1",0)
if b:
if b.room:
output=Stanze.creaBox(q3,b.room.key())
else:
output=Stanze.creaBox(q3,None)
else:
output=Stanze.creaBox(q3,None)
q3=db.GqlQuery("SELECT * FROM SellBook
WHERE book= :1 LIMIT 0,1",b)
for sb in q3:
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 47

sd=SellDetails()
sd.price=sb.price
sd.location=sb.location
sd.key=sb.key()
if sb.sellType==1:
sd.directSell=1
else:
if sb.sellType==2:
sd.onlineSell=1
else:
if sb.sellType==3:
sd.directSell=1
sd.onlineSell=1
q2=db.GqlQuery("SELECT * FROM TaggedBook
WHERE book= :1",b)
tags=Cloud.tagCorrelati(self,q2,tags)
else:
q1=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1 ORDER BY book DESC",users.GetCurrentUser())
q2=db.GqlQuery("SELECT * FROM Rooms
WHERE livello= :1",0)
output=Stanze.creaBox(q2,None)
if id_key:
try:
b=db.get(id_key)
except:
b=None
self.redirect("/libri")
else:
q3=db.GqlQuery("SELECT * FROM Rooms
WHERE livello= :1",0)
if b:
if b.room:
output=Stanze.creaBox(q3,b.room.key())
else:
output=Stanze.creaBox(q3,None)
else:
output=Stanze.creaBox(q3,None)
q3=db.GqlQuery("SELECT * FROM SellBook
WHERE book= :1 LIMIT 0,1",b)
for sb in q3:
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 48

sd=SellDetails()
sd.price=sb.price
sd.key=sb.key()
sd.location=sb.location
if sb.sellType==1:
sd.directSell=1
else:
if sb.sellType==2:
sd.onlineSell=1
else:
if sb.sellType==3:
sd.directSell=1
sd.onlineSell=1
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
valori={
’user’: users.GetCurrentUser(),
’loginUrl’: users.CreateLoginURL(self.request.uri),
’logoutUrl’: users.CreateLogoutURL("/"),
’contacts’: cont,
’books’: q1,
’details’: b,
’sellDetails’:sd,
’imgBook’:imgBook,
’id_key’:id_key,
’tags’:tags,
’selectBox’:output,
’msg’: msg
}
self.response.out.write(template.render(link,valori))

#gestisce le richieste post
def post(self):
p=re.compile(’(\!|\"|\%|\&|\/|\=|\’|\?|\^)’)
if not cmp(self.request.get("action"),"Inserisci"):
try:
room=p.sub(’’,self.request.get("roomBook"))
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 49

if len(room)>0:
room=db.get(room)
if not room:
room=None
else:
room=None
book=Book()
book.title=p.sub(’’,self.request.get("titolo"))
book.author=p.sub(’’,self.request.get("autore"))
book.casaEd=p.sub(’’,self.request.get("casaEd"))
book.year=int(p.sub(’’,self.request.get("annoP")))
book.version=int(p.sub(’’,
self.request.get("versione")))
if room:
book.room=room.key()
else:
book.room=None
src=self.request.get("imgBook")
if len(src)>0:
img=images.Image(src)
img.resize(100,100)
img.im_feeling_lucky()
book.img=db.Blob(img.execute_transforms(
output_encoding=images.PNG))
else:
book.img=None
sbook=SellBook()
sbook.user=users.GetCurrentUser()
sbook.price=float(p.sub(’’,
self.request.get("prezzo")))
reg=re.compile(’(\:|\;|\!|\"|\$|\%|\&|\/|\
(|\)|\=|\’|\?|\^)’)
loc=reg.sub(’’,self.request.get("location"))
geo=split(loc,",")
sbook.location=db.GeoPt(float(geo[0]),
float(geo[1]))
directSell=self.request.get("directSell")
onlineSell=self.request.get("onlineSell")
sbook.sellType=0
if directSell:
if onlineSell:
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 50

sbook.sellType=3
else:
sbook.sellType=1
else:
if onlineSell:
sbook.sellType=2
db.put(book)
sbook.book=book.key()
db.put(sbook)
self.tagga(self,book.key())
memcache.delete("lastBooks")
memcache.delete("tagCloud")
self.redirect(’/libri’)
except:
self.redirect(’/libri?msg=1’)
else:
if not cmp(self.request.get("action"),"Modifica"):
try:
k=p.sub(’’,self.request.get(’id_key’))
b=db.get(k)
b.title=p.sub(’’,self.request.get("titolo"))
b.author=p.sub(’’,self.request.get("autore"))
b.casaEd=p.sub(’’,self.request.get("casaEd"))
b.year=int(p.sub(’’,self.request.get("annoP")))
b.version=int(p.sub(’’,self.request.get(
"versione")))
k=p.sub(’’,self.request.get("sell_key"))
sbook=db.get(k)
directSell=self.request.get("directSell")
onlineSell=self.request.get("onlineSell")
sbook.sellType=0
if directSell:
if onlineSell:
sbook.sellType=3
else:
sbook.sellType=1
else:
if onlineSell:
sbook.sellType=2
sbook.price=float(p.sub(’’,
self.request.get("prezzo")))
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 51

reg=re.compile(’(\:|\;|\!|\"|\$|\%|\&|\/|\(
|\)|\=|\’|\?|\^)’)
loc=reg.sub(’’,self.request.get("location"))
geo=split(loc,",")
sbook.location=db.GeoPt(float(geo[0]),
float(geo[1]))
db.put(b)
db.put(sbook)
memcache.delete("tagCloud")
memcache.delete("lastBooks")
self.redirect(’/libri’)
except:
self.redirect(’/libri?msg=1’)
else:
if not cmp(self.request.get("mail"),"write"):
link=’mail.html’
id_book=db.get(p.sub(’’,
self.request.get(’id_book’)))
id_sell=db.get(p.sub(’’,
self.request.get(’id_sell’)))
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
valori={
’user’: users.GetCurrentUser(),
’loginUrl’: users.CreateLoginURL(
self.request.uri),
’logoutUrl’: users.CreateLogoutURL("/"),
’book’:id_book,
’contacts’:cont,
’sell’:id_sell
}
self.response.out.write(
template.render(link,valori))
else:
if not cmp(self.request.get("mail"),"send"):
mittente=p.sub(’’,
self.request.get(’address’))
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 52

oggetto=p.sub(’’,
self.request.get(’oggetto’))
mex=p.sub(’’,self.request.get(’mex’))
try:
id_sell=db.get(p.sub(’’,
self.request.get(’id_sell’)))
except:
id_sell=None
self.redirect("/libri")
if id_sell:
seller=(id_sell.user).email()
if not seller or not mex or not oggetto
or not mittente or len(mittente)==0 or len(oggetto)==0
or len(mex)==0:
link=’mail.html’
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
valori={
’user’: users.GetCurrentUser(),
’loginUrl’: users.CreateLoginURL(
self.request.uri),
’logoutUrl’:
users.CreateLogoutURL("/"),
’book’:id_sell.book,
’sell’:id_sell,
’body’:mex,
’contacts’: cont,
’msg’:"Attenzione: compilare
correttamente tutti i campi!"
}
self.response.out.write(
template.render(link,valori))
else:
mail.send_mail(sender=mittente,
to=seller,subject=oggetto,body=mex)
if id_sell.contact is not None:
id_sell.contact=id_sell.contact+1
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 53

else:
id_sell.contact=1
db.put(id_sell)
self.redirect(’/’)
else:
self.elimina()
memcache.delete("tagCloud")
memcache.delete("lastBooks")
self.redirect(’/libri’)

#elimina un libro dal db
def elimina(self):
k=self.request.get(’id_key’)
try:
b=db.get(k)
except:
b=None
self.redirect(’/libri’)
else:
q1=db.GqlQuery("SELECT * FROM SellBook
WHERE book= :1",b)
db.delete(q1)
q2=db.GqlQuery("SELECT * FROM TaggedBook
WHERE book= :1",b)
for t in q2:
tag=db.get(t.tag.key())
if tag.nTag<=1:
db.delete(tag)
else:
tag.nTag-=1
db.put(tag)
db.delete(q2)
db.delete(b)

@staticmethod
def tagga(self,book):
titolo=lower(self.request.get("titolo"))
newList=[]
q=db.GqlQuery("SELECT * FROM Tag")
oldList=[]
fullOldList=[]
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 54

for q1 in q:
oldList.append(q1.descr)
fullOldList.append(q1)
stopWords = [’in’,’from’, ’of’, ’to’, ’and’,\
’an’, ’with’, ’that’, ’a’, ’on’, \
’the’, ’lo’, ’di’,’dei’, ’dell\’’, ’e’, \
’into’, ’E\’’, ’The’, ’by’, ’for’, ’about’, \
’as’, ’’, ’A’, ’E’, ’nei’, ’negli’, \
’based’, ’driven’, ’guided’, ’means’, \
’priori’, ’About’, ’An’, ’0’, ’I’, ’On’, \
’Proc’, ’agli’, ’like’, ’s’, ’valued’, \
’degli’, ’delle’, ’per’, ’un’, ’uno’, \
’una’,’l’,’la’,’le’, ’d’,’i’,’della’, \
’degli’,’il’,’dai’,’del’,’-’,’con’]
p=re.compile(’(\.|\:|\,|\;|\d|\!|\"|\$
|\%|\&|\/|\(|\)|\=|\’|\?|\^)’)
titolo=p.sub(’ ’,titolo)
tags=split(titolo," ")
for t in tags:
if t not in stopWords:
if t not in oldList:
tag=Tag()
tag.descr=t
tag.nTag=1
tag.put()
tagged=TaggedBook()
tagged.tag=tag.key()
tagged.book=book
tagged.put()
else:
for f in fullOldList:
if t == f.descr:
q=db.GqlQuery("SELECT * FROM Tag
WHERE descr= :1 LIMIT 1",f.descr)
for q1 in q:
q2=db.get(q1.key())
if q2:
q2.nTag=(q2.nTag+1)
db.put(q2)
tagged=TaggedBook()
tagged.tag=f.key()
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 55

tagged.book=book
tagged.put()

class SellDetails():
price=None
directSell=None
onlineSell=None
location=None
key=None

class Stanze():
@staticmethod
def creaBox(query,compare):
output=None
if query.count()>0:
output="<form method=\"post\" action=\"\">"
output+="<select name=\"roomBook\">"
for q in query:
if q.listaFigli:
if len(q.listaFigli)>0:
output+="<optgroup label=\""+
str(q.livello)+"&minus;"+q.descr+"\">"
output=Stanze.creaBoxRicorsiva(output,q,compare)
output+="</optgroup>"
else:
if q.key() == compare:
output+="<option value=\""+
str(q.key())+"\" selected=\"selected\">"+q.descr+"</option>"
else:
output+="<option value=\""+
str(q.key())+"\">"+q.descr+"</option>"
else:
if q.key() == compare:
output+="<option value=\""+
str(q.key())+"\" selected=\"selected\">"+q.descr+"</option>"
else:
output+="<option value=\""+
str(q.key())+"\">"+q.descr+"</option>"
if query.count()>0:
output+="</select>"
return output
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 56

@staticmethod
def creaBoxRicorsiva(output,padre,compare):
for l in padre.listaFigli:
tmp=db.get(l)
if tmp:
if tmp.listaFigli:
if len(tmp.listaFigli)>0:
output+="<optgroup label=\""+
str(tmp.livello)+"&minus;"+tmp.descr+"\">"
output=Stanze.creaBoxRicorsiva(
output,tmp,compare)
output+="</optgroup>"
else:
if tmp.key() == compare:
output+="<option value=\""+
str(tmp.key())+"\" selected=\"selected\">"+
tmp.descr+"</option>"
else:
output+="<option value=\""+
str(tmp.key())+"\">"+tmp.descr+"</option>"
else:
if tmp.key() == compare:
output+="<option value=\""+
str(tmp.key())+"\" selected=\"selected\">"+
tmp.descr+"</option>"
else:
output+="<option value=\""+
str(tmp.key())+"\">"+tmp.descr+"</option>"
return output

def main():
app=webapp.WSGIApplication([(’/libri’,Libri),
(’/libro’,Libri),(’/img’,Image)],debug=_DEBUG)
wsgiref.handlers.CGIHandler().run(app)

if __name__ == ’__main__’:
main()
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 57

Il file stanze.py
import wsgiref.handlers
import re

from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.api import datastore
from google.appengine.api import datastore_types
from google.appengine.ext import webapp
from google.appengine.api import memcache
from google.appengine.ext.webapp \
import template

#da impostare a True se si vuole
#avere il report degli errori a runtime
_DEBUG=False

class Rooms(db.Model):
descr=db.StringProperty()
listaFigli=db.ListProperty(db.Key)
father=db.SelfReferenceProperty()
livello=db.IntegerProperty()
owner=db.UserProperty()

class Book(db.Model):
title=db.StringProperty()
author=db.StringProperty()
version=db.IntegerProperty()
year=db.IntegerProperty()
room=db.ReferenceProperty(Rooms)
casaEd=db.StringProperty()
img=db.BlobProperty(required=False)

class Tag(db.Model):
descr=db.StringProperty()
nTag=db.IntegerProperty()

class TaggedBook(db.Model):
tag=db.ReferenceProperty(Tag)
book=db.ReferenceProperty(Book)
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 58

class SellBook(db.Model):
book=db.ReferenceProperty(Book)
user=db.UserProperty()
price=db.FloatProperty()
sellType=db.IntegerProperty()
contact=db.IntegerProperty()
dataSell=db.DateTimeProperty(auto_now_add=True)
location=db.GeoPtProperty(required=False)

class Stanze(webapp.RequestHandler):
#gestisce le richieste get
def get(self):
valori=None
link=’stanze.html’
p=re.compile(’(\!|\"|\%|\&|\/|\=|\’|\?|\^)’)
if not cmp(self.request.get("action"),"add"):
if not users.get_current_user():
self.redirect(’/’)
else:
stanza=p.sub(’’,self.request.get("stanza"))
link="nuovaStanza.html"
if not cmp(stanza,"None"):
stanza=None
nomeStanza=None
else:
try:
nomeStanza=db.get(stanza)
except:
nomeStanza=None
self.redirect(’/stanze’)
if nomeStanza:
nomeStanza=nomeStanza.descr
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
valori={
’user’: users.GetCurrentUser(),
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 59

’loginUrl’:
users.CreateLoginURL(self.request.uri),
’logoutUrl’:
users.CreateLogoutURL(self.request.uri),
’contacts’: cont,
’father’:stanza,
’stanza’:nomeStanza
}
else:
if not cmp(self.request.get("action"),"remove"):
if not users.get_current_user():
self.redirect(’/’)
else:
link="stanze.html"
stanza=p.sub(’’,self.request.get("stanza"))
try:
r=db.get(stanza)
except:
stanza=None
self.redirect(’/stanze’)
else:
if r is not None:
if r.father is not None:
r.father.listaFigli.remove(r.key())
self.removeStanza(stanza)
memcache.delete("stanze")
valori=self.visualizzaStanze()
else:
if not cmp(self.request.get("action"),"modify"):
try:
stanza=db.get(p.sub(’’,
self.request.get("stanza")))
except:
stanza=None
self.redirect(’/stanze’)
else:
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 60

cont+=t.contact
if stanza is not None:
if not users.get_current_user():
self.redirect(’/’)
else:
if users.get_current_user()!=
stanza.owner:
self.redirect(’/stanze’)
else:
if stanza.father is not None:
idFather=stanza.father.key()
else:
idFather=None
link="nuovaStanza.html"
valori={
’user’: users.GetCurrentUser(),
’loginUrl’:
users.CreateLoginURL(self.request.uri),
’logoutUrl’:
users.CreateLogoutURL(self.request.uri),
’contacts’: cont,
’father’:idFather,
’stanza’:stanza.descr,
’modifica’:1
}
else:
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
valori={
’user’: users.GetCurrentUser(),
’loginUrl’:
users.CreateLoginURL(self.request.uri),
’logoutUrl’:
users.CreateLogoutURL(self.request.uri),
’father’:idPadre,
’stanza’:nomeStanza,
’contacts’:cont,
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 61

’message’:
"Attenzione: Stanza inesistente."
}
link="stanze.html"
else:
if not cmp(self.request.get("action"),"view"):
link="stanza.html"
books=None
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
try:
stanza=db.get(p.sub(’’,
self.request.get("stanza")))
except:
stanza=None
self.redirect(’/stanze’)
listaFigli=[]
if stanza:
for s in stanza.listaFigli:
tmp=db.get(s)
if tmp:
listaFigli.append(tmp)
books=db.GqlQuery("SELECT * FROM Book
WHERE room= :1", stanza.key())
if books:
if books.count()<=0:
books=None
valori={
’user’: users.GetCurrentUser(),
’loginUrl’:
users.CreateLoginURL(self.request.uri),
’logoutUrl’:
users.CreateLogoutURL(self.request.uri),
’contacts’: cont,
’stanza’: stanza,
’stanzeFiglie’: listaFigli,
’books’: books
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 62

}
else:
valori=self.visualizzaStanze()

#reindirizzamento a pagina html
self.response.out.write(template.render(link,valori))

#gestisce le richieste post
def post(self):
if not users.get_current_user():
self.redirect(’/’)
p=re.compile(’(\!|\"|\%|\&|\/|\=|\’|\?|\^)’)
if not cmp(self.request.get("action"),"add"):
if not self.request.get("nuovaStanza"):
if not cmp(self.request.get("idPadre"),"None"):
idPadre=None
nomeStanza=None
else:
idPadre=p.sub(’’,self.request.get("idPadre"))
try:
nomeStanza=db.get(idPadre)
nomeStanza=nomeStanza.descr
except:
nomeStanza=None
self.redirect("/stanze")
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
valori={
’user’: users.GetCurrentUser(),
’loginUrl’:
users.CreateLoginURL(self.request.uri),
’logoutUrl’:
users.CreateLogoutURL(self.request.uri),
’contacts’: cont,
’father’:idPadre,
’stanza’:nomeStanza,
’message’:"Attenzione:
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 63

Inserire un nome per la nuova stanza."
}
#reindirizzamento a pagina html
self.response.out.write(
template.render("nuovaStanza.html",valori))
else:
r=Rooms()
idfather=p.sub(’’,self.request.get("idPadre"))
if not cmp(self.request.get("idPadre"),"None"):
r.livello=0
r.descr=p.sub(’’,
self.request.get("nuovaStanza"))
r.owner=users.get_current_user()
r.father=None
db.put(r)
memcache.delete("stanze")
else:
try:
father=db.get(idfather)
except:
print father.descr
father=None
self.redirect(’/stanze’)
else:
if father is not None:
r.livello=(father.livello)+1
r.descr=p.sub(’’,
self.request.get("nuovaStanza"))
r.owner=users.get_current_user()
r.father=father
db.put(r)
father.listaFigli.append(r.key())
db.put(father)
memcache.delete("stanze")
#reindirizzamento a pagina html
self.redirect(’/stanze’)
else:
if not cmp(self.request.get("action"),"modify"):
try:
stanza=db.get(p.sub(’’,
self.request.get("stanza")))
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 64

except:
stanza=None
self.redirect("/stanze")
if stanza:
stanza.descr=p.sub(’’,
self.request.get("nuovaStanza"))
db.put(stanza)
memcache.delete("stanze")
self.redirect(’/stanze’)

def visualizzaStanze(self):
output=None
q=db.GqlQuery("SELECT * FROM Rooms
WHERE livello= :1",0)
output=""
if q.count()!=0 or q is not None:
output=self.creaAlbero(q)
if not users.get_current_user():
output="Stanze presenti:<br/>"+output
else:
output="<a href=\"stanze?
action=add&stanza=None\"><img src=\"
img/icons/list-add.png\">&nbsp;Inserisci&nbsp;
stanza&nbsp;al&nbsp;livello&nbsp;radice</a>"+output
else:
if not users.get_current_user():
output="Nessuna stanza presente.<br/>"+output
else:
output="<a href=\"stanze?
action=add&stanza=None\"><img src=\"
img/icons/list-add.png\">&nbsp;Inserisci&nbsp;
stanza&nbsp;al&nbsp;livello&nbsp;radice</a>"+output
tot=db.GqlQuery("SELECT * FROM SellBook
WHERE user= :1", users.GetCurrentUser())
cont=0
for t in tot:
if t.contact is not None:
cont+=t.contact
valori={
’user’: users.GetCurrentUser(),
’loginUrl’: users.CreateLoginURL(self.request.uri),
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 65

’logoutUrl’:
users.CreateLogoutURL(self.request.uri),
’contacts’: cont,
’rooms’:output
}
return valori

def creaAlbero(self,q):
output=""
output+="<ul align=\"center\">"
for q1 in q:
if q1 is not None:
output+="<li>"
output+="<a href=\"stanze?action=view&stanza="
+str(q1.key())+"\">"+q1.descr+"</a>&nbsp;&nbsp;&nbsp;"
if users.get_current_user():
output+="|&nbsp;<a href=\"
stanze?action=add&stanza="+str(q1.key())+"\">
<img src=\"img/icons/list-add.png\"></a>&nbsp;"
if users.get_current_user()==q1.owner or
users.is_current_user_admin():
output+="<a href=\"
stanze?action=remove&stanza="+str(q1.key())+"\">
<img src=\"img/icons/list-remove.png\"></a>&nbsp;"
output+="<a href=\"stanze?
action=modify&stanza="+str(q1.key())+"\"><img src=\"
img/icons/accessories-text-editor.png\"></a>"
if len(q1.listaFigli)>0:
output+="<ul align=\"center\">"
output=self.creaAlberoRicorsiva(q1,output)
output+="</ul>"
output+="</li>"
output+="</ul>"
return output

def creaAlberoRicorsiva(self,q,output):
lista=q.listaFigli
for l in lista:
tmp=db.get(l)
if tmp is not None:
output+="<li>"
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 66

output+="<a href=\"stanze?action=view&stanza="
+str(l)+"\">"+tmp.descr+"</a>&nbsp;&nbsp;&nbsp;"
if users.get_current_user():
output+="|&nbsp;<a href=\"stanze?
action=add&stanza="+str(l)+"\"><img src=\"
img/icons/list-add.png\"></a>"
if users.get_current_user()==tmp.owner or
users.is_current_user_admin():
output+="<a href=\"stanze?
action=remove&stanza="+str(l)+"\"><img src=\"
img/icons/list-remove.png\"></a>&nbsp;"
output+="<a href=\"stanze?
action=modify&stanza="+str(l)+"\"><img src=\"
img/icons/accessories-text-editor.png\"></a>"
if len(tmp.listaFigli)>0:
output+="<ul align=\"center\">"
output=self.creaAlberoRicorsiva(tmp,output)
output+="</ul>"
output+="</li>"
return output

def removeStanza(self,stanza):
try:
r=db.get(stanza)
except:
r=None
if r is not None:
for l in r.listaFigli:
self.removeStanza(l)
r.listaFigli.remove(l)
db.delete(l)
db.delete(r)

def main():
app=webapp.WSGIApplication([(r’.*’,Stanze)],debug=_DEBUG)
wsgiref.handlers.CGIHandler().run(app)

if __name__ == ’__main__’:
main()
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 67

Codice javascript per Google Maps
var map=null;
var geocoder=null;
function initialize(){
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"));
coord=window.document.getElementById("coord");
if(!coord){
var center=new GLatLng(45.0897609,7.658761);
}
else{
var c=coord.value;
geo=c.split(",");
var center=new GLatLng(geo[0],geo[1]);
}
map.setCenter(center, 13);
map.addControl(new GLargeMapControl);
map.addControl(new GMapTypeControl);
geocoder = new GClientGeocoder();
window.document.forms[’formDati’].location.value=center;
geocoder.getLocations(center, addAddressToMap);
}
}

function showAddress(e){
var address=window.document.forms[’formDati’].address.value;
if (geocoder) {
geocoder.getLatLng(
address,
function(point) {
if (!point) {
alert(address + " not found");
}
else{
map.setCenter(point, 13);
window.document.forms[’formDati’]
.location.value=point;
geocoder.getLocations(address, addAddressToMap);
}
}
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 68

);}
}

function addAddressToMap(response){
map.clearOverlays();
if (!response || response.Status.code != 200) {
alert("Sorry, we were unable to geocode that address");
}else{
place = response.Placemark[0];
point = new GLatLng(place.Point.coordinates[1],
place.Point.coordinates[0]);
marker = new GMarker(point);
map.addOverlay(marker);
marker.openInfoWindowHtml(
’<b>Indirizzo:</b> ’ + place.address + ’<br>’ +
’<b>Codice nazione:</b> ’ +
place.AddressDetails.Country.CountryNameCode);
}
}

Il file di configurazione app.yaml
application: sbook
version: 1
runtime: python
api_version: 1

handlers:
- url: /lib
static_dir: lib
- url: /img
static_dir: img
- url: /src
static_dir: src
- url: /
script: main.py
- url: /tag
script: main.py
- url: /libri
script: libri.py
CAPITOLO 5. UN CASO DI STUDIO: SELLBOOK 69

- url: /libro
script: libri.py
- url: /img
script: libri.py
- url: /stanze
script: stanze.py
- url: /info
script: info.py
- url: /.*
script: not_found.py
Bibliografia

[1] http://code.google.com/intl/it-IT/appengine/

[2] http://code.google.com/intl/it-IT/appengine/docs/

[3] http://code.google.com/intl/it-IT/appengine/kb/

[4] http://code.google.com/intl/it-IT/appengine/articles/

[5] http://googleappengine.blogspot.com/

[6] http://groups.google.com/group/google-appengine

[7] http://code.google.com/intl/it-IT/appengine/terms.html

[8] http://code.google.com/intl/it-IT/appengine/downloads.html

[9] http://code.google.com/intl/it-IT/
appengine/docs/python/gettingstarted/

[10] Bigtable: A Distributed Storage System for Structured Data
Autori: Fay Chang, Jeffrey Dean, Sanjay Ghemawat, Wilson C. Hsieh,
Deborah A. Wallach, Mike Burrows, Tushar Chandra, Andrew Fikes e
Robert E. Gruber
Anno: 2006

[11] The Google File System
Autori: Sanjay Ghemawat, Howard Gobioff e Shun-Tak Leung
Anno: 2003

[12] http://www.di.unito.it/ argo/biblio/bibliofolks.py

[13] http://www.di.unito.it/ argo/biblio/biblio_graph.php?author=Baldoni

[14] http://www.di.unito.it/ argo/biblio/biblio_words.php?author=Baldoni

70
Bibliografia e sitografia 71

[15] Un’introduzione alla programmazione orientata agli oggetti attraverso
lo schema“Kernel-Modulo”
Autore: Matteo Baldoni
Anno: 2002

[16] http://www.aleax.it/it_gae.pdf

[17] http://www.educ.di.unito.it/VisualizzaCorsi/tag_cloud.php

[18] http://www.educ.di.unito.it/VisualizzaCorsi/tag.php?tag=web&year=

[19] http://www.salesforce.com/it/

[20] http://aws.amazon.com/

[21] http://en.wikipedia.org/wiki/Amazon_Web_Services

[22] http://en.wikipedia.org/wiki/Cloud_computing

[23] http://it.wikipedia.org/wiki/Cloud_computing

[24] http://en.wikipedia.org/wiki/Software_as_a_service

[25] http://www.python.it/

[26] http://www.python.org/doc/

[27] http://www.python.org/doc/current/

[28] http://it.diveintopython.org/

[29] http://docs.python.it/html/lib/

[30] http://www.ebay.it/