SeamFramework.orgCommunity Documentation

Capítulo 7. Mergulhando nos exemplos do Weld

7.1. O exemplo numberguess em detalhes
7.1.1. O exemplo numberguess no Apache Tomcat ou Jetty
7.2. O exemplo numberguess para Java SE com Swing
7.2.1. Criando o projeto no Eclipse
7.2.2. Rodando o exemplo dentro do Eclipse
7.2.3. Rodando o exemplo a partir da linha de comando
7.2.4. Entendendo o código
7.3. O exemplo translator em detalhe

É hora de tirar as tampas e mergulhar dentro das aplicações de exemplo do Weld. Vamos começar com o mais simples dos dois exemplos, weld-numberguess.

Na aplicação numberguess você tem 10 tentativas para adivinhar um número entre 1 e 100. Depois de cada tentativa, você é informado se seu palpite foi muito alto ou muito baixo.

O exemplo numberguess é composto de uma série de beans, arquivos de configuração e páginas em Facelets (JSF), empacotados como um módulo war. Vamos começar examinando os arquivos de configuração.

Todos os arquivos de configuração para este exemplo estão localizados no WEB-INF/, o qual pode ser encontrado no diretório src/main/webapp do exemplo. Primeiro, nós temos a versão JSF 2.0 de faces-config.xml. Uma versão padronizada de Facelets é o view handler padrão em JSF 2.0, então não há realmente nada a ser configurado. Assim, a configuração consiste apenas ao elemento raíz.


<faces-config version="2.0"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
</faces-config
>

Existe também um arquivo beans.xml vazio, que diz ao contêiner para procurar por beans nesta aplicação e ativar os serviços CDI.

Finalmente, temos o familiar web.xml:

Let's take a look at the main JSF view, src/main/webapp/home.xhtml.


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:f="http://java.sun.com/jsf/core">
(1)ml_plain">
   <ui:composition template="/template.xhtml">
      <ui:define name="content">
         <h1
(2)ml_tag_symbols">>Guess a number...</h1>
         <h:form id="numberGuess">
            <div style="color: red">
               <h:messages id="messages" globalOnly="false"/>
               <h:outputText id="Higher" value="Higher!"
                  rendered="#{game.number gt game.guess and game.guess ne 0}"/>
               <h:outputText id="Lower" value="Lower!"
                  rendered="#{game.number lt game.guess and game.guess ne 0}"/>
(3)ml_plain">            </div>
    
            <div>
               I'm thinking of a number between #{game.smallest} and #{game.biggest}.
               You have #{game.remainingGuesses} guesses remaining.
            </div>
       
(4)ml_plain">            <div>
               Your guess: 
(5)ml_plain">               <h:inputText id="inputGuess" value="#{game.guess}"
(6)ml_plain">                  size="3" required="true" disabled="#{game.number eq game.guess}"
                  validator="#{game.validateNumberRange}"/>
               <h:commandButton id="guessButton" value="Guess" 
                  action="#{game.check}" disabled="#{game.number eq game.guess}"/>
            </div>
            <div>
              <h:commandButton id="restartButton" value="Reset" action="#{game.reset}" immediate="true"/>
            </div>
         </h:form>
      </ui:define>
   </ui:composition>
</html
>

1

Facelets is the built-in templating language for JSF. Here we are wrapping our page in a template which defines the layout.

2

There are a number of messages which can be sent to the user, "Higher!", "Lower!" and "Correct!"

3

As the user guesses, the range of numbers they can guess gets smaller - this sentence changes to make sure they know the number range of a valid guess.

4

This input field is bound to a bean property using a value expression.

5

A validator binding is used to make sure the user doesn't accidentally input a number outside of the range in which they can guess - if the validator wasn't here, the user might use up a guess on an out of bounds number.

6

And, of course, there must be a way for the user to send their guess to the server. Here we bind to an action method on the bean.

O exemplo consiste em 4 classes, as duas primeiras são qualificadores. Primeiramente temos o qualificador @Random, usado para injetar um número aleatório:

@Qualifier

@Target( { TYPE, METHOD, PARAMETER, FIELD })
@Retention(RUNTIME)
public @interface Random {}

Existe também o qualificador @MaxNumber, usado para injetar o número máximo que pode ser gerado:

@Qualifier

@Target( { TYPE, METHOD, PARAMETER, FIELD })
@Retention(RUNTIME)
public @interface MaxNumber {}

A classe Generator com escopo de aplicação é responsável por criar o número aleatório, através de um método produtor. Ela também expõe o número máximo possível através de um método produtor:

@ApplicationScoped

public class Generator implements Serializable {
   private java.util.Random random = new java.util.Random(System.currentTimeMillis());
   
   private int maxNumber = 100;
   
   java.util.Random getRandom() {
      return random;
   }
   
   @Produces @Random int next() { 
      return getRandom().nextInt(maxNumber); 
   }
   
   @Produces @MaxNumber int getMaxNumber() {
      return maxNumber;
   }
}

O bean Generator possui escopo de aplicação, assim não obtemos uma instância de Random diferente a cada vez.

O último bean na aplicação é a classe Game com escopo de sessão. Este é o principal ponto de entrada da aplicação. É responsável por criar ou redefinir o jogo, capturando e validando o palpite do usuário e fornecendo resposta ao usuário com uma FacesMessage. Nós utilizamos o método de pós-construção para inicializar o jogo recuperando um número aleatório a partir do bean @Random Instance<Integer>.

Você notará que também adicionamos a anotação @Named nesta classe. Esta anotação somente é necessária quando você quer tornar o bean acessível em uma página JSF por meio de EL (ou seja, #{game}).

@Named

@SessionScoped
public class Game implements Serializable {
   private int number;
   private int guess;
   private int smallest;
   private int biggest;
   private int remainingGuesses;
   @Inject @MaxNumber private int maxNumber;
   @Inject @Random Instance<Integer
> randomNumber;
   
   public Game() {}
   
   public void check() {
      if (guess 
> number) {
         biggest = guess - 1;
      }
      else if (guess < number) {
         smallest = guess + 1;
      }
      else if (guess == number) {
         FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Correct!"));
      }
      remainingGuesses--;
   }
   
   @PostConstruct
   public void reset() {
      this.smallest = 0;
      this.guess = 0;
      this.remainingGuesses = 10;
      this.biggest = maxNumber;
      this.number = randomNumber.get();
   }
   
   public void validateNumberRange(FacesContext context,  UIComponent toValidate, Object value) {
      if (remainingGuesses <= 0) {
         FacesMessage message = new FacesMessage("No guesses left!");
         context.addMessage(toValidate.getClientId(context), message);
         ((UIInput) toValidate).setValid(false);
         return;
      }
      int input = (Integer) value;
      if (input < smallest || input 
> biggest) {
         ((UIInput) toValidate).setValid(false);
         FacesMessage message = new FacesMessage("Invalid guess");
         context.addMessage(toValidate.getClientId(context), message);
      }
   }
   public int getNumber() {
      return number;
   }
   
   public int getGuess() {
      return guess;
   }
   
   public void setGuess(int guess) {
      this.guess = guess;
   }
   
   public int getSmallest() {
      return smallest;
   }
   
   public int getBiggest() {
      return biggest;
   }
   
   public int getRemainingGuesses() {
      return remainingGuesses;
   }
}

Este exemplo mostra como utilizar a extensão Weld SE em uma aplicação Java SE feita em Swing com nenhum EJB ou dependências com servlet. Este exemplo pode ser encontrado na pasta examples/se/numberguess da distribuição padrão do Weld.

Para usar o exemplo numberguess com Weld SE no Eclipse, você pode abrir o exemplo normalmente utilizando o plugin m2eclipse.

If you have m2eclipse installed, you can open any Maven project directly. From within Eclipse, select File -> Import... -> Maven Projects. Then, browse to the location of the Weld SE numberguess example. You should see that Eclipse recognizes the existence of a Maven project.

Isto criará um projeto em seu workspace com o nome weld-se-numberguess.

Se você não está usando o plugin m2eclipse, você tem que seguir diferentes passos para importar o projeto. Primeiro, entre no exemplo numberguess do Weld em SE, então execute o plugin Maven Eclipse com o profile jetty ativado, conforme a seguir:

É hora de ver o exemplo rodando!

Vamos dar uma olhada no código e arquivos de configuração interessantes que compõem este exemplo.

Como usual, existe um arquivo beans.xml vazio no pacote raíz (src/main/resources/beans.xml), o qual marca esta aplicação como uma aplicação CDI.

A lógica principal do jogo está em Game.java. Aqui está o código para esta classe, destacando os pontos que diferem da versão web:

(1)@ApplicationScoped

(2)public class Game
{
   public static final int MAX_NUM_GUESSES = 10;
   private Integer number;
   private int guess = 0;
   private int smallest = 0;
   @Inject
   @MaxNumber
   private int maxNumber;
   private int biggest;
   private int remainingGuesses = MAX_NUM_GUESSES;
   private boolean validNumberRange = true;
   @Inject
   Generator rndGenerator;
   public Game()
   {
   }
(3)   ...
   public boolean isValidNumberRange()
   {
      return validNumberRange;
   }
   public boolean isGameWon()
   {
      return guess == number;
   }
   public boolean isGameLost()
   {
      return guess != number && remainingGuesses <= 0;
(4)   }
   public boolean check()
   {
      boolean result = false;
      if (checkNewNumberRangeIsValid())
      {
         if (guess 
> number)
         {
            biggest = guess - 1;
         }
         if (guess < number)
         {
            smallest = guess + 1;
         }
         if (guess == number)
         {
            result = true;
         }
         remainingGuesses--;
      }
      return result;
   }
   private boolean checkNewNumberRangeIsValid()
   {
(5)      return validNumberRange = ((guess 
>= smallest) && (guess <= biggest));
   }
   @PostConstruct
   public void reset()
   {
      this.smallest = 0;
      this.guess = 0;
      this.remainingGuesses = 10;
      this.biggest = maxNumber;
      this.number = rndGenerator.next();
   }
}

1

The bean is application scoped rather than session scoped, since an instance of a Swing application typically represents a single 'session'.

2

Notice that the bean is not named, since it doesn't need to be accessed via EL.

3

In Java SE there is no JSF FacesContext to which messages can be added. Instead the Game class provides additional information about the state of the current game including:

  • If the game has been won or lost

  • If the most recent guess was invalid

This allows the Swing UI to query the state of the game, which it does indirectly via a class called MessageGenerator, in order to determine the appropriate messages to display to the user during the game.

4

Since there is no dedicated validation phase, validation of user input is performed during the check() method.

5

The reset() method makes a call to the injected rndGenerator in order to get the random number at the start of each game. Note that it can't use Instance.get() like the JSF example does because there will not be any active contexts like there are during a JSF request.

The MessageGenerator class depends on the current instance of Game and queries its state in order to determine the appropriate messages to provide as the prompt for the user's next guess and the response to the previous guess. The code for MessageGenerator is as follows:

public class MessageGenerator

{
(1)   @Inject
   private Game game;
(2)   public String getChallengeMessage()
   {
      StringBuilder challengeMsg = new StringBuilder("I'm thinking of a number between ");
      challengeMsg.append(game.getSmallest());
      challengeMsg.append(" and ");
      challengeMsg.append(game.getBiggest());
      challengeMsg.append(". Can you guess what it is?");
      return challengeMsg.toString();
   }
(3)   public String getResultMessage()
   {
      if (game.isGameWon())
      {
         return "You guessed it! The number was " + game.getNumber();
      }
      else if (game.isGameLost())
      {
         return "You are fail! The number was " + game.getNumber();
      }
      else if (!game.isValidNumberRange())
      {
         return "Invalid number range!";
      }
      else if (game.getRemainingGuesses() == Game.MAX_NUM_GUESSES)
      {
         return "What is your first guess?";
      }
      else
      {
         String direction = null;
         if (game.getGuess() < game.getNumber())
         {
            direction = "Higher";
         }
         else
         {
            direction = "Lower";
         }
         return direction + "! You have " + game.getRemainingGuesses() + " guesses left.";
      }
   }
}

1

The instance of Game for the application is injected here.

2

The Game's state is interrogated to determine the appropriate challenge message ...

3

... and again to determine whether to congratulate, console or encourage the user to continue.

Finally we come to the NumberGuessFrame class which provides the Swing front end to our guessing game.

public class NumberGuessFrame extends javax.swing.JFrame

{
(1)   @Inject
   private Game game;
(2)   @Inject
   private MessageGenerator msgGenerator;
(3)   public void start(@Observes ContainerInitialized event)
   {
      java.awt.EventQueue.invokeLater(new Runnable()
      {
         public void run()
         {
            initComponents();
            setVisible(true);
         }
      });
   }
(4)   private void initComponents()
   {
      buttonPanel = new javax.swing.JPanel();
      mainMsgPanel = new javax.swing.JPanel();
      mainLabel = new javax.swing.JLabel();
      messageLabel = new javax.swing.JLabel();
      guessText = new javax.swing.JTextField();
      ...
      mainLabel.setText(msgGenerator.getChallengeMessage());
      mainMsgPanel.add(mainLabel);
      messageLabel.setText(msgGenerator.getResultMessage());
      mainMsgPanel.add(messageLabel);
      ...
   }
(5)   private void guessButtonActionPerformed( java.awt.event.ActionEvent evt )
   {
      int guess =  Integer.parseInt(guessText.getText());
      game.setGuess( guess );
      game.check();
      refreshUI();
   }
   private void replayBtnActionPerformed(java.awt.event.ActionEvent evt)
   {
(6)      game.reset();
      refreshUI();
   }
   private void refreshUI() {
      mainLabel.setText( msgGenerator.getChallengeMessage() );
      messageLabel.setText( msgGenerator.getResultMessage() );
      guessText.setText( "" );
      guessesLeftBar.setValue( game.getRemainingGuesses() );
      guessText.requestFocus();
   }
   // swing components
   private javax.swing.JPanel borderPanel;
   ...
   private javax.swing.JButton replayBtn;
}

1

The injected instance of the game (logic and state).

2

The injected message generator for UI messages.

3

This application is started in the prescribed Weld SE way, by observing the ContainerInitialized event.

4

This method initializes all of the Swing components. Note the use of the msgGenerator here.

5

guessButtonActionPerformed is called when the 'Guess' button is clicked, and it does the following:

  • Gets the guess entered by the user and sets it as the current guess in the Game

  • Calls game.check() to validate and perform one 'turn' of the game

  • Calls refreshUI. If there were validation errors with the input, this will have been captured during game.check() and as such will be reflected in the messages returned by MessageGenerator and subsequently presented to the user. If there are no validation errors then the user will be told to guess again (higher or lower) or that the game has ended either in a win (correct guess) or a loss (ran out of guesses).

6

replayBtnActionPerformed simply calls game.reset() to start a new game and refreshes the messages in the UI.

O exemplo translator pegará qualquer sentença que vocẽ entrar e as traduzirá para Latim. (Bem, não realmente, mas a base está aí para você implementar. Boa sorte!)

O exemplo translator é construído como um ear e contém EJBs. Como resultado, sua estrutura é mais complexa do que o exemplo numberguess.

Primeiro, vamos dar uma olhada no agregador eear, que está localizado no diretório ear do exemplo. O Maven automaticamente gera o application.xml para nós com esta configuração de plugin:


<plugin>
   <groupId
>org.apache.maven.plugins</groupId>
   <artifactId
>maven-ear-plugin</artifactId>
   <configuration>
      <modules>
         <webModule>
            <groupId
>org.jboss.weld.examples.jsf.translator</groupId>
            <artifactId
>weld-jsf-translator-war</artifactId>
            <contextRoot
>/weld-translator</contextRoot>
         </webModule>
      </modules>
   </configuration>
</plugin
>

Esta configuração sobrescreve o caminho do contexto web, resultando nesta URL para a aplicação: http://localhost:8080/weld-translator.

Nota

Se você não estiver utilizando o Maven para gerar estes arquivos, você deve precisar de um META-INF/application.xml:


<application version="5"
   xmlns="http://java.sun.com/xml/ns/javaee" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/application_5.xsd">

  <display-name
>weld-jsf-translator-ear</display-name>
  <description
>The Weld JSF translator example (ear)</description>
  
  <module>
    <web>
      <web-uri
>weld-translator.war</web-uri>
      <context-root
>/weld-translator</context-root>
    </web>
  </module>
  <module>
    <ejb
>weld-translator.jar</ejb>
  </module>
</application
>

Agora, vamos dar uma olhada no war, que está localizado no diretório war do exemplo. Da mesma forma do exemplo numberguess, temos um faces-config.xml para JSF 2.0 e um web.xml (para ativar JSF), ambos dentro de src/main/webapp/WEB-INF.

O mais interessante é a visão JSF usada para traduzir texto. Como no exemplo numberguess, possuímos um template (aqui omitido por brevidade) que circunda o formulário:


<h:form id="translator">

   <table>
      <tr align="center" style="font-weight: bold">
         <td>
            Your text
         </td>
         <td>
            Translation
         </td>
      </tr>
      <tr>
         <td>
            <h:inputTextarea id="text" value="#{translator.text}" required="true" rows="5" cols="80"/>
         </td>
         <td>
            <h:outputText value="#{translator.translatedText}"/>
         </td>
      </tr>
   </table>
   <div>
      <h:commandButton id="button" value="Translate" action="#{translator.translate}"/>
   </div>
   
</h:form
>

O usuário pode digitar algum texto no textarea à esquerda, e pressionar o botão translate para ver o resultado à direita.

Finalmente vamos dar uma olhada no módulo EJB, o qual está localizado no diretório ejb do exemplo. Em src/main/resources/META-INF existe apenas um beans.xml vazio, usado para indicar que o jar possui beans.

Nós temos deixado a parte mais interessante por último, o código! O projeto possui dois simples beans, SentenceParser e TextTranslator, e dois session beans, TranslatorControllerBean e SentenceTranslator. Você já deve estar bem familiarizado com beans agora, assim destacamos apenas as partes mais interessantes.

Tanto SentenceParser quanto TextTranslator são beans dependentes, e TextTranslator utiliza injeção no construtor:

public class TextTranslator implements Serializable { 


   private SentenceParser sentenceParser; 
   @EJB private Translator translator; 
   
   @Inject public TextTranslator(SentenceParser sentenceParser) { 
      this.sentenceParser = sentenceParser; 
   }
   
   public String translate(String text) { 
      StringBuilder sb = new StringBuilder(); 
      for (String sentence: sentenceParser.parse(text)) { 
         sb.append(translator.translate(sentence)).append(". "); 
      } 
      return sb.toString().trim(); 
   }
}

TextTranslator usa o bean SentenceParser (realmente apenas uma simples classe Java!) para analisar a sentença e então chama o stateless bean com a interface local de negócio Translator para realizar a tradução. É onde a mágica acontece. Certamente, nós não pudemos desenvolver um tradutor completo, mas é suficientemente convincente para quem não conhece Latim!

@Stateless

public class SentenceTranslator implements Translator { 
   public String translate(String sentence) { 
      return "Lorem ipsum dolor sit amet"; 
   }
}

Finalmente, existe um controlador orientado na interface com o usuário. Este é um stateful session bean com escopo de requisição e nomeado, o qual injeta o tradutor. Ele coleta o texto do usuário e o despacha para o tradutor. O bean também possui getters e setters para todos os campos na página.

@Stateful

@RequestScoped
@Named("translator")
public class TranslatorControllerBean implements TranslatorController {
   @Inject private TextTranslator translator;
   
   private String inputText;
   
   private String translatedText;
   
   public void translate() {
      translatedText = translator.translate(inputText);
   }
   
   public String getText() {
      return inputText;
   }
   
   public void setText(String text) {
      this.inputText = text;
   }
   
   public String getTranslatedText() {
      return translatedText;
   }
   
   @Remove public void remove() {}
}

Isto conclui nosso rápido passeio com os exemplos de partida do Weld. Para mais informações sobre o Weld, por favor visite http://www.seamframework.org/Weld.