Raible's Wiki

Raible Designs
Wiki Home
News
Recent Changes

AppFuse

Homepage
  - Korean
  - Chinese
  - Italian
  - Japanese

QuickStart Guide
  - Chinese
  - French
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish
  - Japanese

User Guide
  - Korean
  - Chinese

Tutorials
  - Chinese
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish

FAQ
  - Korean

Latest Downloads

Other Applications

Struts Resume
Security Example
Struts Menu

Set your name in
UserPreferences

Edit this page


Referenced by
Articles
CreateManager_it
ValidationAndList_it




JSPWiki v2.2.33

[RSS]


Hide Menu

CreateActions_it


Parte III: Creare Action e JSP - Un HowTo per la creazione di Action di Struts e JSP nell'architettura di AppFuse.

Questo tutorial dipende da Parte II: Creare nuovi Manager.

Informazioni su questo Tutorial

Questo tutorial ti mostrerà come creare una Action di Struts, un Test JUnit (usando StrutsTestCase), ed una JSP per il form. La Action che creiamo parlerà con il PersonManager che abbiamo creato nel tutorial Creare Manager.

Per default, AppFuse esce con Struts come framework web. Dalla 1.6+, puoi usare Spring o WebWork come tuo framework web. Nella 1.7, è stato aggiunto il supporto per usare JSF o Tapestry.

Per installare uno di questi framework web invece di Struts, è sufficiente che tu vada nella directory extras e da qui nella directory del framework che vuoi installare. Il file README.txt in questa directory contiene ulteriori informazioni. I tutorial per questi altri framework sono elencati sotto.

Iniziamo a creare una nuova Struts Action ed una JSP per il tuo progetto AppFuse.

Ti dirò come faccio le cose nel Mondo Reale in un testo come questo.

Indice

  • [1] Aggiungi Tag XDoclet a Person per generare PersonForm
  • [2] Crea lo scheletro delle JSP usando XDoclet
  • [3] Crea PersonActionTest per effettuare test su PersonAction
  • [4] Crea PersonAction
  • [5] Esegui PersonActionTest
  • [6] Ripulisci la JSP per renderla presentabile
  • [7] Crea dei WebTests Canoo per effettuare test di interazioni browser-like

Aggiungi Tag XDoclet a Person per generare PersonForm [#1]

Ora generiamo il nostro oggetto PersonForm per Struts ed il nostro strato web. Per far questo, dobbiamo aggiugere dei tag XDoclet all'oggetto Person.java per creare la nostra ActionForm di Struts. Nel JavaDoc del file Person.java, aggiungi il seguente tag @struts.form (vedi User.java per un esempio):


* @struts.form include-all="true" extends="BaseForm"

Estendiamo org.appfuse.webapp.form.BaseForm perché ha un metodo toString() che ci permette di chiamare log.debug(formName) per stampare una visualizzazione più leggibile dell'oggetto Form.
Se non hai rinominato i package "org.appfuse" in "com.company" o in alternativa non hai la tua classe di modello nel package di default, può rivelarsi necessario specificare completamente il riferimento a org.appfuse.webapp.form.BaseForm nel tag @struts.form.

Ora se esegui ant gen-forms, Ant (ed XDoclet) genererà un PersonForm.java per te in build/web/gen/**/form.

Crea lo scheletro delle JSP usando XDoclet [#2]

In questo passo, genererai una pagina JSP per visualizzare le informazioni presenti nell'oggetto Person. Conterrà tag JSP di Struts per visualizzare righe di una tabella per ogni proprietà in Person.java. Lo strumento AppGen che viene utilzzato per far questo è basato su uno strumento StrutsGen - che è stato scritto in origine da Erik Hatcher. Si tratta in poche parola di giusto un paio di classi ed un po' di template XDoclet. Tutti questi file si trovano in extras/appgen.

Ecco i semplici passi per generare una JSP ed un file properties contenente le etichette per gli elementi del form:

  • Dalla linea di comando, naviga in "extras/appgen"
  • Esegui ant -Dobject.name=Person -Dappgen.type=pojo per generare una serie di file in extras/appgen/build/gen. Infatti, genererà tutti i file di cui hai bisogno per completare questo tutorial. Tuttavia, prendiamo solo quelli di cui abbiamo bisogno.
    • web/WEB-INF/classes/Person.properties (etichette per gli elementi del tuo form)
    • web/pages/personForm.jsp (file JSP per visualizzare una singola Persona)
    • web/pages/personList.jsp (file JSP per visualizzare un elenco di Persone)
  • Copia il contenuto di Person.properties in web/WEB-INF/classes/ApplicationResources.properties. Queste sono tutte le chiavi di cui avrai bisogno per titoli/intestazioni e proprietà del form. Ecco un esempio di quello che dovrai aggiungere ad ApplicationResources.properties:
# -- person form --
personForm.id=Id
personForm.firstName=First Name
personForm.lastName=Last Name

person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.

# -- person list page --
personList.title=Person List
personList.heading=Persons

# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
  • Copia personForm.jsp in web/pages/personForm.jsp. Copia personList.jsp in web/pages/personList.jsp.
I file nella directory "pages" finiranno in "WEB-INF/pages" al momento del deployment. Il container mette in sicurezza tutti i file sotto WEB-INF. Ciò si applica alle richieste del client, ma non ai forward dalla ActionServlet di Struts. Mettere tutte le JSP sotto WEB-INF assicura che queste siano accedute solo tramite Action, e non direttamente dal client o fra loro. Ciò consente di spostare la sicurezza nella Action, dove può essere gestita in modo più efficiente, e fuori dallo strato di presentazione di base.

La sicurezza a livello di web application per AppFuse specifica che tutti le url del tipo *.html siano protette (ad eccezione di /signup.html e /passwordHint.html). Ciò garantisce che i client passino attraverso una Action per arrivare ad una JSP (o almeno a quelle in pages).

NOTA: Se vuoi personalizzare il CSS per una pagina particolare, puoi aggiungere <body id="pageName"/> in cima al file. Questo verrà risucchiato da SiteMesh e messo nella pagina finale. Dopo puoi personalizzare il tuo CSS pagina per pagina usando qualcosa del tipo seguente:
body#pageName element.class { background-color: blue } 
  • Aggiungi le chiavi ApplicationResources.properties relative a titoli ed intestazioni nelle JSP
Nelle JSP generate, ci sono due chiavi per il titolo (in cima alla finestra del browser) e l'intestazione (in testa alla pagina). Questi campi sono forniti sopra con i nomi di chiavi di personDetail.title personDetail.heading.

Appena sopra, abbiamo aggiunto delle chiavi del tipo "personForm.*" a questo file, per cui per quale motivo ora uso personDetail invece di personForm per i titoli e le intestazioni? Il motivo migliore è che dà una buona separazione fra le etichette del form ed il testo nella pagina. Un altro motivo è perché tutti i *Form.* ti danno una buona rappresentazione give di tutti i campi nel tuo database.

Recentemente ho avuto un cliente che voleva tutti i campi nel database ricercabili. Ciò è stato piuttosto semplice da fare. Ho semplicemente cercato tutte le chiavi in ApplicationResources.properties che contenessero "Form." e le ho messe un un menu a discesa. Sulla UI, l'utente poteva inserire un termine da cercare e selezionare la colonna in cui volesse effettuare la ricerca. Sono stato contento di aver seguito questa distinzione Form vs. Detail in quel progetto!

Crea PersonActionTest per effettuare test su PersonAction [#3]

Per creare un Test StrutsTestCase per PersonAction, inizia con il creare un file PersonActionTest.java nella directory test/web/**/action:


package org.appfuse.webapp.action;

import org.appfuse.Constants;
import org.appfuse.webapp.form.PersonForm;

public class PersonActionTest extends BaseStrutsTestCase {
    
    public PersonActionTest(String name) {
        super(name);
    }

    public void testEdit() throws Exception {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Edit");
        addRequestParameter("id""1");
        actionPerform();

        verifyForward("edit");
        assertTrue(request.getAttribute(Constants.PERSON_KEY!= null);
        verifyNoActionErrors();
    }

    public void testSave() throws Exception {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Edit");
        addRequestParameter("id""1");

        actionPerform();

        PersonForm personForm =
            (PersonFormrequest.getAttribute(Constants.PERSON_KEY);
        assertTrue(personForm != null);
        
        setRequestPathInfo("/savePerson");
        addRequestParameter("method""Save");

        // update the form from the edit and add it back to the request
        personForm.setLastName("Feltz");
        request.setAttribute(Constants.PERSON_KEY, personForm);

        actionPerform();

        verifyForward("edit");
        verifyNoActionErrors();
    }

    public void testRemove() throws Exception {
        setRequestPathInfo("/editPerson");
        addRequestParameter("method""Delete");
        addRequestParameter("id""2");
        actionPerform();

        verifyForward("mainMenu");
        verifyNoActionErrors();
    }
}

Devi aggiungere PERSON_KEY come variabile alla classe src/dao/**/Constants.java. Il nome, "personForm", corrisponde al nome dato al form nel file struts-config.xml.


    /**
     * The request scope attribute that holds the person form.
     */
    public static final String PERSON_KEY = "personForm";

Se provi ad eseguire questo test, otterrai una serie di NoSuchMethodErrors - pertanto definiamo i metodi edit, save, e delete della classe PersonAction.

Crea PersonAction [#4]

In src/web/**/action, crea un file PersonAction.java con il seguente contenuto:


package org.appfuse.webapp.action;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;

import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;
import org.appfuse.webapp.form.PersonForm;

/**
 * @struts.action name="personForm" path="/editPerson" scope="request"
 *  validate="false" parameter="method" input="mainMenu"
 */
public final class PersonAction extends BaseAction {
    
    public ActionForward cancel(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
    throws Exception {
        return mapping.findForward("mainMenu");
    }

    public ActionForward delete(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'delete' method");
        }

        ActionMessages messages = new ActionMessages();
        PersonForm personForm = (PersonFormform;

        // Exceptions are caught by ActionExceptionHandler
        PersonManager mgr = (PersonManagergetBean("personManager");
        mgr.removePerson(personForm.getId());

        messages.add(ActionMessages.GLOBAL_MESSAGE,
                     new ActionMessage("person.deleted"));

        // save messages in session, so they'll survive the redirect
        saveMessages(request.getSession(), messages);

        return mapping.findForward("mainMenu");
    }

    public ActionForward edit(ActionMapping mapping, ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'edit' method");
        }

        PersonForm personForm = (PersonFormform;

        // if an id is passed in, look up the user - otherwise
        // don't do anything - user is doing an add
        if (personForm.getId() != null) {
            PersonManager mgr = (PersonManagergetBean("personManager");
            Person person = mgr.getPerson(personForm.getId());
            personForm = (PersonFormconvert(person);
            updateFormBean(mapping, request, personForm);
        }

        return mapping.findForward("edit");
    }

    public ActionForward save(ActionMapping mapping, ActionForm form,
                              HttpServletRequest request,
                              HttpServletResponse response)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("Entering 'save' method");
        }

        // Extract attributes and parameters we will need
        ActionMessages messages = new ActionMessages();
        PersonForm personForm = (PersonFormform;
        boolean isNew = ("".equals(personForm.getId()));

        if (log.isDebugEnabled()) {
            log.debug("saving person: " + personForm);
        }

        PersonManager mgr = (PersonManagergetBean("personManager");
        Person person = (Personconvert(personForm);
        mgr.savePerson(person);

        // add success messages
        if (isNew) {
            messages.add(ActionMessages.GLOBAL_MESSAGE,
                         new ActionMessage("person.added"));

            // save messages in session to survive a redirect
            saveMessages(request.getSession(), messages);

            return mapping.findForward("mainMenu");
        else {
            messages.add(ActionMessages.GLOBAL_MESSAGE,
                         new ActionMessage("person.updated"));
            saveMessages(request, messages);

            return mapping.findForward("edit");
        }
    }
}

Noterai che nel codice sopra ci sono molte chiamate per convertire un oggetto PersonForm o Person. Il metodo convert è BaseAction.java (la quale richiama ConvertUtil.convert()) ed usa BeanUtils.copyProperties per convertire i POJO → ActionForm ed ActionForm → POJO.

Se stai eseguendo dentro Eclipse, potresti aver bisogno di fare un "refresh" sul progetto in modo da vedere PersonForm. Si trova in build/web/gen, che dovrebbe essere una delle cartelle sorgenti del tuo progetto. Questo è l'unico modo per Eclipse do vedere ed importare PersonForm, in quanto viene generato da XDoclet e non fa parte del tuo albero sorgenti regolare. Puoi trovarlo in build/web/gen/org/appfuse/webapp/form/PersonForm.java.
In BaseAction puoi registrare ulteriori Converter (i.e. DateConverter) in modo che BeanUtils.copyProperties sappia come convertire String → Object. Se hai delle List nei tuoi POJO (i.e. per relazioni genitore-figlio), dovrai convertirle manualmente usando il metodo convertLists(java.lang.Object) .

Ora devi aggiungere il forward edit e l'action-mapping savePerson, entrambi specificati in PersonActionTest. Per farlo, aggiungi un altro paio di tag XDoclet in cima al file PersonAction.java. Fai questo subito sotto la dichiarazione della classe. Dovresti avere di già il tag XDoclet per l'action-mapping editPerson, ma te lo mostro qui in modo che tu possa vedere tutti i tag XDoclet in cima a questa classe.


/**
 * @struts.action name="personForm" path="/editPerson" scope="request"
 *  validate="false" parameter="method" input="mainMenu"
 
 * @struts.action name="personForm" path="/savePerson" scope="request"
 *  validate="true" parameter="method" input="edit"
 
 * @struts.action-forward name="edit" path="/WEB-INF/pages/personForm.jsp"
 */
public final class PersonAction extends BaseAction {

La differenza principale fra gli action-mapping editPerson e savePerson è che savePerson ha la validazion attiva (vedi validation="true") nel tag XDoclet sopra. Nota che l'attributo "input" deve riferirsi ad un forward, e non può essre un path (i.e. /editPerson.html). Se preferisci usare il path di save sia per edit che per save, è possibile anche quello. Controlloa solo che sia impostato validate="false", poi nel tuo metodo "save" - dovrai richiamare form.validate() e gestire gli errori in modo appropriato.

Potresti notare che il codice che stai usando per richiamare il PersonManager è lo stesso codice usato nel test PersonManagerTest. Sia PersonAction che PersonManagerTest sono client di PersonManagerImpl, pertanto ciò ha perfettamente senso.

É quasi tutto fatto per questo tutorial, andiamo ad eseguire i test!

Esegui PersonActionTest [#5]

Se guardi il nostro PersonActionTest, tutti i test dipendono dall'avere un record con id=1 nel database (e testRemove dipende da id=2), pertanto aggiungilo al nostro file dati di esempio (metadata/sql/sample-data.xml). Lo aggiungerei in coda - l'ordine non è importante poiché (attualmente) non ha relazioni con altre tabelle.

  <table name='person'>
    <column>id</column>
    <column>first_name</column>
    <column>last_name</column>
    <row>
      <value>1</value>
      <value>Matt</value>
      <value>Raible</value>
    </row>
    <row>
      <value>2</value>
      <value>James</value>
      <value>Davidson</value>
    </row>
  </table>

DBUnit carica questo file prima dell'esecuzione di uno qualsiasi dei nostri test, pertanto questo record sarà disponibile al PersonActionTest.

Ora se esegui ant test-web -Dtestcase=PersonAction - tutto dovrebbe funzionare come previsto. Controlla che Tomcat non sia in esecuzione prima di provare questo.

BUILD SUCCESSFUL
Total time: 1 minute 21 seconds

Ripulisci la JSP per renderla presentabile [#6]

Ora ripuliamo la pagina personForm.jsp generata. Modifica la action di <html:form> in "savePerson" in modo che la validazione sia attiva quando si registra. Modifica anche l'attributo focus da focus="" a focus="firstName" in modo che il cursore sia nel campo firstName quando la pagina viene caricata (questo viene fatto con JavaScript).

Un'altra cosa che devi fare è scommentare le righe seguenti al termine si personForm.jsp. Questo è perché il Validator lancerà un'eccezione se viene specificato un formName e non esistono regole di validazione per esso.

Personalmente, credo che questo sia un bug, ma i Committer di Struts non erano d'accordo.
<html:javascript formName="personForm" cdata="false"
    dynamicJavascript="true" staticJavascript="false"/>
<script type="text/javascript" 
    src="<html:rewrite page="/scripts/validator.jsp"/>"></script>

Ora se esegui ant db-load deploy, fai partire Tomcat e punti il tuo browser si http://localhost:8080/appfuse/editPerson.html?id=1, dovresti vedere qualcosa del genere:

personForm-final.png

NOTA: Usa il target deploy-web se hai modificato un qualsiasi file sotto la directory web. Altrimenti, usa deploy che compila ed effettua il deploy.

Infine, per rendere questa pagina più user friendly, potresti voler aggiungere un messaggio per i tuoi utenti in cima al form, il che può essere facilmente fatto aggiungenfo del testo (usando <fmt:message>) in cima alla pagina personForm.jsp.

[Opzionale] Crea dei WebTests Canoo per effettuare test di interazioni browser-like [#7]

Il passo finale (opzionale) di questo tutorial è creare un WebTest Canoo per effettuare test sulle JSP.

Dico che questo passo è opzionale, perché puoi eseguire gli stessi test attraverso il tuo browser.

Puoi usare le URL seguenti per effettuare test sulle diverse action di aggiunta, modifica e registrazione di un utente.

I test di Canoo sono piuttosto immediati in quanto si configurano tramite un semplice file XML. Per aggiungere test per add, edit, save e delete, apri test/web/web-tests.xml ed aggiungi il seguente XML. Noterai che questo frammento ha un target che si chiama PersonTests per eseguire tutti i test correlati.

Io uso nomi CamelCase per i target (controlo il tradizionale minuscolo, separato da trattini) perché quando digiti -Dtestcase=Name, ho scoperto che sono solito fare il CamelCase per i miei Test JUnit.


<!-- runs person-related tests -->
<target name="PersonTests"
    depends="EditPerson,SavePerson,AddPerson,DeletePerson"
    description="Call and executes all person test cases (targets)">
    <echo>Successfully ran all Person JSP tests!</echo>
</target>

<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson"
    description="Tests editing an existing Person's information">
    <webtest name="editPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
        </steps>
    </webtest>
</target>

<!-- Edit a person and then save -->
<target name="SavePerson"
    description="Tests editing and saving a user">
    <webtest name="savePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set lastName" name="lastName" value="Canoo"/>
            <clickbutton label="Save" description="Click Save"/>
            <verifytitle description="Page re-appears if save successful"
                text=".*${personDetail.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.updated}"/>
        </steps>
    </webtest>
</target>

<!-- Add a new Person -->
<target name="AddPerson"
    description="Adds a new Person">
    <webtest name="addPerson">
        &config;
        <steps>
            &login;
            <invoke description="click Add Button" url="/editPerson.html"/>
            <verifytitle description="we should see the personDetail title"
                text=".*${personDetail.title}.*" regex="true"/>
            <setinputfield description="set firstName" name="firstName" value="Abbie"/>
            <setinputfield description="set lastName" name="lastName" value="Raible"/>
            <clickbutton label="${button.save}" description="Click button 'Save'"/>
            <verifytitle description="Main Menu appears if save successful"
                text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.added}"/>
        </steps>
    </webtest>
</target>

<!-- Delete existing person -->
<target name="DeletePerson"
    description="Deletes existing Person">
    <webtest name="deletePerson">
        &config;
        <steps>
            &login;
            <invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
            <prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
            <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
            <verifyNoDialogResponses/>
            <verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.deleted}"/>
        </steps>
    </webtest>
</target>

Dopo aver aggiunto questo, dovresti essere in grado di eseguire ant test-canoo -Dtestcase=PersonTests con Tomcat in esecuzione o ant test-jsp -Dtestcase=PersonTests se vuoi che Ant faccia lo start/stop di Tomcat per te. Per includere i PersonTests quando vengono eseguiti tutti i test Canoo, aggiungilo come dipendenza al target "run-all-tests".

Noteria che no c'è alcun log nella finestra lato client di Canoo. Se vuoi vedere cosa sta facendo, puoi aggiungere le righe seguenti fra </webtest> e </target> alla fine di ciascun target.

<loadfile property="web-tests.result" 
    srcFile="${test.dir}/data/web-tests-result.xml"/>
<echo>${web-tests.result}</echo>
BUILD SUCCESSFUL
Total time: 11 seconds


Prossima Puntata: Parte IV: Aggiungere la Validazione ed una Schermata Elenco - Aggiungere una logica di validazione al personForm in modo che firstName e lastName siano campi obbligatori ed aggiungere una schermata di elenco per visualizzare tutti i record di tipo person nel database.


Attachments:


Go to top   Edit this page   More info...   Attach file...
This page last changed on 16-Jun-2008 06:53:26 MDT by MarcelloTeodori.