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
ValidationAndListSpr...




JSPWiki v2.2.33

[RSS]


Hide Menu

SpringControllers_pt


Parte III: Criando Controllers e JSPs - Um HowTo para criar Controllers Spring and JSPs em um projeto AppFuse.

Este tutorial depende da Parte II: Criando novos Gerentes (''Managers'').

Sobre este Tutorial

Este tutorial mostrará como criar Controllers Spring e JSPs. Ele também demonstrará como escrever testes unitários JUnit para testar seus Controllers. O Controller que criamos conversará com o PersonManager criado no tutorial Criando Gerentes (''Managers'').
Vou dizer a vocês como faço as coisas no Mundo Real em textos como este.

Vamos começar criando um novo Controller e JSP com a arquitetura do AppFuse. Se você ainda não instalou o módulo Spring MVC, faça isso rodando ant install-springmvc.

Tabela de Conteúdo

  • [1] Crie esqueletos de JSPs utilizando XDoclet
  • [2] Crie o PersonFormControllerTest para testar o PersonFormController
  • [3] Crie o PersonFormController
  • [4] Rode o PersonFormControllerTest
  • [5] Limpe o JSP para deixá-lo apresentável
  • [6] Crie Canoo WebTests para testar ações comuns de navegador

Crie esqueletos de JSPs utilizando XDoclet [#1]

Neste passo, você gerará uma página JSP para mostrar informações sobre um objeto Person. Ele possuirá tags JSP do Spring para renderizar campos editáveis para cada propriedade do Person.java. A ferramenta AppGen, que é baseada em uma ferramenta chamada StrutsGen - escrita inicialmente por Erik Hatcher. São basicamente algumas classes e alguns templates XDoclet. Todos estes arquivos estão localizados em extras/appgen.

Aqui vão alguns passos simples para gerar o JSP e o arquivo de propriedades contendo rótulos para os elementos do formulário:

  • Da linha de comando, navegue para o diretório "extras/appgen"
  • Execute ant -Dobject.name=Person -Dappgen.type=pojo para gerar alguns arquivos em extras/appgen/build/gen. De fato, ele gerará TODOS os arquivos que serão necessários para completar estes tutoriais. Apesar disso, vamos apenas pegar os arquivos necessários para este tutorial.
    • web/WEB-INF/classes/Person.properties (rótulos para nossos elementos de formulário)
    • web/pages/personForm.jsp (arquivo JSP para mostrar uma única pessoa)
    • web/pages/personList.jsp (arquivo JSP para mostrar uma lista de pessoas)
  • Copie o conteúdo do arquivo Person.properties em web/WEB-INF/classes/ApplicationResources.properties (para rodar os testes de action sem erros), e depois copie este conteúdo para o arquivo web/WEB-INF/classes/ApplicationResources_pt_BR.properties, modificando o seu conteúdo. Estas são todas as chaves necessárias para título/cabeçalho e propriedades do formulário. Aqui está um exemplo do que adicionar ao arquivo ApplicationResources.properties:
# -- person form --
person.id=Id
person.firstName=First Name
person.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

E aqui está o exemplo do que adicionar ao arquivo ApplicationResources_pt_BR.properties:

# -- formulário da pessoa --
person.id=Id
person.firstName=Nome
person.lastName=Sobrenome

person.added=A Pessoa foi adicionada com sucesso.
person.updated=A Pessoa foi alterada com sucesso.
person.deleted=A Pessoa foi removida com sucesso.

# -- página de listagem de pessoas --
personList.title=Lista de Pessoas
personList.heading=Pessoas

# -- página de detalhamento de pessoa --

personDetail.title=Detalhamento da Pessoa
personDetail.heading=Informações da Pessoa
  • Copie personForm.jsp para web/pages/personForm.jsp. Copie personList.jsp para web/pages/personList.jsp. Note que o primeiro caracter de cada novo nome de arquivo é minúsculo.
Os arquivos no diretório "pages" serão publicados em "WEB-INF/pages" em tempo de publicação. O container disponibiliza segurança para todos os arquivos dentro de WEB-INF. Isto se aplica a requisições de cliente, mas não a envios para o DispatchServlet. Colocando todos os JSPs em WEB-INF garante que eles serão acessados apenas por Controllers, e não diretamente por um cliente. Isto permite que a segurança seja movida para dentro do Controller, onde pode ser tratada com maior eficiência, retirando ela da camada de apresentação.

A segurança de aplicação web para o AppFuse especifica que todos os padrões de url *.html devem ser protegidos (exceto o /signup.html e /passwordHint.html). Isto garante que o cliente deve acessar uma Action para acessar um JSP (pelo menos, os que residem em pages).

NOTA: Se você deseja customizar o CSS para uma página particular, você pode adicionar <body id="pageName"/> na parte superior do seu arquivo. Isto será processado pelo SiteMesh e colocado ao final da página. Você pode customizar seu CSS em uma base página por página utilizando algo como o seguinte:
body#pageName element.class { background-color: blue } 
  • Adicione chaves no ApplicationResources.properties (no nosso caso, ApplicationResources_pt_BR.properties) para os títulos e cabeçalhos nos JSPs
Nos JSPs gerados, existem duas chaves, uma para o título (alto da janela do navegador) e outra para o cabeçalho (cabeçalho da página). Agora precisaremos adicionar estas duas chaves (personDetail.title and personDetail.heading) ao ApplicationResources.properties (no nosso caso, ApplicationResources_pt_BR.properties.).
# -- person detail page --
personDetail.title=Detalhamento da Pessoa
personDetail.heading=Informações da Pessoa
Agora acima, , adicionamos as chaves "personForm.*" a este arquivo, então porque usar personForm e personDetail? A melhor razão é porque esta prática nos dá uma boa separação entre rótulos de formulários e texto na página. Outra razão é porque todos os *Form.* nos dão uma boa representação de todos os campos de nosso banco de dados.

Recentemente tive um cliente que queria que todos os campos do banco de dados fossem procuráveis. Isto foi fácil de fazer. Apenas procurei todas as chaves no arquivo ApplicationResources.properties que contém o padrão "Form." e os coloquei em um drop-down(select). Na UI, o usuário podia entrar com um termo de busca e selecionar a coluna uqe ele necessitava procurar. Fiquei feliz em ter seguido a distinção Form vs. Detail naquele projeto!

Crie o PersonFormControllerTest para testar o PersonFormController [#2]

Para criar um teste JUnit para o PersonFormController, comece criando um arquivo PersonFormControllerTest.java no diretório test/web/**/action.


package org.appfuse.webapp.action;

import org.appfuse.model.Person;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;

public class PersonFormControllerTest extends BaseControllerTestCase {
    private PersonFormController c;
    private MockHttpServletRequest request;
    private ModelAndView mv;

    protected void setUp() throws Exception {
        // needed to initialize a user
        super.setUp();
        c = (PersonFormControllerctx.getBean("personFormController");
    }

    protected void tearDown() {
        c = null;
    }

    public void testEdit() throws Exception {
        log.debug("testing edit...");
        request = newGet("/editPerson.html");
        request.addParameter("username""tomcat");

        mv = c.handleRequest(request, new MockHttpServletResponse());

        assertEquals("personForm", mv.getViewName());
    }

    public void testSave() throws Exception {
        request = newGet("/editPerson.html");
        request.addParameter("id""1");

        mv = c.handleRequest(request, new MockHttpServletResponse());

        Person person = (Personmv.getModel().get(c.getCommandName());
        assertNotNull(person);
        
        request = newPost("/editPerson.html");
        super.objectToRequestParameters(person, request);
        request.addParameter("lastName""Updated Last Name");
        
        mv = c.handleRequest(request, new MockHttpServletResponse());
        
        Errors errors =
            (Errorsmv.getModel().get(BindException.ERROR_KEY_PREFIX + "person");
        assertNull(errors);
        assertNotNull(request.getSession().getAttribute("messages"));        
    }
    
    public void testRemove() throws Exception {
        request = newPost("/editPerson.html");
        request.addParameter("delete""");
        request.addParameter("id""2");
        mv = c.handleRequest(request, new MockHttpServletResponse());
        assertNotNull(request.getSession().getAttribute("messages"));
    }
}

Nada irá compilar agora (ant compile) porque é necessário criar o PersonFormController referenciado neste teste.

Crie o PersonFormController [#3]

Em src/web/**/action, crie um arquivo PersonFormController.java com o seguinte conteúdo:


package org.appfuse.webapp.action;

import java.util.Locale;

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

import org.apache.commons.lang.StringUtils;
import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

public class PersonFormController extends BaseFormController {
    private PersonManager mgr = null;

    public void setPersonManager(PersonManager mgr) {
        this.mgr = mgr;
    }

    protected Object formBackingObject(HttpServletRequest request)
    throws Exception {
        String id = request.getParameter("id");
        Person person = null;

        if (!StringUtils.isEmpty(id)) {
            person = mgr.getPerson(id);
        else {
            person = new Person();
        }


        return person;
    }

    public ModelAndView processFormSubmission(HttpServletRequest request,
                                              HttpServletResponse response,
                                              Object command,
                                              BindException errors)
    throws Exception {
        if (request.getParameter("cancel"!= null) {
            return new ModelAndView(new RedirectView(getSuccessView()));
        }

        return super.processFormSubmission(request, response, command, errors);
    }

    public ModelAndView onSubmit(HttpServletRequest request,
                                 HttpServletResponse response, Object command,
                                 BindException errors)
    throws Exception {
        if (log.isDebugEnabled()) {
            log.debug("entering 'onSubmit' method...");
        }

        Person person = (Personcommand;
        boolean isNew = (person.getId() == null);
        String success = getSuccessView();
        Locale locale = request.getLocale();

        if (request.getParameter("delete"!= null) {
            mgr.removePerson(person.getId().toString());

            saveMessage(request, getText("person.deleted", locale));
        else {
            mgr.savePerson(person);

            String key = (isNew"person.added" "person.updated";
            saveMessage(request, getText(key, locale));

            if (!isNew) {
                success = "editPerson.html?id=" + person.getId();
            }
        }

        return new ModelAndView(new RedirectView(success));
    }
}

Na classe acima, existem alguns métodos que você pode não estar familiarizado. O método formBackingObject() é utilizado para fornecer o objeto que este Controller opera. O método processFormSubmission() é utilizado para detectar o botão cancel, e o onSubmit() é chamado em requisições POST e manipula remoção/adição/alteração no formulário.

Existem algumas chaves que você (deve) adicionar ao arquivo ApplicationResources.properties para mostrar as mensagens de sucesso. Este arquivo é localizado em web/WEB-INF/classes - abra e adicione o seguinte:

Geralmente eu adiciono abaixo do comentário # -- Mensagens de Sucesso --.
person.added=A Pessoa foi adicionada com sucesso.
person.updated=A Pessoa foi alterada com sucesso.
person.deleted=A Pessoa foi removida com sucesso.

Você pode utilizar mensagens genéricas para added, deleted and updated, o que melhor funcionar para você. É bom separar mensagens caso seja necessária uma mudança baseada por entidade.

Você pode notar que o código que estamos utilizando para chamar o PersonManager é o mesmo que chamamos em nosso PersonManagerTest. Tanto o PersonFormController quanto o PersonManagerTest são clientes do PersonManagerImpl, o que faz sentido.

Agora vamos precisar adicionar o mapeamento url para este controller no arquivo web/WEB-INF/action-servlet.xml. No bloco abaixo, uma linha nova está na parte inferior, com <prop key="/editPerson.html">:


    <bean id="urlMapping" 
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/editProfile.html">userFormController</prop>
                <prop key="/mainMenu.html">filenameController</prop>
                <prop key="/editUser.html">userFormController</prop> 
                <prop key="/selectFile.html">filenameController</prop>
                <prop key="/uploadFile.html">fileUploadController</prop>
                <prop key="/passwordHint.xml">passwordHintController</prop>
                <prop key="/signup.xml">signupController</prop>
                <prop key="/editPerson.html">personFormController</prop>
            </props>
        </property>
    </bean>

Também é necessário adicionar a definição <bean> para o personFormController no mesmo arquivo:


    <bean id="personFormController" class="org.appfuse.webapp.action.PersonFormController">
        <property name="commandName"><value>person</value></property>
        <property name="commandClass"><value>org.appfuse.model.Person</value></property>
        <!--property name="validator"><ref bean="beanValidator"/></property-->
        <property name="formView"><value>personForm</value></property>
        <property name="successView"><value>mainMenu.html</value></property>
        <property name="personManager"><ref bean="personManager"/></property>
    </bean>

A propriedade "validator" está comentada no bloco XML acima porque não definimos nenhuma regra de validações para o objeto Person. Removeremos este comentário quando adicionarmos a validação.

Rode o PersonFormControllerTest [#4]

Se você olhar nosso PersonFormControllerTest, todos os testes dependem de um registro com o id=1 no banco de dados (e o testRemove depende do id=2), então vamos adicionar estes registros em nosso arquivo de dados para teste (metadata/sql/sample-data.xml). Apenas adiciono eles ao final - a ordem não é importante porque (atualmente) não é relacionado com outras tabelas.
  <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>

O DBUnit carrega o arquivo antes de rodarmos qualquer teste, então os registros serão disponibilizados para os testes do nosso Controller.

Certifique-se que está no diretório base de seu projeto. Se você rodar ant test-web -Dtestcase=PersonFormController - tudo deve funcionar como planejado.

BUILD SUCCESSFUL
Total time: 21 seconds

Limpe o JSP para deixá-lo apresentável [#5]

Se você quer adicionar uma melhoria de utilização para seu formulário, você pode setar o cursor para focar no seu primeiro campo quando a página carregar. Simplesmente adicione o seguinte JavaScript na parte inferior do arquivo:

<script type="text/javascript">
    document.forms["person"].elements["firstName"].focus();
</script>

Agora se você executar ant db-load deploy, iniciar o Tomcat e apontar seu navegador para http://localhost:8080/appfuse/editPerson.html?id=1. Você terá um resultado semelhante a este:

editPerson.PNG(info)

Finalmente, para deixar esta página mais amigável, você pode adicionar uma mensagem para seu usuário na parte superior do seu formulário, o que pode ser feito facilmente (usando a tag <fmt:message>) na parte superior de sua página personForm.jsp.

[Opcional] Crie Canoo WebTests para testar ações comuns de navegador [#6]

O passo final (opcional) para este tutorial é criar um Canoo WebTest para testar osJSPs.
Digo que este teste é opcional porque você pode rodar os mesmos testes no seu navegador.

Você pode utilizar as seguintes URLs para testar diferentes ações para adicionar, editar e salvar uma pessoa.

Testes Canoo são simples, configurados apenas por um arquivo XML. Para adicionarmos testes para as operações CRUD(add, edit, save e delete), abriremos test/web/web-tests.xml e adicionaremos o seguinte XML. Note que este fragmento possui um alvo(target) nomeado PersonTests que executa todos os testes relacionados.

Utilizo notação composta(CamelCase) para os nomes de alvos (target)(vs. letras minúsculas tradicionais, separadas por hífen) por causa das execuções utilizando o JUnit, como por exemplo, -Dtestcase=Name, Percebi que estou acostumado com a notação composta para meus testes 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"/>
            <clickbutton label="${button.delete}" description="Click button 'Delete'"/>
            <verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
            <verifytext description="verify success message" text="${person.deleted}"/>
        </steps>
    </webtest>
</target>

Após adicionar isto, seremos capazes de rodar ant test-canoo -Dtestcase=PersonTests com o Tomcat rodando ou ant test-jsp -Dtestcase=PersonTests se quisermos que o Ant inicialize e pare o Tomcat. Para incluir o PersonTests quando todos os testes canoo são executados, adicionaremos sua dependência ao alvo(target) "run-all-tests".

Você perceberá que não há registro(logging) na janela cliente do canoo. Se desejarmos ver o que o Canoo está fazendo, podemos adicionar o seguinte código entre </canoo> e </target> no final do alvo(target).

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


Próximo: Parte IV: Adicionando Validação e Tela de Listagem - Adicionando lógica de validação para o personForm para que o firstName e o lastName sejam campos obrigatórios, e adicionando uma tela de listagem para mostrar todas as tuplas de pessoas no banco de dados.


Attachments:
editPerson.PNG Info on editPerson.PNG 5649 bytes


Go to top   Edit this page   More info...   Attach file...
This page last changed on 06-Nov-2006 13:53:00 MST by Gilbertoca.