SeamFramework.orgCommunity Documentation

Chapter 7. Diving into the Weld examples

7.1. The numberguess example in depth
7.1.1. The numberguess example in Apache Tomcat or Jetty
7.2. The numberguess example for Java SE with Swing
7.2.1. Creating the Eclipse project
7.2.2. Running the example from Eclipse
7.2.3. Running the example from the command line
7.2.4. Understanding the code
7.3. The translator example in depth

It's time to pull the covers back and dive into the internals of Weld example applications. Let's start with the simpler of the two examples, weld-numberguess.

In the numberguess application you get 10 attempts to guess a number between 1 and 100. After each attempt, you're told whether your guess was too high or too low.

The numberguess example is comprised of a number of beans, configuration files and Facelets (JSF) views, packaged as a war module. Let's start by examining the configuration files.

All the configuration files for this example are located in WEB-INF/, which can be found in the src/main/webapp directory of the example. First, we have the JSF 2.0 version of faces-config.xml. A standardized version of Facelets is the default view handler in JSF 2.0, so there's really nothing that we have to configure. Thus, the configuration consists of only the root element.


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

There's also an empty beans.xml file, which tells the container to look for beans in this application and to activate the CDI services.

Finally, there's the 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">

   <ui:composi(1)tion template="/template.xhtml">
      <ui:define name="content">
         <h1>Guess a number...</h1>
         <h:form id="numberGuess">
            <d(2)iv 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}"/>
            </div>
    
            <d(3)iv>
               I'm thinking of a number between #{game.smallest} and #{game.biggest}.
               You have #{game.remainingGuesses} guesses remaining.
            </div>
       
            <div>
               Your guess: 
              (4) <h:inputText id="inputGuess" value="#{game.guess}"
                  size="3" required="true" disabled="#{game.number eq game.guess}"
              (5)    validator="#{game.validateNumberRange}"/>
              (6) <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.

The example exists of 4 classes, the first two of which are qualifiers. First, there is the @Random qualifier, used for injecting a random number:

@Qualifier

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

There is also the @MaxNumber qualifier, used for injecting the maximum number that can be injected:

@Qualifier

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

The application-scoped Generator class is responsible for creating the random number, via a producer method. It also exposes the maximum possible number via a producer method:

@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;
   }
}

The Generator is application scoped, so we don't get a different random each time.

The final bean in the application is the session-scoped Game class. This is the primary entry point of the application. It's responsible for setting up or resetting the game, capturing and validating the user's guess and providing feedback to the user with a FacesMessage. We've used the post-construct lifecycle method to initialize the game by retrieving a random number from the @Random Instance<Integer> bean.

You'll notice that we've also added the @Named annotation to this class. This annotation is only required when you want to make the bean accessible to a JSF view via EL (i.e., #{game}).

import javax.enterprise.inject.Instance;


@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;
   }
}

This example shows how to use the Weld SE extension to in a Java SE based Swing application with no EJB or servlet dependencies. This example can be found in the examples/se/numberguess folder of the Weld distribution.

To use the Weld SE numberguess example in Eclipse, you can open the example natively using the m2eclipse plugin.

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.

This will create a project in your workspace called weld-se-numberguess.

If you are not using the m2eclipse plugin, you have to follow different steps to import the project. First, switch into the Weld SE numberguess example, then execute the Maven Eclipse plugin with the jetty profile activated, as follows:

It's time to get the example running!

Let's have a look at the significant code and configuration files that make up this example.

As usual, there is an empty beans.xml file in the root package (src/main/resources/beans.xml), which marks this application as a CDI application.

The game's main logic is located in Game.java. Here is the code for that class, highlighting the ways in which this differs from the web application version:

(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()
   {
      return validNumberRange = ((guess >= smallest) && (guess <= biggest));
(5)   }
   @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.

import javax.enterprise.event.Observes;


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();
   }
(6)   private void replayBtnActionPerformed(java.awt.event.ActionEvent evt)
   {
      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.

The translator example will take any sentences you enter, and translate them to Latin. (Well, not really, but the stub is there for you to implement, at least. Good luck!)

The translator example is built as an ear and contains EJBs. As a result, it's structure is more complex than the numberguess example.

First, let's take a look at the ear aggregator, which is located in the example's ear directory. Maven automatically generates the application.xml for us from this plugin configuration:


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

This configuration overrides the web context path, resulting in this application URL: http://localhost:8080/weld-translator.

Note

If you weren't using Maven to generate these files, you would need 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>

Next, lets look at the war, which is located in the example's war directory. Just as in the numberguess example, we have a faces-config.xml for JSF 2.0 and a web.xml (to activate JSF) under WEB-INF, both sourced from src/main/webapp/WEB-INF.

More interesting is the JSF view used to translate text. Just as in the numberguess example we have a template, which surrounds the form (omitted here for brevity):


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

The user can enter some text in the left-hand text area, and hit the translate button to see the result to the right.

Finally, let's look at the EJB module, which is located in the example's ejb directory. In src/main/resources/META-INF there is just an empty beans.xml, used to mark the archive as containing beans.

We've saved the most interesting bit to last, the code! The project has two simple beans, SentenceParser and TextTranslator and two session beans, TranslatorControllerBean and SentenceTranslator. You should be getting quite familiar with what a bean looks like by now, so we'll just highlight the most interesting bits here.

Both SentenceParser and TextTranslator are dependent beans, and TextTranslator uses constructor injection:

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 uses the simple bean (really just a plain Java class!) SentenceParser to parse the sentence and then calls on the stateless bean with the local business interface Translator to perform the translation. That's where the magic happens. Of course, we couldn't develop a full translator, but it's convincing enough to anyone who doesn't understand Latin!

@Stateless

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

Finally, there is UI orientated controller. This is a request scoped, named, stateful session bean, which injects the translator. It collects the text from the user and dispatches it to the translator. The bean also has getters and setters for all the fields on the page.

@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() {}
}

That concludes our short tour of the Weld starter examples. For more information on Weld, please visit http://weld.cdi-spec.org/.