Enterprise Java

Programmatically caching PrimeFaces charts via OmniFaces Cache component

In this post, you will see how to combine PrimeFaces and OmniFaces to obtain cacheable charts. In order to keep the things simple, we will use a PrimeFaces Line Chart. For this kind of chart we can use <p:chart/> tag in page and a simple managed bean. So, in page we can have:

 <p:chart id="someChartId" type="line"
         model="#{chartView.lineModel}"
         style="height:300px;width:600px;"/>

The ChartView can be written as below:

 @Named
@ViewScoped
public class ChartView implements Serializable {

 private LineChartModel lineModel;

 @PostConstruct
 public void init() {
  createLineModels();
 }

 private void createLineModels() {
  lineModel = initLinearModel();
  lineModel.setTitle("Linear Chart");
  lineModel.setLegendPosition("e");
  lineModel.setZoom(true);
  Axis yAxis = lineModel.getAxis(AxisType.Y);
  yAxis.setMin(0);
  yAxis.setMax(10);
 }

 private LineChartModel initLinearModel() {
  LineChartModel model = new LineChartModel();

  LineChartSeries series1 = new LineChartSeries();
  series1.setLabel("Series 1");

  Random rnd = new Random();

  series1.set(rnd.nextInt(10), rnd.nextInt(10));
  series1.set(rnd.nextInt(10), rnd.nextInt(10));
  series1.set(rnd.nextInt(10), rnd.nextInt(10));
  series1.set(rnd.nextInt(10), rnd.nextInt(10));
  series1.set(rnd.nextInt(10), rnd.nextInt(10));

  LineChartSeries series2 = new LineChartSeries();
  series2.setLabel("Series 2");
  series2.set(rnd.nextInt(10), rnd.nextInt(10));
  series2.set(rnd.nextInt(10), rnd.nextInt(10));
  series2.set(rnd.nextInt(10), rnd.nextInt(10));
  series2.set(rnd.nextInt(10), rnd.nextInt(10));

  model.addSeries(series1);
  model.addSeries(series2);

  return model;
 }

 public LineChartModel getLineModel() {
  return lineModel;
 }
}

This code will produce a simple line chart as in figure below:

primfaces linear chart sample

Now, let’s suppose that, during the application run, this chart is periodically updated or re-created (we will simulate this via random series values and a Refresh button). Each time this is happening, we will lose the current chart. But, it may be useful to cache (like save) some of those charts, and have the possibility to load them later during the current session (for charts that belongs to certain users)/application (for charts common to all users).

In order to accomplish this task, we can use the OmniFaces Cache component. Basically, this component is very well described in OmniFaces Showcase and Mastering OmniFaces book, but the major ideas are:

  • Cache component is exposed to JSF page authors via
    <o:cache> tag.
  • Cache encapusaltes a server-side caching mechanism for the markup produced by the Render Response phase.
  • Cache takes action in Render Response phase.
  • The cached markup is stored under a key generated by OmniFaces or indicating via the optional key attribute of <o:cache>.
  • Caching can be disabled per request via the optional disabled flag attribute of <o:cache>.
  • A cached entry can be re-cached via reset flag attribute of the <o:cache>.
  • By default, cached data is stored in session scope (application scope is also supported).

Per example, from JSF page author perspective, we can indicate that we want to re-cache a piece of markup under the key foo, like below:

 <o:cache id="cacheId" key="foo" disabled="false" reset="true">      
 ... // the markup produced for this snippet of code will be cached
</o:cache>

Obviously, the disabled attribute can be skipped in this example, since that is its implicit value. If key is also skipped, then OmniFaces will generate one. If reset is skipped, that the markup will not be re-cached.

Since we want to have the possibility to decide which charts are cached and load/delete a certain chart from cache we cannot simply do only this:

 <o:cache id="cacheId">      
 <p:chart id="someChartId" type="line"
          model="#{chartView.lineModel}"
          style="height:300px;width:600px;"/>                   
</o:cache>

Basically, this will cache the first chart, and, at each postback, will serve this chart from cache.

So, a quick approach will consist in juggling with <o:cache> attributes programmatically. As I said above, Cache takes action in the Render Response phase. This means that we can control from our
ChartView bean the Cache component before the caching is actually happening. The centerpiece of this implementation will consist in the below private method which allows us to programmatically configure the Cache component:

 private void configureCache(String key, boolean disabled, boolean reset) {
 Cache cache = Components.findComponent("cacheId");
 cache.setDisabled(disabled);
 cache.setReset(reset);
 cache.setKey(key);
}

Now, we will add one be one the UIs needed to control the caching. First we add a button labeled,
Refresh. Practically, each time we press this button, a new chart will be generated (new data). This is for simulating the chart update.

<h:commandButton action="#{chartView.redrawAction()}" value="Refresh"/>

The redrawAction() ensures that the new chart is not cached, so caching is disabled and key is not relevant:

 public void redrawAction() {
 configureCache("none", true, false);
 createLineModels();
}

Further, we add a button labeled, Save. When this button is pressed, the current chart is cached under a key of type, key_random-number (in real cases, you may want to allow the user to provide the key as the chart title). The key will be expose to the user in a list representing the saved charts:

<h:commandButton action="#{chartView.saveChart()}" value="Save"/>

The saveChart() method enables caching and generates a new key. The key is stored in a list:

 private List<String> keys;
...
public void saveChart() {
 String key = "key_" + new Random().nextInt(1000);
 configureCache(key, false, true);
 keys.add(key);
}

Next, we list the cached keys and a button labeled, Load. The user can select a key and click the
Load button to load a cached chart:

 <h:selectOneMenu value="#{chartView.selected}">
 <f:selectItem itemLabel="Select a chart ..." noSelectionOption="true"/>
 <f:selectItems value="#{chartView.keys}" var="t" itemLabel="#{t}" itemValue="#{t}"/>
</h:selectOneMenu>
   
<h:commandButton value="Load Chart" action="#{chartView.loadChart()}"
                 disabled="#{chartView.keys.size() eq 0}"/>

The loadChart() is:

 public void loadChart() {
 if (selected != null) {
     configureCache(selected, false, false);
 }
}

Finally, we add a button labeled, Delete, which will delete from cache the selected chart:

 <h:commandButton value="Delete Chart" action="#{chartView.deleteChart()}"
                 disabled="#{chartView.keys.size() eq 0}"/> |

And, deleteChart() is:

 public void deleteChart() {
 if (selected != null) {
     CacheFactory.getCache(Faces.getContext(), "session").remove(selected);
     keys.remove(selected);
     configureCache("none", true, false);           
     resetLineModels();
 }
}

private void resetLineModels(){       
 lineModel.getSeries().clear();
}

Notice here how we can programmatically delete by key an entry from cache using the CacheFactory.

Here it is a suggestive screenshot:

primefaces and omnifaces chart

  • The complete application is available here.

Anghel Leonard

Anghel Leonard is a senior Java developer with more than 13 years of experience in Java SE, Java EE, and related frameworks. He has written and published more than 50 articles about Java technologies and more than 500 tips and tricks for many websites that are dedicated to programming. In addition, he has written many books including Mastering JavaServer Faces 2.2.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button