Skip to content

7. Aplicativo QuiEst

Descrevemos aqui uma aplicação Struts um pouco mais sofisticada do que as anteriores, que precisavam ser simples para fins didáticos.

7.1. A classe users

Temos uma classe Java que armazena as informações sobre os usuários de uma máquina Unix. Essas informações são registradas em três arquivos específicos:

  • /etc/passwd: lista de usuários
  • /etc/group: lista de grupos
  • /etc/aliases: lista de aliases para e-mail

O conteúdo desses três arquivos é o seguinte:

- /etc/passwd

As linhas deste arquivo têm o seguinte formato:


    login:pwd:uid:gid:id:dir:shell

com

login
nome de usuário
pwd
sua senha criptografada
uid
seu número de usuário
gid
seu número de grupo
id
sua identidade
dir
seu diretório de conexão
shell
seu interpretador de comandos

Assim, a linha de um usuário poderia ser a seguinte:

dupond:xg675SDFEkl09:110:57:Guillaume Dupond:/home/iup2-auto/dupond:/bin/bash

O usuário anterior tem o número 110 e pertence ao grupo 57. A definição do grupo 57 pode ser encontrada no arquivo /etc/group.

- /etc/group

As linhas desse arquivo têm o seguinte formato:


    nomGroupe:pwd:gid:membre1,membre2,....

com

nomGroupe
nome do grupo
pwd
sua senha criptografada — na maioria das vezes, esse campo fica em branco
gid
o número do grupo
membrei
nomes de usuário — esse campo pode estar vazio

Assim, a linha do grupo 57 anterior poderia ser a seguinte:

iup2-auto::57:

o que indica que o grupo 57 tem o nome iup2-auto.

- /etc/aliases

As linhas deste arquivo têm o seguinte formato:

alias:[tab]login

com

alias
alias
[tab]
uma ou mais tabulações
login
login do usuário ao qual o alias pertence

Assim, a linha


    guillaume.dupond:    dupond

significa que o alias guillaume.dupond pertence ao usuário com login dupond. Vale lembrar que os aliases são utilizados em endereços de e-mail. Assim, se no exemplo anterior a máquina Unix se chamar shiva.istia.univ-angers.fr, um e-mail endereçado a guillaume.dupond@shiva.istia.univ-angers.fr será entregue na caixa de correio do usuário de login dupond dessa máquina.

Aqui, não vamos nos debruçar sobre toda a interface da classe users, mas apenas sobre seu construtor e alguns métodos:

import java.io.*;
import java.util.*;

public class users{


   // atributos
  private Hashtable usersByLogin=new Hashtable();       // login --> login, senha, ..., diretório
    private ArrayList erreurs=new ArrayList();             // lista de mensagens de erro

....

   // construtor
  public users(String usersFileName, String groupsFileName, String aliasesFileName) throws Exception {
        // usersFileName: nome do arquivo de usuários com linhas no formato
         // login:pwd:uid:gid:id:dir:shell
         // groupsFileName: nome do arquivo dos grupos com linhas no formato
         // nome:senha:número:membro1,membro2,..
         // aliasesFileName: nome do arquivo dos aliases com linhas no formato
         // alias:[tab]login
         // constrói o dicionário usersByLogin
....
    }// construtor

     // lista de usuários
  public Hashtable getUsersByLogin(){
    return usersByLogin;
  }

   // erros
  public ArrayList getErreurs(){
    return erreurs;
  }
usersByLogin
dicionário (Hashtable) cujas chaves são os logins do arquivo passwd. O valor associado à chave é uma matriz de strings (String [7]) cujos elementos são os 7 campos da linha do arquivo passwd associada ao login. Alguns campos podem estar vazios se a linha tiver menos de 7 campos.
erreurs
lista de mensagens de erro — vazia se não houver erros

7.2. A aplicação web que é

Propõe-se construir o seguinte aplicativo web (página de formulário):

n.º
nome
tipo HTML
função
1
cmbLogins
<select ...>...</select>
apresenta a lista de todos os logins sobre os quais é possível solicitar informações
2
btnChercher
<input type="submit" ...>
para iniciar a pesquisa

Quando o usuário clica no botão [Chercher] (2), o login de (1) é solicitado a um objeto U do tipo users. Se o login existir, obtém-se a seguinte resposta (página de informações):

Como mostra o URL do navegador acima, os parâmetros do formulário são enviados ao servidor por meio de um GET. Portanto, é possível fornecer diretamente ao navegador esse URL com os parâmetros definidos. É isso que fazemos aqui, para inserir um login que não existe. Obtemos a seguinte resposta (página de erros):

7.3. A arquitetura do aplicativo

Nessa arquitetura, encontramos os seguintes componentes:

  • as visualizações:
    • logins.jsp, utilizada para exibir a lista de logins (visualização 1)
    • infos.jsp, utilizada para exibir as informações relacionadas a um login (visualização 2)
    • erreurs.jsp, utilizada para exibir uma lista de erros (visualização 3)
  • os formulários do tipo ActionForm utilizados pelas ações:
    • formLogins, utilizado para coletar os dados do formulário logins.jsp
  • as ações:
    • SetupLoginAction, que prepara o conteúdo de formulaire.jsp e, em seguida, exibe essa visualização
    • InfosLoginAction, que processa o conteúdo de logins.jsp assim que este é enviado ao servidor
    • ForwardAction, que processa o link [Retour vers le formulaire] das visualizações infos.jsp e erreurs.jsp
  • a classe de negócios `users`, utilizada pelas ações para obter seus dados
  • o modelo fornecido pelos três arquivos de texto simples passwd, group e aliases

7.4. Os arquivos de configuração da aplicação web

7.4.1. O arquivo server.xml

O contexto da aplicação será chamado de /strutsquiest2. Portanto, adicionaremos a seguinte linha no arquivo server.xml do Tomcat:

    <Context path="/strutsquiest2" docBase="..." />

Feito isso, reiniciamos o Tomcat, se necessário, para que ele reconheça o novo contexto. Podemos verificar se ele está válido acessando o URL http://localhost:8080/strutsquiest2.

7.4.2. O arquivo web.xml

O arquivo de configuração web.xml do aplicativo será o seguinte:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
    <servlet>
        <servlet-name>strutsquiest2</servlet-name>
        <servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>            
        </init-param>
        <init-param>
            <param-name>passwdFileName</param-name>
            <param-value>data/passwd</param-value>            
        </init-param>
        <init-param>
            <param-name>groupFileName</param-name>
            <param-value>data/group</param-value>            
        </init-param>        
    </servlet>

    <servlet-mapping>
        <servlet-name>strutsquiest2</servlet-name>
        <url-pattern>*.do</url-pattern>        
    </servlet-mapping>
</web-app>

Este arquivo web.xml traz uma novidade. O controlador Struts não é mais o org.apache.struts.action.ActionServlet, mas uma classe derivada que chamamos aqui de istia.st.struts.quiest.Quiest2ActionServlet. Isso nos permitirá recuperar os dois parâmetros de inicialização, que são passwdFileName (localização do arquivo passwd) e groupFileName (localização do arquivo group). O arquivo aliases é desnecessário nesta aplicação.

7.4.3. O arquivo struts-config.xml

O arquivo struts-config.xml será o seguinte:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
    <form-beans>
        <form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
            <form-property name="cmbLogins" type="java.lang.String" initial=""/>
            <form-property name="tLogins" type="java.lang.String[]"/>            
        </form-bean>            
    </form-beans>

    <action-mappings>

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />

    </action-mappings>

        <message-resources 
          parameter="istia.st.struts.quiest.ApplicationResources"
    null="false"
  />    

</struts-config>

Nele, encontramos as três seções principais:

  • a declaração dos formulários na seção <form-beans>
  • a declaração das ações na seção <action-mappings>
  • a declaração do arquivo de recursos em <message-ressources>

7.4.4. Os objetos (beans) de formulário do aplicativo

    <form-beans>
        <form-bean name="formLogins" type="org.apache.struts.action.DynaActionForm">
            <form-property name="cmbLogins" type="java.lang.String" initial=""/>
            <form-property name="tLogins" type="java.lang.String[]"/>            
        </form-bean>            
    </form-beans>    

Há apenas um bean de formulário em nossa aplicação, chamado formLogins e de tipo derivado de DynaActionForm. Ele será utilizado nas seguintes situações:

  • conter os dados necessários para a exibição da visualização nº 1
  • recuperar os valores do formulário da visualização nº 1 quando o usuário o enviar (submit)

A estrutura do bean formLogins está vinculada ao formulário da visualização nº 1. Vamos analisá-la:

n.º
nome
tipo HTML
função
1
cmbLogins
<select ...>...</select>
apresenta a lista de todos os logins sobre os quais é possível solicitar informações
2
btnChercher
<input type="submit" ...>
para iniciar a pesquisa

Distinguamos vários casos:

  • do cliente para o servidor, o objeto formLogins é usado para conter os valores do formulário HTML acima, que será enviado pelo botão [Envoyer]. Portanto, é necessário um campo cmbLogins que receberá o valor do campo HTML, cmbLogins e c.a.d, ou seja, o login escolhido pelo usuário.
  • Do servidor para o cliente, o objeto formLogins é utilizado para fornecer o conteúdo inicial da visualização nº 1. Seu campo tLogins servirá como conteúdo para a lista 1. Seu campo cmbLogins permitirá definir o elemento da lista 1 a ser selecionado.

7.4.5. As ações do aplicativo

As ações são executadas por objetos do tipo Action ou derivados. A configuração das ações é feita dentro das tags <action-mappings>:

    <action-mappings>

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />

    </action-mappings>

A ação /init

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

Vamos descrever o funcionamento da ação /init:

  • A ação /init ocorre normalmente uma única vez durante o primeiro ciclo de solicitação-resposta, quando o usuário solicita o URL http://localhost:8080/strutsquiest2/init.do
  • o objeto formsLogins é criado ou reciclado. Ele é recuperado (reciclagem) ou inserido (criação) na sessão, conforme determinado pelo atributo scope.
  • Seu método `reset` é chamado. Vale lembrar que esse método não faz nada por padrão nas classes ActionForm e derivadas. Ele é chamado imediatamente antes da cópia dos dados da solicitação do cliente para o objeto ActionForm e serve para limpar o objeto antes dessa cópia. Qual é, neste caso, a solicitação do cliente? A ação /init é acionada quando a URL solicitada é http://localhost:8080/strutsquiest2/init.do. Este URL pode ser solicitado por um GET ou um POST. Basta incluir nessa solicitação parâmetros com os nomes dos campos do formLogins para que estes sejam inicializados, conforme mostra o exemplo a seguir:

Image

  • a solicitação contém o parâmetro cmbLogins (afterpak). O controlador Struts, portanto, copiou o valor desse parâmetro para o campo cmbLogins de formLogins. A ação SetupLoginsAction foi então executada e concluída com a exibição da visualização logins.jsp. Essa visualização possui um formulário cujos campos recebem seus valores de formLogins. Assim, o campo select HTML, denominado cmbLogins, recebeu seu valor do campo cmbLogins (=afterpak) de formLogins. É por isso que a lista de logins aparece posicionada no login afterpak.
  • Seria possível, por diversão, passar também um parâmetro tLogins da seguinte maneira:
http://localhost:8080/strutsquiest2/init.do?cmbLogins=afterpak&tLogins=login1&tLogins=login2

Isso teria o efeito de inicializar o campo tLogins a partir de formLogins com um array {"login1", "login2"}. No entanto, veremos mais adiante que a ação SetupLoginsAction atribui um valor ao campo tLogins e substitui a matriz assim criada por uma nova matriz. É essa última, portanto, que aparece na visualização logins.jsp.

  • A discussão anterior, embora um pouco complexa, tem o mérito de mostrar que não se pode partir do pressuposto de que a ação /init será acionada sem parâmetros provenientes do cliente. Portanto, pode ser útil utilizar o método reset para limpar formLogins. Nesse caso, precisaríamos derivar a classe DynaActionForm. Não fizemos isso aqui.
    • Uma vez chamado o método reset de formLogins, o controlador copia os dados da solicitação do cliente para os campos com o mesmo nome em formLogins. Normalmente, a ação /init é chamada sem parâmetros do cliente, mas mostramos anteriormente que nada impede o cliente de invocar a ação /init com parâmetros arbitrários. Ao final dessa fase, os campos cmbLogins e tLogins podem, portanto, muito bem ter um valor. Vimos que o campo cmbLogins manteria esse valor, mas não o campo tLogins.
    • Em seguida, o controlador verifica o atributo `validate` da ação. Aqui, ele tem o valor “false”. O método `validate` de formLogins não será chamado. Portanto, não o escreveremos.
    • O objeto SetupLoginsAction é criado ou reciclado, caso já existisse, e seu método execute é executado. Sua única função é atribuir um valor ao campo tLogins de formLogins. Esse valor é a tabela de logins, que será solicitada à classe de negócios users. Essa operação pode falhar. É por isso que a ação /init pode ser seguida por duas visualizações:
      • a visualização erreurs.jsp, caso a classe “users” não tenha conseguido fornecer a tabela de logins
      • a visualização logins.jsp, caso contrário
    • o controlador exibirá uma dessas duas visualizações
    • o ciclo de solicitação-resposta da ação /init está concluído.

A ação /infosLogin

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

Vamos descrever o funcionamento da ação / infosLogin:

  • a ação /infosLogin ocorre normalmente quando o usuário clica no botão [Chercher] da visualização logins.jsp. Em seguida, é enviada uma solicitação ao servidor, definida pela tag HTML <form> da visualização:
<html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
  • percebe-se que a solicitação é enviada ao servidor pelo método GET. O usuário pode, portanto, digitá-la manualmente:

Image

  • o objeto formsLogins é criado ou reciclado. Ele é recuperado (reciclagem) ou inserido (criação) na sessão, conforme determinado pelo atributo scope.
  • Seu método `reset` é chamado imediatamente antes da cópia dos dados da solicitação do cliente para o objeto ActionForm. Normalmente, ela tem o formato http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=xx, onde xx é um login escolhido na lista de logins. Mas também pode ser qualquer coisa, caso o usuário tenha utilizado o URL anterior passando parâmetros arbitrários. Consideremos a seguinte sequência de páginas:

Image

  • a ação /infosLogin foi chamada com a sequência de parâmetros cmbLogins=xx&tLogins=login1&tLogins=login2. Os campos cmbLogins e tLogins de formLogins receberão, portanto, respectivamente, os valores “xx” e {"login1","login2"}. A ação /infosLogin solicitará à classe de negócios users as informações associadas ao login “xx”. A classe users responderá que esse login não existe. Daí a visualização enviada acima. Agora, vamos usar o link [Retour au formulaire] acima:

Image

  • É a ação /retourLogins que é acionada pelo link [Retour au formulaire]. Essa ação se limita a exibir a visualização logins.jsp sem nenhuma ação intermediária. Vale lembrar que o campo tLogins serve para alimentar a lista de logins da visualização logins.jsp. Como o usuário alterou esse valor para {"login1","login2"}, são esses dois logins que agora aparecem na lista. Mais uma vez, não podemos deixar de enfatizar a necessidade absoluta de levar em conta, no funcionamento de um aplicativo, o caso de parâmetros arbitrários definidos por um usuário ou por um programa. A solução para o problema apresentado aqui seria que o link [Retour au formulaire] apontasse para a ação /init. Assim, teríamos a certeza de obter a lista correta de logins.
  • Voltemos a uma solicitação normal para a ação /infosLogin, do tipo:

http://localhost:8080/strutsquiest2/infosLogin.do?cmbLogins=afterpak

  • O controlador Struts atribuirá um valor ao campo cmbLogins do objeto ActionForm. Já o campo tLogins não receberá nenhum valor (não há campo correspondente na solicitação enviada). Esse comportamento é o que desejamos. Portanto, não precisaremos escrever um método reset personalizado para formLogins.
    • Assim que o método reset de formLogins for chamado, o controlador copia os dados da solicitação do cliente para os campos com o mesmo nome em formLogins. O campo cmbLogins receberá um valor: o login escolhido pelo usuário (afterpak).
    • Em seguida, o controlador verifica o atributo `validate` da ação. Aqui, ele tem o valor “false”. O método `validate` de formLogins não será chamado.
    • O objeto InfosLoginAction é criado ou reutilizado, caso já existisse, e seu método `execute` é executado. Sua função é obter as informações associadas ao login cmbLogins. Essas informações serão solicitadas à classe de negócios `users`. Essa operação pode falhar (por exemplo, login inexistente). É por isso que a ação /infosLogin pode ser seguida por duas visualizações:
      • a visualização erreurs.jsp, caso a classe “users” não tenha conseguido fornecer as informações solicitadas
      • a visualização infos.jsp, caso contrário
    • o controlador exibirá uma dessas duas visualizações
    • o ciclo de solicitação-resposta da ação /infosLogin está concluído.

A ação /retourLogins

      <action
          path="/retourLogins"
            parameter="/vues/logins.jsp"
          type="org.apache.struts.actions.ForwardAction"
      />
  • A ação /retourLogins é acionada pela ativação do link [Retour au formulaire] nas visualizações erreurs.jsp e infos.jsp.
  • Aqui não há nenhum formulário associado à ação. Portanto, passa-se imediatamente para a execução do método `execute` de um objeto `ForwardAction`, que retornará um objeto `ActionForward` apontando para a visualização `/vues/logins.jsp`.

7.4.6. O arquivo de mensagens do aplicativo

A terceira seção do arquivo struts-config.xml é a do arquivo de mensagens:

        <message-resources 
      parameter="istia.st.struts.quiest.ApplicationResources"
    null="false"
  />        

O arquivo ApplicationResources.properties está localizado em WEB-INF/classes/istia/st/struts/quiest. Seu conteúdo é o seguinte:

errors.header=<ul>
errors.footer=</ul>
parametreManquant=<li>Le paramètre [{0}] n'a pas été initialisé</li>
usersException=<li>Erreur d'initialisation de l'application : {0}</li>
loginInconnu=<li>Le login [{0}] n'existe pas</li>

7.5. O código das visualizações

Recomenda-se ao leitor que releia a lição sobre gerenciamento de formulários caso não compreenda o código das visualizações apresentadas abaixo.

7.5.1. A visualização logins.jsp

Vale lembrar que essa visualização é exibida em dois casos:

  • ao chamar a ação /init durante o primeiro ciclo de solicitação-resposta
  • ao chamar a ação /retourLogins nos ciclos seguintes

O código da visualização logins.jsp é o seguinte:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>
    <head>
      <title>Quiest - formulaire</title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
      <center>
        <h2>Application QuiEst</h2>
      <hr>
      <html:form name="formLogins" method="get" action="/infosLogin" type="org.apache.struts.action.DynaActionForm">
          <table>
            <tr>
              <td>Login cherché</td>
            <td>
                <html:select name="formLogins" property="cmbLogins">
                      <html:options name="formLogins" property="tLogins"/>
                  </html:select>
            </td>
            <td>
                <html:submit value="Chercher"/>
            </td>
          </tr>
        </table>
      </html:form>
    </center>
  </body>
</html>

7.5.2. A visualização infos.jsp

Esta visualização é exibida após uma chamada bem-sucedida à ação /infosLogin. Seu código é o seguinte:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html>
    <head>
      <title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
      <h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
    <hr>
    <table border="1">
            <tr>
                <th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
            </tr>
            <tr>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>            
            </tr>                                                                                                                                                                                                                
    </table>                                      
    <br>
    <html:link page="/retourLogins.do">
            Retour au formulaire
        </html:link>        
  </body>
</html>

Esta visualização utiliza um objeto chamado infosLoginBean, inserido na consulta pela ação /infosLogin. Esse objeto possui dois campos:

String titre;                        // título a ser exibido na visualização
String[] infosLogin;        // tabela de informações a serem exibidas na visualização

Abordaremos essa classe em detalhes quando discutirmos o código da classe InfosLoginAction.

7.5.3. A visualização erreurs.jsp

Essa visualização é exibida quando as ações /init ou /infosLogin terminam com um erro. Seu código é o seguinte:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>
    <head>
      <title>Application QuiEst - erreurs</title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
        <h2 align="center">Application QuiEst - Erreurs</h2>
        <hr>
      <h2>Les erreurs suivantes se sont produites</h2>
        <html:errors/>
    <html:link page="/retourLogins.do">
            Retour au formulaire
        </html:link>    
  </body>
</html>

7.6. As classes Java

O arquivo web.xml faz referência a uma classe Java:

<web-app>
    <servlet>
      <servlet-name>strutsquiest2</servlet-name>
    <servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
....
  </servlet>

...
</web-app>

O arquivo de configuração struts-config.xml faz referência a duas classes Java:

      <action
          path="/infosLogin"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.InfosLoginAction"
      >
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
            <forward name="afficherInfos" path="/vues/infos.jsp"/>            
        </action>

      <action
          path="/init"
            name="formLogins"
            validate="false" 
            scope="session"
          type="istia.st.struts.quiest.SetupLoginsAction"
      >
            <forward name="afficherLogins" path="/vues/logins.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>            
        </action>

7.6.1. A classe Quiest2ActionServlet

A classe Quiest2ActionServlet é derivada da classe ActionServlet, a classe do controlador Struts. Derivamos a classe ActionServlet para personalizar seu método init. De fato, esse método, executado uma única vez no momento do carregamento inicial do servlet, nos permitirá construir um objeto de negócio do tipo users. Esse objeto, na verdade, precisa ser construído apenas uma vez, e o método init é um bom local para realizar essa construção. O objeto users precisa de dois arquivos para ser construído: os arquivos passwd e group. A localização desses dois arquivos é passada como parâmetros para o servlet no arquivo **web.xml** do aplicativo:

    <servlet>
      <servlet-name>strutsquiest2</servlet-name>
    <servlet-class>istia.st.struts.quiest.Quiest2ActionServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
        <param-name>passwdFileName</param-name>
      <param-value>data/passwd</param-value>
    </init-param>
    <init-param>
        <param-name>groupFileName</param-name>
      <param-value>data/group</param-value>
    </init-param>        
  </servlet>

O código do servlet é o seguinte:

package istia.st.struts.quiest;

import java.util.*;
import javax.servlet.*;
import org.apache.struts.action.*;
import istia.st.users.*;

public class Quiest2ActionServlet
  extends ActionServlet {

   // atributos do servlet
  private users u = null;
  private ActionErrors erreurs = new ActionErrors();
  private String[] tLogins;

   //init
  public void init() throws ServletException {

    // não se esqueça de inicializar a classe pai
    super.init();

     // variáveis locais
    final String[] initParams = {"passwdFileName", "groupFileName"};
    Properties params = new Properties();

    // recuperamos os parâmetros de inicialização do servlet
    ServletConfig config = getServletConfig();
    String servletPath = config.getServletContext().getRealPath("/");
    for (int i = 0; i < initParams.length; i++) {
      String valeur = config.getInitParameter(initParams[i]);
      if (valeur == null) {
        erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("parametreManquant", initParams[i]));
        valeur = "";
      }
       // armazenamos o parâmetro
      params.setProperty(initParams[i], valeur);
    } //for
     // retorno caso tenha ocorrido algum erro de inicialização
    if (erreurs.size() != 0) {
      return;
    }
     // cria-se um objeto users
    try {
      u = new users(servletPath + "/" + params.getProperty("passwdFileName"),
                    servletPath + "/" + params.getProperty("groupFileName"), null);
    }
    catch (Exception ex) {
      erreurs.add(ActionErrors.GLOBAL_ERROR, new ActionError("usersException", ex.getMessage()));
      return;
    } //catch
     // recupera-se a lista de logins
    tLogins = new String[u.getUsersByLogin().size()];
    Enumeration eLogins = u.getUsersByLogin().keys();
    for (int i = 0; i < tLogins.length; i++) {
      tLogins[i] = (String) eLogins.nextElement();
    }
     // classifica-se os logins
    Arrays.sort(tLogins);
  } //init

   // método de acesso às informações privadas do servlet
  public Object[] getInfos() {
    return new Object[] {erreurs, u, tLogins};
  }
}

Resumidamente, o funcionamento do método init é o seguinte:

  • primeiramente, é chamado o método `init` da classe pai (ActionServlet) para que esta seja inicializada corretamente
  • em seguida, os parâmetros de inicialização são lidos. Se algum estiver faltando, o atributo privado ActionErrors erros é preenchido.
  • Se os parâmetros de inicialização estiverem presentes, um objeto `users` é construído. Essa construção pode lançar uma exceção. Nesse caso, o atributo `ActionErrors` `erros` é preenchido.
  • Se a criação for bem-sucedida, obtém-se do objeto criado a lista de todos os logins e essa lista é classificada em um array que é armazenado no atributo privado String[] tLogins.
  • O objeto `users` criado é armazenado no atributo privado `users u`.
  • O método público getInfos permite obter os três atributos privados (u, erros, tLogins) em uma matriz de objetos.

7.6.2. A classe SetupLoginsAction

O objetivo desta ação é inicializar o objeto DynaActionForm formLogins. Esse objeto, inserido na sessão, não precisará ser reinicializado posteriormente. A ação SetupLoginsAction ocorre, portanto, apenas uma vez. Seu código é o seguinte:

package istia.st.struts.quiest;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;

public class SetupLoginsAction
  extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
                               HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {

     // prepara o formulário a ser exibido
     // recupera-se as informações do servlet controlador
     // informações=(ActionErrors erros, usuários u, String[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // houve erros de inicialização?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // colocamos os logins no formulário
    DynaActionForm formLogins=(DynaActionForm) form;
    formLogins.set("tLogins",infos[2]);
    return mapping.findForward("afficherLogins");
  }
}

Assim como em todas as ações do Struts, o código está no método execute. Este:

  • obtém do controlador Struts as informações que este armazenou por meio de seu método init. É o método getServlet() da classe Action que permite isso.
  • Entre elas está o atributo ActionErrors, que contém os erros do controlador. Se essa lista de erros não estiver vazia, ela é inserida na consulta e solicita-se a exibição da visualização erreurs.jsp.
  • Se a lista de erros estiver vazia, atribui-se ao campo tLogins do bean formLogins a lista de logins criada inicialmente pelo controlador. Em seguida, solicita-se a exibição da vista logins.jsp, que exibirá a lista de logins.

7.6.3. As classes InfosLoginBean e InfosLoginAction

A ação InfosLoginAction tem como objetivo recuperar as informações associadas ao login escolhido pelo usuário e apresentá-las a ele. As informações serão reunidas em um objeto do tipo InfosLoginBean:

package istia.st.struts.quiest;

public class InfosLoginBean implements java.io.Serializable{

   // bean que contém as informações necessárias para a página de informações
  private String titre;
  private String[] infosLogin;

  // construtor
  public InfosLoginBean(String titre, String[] infosLogin){
    this.titre=titre;
    this.infosLogin=infosLogin;
  }

   // getters
  public String getTitre(){
    return this.titre;
  }
  public String[] getInfosLogin(){
    return this.infosLogin;
  }
  public String getInfosLogin(int i){
    return this.infosLogin[i];
  }
}

A classe anterior é um bean, c.a.d. Trata-se de uma classe Java na qual um atributo privado T unAttribut é automaticamente associado a dois métodos privados:

  • void setUnAttribut(T valor){unAttribut=valor;}
  • T getUnAttribut(){ return unAttribut;}

Observe-se a sintaxe especial dos métodos get e set. Se o atributo for uma matriz T[] unAttribut, é possível criar métodos get e set para os elementos da matriz:

  • void setUnAttribut(T valor, int i){unAttribut[i]=valor;}
  • T getUnAttribut(int i){ return unAttribut[i];}

Para entender melhor, vamos analisar o código da visualização infos.jsp, que deve ser enviada após a ação InfosLoginAction:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>

<html>
    <head>
      <title><bean:write name="infosLoginBean" scope="request" property="titre"/></title>
  </head>
  <body background="<html:rewrite page="/images/standard.jpg"/>">
      <h2><bean:write name="infosLoginBean" scope="request" property="titre"/></h2>
    <hr>
    <table border="1">
            <tr>
                <th>login</th><th>pwd</th><th>uid</th><th>gid</th><th>id</th><th>dir</th><th>shell</th>
            </tr>
            <tr>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[1]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[2]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[3]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[4]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[5]"/></td>
                <td><bean:write name="infosLoginBean" scope="request" property="infosLogin[6]"/></td>            
            </tr>                                                                                                                                                                                                                
    </table>                                      
    <br>
    <html:link page="/retourLogins.do">
            Retour au formulaire
        </html:link>        
  </body>
</html>

Vamos considerar a seguinte tag:

<bean:write name="infosLoginBean" scope="request" property="titre"/>

Ela solicita que seja gravado o valor do campo “título” (property) do objeto infosLoginBean (name) incluído na consulta (scope). O valor a ser gravado será obtido por meio de request.getAttribute("infosLoginBean").getTitre(). Portanto, é necessário que o método getTitre exista na classe InfosLoginBean. Esse é o caso. A tag

<bean:write name="infosLoginBean" scope="request" property="infosLogin[0]"/>

solicita que seja gravado o valor do elemento infosLogin[0] do objeto infosLoginBean incluído na consulta. O valor a ser gravado será obtido por meio de request.getAttribute("infosLoginBean").getInfosLogin(0). Portanto, é necessário que o método getInfosLogin(int i) exista na classe InfosLoginBean. Esse é o caso.

A classe InfosLoginAction tem como objetivo construir o objeto InfosLoginBean anterior a partir de um login escolhido pelo usuário. Seu código é o seguinte:

package istia.st.struts.quiest;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import istia.st.users.*;

public class InfosLoginAction
  extends Action {

  public ActionForward execute(ActionMapping mapping, ActionForm form,
                               HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException {

     // deve exibir as informações relacionadas a um login

     // recupera-se as informações do servlet controlador
     // informações=(ActionErrors erros, usuários u, LoginBean[] tLogins)
    Object[] infos = ( (Quiest2ActionServlet)this.getServlet()).getInfos();

     // houve erros de inicialização?
    ActionErrors erreurs = (ActionErrors) infos[0];
    if (!erreurs.isEmpty()) {
      this.saveErrors(request, erreurs);
      return mapping.findForward("afficherErreurs");
    }

     // primeiro, recuperar esse login
    String login = (String) ( (DynaActionForm) form).get("cmbLogins");

    // Tem alguma coisa?
    if (login == null) {
      // Isso não está certo — vamos reenviar o formulário de login
            DynaActionForm formLogins=(DynaActionForm) form;
            formLogins.set("tLogins",infos[2]);
            return mapping.findForward("afficherLogins");
    }

     // temos um login — estamos procurando
    String[] infosLogin = (String[]) ( (users) infos[1]).getUsersByLogin().get(login);

     // Encontramos?
    if (infosLogin == null) {
      // o login não foi encontrado — exibimos a página de erros
      ActionErrors erreurs2=new ActionErrors();
      erreurs2.add(ActionErrors.GLOBAL_ERROR, new ActionError("loginInconnu", login));
      this.saveErrors(request, erreurs2);
      return mapping.findForward("afficherErreurs");
    }

     // o login foi encontrado — colocamos as informações encontradas na solicitação
    String titre="Application QuiEst - login["+login+"]";
    InfosLoginBean infosLoginBean= new InfosLoginBean(titre,infosLogin);
    request.setAttribute("infosLoginBean",infosLoginBean);
    return mapping.findForward("afficherInfos");
  }

}

O funcionamento do método **execute** é o seguinte:

  • recuperam-se as informações coletadas pelo controlador Struts durante sua inicialização. Caso ele tenha detectado erros, a execução é interrompida nesse ponto, com uma solicitação para exibir esses erros.
  • Verifica-se se há, de fato, um login. Se o usuário tiver passado pelo formulário de seleção de login, o login estará presente. No entanto, o usuário pode muito bem digitar a URL da ação diretamente no navegador, sem passar parâmetros. Se não houver login, a lista de logins é exibida novamente.
  • Se houver um login, solicitam-se as informações associadas à classe de negócios `users`. Se essa classe não encontrar o login procurado, a página de erros é exibida. Caso contrário, um objeto InfosLoginBean é criado para armazenar as informações necessárias à visualização infos.jsp. Esse objeto é inserido na solicitação e a página infos.jsp é exibida.

7.7. Implantação

A estrutura da aplicação é a seguinte:

 

7.8. Conclusão

Utilizamos o Struts em uma aplicação realista que emprega uma classe de negócio. Além disso, demonstramos que é necessário prestar atenção especial à solicitação enviada por um cliente e não fazer nenhuma suposição sobre sua natureza. Uma solicitação pode ser de qualquer tipo, e toda aplicação deve começar verificando sua validade.