Passo a Passo de uma aplicação web com o Mentawai!

Abaixo segue um passo a passo de uma aplicação completa com o Mentawai.

Fiquem a vontade para usar esse tópico para fazer comentários, sugerir melhorias, tirar dúvidas, discutir a arquitetura, as tags, o IoC, o auto-wiring, a validação, etc e tal.

===========================================

Iniciamos criando um novo projeto web (Tomcat Project) no eclipse, chamado MentaNews. Estamos utilizando o plugin sysdeo para projetos web integrados com o Tomcat dentro do Eclipse, e recomendamos a todos que utilizem esse excelente plugin.

Copiamos o jar do mentawai pra dentro do diretório WEB-INF/lib, não esquecendo de ir nas propriedades do projeto e adicionar esse jar no classpath do projeto.

Começamos criando nossa primeira action, que se chamará NewsAction.java e ficará no pacote org.mentanews.action:

package org.mentanews.action;

import org.mentawai.core.BaseAction;

public class NewsAction extends BaseAction {
   
   public String test() throws Exception {
      
      output.setValue("hello", "Hello MentaNews Application!");
      
      return SUCCESS;
   }
}

Repare que adicionamos uma inner action test que apenas coloca uma mensagem no output da action e retorna SUCCESS. Mais informações sobre Inner Action podem ser obtidas aqui.

Feito isso criamos o ApplicationManager da nossa aplicação, que ficará no pacote org.mentanews:

package org.mentanews;

import org.mentanews.action.NewsAction;
import org.mentawai.coc.InnerActionConsequenceProvider;
import org.mentawai.core.Context;

public class ApplicationManager extends org.mentawai.core.ApplicationManager {
   
   @Override
   public void init(Context application) {

      /*
       * Convention Over Configuration that will provide a default consequence for all
       * inner actions when they return SUCCESS or ERROR.
       * 
       * Ex: When NewsAction.test.mtw returns SUCCESS or ERROR, it will FORWARD to:
       * 
       *     /news/test.jsp
       */
      setConsequenceProvider(new InnerActionConsequenceProvider());
      
      setDebugMode(true); // turn on debug mode...
      
   }
   
   public void loadActions() {
      
      action(NewsAction.class);

      // If not using InnerActionConsequenceProvider we would have to do:
     // .on(SUCCESS, fwd("/news/test.jsp"));
   }
}

Inicialmente o nosso application manager está apenas ligando o modo debug do Mentawai e setando o InnerActionConsequenceProvider como o nosso esquema de CoC (Convention over Configuration). Mais embaixo declaramos a nossa primeira action, através do método action(NewsAction.class);.

[color=blue]O que está acontecendo aqui?[/color] Como não passamos nenhum nome quando declaramos a action NewsAction.class, o Mentawai assume que o nome será o mesmo nome da classe, ou seja, NewsAction. Como não definimos nenhuma consequencia para a action NewsAction.class (e poderíamos ter feito isso) o Mentawai através do InnerActionConsequenceProvider vai assumir que se a action retornar SUCCESS ou ERROR, ela deverá sofrer um forward para /news/NOMEDAINNERACTION.jsp. Abaixo temos alguns exemplos de como a InnerActionConsequenceProvider funciona:

/User.view.mtw = FORWARD para /user/view.jsp

/UserAction.view.mtw = FORWARD para /user/view.jsp

/UserAction.mtw = ERRO pois a InnerActionConsequenceProvider só funciona para Inner Actions como o nome já diz

[color=“blue”]Nota:[/color] Vc pode também implementar a interface ConsequenceProvider para criar o seu próprio esquema de Convention Over Configuration para a camada view.

De acordo com o esquema acima, quando acessarmos NewsAction.test.mtw, o Mentawai assumirá a consequencia FORWARD para /news/test.jsp. Então vamos criar essa página test.jsp:

<%@ page contentType="text/html; charset=ISO-8859-1" %> 
<%@ taglib uri="/WEB-INF/lib/mentawai.jar" prefix="mtw" %>
<h1>Hello MentaNews!</h1>
<h5><mtw:out value="hello" /></h5>

Nessa página estamos declarando a taglib do Mentawai (mtw) e estamos usando a tag mtw:out para imprimir o valor "hello" do output da action.

Feito isso, o último passo para colocarmos a nossa aplicação para rodar é definir o web.xml, que fica dentro de WEB-INF/.

&lt?xml version="1.0" encoding="ISO-8859-1"?&gt

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
	<display-name>MentaNews</display-name>
	<description></description>

	<servlet>
        <servlet-name>Controller</servlet-name>
        <servlet-class>org.mentawai.core.Controller</servlet-class>
        <init-param>
    	    <param-name>applicationManager</param-name>
        	<param-value>org.mentanews.ApplicationManager</param-value>
	    </init-param>    
		<load-on-startup>1</load-on-startup>		
    </servlet>

    <!-- You must choose an extension to indicate a mentawai action -->
    <servlet-mapping>
        <servlet-name>Controller</servlet-name>
        <url-pattern>*.mtw</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>DebugFilter</filter-name>
        <filter-class>
            org.mentawai.util.DebugServletFilter
        </filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>DebugFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher> 
        <dispatcher>FORWARD</dispatcher>    
        <dispatcher>INCLUDE</dispatcher>    
        <dispatcher>ERROR</dispatcher>    
    </filter-mapping>

</web-app>

O que estamos fazendo no web.xml é:

  • Indicamos que será o controlador do Mentawai que tratará as requisições terminadas por *.mtw.

  • Indicamos que o ApplicationManager que deverá ser usado é o org.mentanews.ApplicationManager.

  • Fazemos as configurações necessárias para o modo debug do Mentawai, que é um filtro de servlet.

Agora a melhor parte: Colocamos o Tomcat para ordar e acessamos a URL http://localhost:8080/MentaNews/NewsAction.test.mtw. Você deverá ver o seguinte no seu browser:

Hello MentaNews!

Hello MentaNews Application!

(Debug do Mentawai omitido)

Agora que temos o básico da nossa aplicação rodando, vamos criar o esquema do banco-de-dados. No nosso exemplo estaremos utilizando o MySQL, mas nada impede vc de utilizar qualquer outro banco. Como veremos mais a frente, sua aplicação pode suportar N bancos-de-dados através de N implementações diferentes do seu repositório de dados (DAO ou Repository).

O esquema completo do banco de dados (para MySQL) pode ser baixado aqui.


|=========================| |===================|
|News                     | |Sections           |
|=========================| |===================|
|id                 pk int| |id           pk int|
|-------------------------| |-------------------|
|section               int| |name    varchar(50)|
|create_date      datetime| |===================|
|title        varchar(200)|
|body             longtext|
|=========================|

A tabela News possui inicialmente uma entrada com o id = 1.

A tabela Sections possui 4 entradas: (1, Esportes), (2, Economia), (3, País), (4, Política)

Criamos também um usuário do mysql "mentanews" com a senha "mentanews" com acesso total ao database "mentanews".

grant all privileges on mentanews.* to mentanews@"%" identified by 'mentanews';

Nesse ponto precisamos de um pool de conexões com o banco-de-dados. Isso é básico de toda aplicação web. Logo vc pode ir aprender Commons DBCP, C3P0, configurar um XML do tomcat para fazer o pool de conexões funcionar e [color=“red”]voltar daqui a 1 hora, um dia ou um mês[/color]. Ou vc pode deixar que o Mentawai resolva isso pra vc. O Mentawai já vem de fábrica com uma abstração de um pool de conexões (interface org.mentawai.sql.ConnectionHandler) dentro dele, e com implementações que utilizam por trás o DBCP e o C3P0, dois dos mais famosos pools de conexões do mercado. Então para por exemplo utilizar o C3P0 com sua aplicação, tudo que vc tem que fazer é:


   public void loadActions() {
      
      // create a C3P0 connection pool...
      ConnectionHandler connHandler = new C3P0ConnectionHandler("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/mentanews?autoReconnect=true", "mentanews", "mentanews");
      
      // create a global connection filter...
      filter(new ConnectionFilter("conn", connHandler));
      
     ...
   }

Utilizamos o ConnectionFilter para disponibilizar uma connection para todas as actions da nossa aplicação quando, e apenas quando, uma connection for necessária. O ConnectionFilter se encarrega de obter a connection do pool de conexões e devolve-la para o pool quando a nossa action termina, sem que precisemos nos preocupar com isso. Mais informações sobre o ConnectionFilter pode ser obtido aqui. Não se esquecer de colocar o jar do C3P0 e do Mysql Connector (driver JDBC) dentro de WEB-INF/lib ! Esses jars vem junto com a versão full do Mentawai.

Para testarmos o nosso pool de conexões, vamos modificar a inner action test da nossa NewsAction:

   public String test() throws Exception {
       
      Connection conn = (Connection) input.getValue("conn");
      
      PreparedStatement stmt = conn.prepareStatement("select * from Sections limit 1");
      
      ResultSet rset = stmt.executeQuery();
      
      if (rset.next()) {
         
         output.setValue("hello", rset.getString(1) + ": " + rset.getString(2));
      }
      
      rset.close();
      stmt.close();
       
      return SUCCESS;
   }

Estamos obtendo uma connection (através do ConnectionFilter) e fazendo uma simples query para testarmos nossa conexão com o banco. Acesse http://localhost:8080/MentaNews/News.test.mtw e vc deverá visualizar a primeira linha da tabela Sections sendo exibida pelo JSP test.jsp:

Hello MentaNews!

1: Esportes

Vamos seguir em frente e carregar agora uma lista de dados para as Sections. Uma aplicação web normalmente terá várias listas estáticas de dados como países, categorias, métodos de pagamento aceitos, sexo (masculino/feminino), etc. Essas listas serão normalmente exibidas dentro de uma combo box. Para carregar uma lista de dados do banco de dados com o Mentawai, tudo que vc tem que fazer no ApplicationManager é:


      // load list...
      DBListData servicos = new DBListData("sections", "ID", "NAME", "SECTIONS", "NAME");

      addList(servicos, connHandler);

Feito isso vc tem uma lista estática carregada em memória que pode ser acessada de qualquer lugar da sua aplicação através do método ListManager.getList(“sections”); Como veremos mais adiante, também poderemos facilmente construir uma combo box de formulário HTML utilizando essa lista e as tags do Mentawai.

Note que para o construtor de DBListData estamos passando o nome da lista, o nome da coluna quem contém o id do elemento da lista, o nome da coluna que contém o nome do elemento da lista, o nome da tabela de onde virá essa lista e o campo pelo qual queremos ordenar nossa lista. Informações completas sobre as listas de dados do Mentawai podem ser obtidas aqui.

Seguindo em frente, vamos falar agora sobre outro tópico importantíssimo para todo projeto web. ORM ou mapeamento dos seus objetos para dentro das tabelas do seu banco de dados.

Abaixo temos o código News.java para a nossa entidade News (Notícia), que deverá ser colocado em org.mentanews.bean.


package org.mentanews.bean;

import java.util.Date;

public class News {
   
   private int id;
   private int section;
   private Date createDate;
   private String title;
   private String body;
   
   public News() { }
   
   public News(int id) {
      
      this.id = id;
   }

   // setters and getters here (omitted) 

Nesse ponto vc pode parar, ir aprender Hibernate e [color=“red”]voltar daqui a um dia, uma semana ou um mês[/color]. A outra opção é utilizar SQL puro via JDBC dentro dos seus repositories ou DAOs, ou ainda melhor: utilizar o MentaBeans para te ajudar com suas queries e CRUDs (Create, Update, Delete) de beans. O MentaBeans é um facilitador para quem não sabe ou não quer usar uma ferramenta de ORM completa e/ou prefere utilizar JDBC puro em suas aplicações.

Note que os repositories (ou DAOs, depende de como vc estiver arquitetando sua aplicação) servem exatamente para deixar sua aplicação aberta a qualquer outro banco-de-dados ou esquema de persistência. Se amanhã vc decidir utilizar Hibernate, iBatis, LDAP, arquivos textos, MS Access, tudo que vc terá que fazer é codificar uma nova implementação dos seus repositories ou DAOs.

Para manter as coisas simples estaremos utilizando o MentaBeans para a nossa aplicação. Para isso, tudo que temos que fazer é definir os BeanConfigs dentro do nosso ApplicationManager. (O Mentawai vem com o aplicativo helper org.mentawai.tools.BeanConfig para gerar automáticamente a configuração de um BeanConfig a partir de qualquer POJO). Note que não precisamos de XML ou Annotations para mapear os campos e que em nenhum momento estamos poluindo as nossas entidades com annotations. O seu objeto News.java ficará totalmente independente do MentaBeans e do Mentawai.

   @Override
   public void loadBeans() {
      
      bean(News.class, "news")
         .pk("id", DBTypes.INTEGER)
         .field("section", DBTypes.INTEGER)
         .field("createDate", "create_date", DBTypes.TIMESTAMP)
         .field("title", DBTypes.STRING)
         .field("body", DBTypes.STRING);
   }

Feito isso partimos para o desenvolvimento dos nossos repositories:

package org.mentanews.repository;

import java.util.List;

import org.mentanews.bean.News;

public interface NewsRepository {
   
   public News get(int id);
   
   public void add(News news);
   
   public void remove(int id);
   
   public List<News> getLast(int max);
   
   public List<News> getLastBySection(int sectionId, int max);
   
}

Note que o nosso repositório é independente de qualquer implementação referente ao mecanismo de persistência que estaremos utilizando. O método getLast retorna as últimas notícias cadastradas, por ordem de cadastro e o método getLastBySection retorna as últimas notícias cadastradas naquela sessão, também ordenadas pela data de cadastro.

Agora vamos a implementação do nosso repositório para o MySQL utilizando o MentaBeans para facilitar a nossa vida. Repare que não é necessário escrever nenhum SQL para as operações mais simples de listagem e CRUD de beans:

package org.mentanews.repository.mysql;

import java.sql.Connection;
import java.util.List;

import org.mentanews.bean.News;
import org.mentanews.repository.NewsRepository;
import org.mentawai.bean.jdbc.JdbcBeanSession;
import org.mentawai.bean.jdbc.MySQLBeanSession;

public class MySQLNewsRepository implements NewsRepository {
   
   protected Connection conn;
   
   protected JdbcBeanSession session; // mentabeans
   
   public void setConn(Connection conn) {
      
      this.conn = conn;
      
      this.session = new MySQLBeanSession(conn);
   }
   
   public News get(int id) {
      
      News news = new News(id);
      
      try {
      
         if (session.load(news)) {
            
            return news;
         }
      
      } catch(Exception e) {
         
         throw new RuntimeException(e);
      }
      
      return null;
   }
   
   public void add(News news) {
      
      int id = news.getId();
      
      try {
      
         boolean exists = id &gt 0;
         
         if (exists) {
            
            session.update(news);
            
         } else {
            
            session.insert(news);
         }
         
      } catch(Exception e) {
         
         throw new RuntimeException(e);
      }
   }
   
   public void remove(int id) {
      
      News n = new News(id);
      
      try {
         
         session.delete(n);
         
      } catch(Exception e) {
         
         throw new RuntimeException(e);
      }
   }
   
   public List<News> getLast(int max) {
      
      News n = new News();
      
      try {
      
         return session.loadList(n, "create_date desc", max);
         
      } catch(Exception e) {
         
         throw new RuntimeException(e);
      }
   }
   
   public List<News> getLastBySection(int sectionId, int max) {
      
      News n = new News();
      
      n.setSection(sectionId);
      
      try {
         
         return session.loadList(n, "create_date desc", max);
         
      } catch(Exception e) {
         
         throw new RuntimeException(e);
      }
   }
  
}

As funções insert, update, load e delete do MentaBeans são bem auto-explicativas. As funções loadList recebem um bean que pode ter algumas propriedades preenchidas (ou nenhuma) e será a partir dessas propriedades que a lista será carregada. Logo se queremos carregar uma lista de News com a sectionId igual a alguma coisa, simplesmente construimos um bean com essa propriedade setada e passamos para o loadList. Note que o loadList tb suporta um número máximo para a lista retornada e ordenação (order by do SQL). Mais informações sobre o MentaBeans podem ser obtidas aqui.

Partiremos agora para o próximo passo que é criar uma fachada ou service que será usado pelas nossas actions. Isso vai evitar que coloquemos a lógica do nosso negócio dentro das nossas actions. Abaixo temos o código para NewsService.java. Note que se amanhã vc precisar de acesso remote/distribuído ao seu sistema, vc vai transformar essa classe NewsService numa classe remota via xFire, RMI, EJB, etc.

package org.mentanews.service;

import java.util.List;
import java.util.Date;

import org.mentanews.bean.News;
import org.mentanews.repository.NewsRepository;

public class NewsService {
   
   private static final int MAX = 20;
   
   private NewsRepository newsRepo;
   
   public News load(int id) {
      
      return newsRepo.get(id);
   }
   
   public void delete(int id) {
      
      newsRepo.remove(id);
   }
   
   public List<News> loadList() {
      
      return newsRepo.getLast(MAX);
   }
   
   public void create(News news) {
      
      news.setCreateDate(new Date());
      
      newsRepo.add(news);
      
   }
   
   public void update(News news) {
      
      newsRepo.add(news);
   }
   
   public List<News> loadList(int sectionId) {
      
      return newsRepo.getLastBySection(sectionId, MAX);
   }
}

Agora vamos codificar a nossa NewsAction que utilizará o NewsService para realizar as tarefas necessárias. Vamos começar com as inner actions list e load.

package org.mentanews.action;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;

import org.mentanews.bean.News;
import org.mentanews.service.NewsService;
import org.mentawai.core.BaseAction;
import org.mentawai.filter.ModelDriven;
 
public class NewsAction extends BaseAction implements ModelDriven {
   
   private NewsService newsService = new NewsService();
   
   public Object getModel() {
      
      return newsService;
   }
    
   public String test() throws Exception {

        // omitted!
   }
   
   public String list() throws Exception {
      
      int sectionId = input.getIntValue("sectionId");
      
      List<News> list = null;
      
      if (sectionId &gt 0) {
         
         list = newsService.loadList(sectionId);
         
      } else {
         
         list = newsService.loadList();
      }
      
      output.setValue("list", list);
      
      return SUCCESS;
   }
   
   public String show() throws Exception {
      
      int id = input.getIntValue("id");
      
      if (id &lt= 0) return ERROR;
      
      News news = newsService.load(id);
      
      output.setValue("news", news);
      
      return SUCCESS;
   }
}

É importante notar aqui que estamos utilizando a interface ModelDriven, ou seja, quando utilizarmos o filtro global InjectionFilter, tudo será injetado diretamente em NewsService, que é o objeto retornado pelo método getModel() da interface ModelDriven. Essa opção de arquitetura é muito importante pois deixa a nossa action totalmente desacoplada do nosso modelo de negócios, ou seja, a nossa action usa o nosso modelo de negócios, que no caso aqui é o NewsService, ao invés de implementar o nosso modelo de negócio dentro dela (dentro da action).

Agora vamos partir para a questão do IoC (IoCFilter) e do Auto-Wiring (que no Mentawai é feito pelo DIFilter). Nesse ponto vc pode parar, ir aprender Spring e voltar daqui [color="red"]a um dia, uma semana ou um mês[/color]. Ou vc pode utilizar o excelente suporte que o Mentawai oferece para IoC e Auto-Wiring. Enquanto muitos frameworks web deixam de lado esse importante tópico, o Mentawai oferece uma implementação bastante prática e poderosa através do seu IoCFilter e do DIFilter.

Começamos pelo IoC. Temos que dizer para a nossa aplicação que ela vai usar a implementação MySQLNewsRepository para o nosso NewsRepository:

   @Override
   public void loadActions() {
      
      // create a C3P0 connection pool...
      ConnectionHandler connHandler = new C3P0ConnectionHandler("com.mysql.jdbc.Driver", 
            "jdbc:mysql://localhost/mentanews?autoReconnect=true", "mentanews", "mentanews");
      
      // load list...
      DBListData servicos = new DBListData("sections", "ID", "NAME", "SECTIONS", "NAME");
      addList(servicos, connHandler);
      
      // create a global connection filter...
      filter(new ConnectionFilter("conn", connHandler));
      
      // transform Mentawai in a IOC container...
      filter(new IoCFilter());
      
      // we will be using MYSQL implementation of our NewsRepository
      ioc("newsRepo", MySQLNewsRepository.class);

      // ...
}

Note que se amanhã vc tiver uma outra implementação do seu NewsRepository (ex: OracleNewsRespository, HibernateNewsRepository, etc.) vc só terá que mudar isso no ApplicationManager e toda a sua aplicação passará a utilizar essa nova implementação. Outra vantagem é que o seu modelo de negócios ficou totalmente independente da sua aplicação. Se amanhã vc quiser testar o seu modelo de negócios vc pode, por exemplo, passar para ele uma implementação dummy (boba) que não vai a lugar nenhum e apenas retorna valores estáticos.

Vamos agora para o auto-wiring (DIFilter). Uma dependência clara que temos aqui é MySQLNewsRepository DEPENDE de java.sql.Connection (que virá automaticamente do nosso pool de conexões previamente setado). Então fazemos o seguinte no ApplicationManager:

   @Override
   public void loadActions() {
      
      // create a C3P0 connection pool...
      ConnectionHandler connHandler = new C3P0ConnectionHandler("com.mysql.jdbc.Driver", 
            "jdbc:mysql://localhost/mentanews?autoReconnect=true", "mentanews", "mentanews");
      
      // load list...
      DBListData servicos = new DBListData("sections", "ID", "NAME", "SECTIONS", "NAME");
      addList(servicos, connHandler);
      
      // create a global connection filter...
      filter(new ConnectionFilter("conn", connHandler));
      
      // transform Mentawai in a IOC container...
      filter(new IoCFilter());
      
      // we will be using MYSQL implementation of our NewsRepository
      ioc("newsRepo", MySQLNewsRepository.class);
      
      // auto-wiring
      filter(new DIFilter());
      
      // Everything that depends (needs) on a Connection will receive one
      di("conn", Connection.class);

      // ...
}

Estamos dizendo aqui que "tudo que sair do input da action e precisar de uma java.sql.Connection irá receber uma connection", que por sua vez também sairá do input da action (que por sua vez sairá do pool de conexões previamente setado). Note que diferentemente do IoC, que trabalha com implementações, o auto-wiring trabalha com interfaces. O auto-wiring só se preocupa em ligar as coisas, sem se importar com o que elas são. Mais informações sobre o DIFilter podem ser obtidas aqui. Mais informações sobre o IoCFilter podem ser obtidos aqui.

Feito isso, vc configurou sua aplicação web para utilizar IoC com auto-wiring. Mas ainda falta um ponto importante que é o InjectionFilter.

O IoCFilter é encarregado de instanciar a implementação definida no application manager e colocá-la no input da action. O IoCFilter por si só, não injeta nada em lugar nenhum, apenas coloca uma instância no input da action. Para realizar a injeção, devemos utilizar o filtro global InjectionFilter. Como o InjectionFilter funciona? Ele olha a action e pega uma lista das propriedades da action. De posse dessa lista ele procura no input da action por esses valores e se encontrar injeta na action. A diferença é que estamos utilizando a interface ModelDriven, o que faz com que o InjectionFilter ao invés de procurar e injetar na action ele vai procurar e injetar diretamente no modelo retornado pelo método getModel().


       // ...

       // auto-wiring
       filter(new DIFilter());
       
       // Everything that depends (needs) on a Connection will receive one
       di("conn", Connection.class);

       // injection
       filter(new InjectionFilter());

Como estamos retornando NewsService no método getModel() da nossa action, então o que acontece é o seguinte:

ConnectionFilter cria uma Connection e coloca no input da action; IoCFilter cria um MySQLNewsRepository e coloca no input da action. O DiFilter se encarrega de injetar a connetion dentro de MySQLNewsService (dependencia / auto-wiring). O InjectionFilter detecta que o NewsService possui uma propriedade do tipo NewsRepository e com o nome newsRepo. InjectionFilter procura essa propriedade no input da action e injeta ela no NewsService. A cadeia de filtros chega na action e ela é executada, utilizando o NewsService para fazer o que ela precisa fazer.

É claro que vc poderia fazer todos os passos acima na mão, sem filtros, mas então vc não estaria utilizando IoC nem auto-wiring e a sua aplicação estaria cheia de acoplamentos fortes e não seria uma aplicação loosely coupled (fracamente acoplada).

Vamos partir agora o JSP que exibirá os resultados da nossa action. O nome do JSP, de acordo com nossa convenção proposta pelo InnerActionConsequenceProvider, deverá ser /news/list.jsp:


<%@ page contentType="text/html; charset=ISO-8859-1" %> 
<%@ taglib uri="/WEB-INF/lib/mentawai.jar" prefix="mtw" %>

<h1>Hello MentaNews!</h1>
<table border="1" cellspacing="3" cellpading="3">
<tr><th>ID</th><th>Section</th><th>Title</th><th>Date</th></tr>
<mtw:list value="list">
<mtw:loop>
<tr>
<td><mtw:out value="id" /></td>
<td><mtw:out value="section" list="sections" /></td>
<td><mtw:out value="title" /></td>
<td><mtw:out value="createDate" /></td>
</tr>
</mtw:loop>
</mtw:list>
</table>

Veja como estamos utilizando as tags mtw:list e mtw:loop. A tag list procura pela lista no output da action e coloca ela no context da página (pageContext). A tag loop pega essa lista e faz um loop, colocando cada item da lista no contexto da página. O método mtw:out exibe as propriedades dos itens da lista.

O Mentawai oferece um conjunto bastante poderoso de tags para acelerar o desenvolvimento dos seus JSPs em muitas ordens de grandeza se comparado com JSTL. E vale notar que com o Mentawai vc pode também utilizar JSTL sem qualquer restrição, se assim vc preferir.

Agora vamos partir para inclusão e edição de uma notícia, o que nos dará a oportunidade de ver como a validação do Mentawai funciona. Começamos criando as actions para insert e update:

   public String add() throws Exception {
      
      if (isPost()) {
         
         // we are inserting...
         
         News news = input.getObject(News.class);
         
         newsService.create(news);

         list(); // you can chain actions as well if you want...
         
         return LIST;
         
      } else {
         
         // we want to insert...
         
         return SUCCESS;
      }
   }
   
   public String edit() throws Exception {
      
      if (isPost()) {
         
         // we are updating...
         
         int id = input.getIntValue("id");
         
         if (id &lt= 0) return ERROR;
         
         News news = newsService.load(id);
         
         if (news == null) return ERROR;
         
         news = input.getObject(news); // update object with input values...
         
         newsService.update(news);
         
         return LIST;
         
      } else {
         
         // we want to edit...
         
         int id = input.getIntValue("id");
         
         if (id &lt= 0) return ERROR;
         
         News news = newsService.load(id);
         
         if (news == null) return ERROR;
         
         output.setValue("news", news);
         
         return SUCCESS;      
      }
   }

Estamos utilizando o método isPost() para saber se a requisição foi um POST ou um GET. Se for um POST significa que o formulário HTML de inclusão foi submetido via POST e estamos tentando inserir uma nova notícia. Se for um GET, significa que estamos apenas querendo ir para a página com o formulário de inclusão, que no caso será /news/add.jsp. Para o caso de uma inclusão, utilizamos o método do input da action getObject(Class), que vai retornar um novo objeto News populado com os dados do input da action. De posse do objeto News, tudo que temos que fazer é chamar newsService.create(news) para criar uma nova notícia no banco de dados.

Para o update é um pouco diferente. Primeiro carregamos do banco de dados a notícia que queremo editar. Depois atualizamos esse objeto com os valores que estão no input da action, através do métdo getObject(Object). Com o objeto atualizado, resta apenas chamar newsService.update(news) para fazer o update no banco de dados.

Agora vamos partir para a validação e vc verá como é fácil e prático fazer a validação com o Mentawai.

Precisamos validar as inner actions add e edit e para tal vamos fazer a nossa action NewsAction implementar a interface DynValidatable.

   public void prepareValidator(Validator validator, String innerAction) {
      
      if (isPost() && innerAction != null && (innerAction.equals("add") || innerAction.equals("edit"))) {
         
         // the messages below can move to an i18n file easily...
         
         validator.add("section", RequiredFieldRule.getInstance(), "Required field!");
         validator.add("section", IntegerRule.getInstance(1), "Section must be an intenger!");
         
         validator.add("title", RequiredFieldRule.getInstance(), "Required field!");
         validator.add("title", StringRule.getInstance(10, 100), "Title must be between %min% and %max% characters!");
         
         validator.add("body", RequiredFieldRule.getInstance(), "Required field!");
         validator.add("body", StringRule.getInstance(20, 2000), "Body invalid!");
      }
   }

E no ApplicationManager vc adiciona o filtro de validação global:


      // ...

      // load list...
      DBListData servicos = new DBListData("sections", "ID", "NAME", "SECTIONS", "NAME");
      addList(servicos, connHandler);
      
      // validation for actions that implement Validatable or DynValidatable interface...
      filter(new ValidatorFilter());
      
      // create a global connection filter...
      filter(new ConnectionFilter("conn", connHandler));

      // ...

Vc pode escrever suas próprias rules para a validação ou vc pode utilizar as diversas rules que o Mentawai oferece. Por exemplo, uma StringRule vai validar se o valor do campo é uma string com um tamanho mínimo e máximo. Note também que embora estejamos colocando as mensagens de erro de validação no código, nada nos impede de colocá-las num arquivo i18n onde elas ficarão separadas do código-fonte e poderão ser internacionalizadas e alteradas com mais facilidade. Para as informações completas sobre a validação do Mentawai, clique aqui.

Abaixo temos o JSP para a adição de uma nova notícia:


<%@ page contentType="text/html; charset=ISO-8859-1" %> 
<%@ taglib uri="/WEB-INF/lib/mentawai.jar" prefix="mtw" %>

<h1>Adding news:</h1>
<form action="<mtw:contextPath />/NewsAction.add.mtw" method="post">
<table border="0" cellspacing="3" cellpading="3">
<tr><td>
Section: </td><td><mtw:select name="section" list="sections" emptyField="true" /> 
&lt;mtw:outError field="section"&gt;<font color="red">&lt;mtw:out /&gt;</font>&lt;/mtw:outError&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;
Title: &lt;/td&gt;&lt;td&gt;&lt;mtw:input type="text" name="title" maxlength="200" size="50" /&gt;
&lt;mtw:outError field="title"&gt;<font color="red">&lt;mtw:out /&gt;</font>&lt;/mtw:outError&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;
Body: &lt;/td&gt;&lt;td&gt;&lt;mtw:textarea name="body" cols="50" rows="10" /&gt;
&lt;mtw:outError field="body"&gt;<font color="red">&lt;mtw:out /&gt;</font>&lt;/mtw:outError&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&nbsp;&lt;/td&gt;&lt;td align="right"&gt;&lt;input type="submit" value="Add" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;

Repare como estamos as tags do mentawai para facilitar nossa vida. Repare que se houver um erro de validação os valores serão automaticamente re-exibidos no formulário sem qualquer trabalho necessário da sua parte. Repare como estamos exibindo os erros com as tags condicionais mtw:outError.

Agora vamos ao JSP de edição, /news/edit.jsp:


&lt;%@ page contentType="text/html; charset=ISO-8859-1" %&gt; 
&lt;%@ taglib uri="/WEB-INF/lib/mentawai.jar" prefix="mtw" %&gt;

&lt;h1&gt;Editing news:&lt;/h1&gt;
&lt;form action="&lt;mtw:contextPath /&gt;/NewsAction.edit.mtw" method="post"&gt;
&lt;mtw:bean value="news"&gt;
&lt;mtw:input type="hidden" name="id" /&gt;
&lt;table border="0" cellspacing="3" cellpading="3"&gt;
&lt;tr&gt;&lt;td&gt;
Section: &lt;/td&gt;&lt;td&gt;&lt;mtw:select name="section" list="sections" emptyField="true" /&gt; 
&lt;mtw:outError field="section"&gt;<font color="red">&lt;mtw:out /&gt;</font>&lt;/mtw:outError&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;
Title: &lt;/td&gt;&lt;td&gt;&lt;mtw:input type="text" name="title" maxlength="200" size="50" /&gt;
&lt;mtw:outError field="title"&gt;<font color="red">&lt;mtw:out /&gt;</font>&lt;/mtw:outError&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;
Body: &lt;/td&gt;&lt;td&gt;&lt;mtw:textarea name="body" cols="50" rows="10" /&gt;
&lt;mtw:outError field="body"&gt;<font color="red">&lt;mtw:out /&gt;</font>&lt;/mtw:outError&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&nbsp;&lt;/td&gt;&lt;td align="right"&gt;&lt;input type="submit" value="Add" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/mtw:bean&gt;
&lt;/form&gt;

Esse JSP é bem parecido com o de adição, mas com algumas diferenças importantes. Primeiro estamos usando a tag mtw:bean para colocar um bean que veio no output da action no escopo da página. Dessa maneira as nossas tags de formulário podem exibir os valores desse bean para que esses sejam alterados/editados. E veja que como estamos editando precisamos do ID do objeto que estamos editando, e para tal usamos um campo hidden.

Estamos quase finalizando a nossa aplicação e falta agora uma action para visualizar a notícia por inteiro, visto que a nossa listagem apenas mostra o título da notícia e não o seu corpo.

   public String view() throws Exception {
      
      int id = input.getIntValue(&quot;id&quot;);
      
      if (id &lt= 0) return ERROR;
      
      News news = newsService.load(id);
      
      if (news == null) return ERROR;
      
      output.setValue(&quot;news&quot;, news);
      
      return SUCCESS;
   }

/news/view.jsp:

&lt;%@ page contentType="text/html; charset=ISO-8859-1" %&gt; 
&lt;%@ taglib uri="/WEB-INF/lib/mentawai.jar" prefix="mtw" %&gt;

&lt;h1&gt;View news:&lt;/h1&gt;
&lt;mtw:bean value="news"&gt;
&lt;table border="1" cellspacing="3" cellpading="3"&gt;
&lt;tr&gt;&lt;td&gt;
Section: &lt;/td&gt;&lt;td&gt;&lt;mtw:out value="section" list="sections" /&gt; 
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;
Title: &lt;/td&gt;&lt;td&gt;&lt;mtw:out value="title" /&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;
Body: &lt;/td&gt;&lt;td&gt;&lt;mtw:out value="body" /&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/mtw:bean&gt;

Note que estamos utilizando de novo a tag mtw:bean e dessa vez estamos apenas imprimindo no página os valores de section, title e body. Veja que para a section, adicionamos a lista da onde os valores serão pegos, lembrando que section é apenas um ID que corresponde a um valor da lista de dados Sections.

Vamos adicionar os links na primeira página para editar / visualizar a notícia:

&lt;td&gt;<a id" />&quot;&gtView</a> <a id" />&quot;&gtEditar</a>&lt;/td&gt;

E tb um link para adicionar uma nova notícia no alto da tabela:

&lt;h4&gt;<a >Adicionar nova notícia</a>&lt;/h4&gt;

Último detalhe: Note que a data está sendo exibida no formato americano MM/dd/YYYY. Isso se deve ao fato de o Mentawai por default ter o locale en_US.

Para corrigirmos isso podemos criar formatadores para a tag mtw:out, mas isso daria muito trabalho. Uma solução melhor é mudar o default do Mentawai para pt_BR e para isso tudo que temos que fazer é adicionar a seguinte linha no ApplicationManager:


LocaleManager.add("pt_BR");

Feito isso o Mentawai se vira para imprimir as datas no formato pt_BR.

Para abaixar um WAR com essa aplicação completa, clique aqui. (Não se esqueça de criar o seu banco de dados mysql antes de rodar a aplicação!)

Dúvidas e sugestões podem ser colocadas diretamente no nosso fórum, na seção de Comentários Gerais.

Um abraço e até a próxima!

Parabens!!!

A documentação é que faz o Mentawai o meu Framework preferido.

Abraço,
Wallfox