I have one application running in the following environment.
- GlassFish Server 4.0
- JSF 2.2.8-02
- PrimeFaces 5.1 final
- PrimeFaces Extension 2.1.0
- OmniFaces 1.8.1
- EclipseLink 2.5.2 having JPA 2.1
- MySQL 5.6.11
- JDK-7u11
There are several public pages which are lazily loaded from the database. A few CSS menus are displayed on the header of the template page like displaying category/subcategory-wise featured, top seller, new arrival etc products.
The CSS menus are populated dynamically from the database based on various categories of products in the database.
These menus are populated on every page load which is completely unnecessary. Some of these menus require complex/expensive JPA criteria queries.
Currently the JSF managed beans that populate these menus are view scoped. They all should be application scoped, be loaded only once on application start up and be updated only when something in the corresponding database tables (category/subcategory/product etc) is updated/changed.
I made some attempts to understand WebSokets (never tried before, completely new to WebSokets) like this and this. They worked fine on GlassFish 4.0 but they don't involve databases. I'm still not able to understand properly how WebSokets work. Especially when database is involved.
In this scenario, how to notify the associated clients and update the above-mentioned CSS menus with the latest values from the database, when something is updated/deleted/added to the corresponding database tables?
A simple example/s would be great.
Since you are using Primefaces and Java EE 7 it should be easy to implement:
use Primefaces Push ( example here http://www.primefaces.org/showcase/push/notify.xhtml )
Hope this helps If you need some more details just ask
Regards
PrimeFaces has poll features to update the component automatically. In the following example,
<h:outputText>
will be auto updated every 3 seconds by<p:poll>
.Create a listener method like
process()
to select your menu data.<p:poll>
will be auto-update your menu component.Preface
In this answer, I'll assume the following:
<p:push>
(I'll leave the exact reason in the middle, you're at least interested in using the new Java EE 7 / JSR356 WebSocket API).1. Create a WebSocket endpoint
First create a
@ServerEndpoint
class which basically collects all websocket sessions into an application wide set. Note that this can in this particular example only bestatic
as every websocket session basically gets its own@ServerEndpoint
instance (they are unlike servlets thus stateless).The example above has an additional method
sendAll()
which sends the given message to all open websocket sessions (i.e. application scoped push). Note that this message can also quite good be a JSON string.If you intend to explicitly store them in application scope (or (HTTP) session scope), then you can use the
ServletAwareConfig
example in this answer for that. You know,ServletContext
attributes map toExternalContext#getApplicationMap()
in JSF (andHttpSession
attributes map toExternalContext#getSessionMap()
).2. Open the WebSocket in client side and listen on it
Use this piece of JavaScript to open a websocket and listen on it:
As of now it merely logs the pushed text. We'd like to use this text as an instruction to update the menu component. For that, we'd need an additional
<p:remoteCommand>
.Imagine that you're sending a JS function name as text by
Push.sendAll("updateMenu")
, then you could interpret and trigger it as follows:Again, when using a JSON string as message (which you could parse by
$.parseJSON(event.data)
), more dynamics is possible.3a. Either trigger WebSocket push from DB side
Now we need to trigger the command
Push.sendAll("updateMenu")
from the DB side. One of simplest ways it letting the DB to fire a HTTP request on a web service. A plain vanilla servlet is more than sufficient to act like a web service:You've of course the opportunity to parameterize the push message based on request parameters or path info, if necessary. Don't forget to perform security checks if the caller is allowed to invoke this servlet, otherwise anyone else in the world other then the DB itself would be able to invoke it. You could check the caller's IP address, for example, which is handy if both DB server and web server run at the same machine.
In order to let the DB fire a HTTP request on that servlet, you need to create a reusable stored procedure which basically invokes the operating system specific command to execute a HTTP GET request, e.g.
curl
. MySQL doesn't natively support executing an OS specific command, so you'd need to install an user defined function (UDF) for that first. At mysqludf.org you can find a bunch of which SYS is of our interest. It contains thesys_exec()
function which we need. Once installed it, create the following stored procedure in MySQL:Now you can create insert/update/delete triggers which will invoke it (assuming table name is named
menu
):3b. Or trigger WebSocket push from JPA side
If your requirement/situation allows to listen on JPA entity change events only, and thus external changes to the DB does not need to be covered, then you can instead of DB triggers as described in step 3a also just use a JPA entity change listener. You can register it via
@EntityListeners
annotation on the@Entity
class:If you happen to use a single web profile project wherein everything (EJB/JPA/JSF) is thrown together in the same project, then you can just directly invoke
Push.sendAll("updateMenu")
in there.However, in "enterprise" projects, service layer code (EJB/JPA/etc) is usually separated in EJB project while web layer code (JSF/Servlets/WebSocket/etc) is kept in Web project. The EJB project should have no single dependency on web project. In that case, you'd better fire a CDI
Event
instead which the Web project could@Observes
.(note the outcomments; injecting a CDI
Event
is broken in both GlassFish and WildFly in current versions (4.1 / 8.2); the workaround fires the event viaBeanManager
instead; if this still doesn't work, the CDI 1.1 alternative isCDI.current().getBeanManager().fireEvent(new MenuChangeEvent(menu))
)And then in the web project:
Update: at 1 april 2016 (half a year after above answer), OmniFaces introduced with version 2.3 the
<o:socket>
which should make this all less circuitous. The upcoming JSF 2.3<f:websocket>
is largely based on<o:socket>
. See also How can server push asynchronous changes to a HTML page created by JSF?