I'm trying to implement some sort of auditing in a Java EE JPA (2.0) application on GlassFish 3.
I have added a @EntityListeners
annotation on my @MappedSuperclass
entity, the listener has the @PrePersist
and @PreUpdate
annotation on its methods which are invoked happily at runtime.
In these methods, I'm trying to use (@Inject
) a @Named
, @Stateful
, @SessionScoped
bean (UserSession
) in order to get current user's id. The listener class has no annotations at all.
The problem is that I can't get the UserSession
bean injected; I always end up with a null
value. To this time, I tried the plain @Inject UserSession us;
which always injects a null value.I also tried UserSession us = (UserSession) ctx.lookup("java:global/application/module/UserSession");
which always returns a new object (I verified the constructor call, plus the object is empty).
I'm pretty sure I have missed something very important regarding CDI but I can't figure out what. Could someone please point me to the right direction?
I eventually found a workaround, which allows me to get a reference of the @Stateful
bean:
I created a @Named @Singleton @Startup
bean SessionController which holds a local HashMap<String, UserSession> sessionMap
with the references of my @Stateful
beans:
@Named
@Singleton
@Startup
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class SessionController {
private HashMap<String, UserSession> sessionMap;
@PostConstruct
void init() {
sessionMap = new HashMap<String, UserSession>();
}
@PreDestroy
void terminate() {
for (UserSession us : sessionMap.values()) {
us.logoutCleanUp(); //This is annotated as @Remove
}
sessionMap.clear();
}
public void addSession(String sessionId, UserSession us) {
sessionMap.put(sessionId, us);
System.out.println("New Session added: " + sessionId);
}
public UserSession getCurrentUserSession() {
FacesContext context = FacesContext.getCurrentInstance();
String sessionId = ((HttpSession) context.getExternalContext().getSession(false)).getId();
return sessionMap.get(sessionId);
}
}
I add the references from within each bean's @PostConstruct
method:
public class UserSession implements Serializable {
@Inject SessionController sc;
...
@PostConstruct
void init() {
FacesContext context = FacesContext.getCurrentInstance();
String sessionId = ((HttpSession) context.getExternalContext().getSession(true)).getId();
sc.addSession(sessionId, this);
}
Notice the .getSession(true)
which is required since the Session might not be created yet. Also notice that this
is safely passed since the @PostConstruct
is not the constructor...
After all these, I can get the reference in my EntityListener (and any other place) like this:
SessionController sc = (SessionController) new InitialContext().lookup("java:module/SessionController");
UserSession us = sc.getCurrentUserSession();
or like this in CDI beans
@Inject SessionController sc;
The only drawback I see is that this approach works well only for web applications (where FacesContext context = FacesContext.getCurrentInstance()
is meaningful). Some of my beans (and finally my EntityListeners) are also exposed via @javax.jws.WebService
as @Stateless
beans. In this context (actually: absence of), my Singleton wouldn't work (haven't tested yet) since there is no sessionId of any kind (no session at all to be exact). I will have to use a workaround for this, possibly using SessionContext of the bean or inventing a usable sessionId of some sort. I will post back if I create something usable...