MokaByte 70- Gennaio 2003 
Domino R6
Lotus Domino entra nel mondo J2EE
di
Marco Fabbri
Lotus Domino e il suo ingresso nel mondo J2EE: come applicare i nuovi paradigmi Java alle applicazioni web scritte per l'ambiente di collaboration più diffuso al mondo? Quale approccio per portare le applicazioni scritte per Domino all'interno di un moderno web container? Vediamo la risposta IBM e le novità architetturali open source.

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

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it