My JSF Books/Videos My JSF Tutorials OmniFaces/JSF PPTs
JSF 2.3 Tutorial
JSF Caching Tutorial
JSF Navigation Tutorial
JSF Scopes Tutorial
JSF Page Author Beginner's Guide
OmniFaces 2.3 Tutorial Examples
OmniFaces 2.2 Tutorial Examples
JSF Events Tutorial
OmniFaces Callbacks Usages
JSF State Tutorial
JSF and Design Patterns
JSF 2.3 New Features (2.3-m04)
Introduction to OmniFaces
25+ Reasons to use OmniFaces in JSF
OmniFaces Validators
OmniFaces Converters
JSF Design Patterns
Mastering OmniFaces
Reusable and less-verbose JSF code

My JSF Resources ...

Java EE Guardian
Member of JCG Program
Member MVB DZone
Blog curated on ZEEF
OmniFaces is an utility library for JSF, including PrimeFaces, RichFaces, ICEfaces ...

[OmniFaces Utilities] - Find the right JSF OmniFaces 2 utilities methods/functions

Search on blog

Petition by Java EE Guardians

Twitter

luni, 14 decembrie 2015

JSF Scopes Tutorial - JSF Custom Scope

The JSF custom scope allows us to implement our own scopes

When none of the JSF/CDI/OmniFaces/DeltaSpike scopes meet your application needs, you may want to pay attention to the JSF 2 custom scope. Most likely, you will never want to write a custom scope, but if it is absolutely necessary, then, in this post, you can see how to accomplish this task.

The custom scope annotation is @CustomScoped and is defined in the javax.faces.bean package. It is not available in CDI!

In order to implement a custom scope, let's suppose that you want to control the life cycle of several beans that live in the application scope. Normally they live as long as the application lives, but you want to be able to add/remove them from the application scope at certain moments of the application flow. Of course, there are many approaches to do that, but remember that we look for a reason to implement a custom scope; therefore, we will try to write a custom scope nested in the application scope that will allow us to add/remove a batch of beans. Creating and destroying the scope itself will be reflected in creating and destroying the beans, which means that you don't need to refer to each bean.

Actually, since this is just a demo, we will use only two beans: one will stay in the classical application scope (it can be useful for comparison of the application and custom scope lifespan), while the other one will be added/destroyed through the custom scope. The application purpose is not relevant; you should focus on the technique used to write a custom scope and paper over the assumptions and gaps.
Think more on the lines that you can use this knowledge when you really need to implement a custom scope.

The custom scope is represented by a class that extends the ConcurrentHashMap<String, Object> class. We need to allow concurrent access to an usual map because the exposed data may be accessed concurrently from multiple browsers. The code of the CustomScope class is as follows:

public class CustomScope extends ConcurrentHashMap<String, Object> {

 public static final String SCOPE = "CUSTOM_SCOPE";

 public CustomScope(){
  super();
 }

 public void scopeCreated(final FacesContext ctx) {
  ScopeContext context = new ScopeContext(SCOPE, this);
  ctx.getApplication().publishEvent(ctx,PostConstructCustomScopeEvent.class, context);
 }

 public void scopeDestroyed(final FacesContext ctx) {
  ScopeContext context = new ScopeContext(SCOPE,this);
  ctx.getApplication().publishEvent(ctx,PreDestroyCustomScopeEvent.class, context);
 }
}

When our scope is created/destroyed, other components will be informed through events. In the scopeCreated() method, you register PostConstructCustomScopeEvent, while in the scopeDestroyed() method, you register PreDestroyCustomScopeEvent.

Now we have a custom scope, it is time to see how to declare a bean in this scope. Well, this is not hard and can be done with the @CustomScoped annotations and an EL expression, as follows:

import javax.faces.bean.CustomScoped;
import javax.faces.bean.ManagedBean;

@ManagedBean
@CustomScoped("#{CUSTOM_SCOPE}")
public class SponsoredLinksBean {
 ...
}

Resolving a custom scope EL expression
At this point, JSF will iterate over the chain of existing resolvers in order to resolve the custom scope EL expression. Obviously, this attempt will end with an error, since no existing resolver will be able to satisfy this EL expression. So, you need to write a custom resolver as shown in the following code:

public class CustomScopeResolver extends ELResolver {

 private static final Logger logger =
                Logger.getLogger(CustomScopeResolver.class.getName());

 @Override
 public Object getValue(ELContext context, Object base, Object property) {

  logger.log(Level.INFO, "Get Value property : {0}", property);

  if (property == null) {
      throw new PropertyNotFoundException("NULL_PARAMETERS");
  }

  FacesContext facesContext = (FacesContext) context.getContext(FacesContext.class);

  if (base == null) {
      Map<String, Object> applicationMap =
                          facesContext.getExternalContext().getApplicationMap();
      CustomScope scope = (CustomScope) applicationMap.get(CustomScope.SCOPE);

      if (CustomScope.SCOPE.equals(property)) {
          logger.log(Level.INFO, "Found request | base={0} property={1}", new Object[]{base, property});
                context.setPropertyResolved(true);
                return scope;
      } else {
          logger.log(Level.INFO, "Search request | base={0} property={1}", new Object[]{base, property});
          if (scope != null) {
              Object value = scope.get(property.toString());
              if (value != null) {
                  logger.log(Level.INFO, "Found request | base={0} property={1}", new Object[]{base, property});
                  context.setPropertyResolved(true);
              } else {
                  logger.log(Level.INFO, "Not found request | base={0} property={1}", new Object[]{base, property});
                  context.setPropertyResolved(false);
              }
              return value;
          } else {
              return null;
          }
      }
  }

  if (base instanceof CustomScope) {
      CustomScope baseCustomScope = (CustomScope) base;
      Object value = baseCustomScope.get(property.toString());
      logger.log(Level.INFO, "Search request | base={0} property={1}", new Object[]{base, property});

      if (value != null) {
          logger.log(Level.INFO, "Found request | base={0} property={1}", new Object[]{base, property});
          context.setPropertyResolved(true);
      } else {
          logger.log(Level.INFO, "Not found request | base={0} property={1}", new Object[]{base, property});
          context.setPropertyResolved(false);
      }
      return value;
  }

  return null;
 }

 @Override
 public Class<?> getType(ELContext context, Object base, Object property) {
  return Object.class;
 }

 @Override
 public void setValue(ELContext context, Object base, Object property, Object value) {

  if (base != null) {
      return;
  }

  context.setPropertyResolved(false);

  if (property == null) {
      throw new PropertyNotFoundException("NULL_PARAMETERS");
  }

  if (CustomScope.SCOPE.equals(property)) {
      throw new PropertyNotWritableException((String) property);
  }
 }

 @Override
 public boolean isReadOnly(ELContext context, Object base, Object property) {
  return true;
 }

 @Override
 public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
  return null;
 }

 @Override
 public Class<?> getCommonPropertyType(ELContext context, Object base) {
  if (base != null) {
      return null;
  }
  return String.class;
 }
}

Do not forget to put the following resolver into the chain by adding it in the faces-config.xml file:

<el-resolver>book.beans.CustomScopeResolver</el-resolver>

Done! So far, you have created a custom scope, you put a bean into this scope, and learned that the brand new resolver provides access to this bean.

The custom scope must be stored somewhere, so nested in the application scope can be a choice (of course, other scopes can also be a choice, depending on your needs). When the scope is created, it has to be placed in the application map, and when it is destroyed, it has to be removed from the application map. The question is when to create it and when to destroy it? And the answer is, it depends. Most likely, this is a decision strongly tied to the application flow.

Controlling the custom scope lifespan with action listeners
Using action listeners can be a good practice even if it involves control from view declaration. Let's suppose that the button labeled START will add the custom scope in the application map, as shown in the following code:

<h:commandButton value="START">
 <f:actionListener type="beans.CreateCustomScope" />
</h:commandButton>

The following CreateCustomScope class is a straightforward action listener as it implements the ActionListener interface:

public class CreateCustomScope implements ActionListener {

 private static final Logger logger =
                Logger.getLogger(CreateCustomScope.class.getName());

 @Override
 public void processAction(ActionEvent event) throws AbortProcessingException {

  logger.log(Level.INFO, "Creating custom scope ...");

  FacesContext context = FacesContext.getCurrentInstance();
  Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
  CustomScope customScope = (CustomScope) applicationMap.get(CustomScope.SCOPE);

  if (customScope == null) {
      customScope = new CustomScope();
      applicationMap.put(CustomScope.SCOPE, customScope);
      customScope.scopeCreated(context);
  } else {
      logger.log(Level.INFO, "Custom scope exists ...");
  }
 }
}

Following the same approach, the button labeled STOP will remove the custom scope from the application map as follows:

<h:commandButton value="STOP">
 <f:actionListener type="beans.DestroyCustomScope" />
</h:commandButton>

The following DestroyCustomScope class is the action listener as it implements the ActionListener interface:

public class DestroyCustomScope implements ActionListener {

 private static final Logger logger =  
                Logger.getLogger(DestroyCustomScope.class.getName());

 @Override
 public void processAction(ActionEvent event) throws AbortProcessingException {

  logger.log(Level.INFO, "Destroying custom scope ...");

  FacesContext context = FacesContext.getCurrentInstance();
  Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
  CustomScope customScope = (CustomScope) applicationMap.get(CustomScope.SCOPE);

  if (customScope != null) {
      customScope.scopeDestroyed(context);
      applicationMap.remove(CustomScope.SCOPE);
  } else {
      logger.log(Level.INFO, "Custom scope does not exists ...");
  }
 }
}

The complete application is available here.

Controlling the custom scope lifespan with the navigation handler
Another approach is to control the custom scope lifespan based on the page's navigation. This solution is more flexible and is hidden from the user. You can write a custom navigation handler by extending NavigationHandler. The next implementation puts the custom scope in the application map when the navigation reaches the page named sponsored.xhtml, and will remove it from the application map in any other navigation case. The code of the CustomScopeNavigationHandler class is as follows:

public class CustomScopeNavigationHandler extends NavigationHandler {

 private static final Logger logger =
         Logger.getLogger(CustomScopeNavigationHandler.class.getName());
 private final NavigationHandler navigationHandler;

 public CustomScopeNavigationHandler(NavigationHandler navigationHandler) {
  this.navigationHandler = navigationHandler;
 }

 @Override
 public void handleNavigation(FacesContext context, String fromAction, String outcome){

  if (outcome != null) {
      if (outcome.equals("sponsored")) {
          logger.log(Level.INFO, "Creating custom scope ...");

          Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
          CustomScope customScope = (CustomScope) applicationMap.get(CustomScope.SCOPE);

          if (customScope == null) {
              customScope = new CustomScope();
              applicationMap.put(CustomScope.SCOPE, customScope);

              customScope.scopeCreated(context);
          } else {
              logger.log(Level.INFO, "Custom scope exists ...");
          }
      } else {
          logger.log(Level.INFO, "Destroying custom scope ...");

          Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
          CustomScope customScope = (CustomScope) applicationMap.get(CustomScope.SCOPE);

          if (customScope != null) {
              customScope.scopeDestroyed(context);
              applicationMap.remove(CustomScope.SCOPE);
          } else {
              logger.log(Level.INFO, "Custom scope does not exist ...");
          }
      }
  }

  navigationHandler.handleNavigation(context, fromAction, outcome);
 }
}

Do not forget to register the following navigation handler in the faces-config.xml file:

<navigation-handler>
 beans.CustomScopeNavigationHandler
</navigation-handler>

The complete application is available here.

For testing on localhost, use two browsers!

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

Follow by Email

Visitors Starting 4 September 2015

Locations of Site Visitors