|
Introduzione
Ci siamo: la prima settimana di ottobre è uscita
la nuova release di Lotus Domino, la versione 6. Questa
nuova release porta con sè una rivoluzione dopo
un lungo periodo di beta testing che ha riservato anche
diverse sorprese. La genesi della R6 infatti nasce nell'estate
del 2001 con crismi per certi versi rivoluzionari e
sorprendenti: la beta 4 del Domino server infatti include
Tomcat (nella versione 3.2) come web container e servlet
container. Lotus sembra voler confermare la sua tendenza
a implementare le ultime tecnologie all'interno del
suo application server con un certo anticipo così
come fece ad esempio nel 1997 rendendo Domino un XML
repository. Ma IBM riaggiusta il tiro in breve tempo:
forse troppo pericoloso avere un concorrente di Websphere
proprio in casa. Così la pre-release 1 che fa
seguito all'ultima beta vede sparire Tomcat ma il principio
è ormai sancito: Domino e le sue applicazioni
entreranno a far parte del mondo J2EE. Infatti poco
prima dell'uscita della versione Gold della R6 che chiude
un ciclo di testing lungo quasi 18 mesi, IBM pubblica
un white paper che indica le guidelines per gli sviluppatori
Lotus. Si avvia un processo lungo e complesso che, nelle
prossime due versioni dell'application server, dovrebbe
portare a una stretta integrazione con Websphere e all'utilizzo
sempre più pervasivo di Java e degli ambienti
di sviluppo dell'ultima generazione come Websphere Studio.
Domino
e J2EE: quali alternative?
IBM ha messo a disposizione un documento [1] per illustrare
in che modo dovrebbe avvenire la transizione verso il
mondo J2EE. Questo passaggio dovrebbe comprendere il
progressivo utilizzo di Websphere Application Server
per la creazione delle applicazioni Domino, l'utilizzo
di Websphere Studio in alternativa al Domino Designer
e la ridefinizione del ruolo di Domino Application Server
come Collaboration Server e puro repository. Ma al di
là delle guidelines, quali strumenti sono messi
a disposizione agli sviluppatori Domino per decidere
una strategia concreta per la conversione delle applicazioni?
Per quanto riguarda i tool di sviluppo, IBM metterà
a disposizione un plugin all'interno di Websphere Studio
5 per gestire direttamente gli oggetti Domino [2]. Per
quanto riguarda invece le scelte progettuali, sono stati
messi a disposizione diversi tutorials sul sito IBM
che propongono approcci diversi al tema della conversione;
vediamoli insieme.
Domino
e Websphere: utilizziamo le JSP e i custom tags
Il primo approccio che analizziamo è quello basato
sull'utilizzo di una libreria di custom tags che erano
stati messi a disposizione sin dalle prime versioni
beta della nuova release.
Sulla Notes.net, il sito di riferimento per gli sviluppatori
Lotus, compare quindi il tutorial per rivedere le applicazioni
Domino usando questa libreria [3]. Ma il dibattito si
apre immediatamente: siamo sicuri che questa modalità
di conversione delle applicazioni Domino sia realmente
vantaggiosa? Bob Balaban, uno degli sviluppatori Domino
- Java più famosi, fa molto discutere con un
articolo provocatorio [4] sulla inutilità delle
JSP. Al di là delle provocazioni, il dubbio più
consistente è quello di riportare nell'ambiente
Java i problemi di intrecciamento fra dati e presentazione
intrisechi nell'ambiente Domino senza risolverli. Un
nuovo tutorial ci propone una soluzione più in
linea con il paradigma J2EE.
Domino
e Websphere: il modello MVC
Il secondo approccio compie un passo in avanti rispetto
al precedente; ci consente di scindere in modo più
pulito i vari livelli di una web application e, per
usare uno schema presente nel Lotus Developers' Roadmap,
portare Domino nel Data level.

Figura 1 - Livelli di una web application
Nel tutorial distribuito da IBM [5], viene creato un
bean DiscussionTree molto semplice:
package
org.gallardo.domino.example;
import java.util.*;
public class DiscussionTree {
private Vector indent = null;
private Vector text = null;
public DiscussionTree(){
}
public DiscussionTree(Vector i, Vector t){
indent = i;
text = t;
}
public int getSize() {
if (text.size() == indent.size())
{
return text.size();
} else {
return 0;
}
}
public String textAt(int i) {
return (String) text.elementAt(i);
}
public int indentAt(int i) {
return ((Integer)indent.elementAt(i)).intValue();
}
}
il
quale viene popolato grazie alle classi di backend Java
di Domino
Session
nses = null;
try {
String server = "noizmaker";
String user = "dgallard";
String pwd = "minfishy";
String dbname = "wholelot.nsf";
nses = NotesFactory.createSession(server,
user, pwd);
Database db = nses.getDatabase("",
dbname);
if (!db.isOpen()) {
db.open();
}
String viewname = "All Documents";
View view = db.getView(viewname);
ViewNavigator viewnav = view.createViewNav();
ViewEntry ventry = viewnav.getFirst();
Vector text = new Vector();
Vector indent = new Vector();
while (ventry != null) {
if (ventry.isDocument()) {
int in = ventry.getIndentLevel();
indent.add(new Integer(in));
Vector colvals =
ventry.getColumnValues();
text.add(colvals.elementAt(3));
String val = colvals.elementAt(3).toString();
ventry = viewnav.getNext();
}
}
...
}
La
servlet che funge da controller instanzierà il
bean ed effettuerà il forward sulla pagina JSP
che implementa il livello di presentazione
DiscussionTree
dt = new DiscussionTree(indent, text);
req.setAttribute("DiscussionTree", dt);
RequestDispatcher rd =
servctx.getRequestDispatcher("/discussiontree.jsp");
rd.forward(req, resp);
La
domanda sorge spontanea: e quindi?
Chi legge frequentemente Mokabyte ha sicuramente visto
e rivisto questi concetti: facciamo quindi un passo
ulteriore verso la modularizzazione.
Domino
e Websphere: il ruolo di Struts
Probabilmente
molti di voi hanno già sentito parlare di Struts.
Questo progetto opensource all'interno dell'ormai famosissimo
Jakarta ha fatto passi da gigante nell'ultimo anno divenendo
lo standard de facto per la progettazione delle webapplications.
Lo spunto a ripensare le applicazioni Domino con questo
framework è nato da un articolo molto interessante
in due parti [6] e [7] comparso su developerWorks di
IBM l'anno scorso.
Struts consente di applicare nel dettaglio numerosi
patterns presenti nelle architetture J2EE e, soprattutto,
ci permette di pensare a Domino come un vero repository
di dati, alla stregua di un database relazionale e di
valorizzarlo nel suo ruolo di server di directory per
regolamentare gli accessi e stabilire i criteri di sicurezza.
La
forza di Struts sta proprio nella semplicità:
per svolgere un'azione all'interno di una webapplication,
occorre estendere una classe che si chiama Action e
definire il comportamento di questa azione attraverso
un file di configurazione XML.
I dati verranno poi visualizzati da una pagina jsp che
implementa il livello View.
Il bean o i bean che popolano la jsp hanno un formato
standard estendendo la classe ActionForm o addirittura
definendo il bean attraverso un file XML.
Niente più combinazioni di jsp:setProperty e
jsp:getProperty; il framework controllerà per
noi la presenza di beans e visualizzerà o ci
consentirà di modificare dati in modo più
trasparente.
In
sintesi il classico modello MVC

Figura
2 - Schema del modello MVC
è
stato esteso con il framework Struts in questo modo

Figura 3 - Schema del framework Struts
Cerchiamo
di fissare meglio questi concetti e iniziamo a vedere
Domino nel ruolo di server di directory per regolamentare
gil accessi.
Lo facciamo con la pagina di login per accedere alla
webapplication.
Struts
ha già implementato lo schema per noi: non ci
resta che definire il percorso dell'utente con la relativa
ActionMapping attraverso semplici tags XML:
/*
flusso di login */
<action path="/LoginLDAP"
type="com.tecla.dominoconv.VerificaLoginLDAPAction"
scope="request"
name="loginDynaForm"
input="/loginnotesldap.jsp">
<forward name="LoginRiuscito" path="/menucategorie.jsp"/>
</action>
Questo
tag XML dice al framework che, a seguito dell'inserimento
di username e password nella pagina di input (loginnotesldap.jsp),
questi dati devono essere passati alla classe com.tecla.dominoconv.VerificaLoginLDAPAction
e contestualmente deve essere popolato il bean loginDynaForm
con quei dati.
Il
framework mi ha consentito di non scrivere neppure il
codice del bean; ho potuto definirlo in un file XML
anch'esso
<form-bean
name="loginDynaForm"
type="org.apache.struts.action.DynaActionForm">
<form-property name="user" type="java.lang.String"/>
<form-property name="password" type="java.lang.String"/>
<form-property name="CN" type="java.lang.String"/>
<form-property name="mail" type="java.lang.String"/>
<form-property name="uid" type="java.lang.String"/>
</form-bean>
La
classe VerificaLoginLDAPAction valida l'utente e ottiene
le sue credenziali attraverso l'LDAP di Domino.
package
com.tecla.dominoconv;
import
javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
import java.util.ResourceBundle;
public
class VerificaLoginLDAPAction extends Action {
public ActionForward execute(ActionMapping
mapping,
ActionForm
form,
HttpServletRequest
req,
HttpServletResponse
res)
throws
Exception {
DynaActionForm dynaForm = (DynaActionForm)form;
ResourceBundle bundle = ResourceBundle.getBundle(
"resources.config");
final Log log;
log = LogFactory.getLog(VerificaLoginLDAPAction.class);
String dominoserver = bundle.getString("server.domino");
String dnprefix = bundle.getString("ldap.prefix.dn");
String dnsuffix = bundle.getString("ldap.suffix.dn");
//Ricavo lo username e la password
dalla ActionForm
String user = (String)dynaForm.get("user");
log.info("Username Form:
" + user);
String password = (String)dynaForm.get("password");
String
username = dnprefix + user + dnsuffix;
ActionErrors errors = new ActionErrors();
try {
Hashtable
env = new Hashtable(5);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://" + dominoserver);
env.put(Context.SECURITY_AUTHENTICATION,
"simple");
log.info("Username
LDAP: " + username);
env.put(Context.SECURITY_PRINCIPAL,
username);
env.put(Context.SECURITY_CREDENTIALS,
password);
DirContext ctx =
new InitialDirContext(env);
// Get the attributes
requested
String[] attrIDs
= { "CN", "mail", "uid"
};
Attributes answer
= ctx.getAttributes(username, attrIDs);
log.info("Risposta
LDAP Mail: " + answer.get("mail").get());
dynaForm.set("mail",answer.get("mail").get());
log.info("Risposta
LDAP CN: " + answer.get("CN").get());
dynaForm.set("CN",answer.get("CN").get());
if ((String)dynaForm.get("CN")
== null) {
errors.add(ActionErrors.GLOBAL_ERROR,
new
ActionError("errors.utente.noncorretto"));
}
ctx.close();
}
catch
(NamingException e) {
log.info(e.getMessage());
errors.add(ActionErrors.GLOBAL_ERROR,
new
ActionError("errors.utente.noncorretto"));
}
if
(!errors.empty()) {
saveErrors(req,
errors);
return
(new ActionForward(mapping.getInput()));
}
HttpSession session = req.getSession();
session.setAttribute("datiutente",
dynaForm);
return
mapping.findForward("LoginRiuscito");
}
Il
listato di questa Action è piuttosto semplice:
prima vengono caricati i parametri da un file properties,
poi si tenta il login sull'LDAP di Domino. Nel caso
l'autenticazione riesca, la Action effettua il forward
verso il menu altrimenti rispedisce l'utente sulla pagina
di login visualizzando un messaggio di errore.
Un
altro esempio, invece, ci illustra più chiaramente
il ruolo di Domino come repository di dati in un'architettura
J2EE.
Prendiamo un database Domino molto semplice fatto con
una vista e un form.
L'unica form raccoglie due dati di un impiegato: il
codice e il nome.
La vista invece li elenca in ordine di codice.

Figura 4 - Elenco impiegati su Domino
Grazie
a Struts possiamo scrivere una piccola applicazione
che lista e modifica questi dati trattando Domino in
modo molto simile a un database relazionale.
Per
prima cosa creiamo una Action che ci elenchi tutti gli
impiegati presenti nel database.
package
com.tecla.dominoconv;
import
javax.servlet.http.*;
import org.apache.struts.action.*;
import lotus.domino.*;
import java.util.Collection;
public
class ListaImpiegatiNotesAction extends Action {
public ActionForward execute(ActionMapping
mapping,
ActionForm
form,
HttpServletRequest
req,
HttpServletResponse
res)
throws
Exception {
ImpiegatoNotesForm impNotes
= new ImpiegatoNotesForm();
HttpSession session = req.getSession();
LoginForm datilogin = (LoginForm)
session.getAttribute("datiutente");
Collection listaimpiegati =
Statements.getView("employee.nsf",
impNotes,
"Impiegati",
datilogin);
req.setAttribute("listaimpiegati",
listaimpiegati);
return mapping.findForward("ElencoImpiegatiNotes");
}
Come
potete notare subito Struts ci consente una grande pulizia
del codice.
L'altra cosa molto importante è la precisa scissione
fra i livelli.
In
questo caso il bean Impiegato è una semplice
classe con metodi get e set e non è stata definita
via file XML.
package
com.tecla.dominoconv;
import
javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.*;
import java.util.HashMap;
import java.util.Map;
public
class ImpiegatoNotesForm extends ActionForm {
private String CODICE = "";
private String NOME = "";
private HashMap mapping = new
HashMap();
public ImpiegatoNotesForm()
{
super();
}
public void setCODICE(String
CODICE) {
this.CODICE = CODICE;
}
public String getCODICE() {
return (this.CODICE);
}
public void setNOME(String NOME)
{
this.NOME = NOME;
}
public String getNOME() {
return (this.NOME);
}
public void reset(ActionMapping
mapping,
HttpServletRequest request){
this.CODICE
= "";
this.NOME
= "";
}
public ActionErrors validate(ActionMapping
mapping,
HttpServletRequest
request){
ActionErrors
errors = new ActionErrors();
if (CODICE == null)
{
errors.add("Codice",
new
ActionError("error.Prodotto.codice"));
}
return errors;
}
Tra
l'altro il framework mi consente anche di implementare
la validazione dei dati direttamente dentro il bean
con il metodo Validate.
Dentro
la Action non esplicito mai operazioni sul modello;
essa fa parte della business logic, non deve gestire
direttamente i dati. A farlo ci pensano altre classi
che si limitano come nel nostro caso a restituirci collezioni
di oggetti che contengono la lista degli impiegati che
abbiamo richiesto.
In
questo caso la classe Statements attraverso il metodo
getView ci restituisce in una collection i dati della
vista Impiegati
package
com.tecla.dominoconv;
import
lotus.domino.Session;
import lotus.domino.NotesException;
import lotus.domino.Database;
import lotus.domino.View;
import lotus.domino.Database;
import lotus.domino.ViewNavigator;
import lotus.domino.Document;
import
java.util.HashMap;
import java.util.Hashtable;
import java.util.Collection;
public
class Statements {
public static final Collection
getView(String database,
Object
target,
String
viewname,
LoginForm
datilogin)
throws
NotesException {
Object
collection = null;
Session
sessioneDomino = null;
ViewNavigator
viewnav = null;
View
view = null;
String
username = null;
String
password = null;
try
{
username
= datilogin.getUser();
password
= datilogin.getPassword();
sessioneDomino
= ConnessioniDomino.creaSessione(
username,
password);
if
(sessioneDomino != null) {
System.out.println("Vista
richiesta: " + viewname);
System.out.println("Database:
" + database);
Database
db = sessioneDomino.getDatabase("",
database);
if
(!db.isOpen()) {
db.open();
}
view
= db.getView(viewname);
}
collection
= ResultSetUtils.getViewEntries(target,
view);
}
catch
(Exception n) {
n.printStackTrace();
}
finally
{
try
{
if
(sessioneDomino != null)
ConnessioniDomino.chiudiSessione(sessioneDomino);
}
catch (Exception n) {
n.printStackTrace();
}
}
return
(Collection) collection;
}
// end getCollection
Questa
classe riceve i dati del login dalla Action in modo
da poter aprire una sessione Domino via IIOP e poi popolare
la collection attraverso la classe ResultSetUtils.
Il metodo getViewEntries è altrettanto semplice:
package
com.tecla.dominoconv;
import
java.lang.reflect.InvocationTargetException;
import
java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import
lotus.domino.ViewNavigator;
import lotus.domino.ViewEntry;
import lotus.domino.View;
import lotus.domino.NotesException;
import lotus.domino.Document;
import lotus.domino.Item;
import
java.util.HashMap;
import java.util.Map;
import java.util.Hashtable;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import
org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class ResultSetUtils {
public static Collection getViewEntries(Object
target, View view)
throws
NotesException {
ViewNavigator viewnav
= view.createViewNav();
int cols = view.getColumnCount();
ArrayList list =
new ArrayList();
Vector columnNames
= view.getColumnNames();
Vector colvals =
new Vector();
String nomeColonna
= null;
String valoreColonna
= null;
Class factory =
target.getClass();
ViewEntry ventry
= viewnav.getFirst();
// Ciclo nella vista
while (ventry !=
null) {
colvals
= ventry.getColumnValues();
HashMap
map = new HashMap(cols, 1);
for
(int i = 0; i <= cols - 1; i++) {
nomeColonna
= (String) columnNames.elementAt(i);
valoreColonna
= (String) colvals.elementAt(i);
map.put(nomeColonna,
valoreColonna);
}
// Creo
il bean e lo popolo con i valori della vista
try
{
Object
bean = factory.newInstance();
BeanUtils.populate(bean,
map);
list.add(bean);
} catch
(Exception e) {
e.printStackTrace();
}
ventry = viewnav.getNext();
}
return ((Collection) list);
}
}
Grazie
alle classi beanutils che fanno parte di Struts, siamo
riusciti a popolare facilmente la collection di bean
Impiegato e restituirla alla Action; è stata
sufficiente un pò di conoscenza delle classi
Java di Domino.
Abbiamo
scritto molto codice; cerchiamo di riassumere i passaggi
effettuati.
Per prima cosa la Action ha richiesto alle classi che
gestiscono il modello una collezione di bean Impiegato.
Per farlo ha passato il nome del database Domino e il
nome della vista come parametri.
A
questo punto la classe Statements ha aperto una sessione
Domino e ne ha ricavato l'oggetto View.
Quest'oggetto è poi stato passato alla classe
ResultSetUtils che, dopo aver ciclato nella vista, ha
utilizzato la classe di utilità BeanUtils per
popolare automaticamente i beans Impiegato relativi
alla vista.
Una
volta ottenuta la collection, Struts ci mette a disposizione
una serie di custom tags che ci aiutano a implementare
il livello View. Infatti la pagina che visualizza la
lista degli impiegati è ridotta al minimo.
<!DOCTYPE
HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/tags/struts-bean" prefix="bean"
%>
<%@ taglib uri="/tags/struts-html" prefix="html"
%>
<%@ taglib uri="/tags/struts-logic" prefix="logic"
%>
<html:html
locale="true">
<head>
<title><bean:message key="welcome.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
<logic:iterate
id="impNotes" name="listaimpiegati"
type="com.tecla.dominoconv.ImpiegatoNotesForm">
<html:link page="/ModificaImpiegatoNotes.do"
name="impNotes" property="mapping">
<bean:write name="impNotes" property="CODICE"
filter="true"/>
</html:link>
<bean:write name="impNotes" property="NOME"
filter="true"/><br>
</logic:iterate>
</body>
</html:html>
Due
soli tags ci consentono di visualizzare tutti gli impiegati:
logic:iterate per ciclare all'interno della collection
e bean:write per mostrare una specifica proprietà
del bean.
La
sequenza della nostra piccola applicazione parte dal
login:

Figura
5 - Pagina di login dell'applicazione
Se
non digito il login corretto, ritorno alla pagina iniziale
con il messaggio di errore:

Figura 6 - Pagina di login dell'applicazione
in caso di errore
altrimenti
vengo forwardato sulla pagina del menu

Figura 7 - Pagina del menu
dell'applicazione
Premendo
il link lancio la Action che popola la collection con
i dati della vista Domino.

Figura 8 - Elenco impiegati
presenti sulla vista
L'utilizzo
di un database Domino con la form Impiegato è
voluto proprio per evidenziare il parallelo con il database
di test Sample che viene creato a seguito dell'installazione
di DB2.
Infatti questo esempio di applicazione basato su Struts
nasceva utilizzando un database relazionale [8] ed è
stato riadattato per usare Domino semplicemente riscrivendo
le due classi che implementano il modello cioè
Statements e ResultSetUtils.
Quindi grazie alla modularizzazione, è stato
possibile utilizzare Domino come repository di dati
intervenendo solo su poche classi e assimilando il resultset
che proviene da una query SQL a quello che si ricava
interrogando una vista Notes.
Conclusioni
Domino quindi di nuovo sotto i riflettori. Con l'arrivo
della nuova versione si aprono numerose strade per il
suo ingresso nel mondo J2EE. Abbiamo visto che Domino
è in grado di divenire la directory per autenticare
gli utenti sulla sua interfaccia LDAP. Ma è semplice
estendere questo concetto anche all'autorizzazione utilizzando
una delle funzioni native più famose della piattaforma
cioè l'ACL (Access Control List). E' sufficiente
creare delle classi a corredo per stabilire se un utente
ha i permessi per eseguire una determinata Action o
meno. Un approccio possibile è quello indicato
da Nic Hobbs nel suo modello di security [9].
Domino è inoltre in grado di essere un efficace
repository di dati e, con la presenza di un web container,
integrarsi molto più semplicemente con basi dati
relazionali.
Struts ci consente di completare l'ambiente di sviluppo
Notes con due caratteristiche prima praticamente assenti:
la costruzione formale di un flusso applicativo e la
possibilità di riciclare codice applicativo con
le più moderne metodologie di sviluppo.
Il flusso applicativo infatti risiede sul file struts-config.xml
e non è più necessario saltare da una
webqueryopen a una webquerysave per ricostruire la sequenza
del codice lotusscript eseguito. Molti sviluppatori
di terze parti hanno creato prodotti per manipolare
visivamente questo file; utilizzando Microsoft Visio
[10] o attraverso una console [11]
IBM sta pesantemente investendo su questo framework
integrandolo nella versione 5 del suo ambiente di sviluppo
[12].
Infine il codice delle applicazioni Domino ora può
risiedere su di un CVS Server e le applicazioni possono
essere costruite attraverso un file build di Ant; questo
consente un livello di condivisione del codice prima
sconosciuto.
Lotus Domino torna di attualità e si candida
per divenire una pedina importante nelle architetture
J2EE di IBM: molto lavoro attende i Business Partners....
Bibliografia
[1] Autore - "Titolo del Riferimento", Rivista/Casa
Editrice, Anno
[1] Lotus - "Technical Strategy and Domino Developers'
Roadmap", http://www.lotus.com/news/news.nsf/public/56E1A6B19621E87E85256C13006D9A66,
2002
[2] IBM - "Lotus Domino Plug-in for WebSphere Studio",
http://www-10.lotus.com/ldd/sandbox.nsf/ecc552f1ab6e46e4852568a90055c4cd/
3bb4deb9ecfa067a00256bd4003ce2ad?OpenDocument, 2002
[3] George Brichacek - "Simplified JSP page development
for Lotus Domino", https://www6.software.ibm.com/reg/devworks/dw-lsjspdev-i?S_TACT=102B7W79,
2002
[4] Bob Balaban - "JSPs: A Total Waste of Time?",
Lotus Advisor Magazine http://j2eeadvisor.com/doc/10022,
2002
[5] David Gallardo - "Building a J2EE application
with
Domino and WebSphere", Lotus Developer Domain http://www-105.ibm.com/developerworks/education.nsf/ibm-onlinecourse-bytitle/17D8C454E9B8D21586256C00004C3FA9?OpenDocument,
2002
[6] Chen Lin - "From Lotus Notes/Domino to WebSphere:
Lessons learned, part 1", http://www-106.ibm.com/developerworks/library/i-migrate/,
2001
[7] Chen Lin - "From Lotus Notes/Domino to WebSphere:
Lessons learned, part 2", http://www-106.ibm.com/developerworks/ibm/library/i-notes/,
2002
[8] Ted Husted - "Struts Pools: Simple poll application.
Demonstrates using a database with Struts", http://husted.com/struts/resources/polls-20011024.zip,
2002
[9] Ted Husted - "Role based security",http://husted.com/struts/resources/struts-security.htm,
2002
[10] Alien Factory - "Struts extensions for Microsoft
Visio",http://www.alien-factory.co.uk/struts/struts-index.html,
2002
[11] James Holmes - "Struts Console", http://www.jamesholmes.com/struts/console/,
2002
[12] IBM - "Websphere Studio Enterprise Developer",
http://www-3.ibm.com/software/ad/studioenterprisedev/about/,
2002
Marco
Fabbri Responsabile Ricerca e Sviluppo di Tecla.it
|