Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin


Wiki Markup
{float:right|background=#eee}
{contentbylabel:title=Related Articles|showLabels=false|showSpace=false|space=@self|labels=persistence}
{float}

Session Storage

Table of Contents
maxLevel4
minLevel2

Most web applications will need to have some data that is shared across multiple pages. Perhaps you are creating a multi-page wizard, or you have an object that tracks the user's identify once logged in, or maybe you need to manage a shopping cart.

Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel = "persistence" and space = currentSpace()

Ordinary page-persistent fields

won't work for this, since persistent fields are available only to a specific page, not shared across multiple pages.

...

A field holding an SSO is marked with the @SessionState annotation.

Wiki Markup
{float:right}
{panel:background=#eee|title=Contents}
{toc:minLevel=2|maxLevel=4}
{panel}
{float}

Example:


Code Block
languagejava
titleMyPage.java (partial)

public class MyPage
{
  @SessionState
  private ShoppingCart shoppingCart;
  
  . . .
}

...

With @SessionState, you are creating a session-wide data storage area that is tied to the type (class) of the variable you annotate. It is not specifically tied to the variable itself, or even to the class in which that variable was annotated. As with all session data, there is the serious possibility of collisions, not just within your application but with other modules/libraries:

Code Block
languagejava
titleExample of Data Collision -- Don't Do This!

  @SessionState
  private String userName;     // Unsafe -- String is not a custom type

  ... then, later in this class or any other:

  @sessionState@SessionState
  private String userCity;     // This overwrites value in userName, because it's also a String!

...

Instead, create a second field with a matching name but with "Exists" appended:

Code Block
java
languagejava

  private boolean shoppingCartExists;

...

Alternately, you may allow for the state being null:

Code Block
java
languagejava

  @SessionState(create=false)
  private ShoppingCart shoppingCart;

...

Persistence Strategies

Main Article: Persistent Page Data PersistentPage

Each SSO is managed according to a persistence strategy. The default persistence strategy, "session", stores the SSOs inside the session. The session is created as needed.

...

A Session State Object is configured using contributions to the ApplicationStateManager service. From your application's module:

Code Block
languagejava
titleAppModule.java (partial)

  public void contributeApplicationStateManager(MappedConfiguration<Class, ApplicationStateContribution> configuration)
  {
    ApplicationStateCreator<MyState> creator = new ApplicationStateCreator<ShoppingCart>()
    {
      public ShoppingCart create()
      {
        return new ShoppingCart(new Date());
      }
    };
  
    configuration.add(ShoppingCart.class, new ApplicationStateContribution("session", creator));
  }

...

As an alternative to SSOs, Tapestry provides a Session Attribute mechanism, which lets you store data in the session by name (rather than type). It is particularly useful when integrating Tapestry with legacy applications that directly manipulate the HttpSession.

Code Block
languagejava
titleMyPage.java - The Old Way

public class PageMyPage {
    @Inject
    private Request request;
    
    public User getUser() {
        return (User) request.getSession(true).getAttribute("loggedInUserName");
    }
}

Starting with Tapestry 5.2, this can be accomplished just by annotating a page or component property with @SessionAttribute. This annotation is used to map a property of a page or component to value stored in session. Unlike Session State Objects, the name (not the type) of the annotated property is used as the name of the session attribute to look for.

Code Block
languagejava
titleMyPage.java - The New Way

public class PageMyPage {
    @SessionAttribute
    private User loggedInUserName;
}

You can also provide a name using the annotation's value parameter:

Code Block
languagejava
titleMyPage.java

public class PageMyPage {
    @SessionAttribute("loggedInUserName")
    private User userName;
}

...

It's best to define the session attribute name as constant, and use that in the annotation's value parameter, rather then defaulting to the instance variable name. This will help prevent subtle runtime errors due to misspellings. For example:

Code Block
languagejava
titleMyPage.java - The Safer Way

public static final String USER_NAME_SESSION_ATTRIBUTE = "com.example.shoppingapp.username";

...

public class PageMyPage {
    @SessionAttribute(USER_NAME_SESSION_ATTRIBUTE)
    private User userName;
}

Include Page
Clustering Issues
Clustering Issues

Session Locking

Starting with version 5.4, by default Tapestry will apply locking semantics around access to the HttpSession. Reading attribute names occurs with a shared read lock, and getting or setting an attribute upgrades the lock to an exclusive write lock. This can tend to serialize threads when a number of simultaneous (Ajax) requests from the client arrive. However, many implementations of HttpSession are not thread safe, and often mutable objects
are stored in the session and shared between threads.

The tapestry.session-locking-enabled configuration symbol can control this behavior. Setting this to true (the default) will yield a more robust application; setting it to false may speed up processing for more Ajax intensive applications (but care should then be given to ensuring that objects shared inside the session are themeselves immutable or thread-safe).

Code Block
languagejava
titleAppModule.java (partial)

  public static void contributeApplicationDefaults(MappedConfiguration<String,String> configuration)
  {
    configuration.add(SymbolConstants.SESSION_LOCKING_ENABLED, true);
    ...
  }

...