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

joi, 17 decembrie 2015

Using OmniFaces ResourceInclude and OutputFormat to display in a JSF page the decorated output from a Servlet/JSP

OmniFaces provides a component capable to  catch the output from a JSP or Servlet resource and render it as output to the JSF writer. For example, let's suppose that we have the following simple Servlet:

@WebServlet("/TextServlet")
public class TextServlet extends HttpServlet {

 protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  response.setContentType("text/html;charset=UTF-8");
  PrintWriter out = response.getWriter();
  out.println("<h3>I'm a servlet that returns text ...</h3>");
 }

 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  processRequest(request, response);
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  processRequest(request, response);
 }
}

The OmniFaces component is named ResourceInclude and can be used via <o:resourceInclude/> tag as in the below example (output to the JSF writer the text output from TextServlet):

<o:resourceInclude path="/TextServlet" />


More examples and details about ResourceInclude are available in Mastering OmniFaces book.

Now, let's suppose that we want to modify (decorate) the Servlet output before we display it in page. A nice trick will consist in using another OmniFaces component named, OutputFormat. This component extends the standard <h:outputFormat/> with support for capturing the output and exposing it into the request scope by the variable name as specified by the var attribute. For example, we can capture the TextServlet output and expose it via <o:outputFormat/> as below:

<o:outputFormat var="_text">
 <o:resourceInclude path="/TextServlet" />
</o:outputFormat>

This time the TextServlet output is not displayed on screen. This output was stored in the request scope and it is available via _text anywhere in page. This means that we can pass this output to an action method which decorates it accordingly:

@Named
@RequestScoped
public class TextBean {

 public String decorate(String text) {
  return "***" + text + "***";
 }
}

<h:outputText value="#{textBean.decorate(_text)}" escape="false"/>


More examples and details about OutputFormat are available in Mastering OmniFaces book.

The complete application is available here.

Special thanks to Arjan Tijms for providing this tip.

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!

duminică, 13 decembrie 2015

JSF Scopes Tutorial - DeltaSpike Grouped Conversations

The DeltaSpike (grouped) conversations boost CDI conversation scope

If you are familiar with CDI conversation scope, then you will quickly adopt the DeltaSpike (grouped) conversations. DeltaSpike provides a very flexible and powerful conversational mechanism for CDI beans. In this post you will see several didactical examples meant to expose this mechanism. Basically, we will cover the follosing topic:

1. "Isolated" DeltaSpike Conversations
    a. Conversation with a single bean ("isolated" conversation)
    b. Parallel "isolated" conversations
    c. Stopping "isolated" conversations control from interface
    d. "Isolated" conversations injection
         i. Injecting conversation ConversationBeanA in ConversationBeanB and vice versa
         ii.Injecting a conversation bean in non-conversation bean (e.g. a view scoped bean)

2. Grouped DeltaSpike Conversations (logical group of beans)
    a. Grouped Conversation Scoped Beans
    b. Closing a conversation group from outside group
    c. Closing all conversation groups
   d. Grouped conversations injection
        i. Injecting conversation group AB in CD and vice versa
        ii.Injecting conversation group AB in a non-conversation bean (e.g. a view scoped bean)

3. Sub-Conversation-Groups
    a. Listing  beans of a sub-group
        i. Explicitly Listing Beans of a sub-group via #subGroup/#of
        ii.Using implicit sub-group
    b. Closing sub-groups

Prerequisites
Before seeing the examples it is important to keep in mind the following coordinates:

- DeltaSpike conversations are based on the same principle as CODI-Conversations.

- DeltaSpike conversations are based on the window-scope (as a consequence of this, do not forget to add the ds:windowId (xmlns:ds="http://deltaspike.apache.org/jsf") component).

- DeltaSpike starts (grouped) conversations automatically as soon as you access conversation scoped beans.

- You can inject the conversation via @Inject and use it to terminate the conversation immediately . The invocation of GroupedConversation#close() leads to an immediate termination of the conversation.

- You can inject the GroupedConversationManager which can be used to terminate a given conversation (group) by invoking, GroupedConversationManager#closeConversationGroup().

- You can inject the GroupedConversationManager which can be used to terminate all conversations (groups) by invoking, GroupedConversationManager#closeConversations().

- All conversations within a window are closed automatically, once WindowContext#closeWindow() gets called for the window.

- DeltaSpike conversations get closed/restarted immediately instead of keeping them until the end of the request like standard conversations do, because the behavior of standard conversations breaks a lot of use-cases. However, if you really need to keep them until the end of the request, you can close them in a @PostRenderView callback.

- DeltaSpike conversations are available for CDI managed beans, therefore after adding @Named (javax.inject.Named) to beans.

1. "Isolated" DeltaSpike Conversations
In CDI and DeltaSpike, the conversation scope allows developers to demarcate the lifespan of the session scope. In CDI, there is just one big conversation which contains all conversation scoped beans. This is not true for DeltaSpike, because DeltaSpike supports parallel "isolated" conversations. By default, every conversation scoped bean exists in an "isolated" conversation. That means there are several parallel conversations within the same window. For example, you can have in the same window multiple hangouts, and each hangout to belong to a conversation. By default, closing a conversation will not close others conversations (hangouts). Moreover, conversations can "interact" via CDI injection (@Inject). Closing a conversation will close the corresponding bean, but will not close the bean that have been injected from other conversation. With other words, separated conversations runs "isolated", and their lifespan vary depending on application flow (e.g. user interactions). They can "interact" for a common goal, can be started/stopped separately or together (ones or multiple times), can be injected in other conversations or even in beans that don't belong to any conversation, etc.

a. Conversation with a single bean ("isolated" conversation)
A CDI managed bean represents the unit of work for a conversation. A single CDI managed bean defines (characterizes) a single conversation if it is annotated with @Named and @GroupedConversationScoped (exposes the org.apache.deltaspike.core.api.scope.GroupedConversationScoped class). In such cases, DeltaSpike uses the class of the bean as conversation group; we can say that we have a conversation group with a single bean. Conversation groups are discussed a little bit later.

In the below image, we have a bean that belongs to a conversation (the blue disc) with local closing capability (the small red disc, up-right side) and some other beans in application that don't belong to any conversation (the green discs with X):


For example, the below bean represents a single conversation:

import java.io.Serializable;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.deltaspike.core.api.scope.GroupedConversation;
import org.apache.deltaspike.core.api.scope.GroupedConversationScoped;

@Named
@GroupedConversationScoped
public class ConversationBeanA implements Serializable {

 @Inject
 private GroupedConversation conversation;

 private int A;

 public ConversationBeanA() {
  A = 0;
 }

 public void increaseA() {
  A++;
  if (A == 4) {
      finish();
  }
 }

 private void finish() {
  this.conversation.close();
 }

 public int getA() {
  return A;
 }

 public void setA(int A) {
  this.A = A;
 }
}

The integer A will be increased until it reach the value, 4. When this will happen the conversation is stopped (closed) and restarted - in the same request, the finish() and ConversationBeanA constructor are called. Pressing again the Increase A button will use the new conversation, for which integer A is 0 again:

<h:commandButton value="Increase A" action="#{conversationBeanA.increaseA()}"/>


The complete application is available here (DSGroupedConversationScoped_0).

b. Parallel "isolated" conversations
In the above example, we have a single conversation with a single bean. We can have in the same time multiple "isolated" conversations in the same window.

In the below image, we have two beans that belongs to two different (parallel) conversations (the blue discs) with local closing capability (the small red discs, up-right side) and some other beans in application that don't belong to any conversation (the green discs with X):


For example, let's add one more bean with its own conversation. This time the bean is named, ConversationBeanB, and is pretty similar with the ConversationBeanA:

import java.io.Serializable;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.deltaspike.core.api.scope.GroupedConversation;
import org.apache.deltaspike.core.api.scope.GroupedConversationScoped;

@Named
@GroupedConversationScoped
public class ConversationBeanB implements Serializable {

 @Inject
 private GroupedConversation conversation;

 private int B;

 public ConversationBeanB() {
  B = 0;
 }

 public void increaseB() {
  B++;
  if (B == 7) {
      finish();
  }
 }

 private void finish() {
  this.conversation.close();
 }

 public int getB() {
  return B;
 }

 public void setB(int B) {
  this.B = B;
 }     
}

The integer B will be increased until it reach the value, 7. When this will happen the conversation is stopped and restarted. Pressing again the Increase B button will use the new conversation, for which integer B is 0 again. In parallel with this conversation, we have the ConversationBeanA  which controls the integer A. Each conversation work and act separately:

<h:commandButton value="Increase A" action="#{conversationBeanA.increaseA()}"/>
<h:commandButton value="Increase B" action="#{conversationBeanB.increaseB()}"/>


The complete application is available here (DSGroupedConversationScoped_I).

c. Stopping "isolated" conversations control from interface
In the above example, the conversations are stopped by internal conditions nested in managed beans. But, you can allow the user to stop the conversations, by providing the proper controls to him. Just provide him a control (e.g. command button) and invoke the finish() method (ensure that you provide this method public):

public void finish() {
 this.conversation.close();
}

<h:commandButton value="Increase A" action="#{conversationBeanA.increaseA()}"/>
<h:commandButton value="Stop A" action="#{conversationBeanA.finish()}"/>

<h:commandButton value="Increase B" action="#{conversationBeanB.increaseB()}"/>
<h:commandButton value="Stop B" action="#{conversationBeanB.finish()}"/>

Notice that in the below screenshot we have increased integers A and B beyond the limits established in the previous example (4 and 7). This time the conversations can be stopped from user interface via two buttons:


Of course, you can find other ways to close conversations also. The main idea is that DeltaSpike conversations can be stopped by users or conditionally, depending on your implementation. By the other hand, DeltaSpike will start them automatically for you, and will stop them also automatically once the WindowContext#closeWindow() gets called for the window.

The complete application is available here (DSGroupedConversationScoped_II).

d. "Isolated" conversations injection
Now, you are familiar with our two isolated conversations (ConversationBeanA and ConversationBeanB).

i. Injecting conversation ConversationBeanA in ConversationBeanB and vice versa
 Further, let's use the CDI @Inject to inject an instance of conversation ConversationBeanA in ConversationBeanB, and vice versa.


Based on this injections we will calculate the sum of  A and B integers (A + B). So, the ConversationBeanA become:

@Named
@GroupedConversationScoped
public class ConversationBeanA implements Serializable {

 @Inject
 private GroupedConversation conversation;
   
 @Inject
 private ConversationBeanB ConversationBeanb;

 private int A;

 public ConversationBeanA() {       
  A = 0;
 }

 public void increaseA() {
  A++;       
 }
   
 public void increaseAWithB(){
  A+=ConversationBeanb.getB();
 }

 public void finish() {       
  this.conversation.close();
 }

 public int getA() {
  return A;
 }

 public void setA(int A) {
  this.A = A;
 }
}

And the ConversationBeanB become:

@Named
@GroupedConversationScoped
public class ConversationBeanB implements Serializable {

 @Inject
 private GroupedConversation conversation;
   
 @Inject
 private ConversationBeanA ConversationBeana;

 private int B;

 public ConversationBeanB() {
  B = 0;
 }

 public void increaseB() {
  B++;      
 }
   
 public void increaseBWithA(){
  B+=ConversationBeana.getA();
 }

 public void finish() {
  this.conversation.close();
 }

 public int getB() {
  return B;
 }

 public void setB(int B) {
  this.B = B;
 }   
}

And the Facelets simple expose the arithmetic operation (A+B):

<h:commandButton value="Increase A" action="#{conversationBeanA.increaseA()}"/>                                             
<h:commandButton value="Stop A" action="#{conversationBeanA.finish()}"/>
<h:commandButton value="Increase A with B" action="#{conversationBeanA.increaseAWithB()}"/>

<h:commandButton value="Increase B" action="#{conversationBeanB.increaseB()}"/>                                             
<h:commandButton value="Stop B" action="#{conversationBeanB.finish()}"/>
<h:commandButton value="Increase B with A" action="#{conversationBeanB.increaseBWithA()}"/>


Each time you press the Increase A with B (or Increase B with A) button the sum will be the current value of A + the current value of B obtained via getA() and getB(). If you stop ConversationBeanA, then A will be 0. If you stop ConversationBeanB, then B will be 0.

The complete application is available here (DSGroupedConversationScoped_XII).

ii. Injecting a conversation bean in non-conversation bean (e.g. a view scoped bean)
 Further, let's use the CDI @Inject to inject an instance of conversation ConversationBeanA in a view scoped bean BeanB. In BeanB, will try to calculate the sum of A and B integers. 


The conversation bean is:

@Named
@GroupedConversationScoped
public class ConversationBeanA implements Serializable {   

 @Inject
 private GroupedConversation conversation;

 private int A;

 public ConversationBeanA() {
  A = 0;
 }

 public void increaseA() {
  A++;
 }
      
 public void finish() {
  this.conversation.close();
 }

 public int getA() {
  return A;
 }

 public void setA(int A) {
  this.A = A;
 }
}

And, the BeanB injects ConversationBeanA:

@Named
@ViewScoped
public class BeanB implements Serializable {

 @Inject
 private ConversationBeanA ConversationBeana;
       
 private int B;

 public BeanB() {
  B = 0;
 }
   
 public void increaseB() {
  B++;      
 }

 public void increaseBWithA() {
  B+=ConversationBeana.getA();      
 }
   
 public int getB() {
  return B;
 }

 public void setB(int B) {
  this.B = B;
 }   
}

And the Facelets simple expose the arithmetic operation (A+B):

<h:commandButton value="Increase A" action="#{conversationBeanA.increaseA()}"/>                                             
<h:commandButton value="Stop A" action="#{conversationBeanA.finish()}"/>
<h:commandButton value="Increase B" action="#{beanB.increaseB()}"/>                                      
<h:commandButton value="Increase B With A" action="#{beanB.increaseBWithA()}"/>  
           

Each time you press the Increase B with A button the sum will be the current value of A + the current value of B. If you stop and restart conversation ConversationBeanA, then A integer will be 0. You can increase A integer anytime you want.

Note Of course, you can inject non-conversation beans in conversation beans also.

The complete application is available here (DSGroupedConversationScoped_X).

2. Grouped DeltaSpike Conversations (logical group of beans)
By default, every conversation scoped bean exists in an "isolated" conversation. That means there are several parallel conversations within the same window. But, DeltaSpike allows us to create logical group of beans as grouped conversations. For example, you can have in the same window multiple wizards, and each wizard to belong to a conversation group. By default, closing a group will not close others groups (wizards). Moreover, groups can "interact" via CDI injection (@Inject). Closing a conversation group will close all the beans that belongs to that group, but will not close the conversation groups that contains beans that have been injected in the closed conversation group. With other words, grouped conversations runs "isolated", and their lifespan vary depending on application flow (e.g. user interactions). They can "interact" for a common goal, can be started/stopped separately or together (ones or multiple times), can be injected in other conversation groups or even beans that don't belong to any conversation group, etc.

a. Grouped Conversation Scoped Beans
In order to create a conversation group, we need to define an interface and annotated the corresponding beans with the @ConversationGroup annotation (this is just a CDI qualifier). For example, let's add our "isolated" conversations (ConversationBeanA and ConversationBeanB) in the same conversation group (let's named it, AB).

First, we define the conversation group interface, as below:

public interface ConversationABInterface {
 // NOPE
}

Further, we annotate accordingly the two beans. First bean, ConversationBeanA:

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationABInterface.class)
public class ConversationBeanA implements Serializable {

 @Inject
 private GroupedConversation conversation;

 private int A;

 public ConversationBeanA() {
  A = 0;
 }

 public void increaseA() {
  A++;       
 }

 public void finish() {
  this.conversation.close();
 }

 public int getA() {
  return A;
 }

 public void setA(int A) {
  this.A = A;
 }
}

And second bean, ConversationBeanB:

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationABInterface.class)
public class ConversationBeanB implements Serializable {

 @Inject
 private GroupedConversation conversation;

 private int B;

 public ConversationBeanB() {
  B = 0;
 }

 public void increaseB() {
  B++;      
 }

 public void finish() {
  this.conversation.close();
 }

 public int getB() {
   return B;
  }

  public void setB(int B) {
   this.B = B;
  }   
}

Note If you do not use @ConversationGroup explicitly, DeltaSpike uses the class of the bean as conversation group.

The Facelets is very simple:

<h:commandButton value="Increase A" action="#{conversationBeanA.increaseA()}"/>
<h:commandButton value=" Stop conversation group from A" action="#{conversationBeanA.finish()}"/>

<h:commandButton value="Increase B" action="#{conversationBeanB.increaseB()}"/> 
<h:commandButton value=" Stop conversation group from B" action="#{conversationBeanB.finish()}"/>

This time, stopping the conversation group will automatically destroy all the beans in the group. For example, if you close the conversation group via, ConversationBeanA#finish() then you will destroy ConversationBeanB also and vice versa.


The complete application is available here (DSGroupedConversationScoped_III).

b. Closing a conversation group from outside group
So, each bean in a conversation group can call, GroupedConversation#close(). When the conversation group is closed from any of the containing beans, all beans in that group conversation will be destroyed. Alternatively, DeltaSpike allows us to close a conversation group from outside the group. This can be done via, GroupedConversationManager#closeConversationGroup(), as below (see, OutsideBeanAB request scoped bean which will close the group conversation created above, AB):

@Named
@RequestScoped
public class OutsideBeanAB implements Serializable {

 @Inject
 private GroupedConversationManager conversationManager;

 public void finish() {
  this.conversationManager.closeConversationGroup(ConversationABInterface.class);
 }
}

In Facelets, just "attach" a command button to the finish() method:

<h:commandButton value="Stop conversation group from outside the conversation group"
                 action="#{outsideBeanAB.finish()}"
                 style="background-color: greenyellow;"/>


The complete application is available here (DSGroupedConversationScoped_V).

c. Closing all conversation groups
When you have multiple conversation groups, you can separately close any of the conversation groups via, GroupedConversationManager#closeConversationGroup(). Alternatively, DeltaSpike allows us to close all conversation groups via, GroupedConversationManager#closeConversations().
In order to exemplify this case, we define one more conversation group, named CD. This will contain two beans, ConversationBeanC, and ConversationBeanD. Basically, these two beans are similar to ConversationBeanA and ConversationBeanB, only that the incremented integers are named C and D.

Now, we will have several different points from where we can close the group conversations, as follows:

- Each bean calls GroupedConversation#close() - this will close only the conversation group that contains that bean.
- Conversation group AB (containing ConversationBeanA and ConversationBeanB) can be closed from outside this group via request scoped bean, OutsideBeanAB. This will not close conversation group, CD.
- Conversation group CD (containing ConversationBeanC and ConversationBeanD) can be closed from outside this group via request scoped bean, OutsideBeanCD. This will not close conversation group, AB.
- Close all conversation groups, via request scoped bean CloseAllConversationsBean. This will close conversation groups AB and CD, and any other conversation group.

 The CloseAllConversationsBean source code is:

@Named
@RequestScoped
public class CloseAllConversationsBean implements Serializable {

 @Inject
 private GroupedConversationManager conversationManager;

 public void finish() {
  this.conversationManager.closeConversations();
 }
}

In Facelets, simply define the corresponding buttons:

<h:commandButton value="Stop conversation group from A" action="#{conversationBeanA.finish()}"/>
<h:commandButton value="Stop conversation group from B" action="#{conversationBeanB.finish()}"/>                   

<h:commandButton value="Stop conversation group AB from outside AB" action="#{outsideBeanAB.finish()}" 
                 style="background-color: greenyellow;"/>

<h:commandButton value="Stop conversation group from C" action="#{conversationBeanC.finish()}"/>
<h:commandButton value="Stop conversation group from D" action="#{conversationBeanD.finish()}"/>                       

<h:commandButton value="Stop conversation group CD from outside CD" action="#{outsideBeanCD.finish()}" 
                 style="background-color: greenyellow;"/>

<h:commandButton value="CLOSE ALL CONVERSATIONS" 
                 action="#{closeAllConversationsBean.finish()}" 
                 style="background-color: black; color: orange"/>


The complete application is available here (DSGroupedConversationScoped_VI).

d. Grouped conversations injection
Now, you are familiar with our two conversation groups (AB and CD). Conversation group AB contains ConversationBeanA and ConversationBeanB beans, and conversation group CD contains  ConversationBeanC and ConversationBeanD beans.

i. Injecting conversation group AB in CD and vice versa
 Further, let's use the CDI @Inject to inject beans of conversation group AB into beans from conversation group CD and vice versa. I choose to inject  ConversationBeanA in ConversationBeanC and vice versa, and ConversationBeanB in ConversationBeanD and vice versa. You can use any other combination, and you don't have to inject all beans of a conversation group!


In order to inject a bean that belongs to a conversation group into a bean that belongs to another conversation group, you need to use @Inject and @ConversationGroup qualifier. In order to test our injections, let's compute A+C integers and B+D integers. The corresponding methods have been highlighted below:

·         ConversationBeanA injects ConversationBeanC

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationABInterface.class)
public class ConversationBeanA implements Serializable {

 @Inject
 private GroupedConversation conversation;
   
 @Inject @ConversationGroup(ConversationCDInterface.class)
 private ConversationBeanC ConversationBeanc;

 private int A;

 public ConversationBeanA() {
  A = 0;
 }

 public void increaseA() {
  A++;       
 }
   
 public void increaseAWithC() {
  A+=ConversationBeanc.getC();
 }

 public void finish() {
  this.conversation.close();
 }

 public int getA() {
  return A;
 }

 public void setA(int A) {
  this.A = A;
 }
}


·         ConversationBeanC injects ConversationBeanA

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationCDInterface.class)
public class ConversationBeanC implements Serializable {

 @Inject
 private GroupedConversation conversation;
   
 @Inject @ConversationGroup(ConversationABInterface.class)
 private ConversationBeanA ConversationBeana;

 private int C;

 public ConversationBeanC() {
  C = 0;
 }

 public void increaseC() {
  C++;
 }

 public void increaseCWithA() {
  C+=ConversationBeana.getA();
 }
   
 public void finish() {
  this.conversation.close();
 }

 public int getC() {
  return C;
 }

 public void setC(int C) {
  this.C = C;
 }
}

·         ConversationBeanB injects ConversationBeanD

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationABInterface.class)
public class ConversationBeanB implements Serializable {

 @Inject
 private GroupedConversation conversation;
  
 @Inject @ConversationGroup(ConversationCDInterface.class)
 private ConversationBeanD ConversationBeand;

 private int B;

 public ConversationBeanB() {
  B = 0;
 }

 public void increaseB() {
  B++;      
 }
   
  public void increaseBWithD() {
   B+=ConversationBeand.getD();
  }

  public void finish() {
   this.conversation.close();
  }

  public int getB() {
   return B;
  }

  public void setB(int B) {
   this.B = B;
  }   
}

·         ConversationBeanD injects ConversationBeanB

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationCDInterface.class)
public class ConversationBeanD implements Serializable {

 @Inject
 private GroupedConversation conversation;
   
 @Inject @ConversationGroup(ConversationABInterface.class)
 private ConversationBeanB ConversationBeanb;

 private int D;

 public ConversationBeanD() {
  D = 0;
 }

 public void increaseD() {
  D++;
 }
   
 public void increaseDWithB() {
  D+=ConversationBeanb.getB();
 }

 public void finish() {
  this.conversation.close();
 }

 public int getD() {
  return D;
 }

 public void setD(int D) {
  this.D = D;
 }
}

The Facelets simple expose the usual buttons:

<h:commandButton value="Increase A With C" action="#{conversationBeanA.increaseAWithC()}" style="background-color: yellow;"/>
<h:commandButton value="Increase B With D" action="#{conversationBeanB.increaseBWithD()}" style="background-color: yellow;"/>


The complete application is available here (DSGroupedConversationScoped_VII).

ii. Injecting conversation group AB in a non-conversation bean (e.g. a view scoped bean)
Further, let's use the CDI @Inject to inject instances of beans from conversation group AB in a view scoped bean BeanC. In this bean, will try to calculate the sum of A, B and C integers (A+B+C).


The BeanC uses @Inject and @ConversationGroup CDI qualifier to indicate the beans to inject. You can inject beans from different groups also. As long as you control the behavior, you can "play" with many kind of injection designs:

@Named
@ViewScoped
public class BeanC implements Serializable {

 private int C;

 @Inject @ConversationGroup(ConversationABInterface.class)
 private ConversationBeanA ConversationBeana;

 @Inject @ConversationGroup(ConversationABInterface.class)
 private ConversationBeanB ConversationBeanb;

 public BeanC() {
  C = 0;
 }

 public void increaseC() {
  C++;
 }

 public void increaseCWithAandB() {
  C += ConversationBeana.getA() + ConversationBeanb.getB();
 }

 public int getC() {
  return C;
 }

 public void setC(int C) {
  this.C = C;
 }
}

The Facelets simple expose the usual buttons:

<h:commandButton value="Increase C With A and B" action="#{beanC.increaseCWithAandB()}" style="background-color: yellow;"/>                   

Note You can inject non-conversation beans into beans that belong to conversation groups also.

The complete application is available here (DSGroupedConversationScoped_XI).

3. Sub-Conversation-Groups
DeltaSpike supports parallel "isolated" conversations and parallel grouped conversations. This features discourages nested (grouped) conversations, because instead of nesting (grouped) conversations, we can simply run them in parallel and obtain a less-coupled model. Moreover, we can rely on DeltaSpike to begin/close the (grouped) conversations, or we can explicitly close a conversation (group) from a conversation (group) bean, from outside the conversation (group), and even close all (grouped) conversations. This provides us the opportunity to  obtain a fine-tuning of parallel (grouped) conversations flow.
But, sometimes it may be really important to have sub-groups. Practically, via sub-groups, we can shutdown only a part of a group (a subset of beans from that group). A sub-group is just a class or an interface used to identify a bunch of beans within a group. To terminate such a sub-group, it is just needed to pass the class/interface to the corresponding API for terminating a conversation. DeltaSpike will use that class/interface to identify the sub-group.

For example, let's define a group named ABCD (contains ConversationBeanA, ConversationBeanB, ConversationBeanC and ConversationBeanD), and a sub-group CD (contains ConversationBeanC and ConversationBeanD). We also provide controls to close group and sub-group, as in figure below:


a. Listing  beans of a sub-group
First, we define the group class/interface (class in this case), ConversationABCDGroup:

public class ConversationABCDGroup {
 // NOPE
}

Each bean will use the @ConversationGroup(ConversationABCDGroup.class) to show its "affiliation" to the ABCD group. Below, you can see the source code of ConversationBeanA (you can easily intuit the source code of ConversationBeanB, ConversationBeanC and ConversationBeanD):

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationABCDGroup.class)
public class ConversationBeanA implements Serializable {

 @Inject
 private GroupedConversation conversation;

 private int A;

 public ConversationBeanA() {
  A = 0;
 }

 public void increaseA() {
  A++;       
 }

 public void finish() {
  this.conversation.close();
 }

 public int getA() {
  return A;
 }

 public void setA(int A) {
  this.A = A;
 }
}

Now, we indicate that ConversationBeanC and ConversationBeanD belongs to a sub-group, CD.

i. Explicitly Listing Beans of a sub-group via #subGroup/#of
First, we need to indicate the class/interface of the sub-group. This class/interface should be annotated with @ConversationSubGroup#subGroup or @ConversationSubGroup#of, as below:

·         List sub-group beans via @ConversationSubGroup#subGroup (in this case, the sub-group class extends the group class (in case of interface, implement it))

@ConversationSubGroup(subGroup = {ConversationBeanC.class, ConversationBeanD.class})
public class ConversationCDSubgroup extends ConversationABCDGroup {
 // NOPE
}

·         List sub-group beans via @ConversationSubGroup#of (no need to extend/implement the group class/interface):

@ConversationSubGroup(of=ConversationABCDGroup.class, subGroup = {ConversationBeanC.class, ConversationBeanD.class})
public class ConversationCDSubgroup {
 // NOPE
}

The complete application is available here (DSGroupedConversationScoped_VIII).

ii. Using implicit sub-groups
Sometimes, listing beans of a sub-group can become a verbose task. For example, imagine that you have multiple sub-groups and dozens of beans, then it will be really unpleasant to maintain those sub-groups by moving, removing, adding beans. So if you have a lot of such beans or you would like to form (sub-)use-case oriented groups, you can use implicit sub-groups. In this case, we follow three steps: first, we define an interface that "marks" all beans from a sub-group; second, we define the sub group class/interface and indicate that interface; third, we implement this interface in each bean that should belong to our sub-group.

So, let's define the interface:

public interface CDInterface {
 // NOPE
}

Further the sub-group class/interface (class in our example):

@ConversationSubGroup(of = ConversationABCDGroup.class, subGroup = CDInterface.class)
public class ConversationCDImplicitSubgroup {
 // NOPE
}

Finally, we add ConversationBeanC and ConversationBeanD to the sub-group:

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationABCDGroup.class)
public class ConversationBeanC implements CDInterface, Serializable {
 ...
}

@Named
@GroupedConversationScoped
@ConversationGroup(ConversationABCDGroup.class)
public class ConversationBeanD implements CDInterface, Serializable {
 ...
}

Practically, each bean which implement the CDInterface interface will belong to the CD sub-group.

The complete application is available here (DSGroupedConversationScoped_IX).

b. Closing sub-groups
In order to close a sub-group, we need to inject the GroupedConversationManager and invoke closeConversationGroup(), as below:

GroupedConversationManager#closeConversationGroup(fooSubGroup.class);

For example, we close the CD sub-group as below:

@Inject
private GroupedConversationManager conversationManager;

public void finish() {         this.conversationManager.closeConversationGroup(ConversationCDSubgroup/ConversationCDImplicitSubgroup.class);
}

The complete application with explicit listing of sub-group beans is available here, and the complete application with implicit sub-groups is available here. Both applications produce the same output (just "play" with the interface to see how CD sub-group works):


Hope you enjoyed DeltaSpike (grouped) conversations. See you in the next post about JSF custom scope.

JSF BOOKS COLLECTION

Postări populare

Visitors Starting 4 September 2015

Locations of Site Visitors