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

duminică, 15 mai 2016

Stateless JSF application via PrimeFaces, OmniFaces, Hazelcast and Payara Server

This is a simple post about writing a stateless JSF application for holding a dummy shopping cart using Hazelcast. The application uses PrimeFaces, OmniFaces and it is deployed on Payara Server 4.1.1.162, which provide a very nice support for Hazelcast.

Notice that this application is far away for being a good example to follow in production, being more a proof of concept meant to show an example of how we can develop JSF stateless (easy to scale out) application and keep user data intact over multiple requests.

For those who don't know, JSF is by default stateful. This refers to the fact that JSF works as expected only in presence of a view state (component tree) which is restored at each postback request. Starting with version 2.2, JSF provides support for stateless mode. This means that there is no view state saved and we have to use only request scoped beans. The advantage of this approach consist in the fact that we can easily scale out the application without taking care of aspects as sessions replication or sticky sessions.

Some dependencies:

<dependencies>
 <dependency>
  <groupId>org.omnifaces</groupId>
  <artifactId>omnifaces</artifactId>
  <version>2.3</version>
 </dependency>
 <dependency> 
  <groupId>org.primefaces</groupId> 
  <artifactId>primefaces</artifactId> 
  <version>5.3</version> 
 </dependency>
 <dependency>
  <groupId>com.hazelcast</groupId>
  <artifactId>hazelcast</artifactId>
  <version>3.6.2</version>
 </dependency>
 <dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-web-api</artifactId>
  <version>7.0</version>
  <scope>provided</scope>
 </dependency>
</dependencies>

Ok, let's see the steps for developing this proof of concept:

1. First, we need to activate stateless mode for JSF like below:

<f:view transient="true">  
 ...
</f:view>

2. Therefore, the index.xhtml page that displays the shopping cart is pretty simple and you can easily understand it by following the code line by line:

<f:view transient="true">           
 <h:form id="cartId">
  <p:dataList value="#{shoppingCart.viewCart()}" var="t" type="ordered">
   <f:facet name="header">
    Your Shopping Cart
   </f:facet>
   #{t.name}, #{t.price}
   <p:commandButton value="Remove" action="#{shoppingCart.removeItemFromCart(t)}" update="@form"/>
  </p:dataList>                       
  <p:commandButton value="Add random item" action="#{shoppingCart.addItemToCart()}" update="@form" />
 </h:form>
</f:view>

3. The shopping cart content is stored in a Hazelcast IList, therefore we need to activate Hazelcast. Commonly this is accomplished via something like below:

Config cfg = new Config();
HazelcastInstance instance = Hazelcast.newHazelcastInstance(cfg);

But, in this case we don't need to do that because Payara already provide Hazelcast support, and all we need to do is to activate it. For example, we have created a cluster named TestCluster with two instances, test1 and test2. As you can see in figure below, all we have to do for having a Hazelcast instance is to enable Hazelcast for our cluster and click Save (by default Payara Server admin console is available at localhost:4848):


As a note not related to this app (we don't need sessions), is good to know that Payara also provide session persistence via Hazelcast. Check the below screen-shot:


4. Now, we have a Hazelcast instance available for our cluster. Payara expose the Hazelcast instance via JNDI under the name payara/Hazelcast:


This means that we can easy obtain the Hazelcast instance in the application via @Resource as below:

@Resource(name = "payara/Hazelcast")
HazelcastInstance hazelcast;

5. In order to configure Hazelcast data structures we can use an XML file. Payara recognize the hazelcast-config.xml (it doesn't exist by default):


But, Hazelcast also supports programmatic configuration. For example, let's suppose that each time we add a new instance in cluster, we want to add a dummy item in the shopping cart (I know, this is a pretty useless use case, but that not important here). This item is added only once or under certain circumnstances, so ideally we will run that code only once or only under certain conditions. There are many ways to accomplish this and different moments in application flow where to do it, but let's do this via a request scoped managed bean instantiated eagerly. For this we can assign a requestURI to a managed bean annotated with the OmniFaces @Eager. The following bean will be instantiated whenever the URI /faces/start.xhtml (relatively to the application root) is requested:

@Eager(requestURI = "/faces/start.xhtml")
@RequestScoped
public class HazelcastInit {

 @Resource(name = "payara/Hazelcast")
 HazelcastInstance hazelcast;

 private static final Logger LOG = Logger.getLogger(HazelcastInit.class.getName());

 @PostConstruct
 public void init() {
  LOG.info("Initialize list of products started ...");

  // since this is a default config we can skip the following 4 lines
  Config config = new XmlConfigBuilder().build();
  ListConfig listConfig = config.getListConfig("cart");
  listConfig.setName("cart");
  hazelcast.getConfig().addListConfig(listConfig);
       
  IList<Item> cartList = hazelcast.getList("cart");
  cartList.add(new Item("Dummy Product", 0));
     
  LOG.info("Initialize list of products successfully done ...");
 }
}

In our case, we have set the start.xhtml as the start page of the application, so the above code is executed at least once (and each time to navigate to the specified URI):

<f:view transient="true">           
 Hazelcast was successfully initialized ...
 <p:link outcome="index">Go to app ...</p:link>
</f:view>

Note that when Hazelcast "meets" a data structure (e.g. IList, IMap, etc) usage, it is smart enough to create that data structure when it doesn't exist and to not re-create it when exists, so if you don't need some specific tasks or configuration then is no need to define that data structure in XML or programmatic. At first use, Hazelcast will create it for you with the default configurations.

6. Finally, we write the ShoppingCart request managed bean a below:

@Named
@RequestScoped
public class ShoppingCart implements Serializable {

 @Resource(name = "payara/Hazelcast")
 HazelcastInstance hazelcast;

 private static final Logger LOG = Logger.getLogger(ShoppingCart.class.getName());

 // the available items
 private static final List<Item> AVAILABLE_PRODUCTS = new ArrayList<Item>() {
  {
  add(new Item("product_1", 23));
  add(new Item("product_2", 53));
  add(new Item("product_3", 13));
  add(new Item("product_4", 58));
  add(new Item("product_5", 21));
  }
 };

 public void addItemToCart() {
  LOG.info("Adding a product to shopping cart ...");
  IList<Item> cartList = hazelcast.getList("cart");
  cartList.add(AVAILABLE_PRODUCTS.get(new Random().nextInt(5)));
  LOG.info("Product successfully added ...");
  viewCart();
 }

 public void removeItemFromCart(Item item) {
  LOG.info("Removing a product to shopping cart ...");
  IList<Item> cartList = hazelcast.getList("cart");
  cartList.remove(item);       
  LOG.info("Product successfully remove ...");
  viewCart();
 }

 public List<Item> viewCart() {
  List<Item> cart = new ArrayList<>();
  LOG.info("View cart ...");
     
  IList<Item> cartList = hazelcast.getList("cart");
  for (int i = 0; i < cartList.size(); i++) {
       cart.add(cartList.get(i));
  }
  return cart;
 }
}

7. Let's test the app. Ensure that you have the WAR of the application and follow these steps:
   
7.1 Start the TestCluster:

           
  7.2 Ensure that the cluster was successfully started and that Hazelcast contains the members:

           
  7.3 Deploy the WAR application in cluster:

         
    7.4 Launch the application

             
 7.5 Click the link for test1 (notice the log below):

           
  7.6 Click the Go to app link (notice the dummy item added at initialization):

     
        7.7 Add few more random items by clicking the button labeled Add random item:

       
      7.8 Start test2 (notice the log below):

           
  7.9 Click the Go to app link (notice the dummy item added at initialization and the rest of items added eariler):


Done! You try further to play by removing and adding items and notice how the shopping cart is maintained by Hazelcast in a JSF stateless application. Of course, if you want to take into account the login part, you can easily go for JWT authentication mechanism.

Notice that we have accomplished many of our tasks (e.g. cluster creation, deploy app, etc) via the visual admin console of Payara. But, you can also accomplish all these steps from CLI. Payara comes with a very handy tool named Asadmin Recorder which is capable to record the actions from visual console and output it in a text file as commands:


Well, I have used this feature and here it is the commands:

copy-config default-config TestCluster-config
create-cluster --config=TestCluster-config TestCluster
create-instance --cluster=TestCluster --node=localhost-domain1 test2
create-instance --cluster=TestCluster --node=localhost-domain1 test1
set-hazelcast-configuration --startPort=5900 --hazelcastConfigurationFile=hazelcast-config.xml --clusterName=development --clusterPassword=D3v3l0pm3nt --dynamic=true --multicastPort=54327 --enabled=true --multicastGroup=224.2.2.3 --jndiName=payara/Hazelcast --target=TestCluster
start-cluster TestCluster
deploy --keepState=false --precompilejsp=false --availabilityEnabled=false --name=JSFStatelessSC-1.0 --verify=false --force=false --contextroot=JSFStatelessSC-1.0 --enabled=true --properties=implicitCdiEnabled=true:preserveAppScopedResources=false --target=TestCluster C:\Users\Leonard\AppData\Local\Temp\JSFStatelessSC-13447695818088979490.0.war

The complete application is available here.

Niciun comentariu :

Trimiteți un comentariu

JSF BOOKS COLLECTION

Postări populare

Visitors Starting 4 September 2015

Locations of Site Visitors