dkaustubh
dkaustubh

Reputation: 454

How to log managed bean name and action method being invoked in a servlet filter

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

Answers (1)

BalusC
BalusC

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

Related Questions