Mentawai Web Framework

Sticky Actions

Sticky Actions are the Mentawai solution for the persistence of state across multiple requests from a single user, without using the old and akward http session object.

Take the classical Number Guess game, where you try to guess a number between 0 and 100 while the computer tells you if your guess is higher or lower. (Ex: http://rifers.org/03_numberguess)

There can be a lot of state that needs to be mantained like:

  • The secret number (game solution)

  • How many guesses have I done so far

  • How long have I been trying to guess

The standard solution to this problem is to place everything inside the user session (HttpSession), but Mentawai offers you a more natural solution: Sticky Actions

Sticky Actions will persist its state across multiple invocations from the time you call adhere() until you release the action by calling disjoin(), in other words, the same action instance will be used for consecutive action requests.

You can also trap the onRemoved() callback for those cases where the session expires or gets invalidated before the disjoin() method is called. (For example when the user leaves the page or closes the browser).

Below is an example of how you could implement the Number Guess game using the session. Then we show an example of the same implementation using a sticky action.

  public class NumberGuess extends BaseAction implements Validatable {
      
      public static final String BINGO = "bingo";
      public static final String FIRST = "first";
      public static final String WRONG = "wrong";    
      
      private static final Random random = new Random();
      
      private void incrementGuesses() {
          
          Integer i = (Integer) session.getAttribute("guesses");
          
          session.setAttribute("guesses", new Integer(i.intValue() + 1));
          
      }
      
      public void initValidator(Validator val, String innerAction) {
              
              val.add("guess", new IntegerRule(0, 100), "Invalid guess!");
              
      }
      
      public String execute() throws Exception {
          
          // Check if a game is in progess, by fetching the current answer...
          
          int answer = -1;
          
          if (session.getAttribute("answer") == null) {
              
              answer = random.nextInt(101);
                
              session.setAttribute("answer", new Integer(answer));
              
              session.setAttribute("guesses", new Integer(0));
              
          } else {
              
              answer = ( (Integer) session.getAttribute("answer")).intValue();
              
          }
          
          if (input.getValue("guess") != null) {
              
              incrementGuesses();
              
              int guess = input.getIntValue("guess");
              
              if (answer == guess) {
                  
                  output.setValue("answer", session.getAttribute("answer").toString());
                  
                  output.setValue("guesses", session.getAttribute("guesses").toString());
                  
                  session.removeAttribute("answer");
                  
                  session.removeAttribute("guesses");
                  
                  return BINGO;
                  
              } else {
                  
                  if (guess < answer) {
                      
                      addMessage("Number is higher!"); // of course this can be i18n...
                      
                  } else {
                      
                      addMessage("Number is lower!"); // of course this can be i18n...
                      
                  }
                  
                  return WRONG;
              }
          }
          
          return FIRST;
      }
  }
	

Now welcome to sticky actions:

  public class NumberGuessSticky extends BaseAction implements Validatable {
      
      public static final String BINGO = "bingo";
      public static final String FIRST = "first";
      public static final String WRONG = "wrong";    
      
      // because this action is sticky, you can keep your values here across requests...
      private int answer = -1;
      private int guesses = 0;
      
      private static final Random random = new Random();
      
      public void initValidator(Validator val, String innerAction) {
              
              val.add("guess", new IntegerRule(0, 100), "Invalid guess!");
              
      }
      
      public String execute() throws Exception {
          
          System.out.println("Executing action: " + this.toString());
          
          // Check if a game is in progess, by fetching the current answer...
          
          if (answer == -1) {
              
              answer = random.nextInt(101);
              
              guesses = 0;
              
              adhere();
              
          }
          
          if (input.getValue("guess") != null) {
              
              guesses++;
              
              output.setValue("guesses", String.valueOf(guesses));
              
              int guess = input.getIntValue("guess");
              
              if (answer == guess) {
                  
                  output.setValue("answer", String.valueOf(answer));
                  
                  disjoin();
                  
                  return BINGO;
                  
              } else {
                  
                  if (guess < answer) {
                      
                      addMessage("Number is higher!"); // of course this can be i18n...
                      
                  } else {
                      
                      addMessage("Number is lower!"); // of course this can be i18n...
                      
                  }
                  
                  return WRONG;
              }
          }
          
          return FIRST;
      }
  }