7.10 Saving and Sharing State

Applications that use Flash for the entire application interface can rely on the Flash client maintaining state while making multiple calls to the server. State in this context refers to data being held in memory in either the Flash client or on the server. Unlike traditional HTML interfaces, where the browser redraws the page for each server request, Flash clients that use Remoting remain as a single stateful instance while handling multiple server requests. As soon as the Flash client is unloaded or reloaded, however, the state that the Flash client held is lost. Flash and Java developers have to work together to preserve the state of the Flash client and restore it when the Flash interface is loaded again.

Additionally, applications that have both HTML and Flash interfaces will often find that they want to share state between the two. One example that we have already discussed is a homegrown security system that uses a User object saved in the user session both to determine if a user is logged in and to retrieve information about that user. A Flash client, the Remoting services it uses, and the code that handles the HTML interface all need to access this user information.

For applications that have Flash-only interfaces, one option for saving state is to use an ActionScript local shared object (an instance of the SharedObject class) to save data to and retrieve data from the user's computer. Local shared objects are effective as long as the size of the data does not exceed the maximum configured by the user in his Flash Player settings. That said, you can make additional requests for local storage by passing the optional minimumDiskSpace parameter to the SharedObject.flush( ) method. Furthermore, you can open the user's Flash Player Settings dialog box, where he can increase the allowed space for local storage, using System.showSettings(1).

However, for situations when the amount of data is too large to save locally, when having the user approve using additional disk space is undesirable, or when the data needs to be accessed by more than the Flash client, applications must store and share the state of the data on the server.

7.10.1 Using JavaBean Services to Store Session State

Section 7.9 offers a generic solution for accessing the user session, which can be used as a place on the server side to store object state. Another solution for storing and sharing state takes advantage of the fact that JavaBean services are already stored in the user session by the Remoting gateway. Objects that have access to the user session can access a stored JavaBean service to change its stored state.

Consider a homegrown authentication system that uses the following custom Authenticator class to log users in. The Authenticator.login( ) method looks for a user in the Directory object with the supplied username. If it finds the user, it stores the User object in the user session under the session attribute key USER_KEY. Once the User object is stored in the user session, other code can retrieve the logged-in user by calling Authenticator.getLoggedInUser( ) and passing in the current request:

public class Authenticator {
  private static final String USER_KEY = "user";

  public static User login(String username,
                           HttpServletRequest request)
    throws Exception
  {
    // Look for a user in the Directory with this username
    User user = Directory.getUser(username);

    // If we don't find it, return null
    if (user == null) return null;

    // Store the logged-in user in the session
    request.getSession( ).setAttribute(USER_KEY, user);

    // Return the logged-in user
    return user;
  }

  public static User getLoggedInUser(HttpServletRequest request)
    throws Exception
  {
    // Return the user stored in the session
    return (User) request.getSession( ).getAttribute(USER_KEY);
  }
}

As is, Remoting services cannot use the Authenticator class, because they do not have access to the current request. We have looked at a solution for getting access to the current request from a Remoting service by associating the request with the running thread using a servlet filter.

Another solution to this problem is to give the Remoting service a reference to the logged-in user when the user logs in. So, instead of the Remoting service needing access to the request or user session, we now need a way to get access to the Remoting service from our application code in the Authenticator.

The following example is a JavaBean Remoting service called LoginService that gives Flash clients access to user login information through the service methods isUserLoggedIn( ) and getLoggedInUser( ). It uses an instance variable, currentUser, to support these service methods.

public class LoginService
  implements java.io.Serializable
{
  private User currentUser = null;

  public User getCurrentUser ( ) {
    return currentUser;
  }

  public void setCurrentUser (User user) {
    this.currentUser = user;
  }

  public boolean isUserLoggedIn ( ) {
    log.info("Checking if user is logged in");
    return currentUser != null;
  }

  public ASObject getLoggedInUser ( ) {
    log.info("Getting logged in user");
    return (ASObject) new ASTranslator( ).toActionScript(currentUser);
  }
}

Since it is a JavaBean service, the Remoting gateway stores LoginService in the user session under a session attribute key equal to LoginService's full class name. The following implementation of the custom ServiceProvider class has a single, static method, getJavaBeanService( ), that looks for a JavaBean service in the user session (given the class of the JavaBean service) and returns a reference to the JavaBean service. If it does not find an existing JavaBean service, it creates the service and stores it in the user session so that it will be there when a Flash client accesses it through Remoting.

public class ServiceProvider {
  public static Object getJavaBeanService(HttpServletRequest request,
                                          Class serviceClass)
    throws InstantiationException, IllegalAccessException
  {
    // Look for an existing instance of the service.
    String sessionKey = serviceClass.getName( );
    Object service    = request.getSession(true).getAttribute(sessionKey);

    // If it's not there, create it.
    if (service == null) {
      service = serviceClass.newInstance( );
      if (service instanceof Serializable) {
        request.getSession(true).setAttribute(sessionKey, service);
      }
    }

    return service;
  }
}

The following example shows a modified Authenticator.login( ) method that uses ServiceProvider.getJavaBeanService( ) to get a reference to the LoginService Remoting service when the user logs in. The Authenticator.login( ) method then gives the LoginService a reference to the logged-in user using LoginService.setCurrentUser( ) so that the LoginService can support its service methods for Flash clients.

public class Authenticator {
  private static final String USER_KEY = "user";

  public static User login(String username,
                           HttpServletRequest request)
    throws Exception
  {
    // Look for a user in the Directory with this username.
    User user = Directory.getUser(username);

    // If we don't find it, return null.
    if (user == null) return null;

    // Store logged-in user in the session.
    request.getSession( ).setAttribute(USER_KEY, user);

    // Get the login service.
    LoginService service = (LoginService)
          ServiceProvider.getJavaBeanService(request, LoginService.class);

    // Set logged-in user in the login service.
    service.setCurrentUser(user);

    // Return the logged-in user.
    return user;
  }
}

This technique of accessing JavaBean services stored in the user session is an easy way to share information between Remoting services and other application classes.

7.10.2 Using Servlet Services to Store Session State

Servlet services are the only service type that has direct access to the request and user session. Servlet services do not need additional support to access application functionality that requires a request or user session to store shared user information.

The following example shows a servlet service called LoginServletService that uses the Authenticator class from the preceding example to provide a login service to a Flash client. The LoginServletService.service( ) method retrieves the username from the first item in the FLASH.PARAMS attribute of the request object, logs the user in using Authenticator.login( ), and returns the logged-in User object to the Flash client by setting it in the FLASH.RESULT attribute of the request object.

public class LoginServiceServlet
  extends HttpServlet
{
  public void service(HttpServletRequest request,
                      HttpServletResponse response)
    throws ServletException
  {
    // Get the method parameters.
    List params = (List) request.getAttribute("FLASH.PARAMS");

    // If no parameters just return;
    if (params.isEmpty( )) return;

    // The username should be the first parameter.
    String username = (String) params.get(0);

    try {
      // Log in using the Authenticator class.
      User user = Authenticator.login(username, request);

      // Set the logged-in user as the result of this service call.
      request.setAttribute("FLASH.RESULT",
                           new ASTranslator( ).toActionScript(user));
    } catch (Exception e) {
      throw new ServletException("Error logging in.", e);
    }
  }
}

A Flash client would log into the application using the LoginServiceServlet (presented in the preceding code listing) as follows:

// Get service references.
var servletServices = gatewayConnection.getService("remotingbook", this);

// Log in as "flashuser".
servletServices.LoginServiceServlet("flashuser");

// Handle the result of calling LoginServiceServlet.
function LoginServiceServlet_Result (user) {  
  trace("LoginServiceServlet_Result:");
  trace("  Logged in: " + user);
}

// Define and register the User class
User = function (name) {
  if (this.username == null) this.username = username;
  this.toString = function ( ) { return "User[" + this.username + "]"; };
};
Object.registerClass("com.oreilly.frdg.java.user.User", User);


    Part III: Advanced Flash Remoting
     
    ASPTreeView.com
     
    Evaluation has ЩМЦ»єПexpired.
    Info...