Reputation: 454
I am working on a web application built on JSF 2 Mojarra. I have a requirement to log some instrumentation information through my servlet filter. To do this I also need to know which ManagedBean and which method is being invoked.
Is there a way to get this information? I can't access FacesContext
as filter is invoked before the request reaches Faces Servlet.
Upvotes: 4
Views: 1846
Reputation: 1108782
I gather that you want to log the UICommand
component being invoked. A servlet filter is insuitable for that as it has no access to the FacesContext
, let alone the UIViewRoot
which you ultimately need to traverse. The FacesContext
(and inherently also the UIViewRoot
, et.al) are created by the FacesServlet
which is as being a decent servlet completely conform the servlet spec invoked after all filters. It's therefore impossible to get a hand of the FacesContext
inside a servlet filter. True, there are ways to create your own FacesContext
instance, but this is absolutely not recommended if there exist a "right way" to achieve the requirement.
You should be using the right tool for the job, which is in this particular case a phase listener. Here's a kickoff example of how a phase listener look like and should be registered:
public class MyPhaseListener implements PhaseListener {
@Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
@Override
public void beforePhase(PhaseEvent event) {
// Do your job here which should run right before the RESTORE_VIEW phase.
}
@Override
public void afterPhase(PhaseEvent event) {
// Do your job here which should run right after the RESTORE_VIEW phase.
}
}
To get it to run, register it as follows in faces-config.xml
:
<lifecycle>
<phase-listener>com.example.MyPhaseListener</phase-listener>
</lifecycle>
You can change the getPhaseId()
outcome to your insight, e.g. PhaseId.RENDER_RESPONSE
, then the phase listener will kick in on before and after the render response phase.
Here's a concrete kickoff example which does the job you're looking for (finding the command component being invoked and logging its action method expression):
public class InvokedCommandComponentLogger implements PhaseListener {
@Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
@Override
public void beforePhase(PhaseEvent event) {
// NOOP. The view hasn't been restored yet at that point, so the component tree wouldn't be available anyway.
}
@Override
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
if (context.isPostback()) {
UICommand component = findInvokedCommandComponent(context);
if (component != null) {
String methodExpression = component.getActionExpression().getExpressionString();
System.out.println("Method expression of the action being invoked: " + methodExpression);
}
}
}
private UICommand findInvokedCommandComponent(FacesContext context) {
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
Set<String> clientIds = new HashSet<>();
if (context.getPartialViewContext().isAjaxRequest()) {
clientIds.add(params.get("javax.faces.source")); // This covers <f:ajax> inside UICommand.
} else {
for (Entry<String, String> entry : params.entrySet()) {
if (entry.getKey().equals(entry.getValue())) { // This covers UIForm and UICommand components.
clientIds.add(entry.getKey());
}
}
}
EnumSet<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
final UICommand[] found = new UICommand[1];
context.getViewRoot().visitTree(VisitContext.createVisitContext(context, clientIds, hints), new VisitCallback() {
@Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target instanceof UICommand) {
found[0] = (UICommand) target;
return VisitResult.COMPLETE;
} else {
return VisitResult.ACCEPT;
}
}
});
return found[0];
}
}
Upvotes: 4