I have an own ExceptionHandler added to my jsf 2.2 project.
web.xml
<factory>
<exception-handler-factory>package.exceptionhandler.MyExceptionHandlerFactory</exception-handler-factory>
</factory>
The exceptionHandlerFactory contains
public ExceptionHandler getExceptionHandler() {
ExceptionHandler handler = new MyExceptionHandler(parent.getExceptionHandler());
return handler;
}
and the ExceptionHandler contains
#Override
public void handle() throws FacesException {
LOGGER.debug("handle exception...");
}
My dummy Converter throw everytime an exception:
#FacesConverter(value = "MyConverter")
public class MyConverter implements Converter {
#Override
public Object getAsObject(final FacesContext context, final UIComponent comp, final String value) {
throw new RuntimeException("error");
}
//...
But the exception is not handled by my own excpetion handler. Why?
I found my problem.
That was not an issue from jsf. I had a PhaseListener in the application, which makes only some of the error messages visible on the page. But this PhaseListener was in the INVOKE_APPLICATION Phase and therefore never executed.
Related
I have the following servlet where I'm injecting CDI bean
public class FBOAuthFilter implements Filter {
#Inject
private Instance<LoginBean> loginBeanSource;
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain fc) throws IOException, ServletException {
try{
String code = request.getParameter("code");
LoginBean loginBean = loginBeanSource.get();
loginBean.doLogin(code);
} catch(Exception ex){
System.out.println("Exception");
}
}
}
CDI Bean:
#Named(value="loginbean")
#SessionScoped
public class LoginBean implements Serializable{
public void doLogin(String code){
//do something
FacesContext context = FacesContext.getCurrentInstance();
System.out.println(context == null);
context.getExternalContext().redirect("somepage");
}
}
However, when I try to access the FacesContext in the CDI Bean, its null. Is there a way to get access to the FacesContext ?
This is not the correct approach.
All filters run before the servlet is hit. The FacesContext is only available when the FacesServlet is hit. So as long as the FacesServlet isn't hit yet, then the FacesContext isn't available yet. So it's always null in all filters.
You need to rewrite the code in such way that you can solely use the readily available request and response objects and CDI in the filter, without relying on the FacesContext. It appears that you only wanted to perform a redirect. The "plain vanilla" servlet way is:
response.sendRedirect("somepage");
In order to properly use that, simply split your LoginBean code into two new beans: one which doesn't anywhere use javax.faces.* stuff, and another one which requires javax.faces.* stuff. The one which doesn't anywhere use javax.faces.* stuff can then be shared by both the filter and the managed bean.
#Dependent
public class LoginBeanWithoutFacesContext implements Serializable {
public void doLogin(String code) {
// do something without faces context
}
}
#Named
#SessionScoped
public class LoginBean implements Serializable {
#Inject
private LoginBeanWithoutFacesContext loginBeanWithoutFacesContext;
public void doLogin(String code) {
loginBeanWithoutFacesContext.doLogin(code);
FacesContext context = FacesContext.getCurrentInstance();
context.getExternalContext().redirect("somepage");
}
}
Finally use the LoginBeanWithoutFacesContext one in your filter.
public class FBOAuthFilter implements Filter {
#Inject
private LoginBeanWithoutFacesContext loginBeanWithoutFacesContext;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
String code = request.getParameter("code");
loginBeanWithoutFacesContext.doLogin(code);
response.sendRedirect("somepage");
}
catch (Exception e) {
throw new ServletException(e);
}
}
}
That said, consider using JEE standard authentication or a well-established library rather than some homegrown authentication for the job you're apparently doing in LoginBeanWithoutFacesContext. See also How to handle authentication/authorization with users in a database?
I'm using JSF 2.2 (Mojarra) and Faclets in a web application. I use a custom ExceptionHandler to handle exceptions. I leverage the JSF implicit navigation system and cause the server to navigate to the 'error.xhtml' page.
public class FrontEndExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
FrontEndExceptionHandler(ExceptionHandler exception) {
this.wrapped = exception;
}
#Override
public ExceptionHandler getWrapped() {
return wrapped;
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> iter = getUnhandledExceptionQueuedEvents().iterator();
while (iter.hasNext()) {
ExceptionQueuedEvent event = iter.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
// get the exception from context
Throwable t = context.getException();
final FacesContext fc = FacesContext.getCurrentInstance();
final Flash flash = fc.getExternalContext().getFlash();
final NavigationHandler nav = fc.getApplication().getNavigationHandler();
try {
// redirect error page
flash.put("erorrDetails", t.getMessage());
nav.handleNavigation(fc, null, "/errors/error.xhtml");
fc.renderResponse();
} finally {
// remove it from queue
iter.remove();
}
}
// parent handle
getWrapped().handle();
}
}
This assumes that the exception to be handling is not happening during the Render Response phase. But the exception occurs within an Facelets page also during the Render Response phase.Therefore following code doesn't work correct:
nav.handleNavigation(fc, null, "/errors/error.xhtml");
Does anybody has a Idea how to convey the desired information? Is there a way to navigate to the error.xhtml without using NavigationHandler?
i have a junit test method that calls a backing bean method as follows:
myBackingBean.signup();
, in the backing bean method there's a call to Faces.getLocale() and it gives null pointer exception in the line
UIViewRoot viewRoot = context.getViewRoot();
please advise how to be able to set locale in test method and fix this error.
solution was as follows:
1- add the following class to project:
public abstract class FacesContextMocker extends FacesContext {
private FacesContextMocker() {
}
private static final Release RELEASE = new Release();
private static class Release implements Answer<Void> {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
setCurrentInstance(null);
return null;
}
}
public static FacesContext mockFacesContext() {
FacesContext context = Mockito.mock(FacesContext.class);
setCurrentInstance(context);
Mockito.doAnswer(RELEASE).when(context).release();
return context;
}
}
2- In #Before for the test use the following code:
FacesContext facesContext = FacesContextMocker.mockFacesContext();
UIViewRoot uiViewRoot = Mockito.mock(UIViewRoot.class);
Mockito.when(facesContext.getCurrentInstance().getViewRoot())
.thenReturn(uiViewRoot);
Mockito.when(
facesContext.getCurrentInstance().getViewRoot().getLocale())
.thenReturn(new Locale("en"));
in my preRender code for a page i add faces message then make navigation to another page as follows:
if(error){
addMessageToComponent(null,"AN ERROR HAS OCCURRED");
FacesContext.getCurrentInstance().getExternalContext().getFlash()
.setKeepMessages(true);
navigateActionListener("myoutcome");
}
and the util methods for adding message and navigation are:
public static String getClientId(String componentId)
{
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot root = context.getViewRoot();
UIComponent c = findComponent(root, componentId);
return c.getClientId(context);
}
public static UIComponent findComponent(UIComponent c, String id)
{
if (id.equals(c.getId())) { return c; }
Iterator<UIComponent> kids = c.getFacetsAndChildren();
while (kids.hasNext())
{
UIComponent found = findComponent(kids.next(), id);
if (found != null) { return found; }
}
return null;
}
/**
* #param componentId
* : the id for the jsf/primefaces component without formId:
* prefix. <br>
* if you use null then the message will be added to the
* h:messages component.
**/
public static void addMessageToComponent(String componentId, String message)
{
if (componentId != null)
componentId = GeneralUtils.getClientId(componentId);
FacesContext.getCurrentInstance().addMessage(componentId,
new FacesMessage(message));
}
public static void navigateActionListener(String outcome)
{
FacesContext context = FacesContext.getCurrentInstance();
NavigationHandler navigator = context.getApplication()
.getNavigationHandler();
navigator.handleNavigation(context, null, outcome);
}
but messages are not saved and so it doesn't appear after redirect.
please advise how to fix that.
The preRenderView event runs in the very beginning of the RENDER_RESPONSE phase. It's too late to instruct the Flash scope to keep the messages. You can do this at the latest during the INVOKE_APPLICATION phase.
Since there's no standard JSF component system event for this, you'd need to homebrew one:
#NamedEvent(shortName="postInvokeAction")
public class PostInvokeActionEvent extends ComponentSystemEvent {
public PostInvokeActionEvent(UIComponent component) {
super(component);
}
}
To publish this, you need a PhaseListener:
public class PostInvokeActionListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
#Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
#Override
public void afterPhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PostInvokeActionEvent.class, context.getViewRoot());
}
}
After registering it as follows in faces-config.xml:
<lifecycle>
<phase-listener>com.example.PostInvokeActionListener</phase-listener>
</lifecycle>
You'll be able to use the new event as follows:
<f:event type="postInvokeAction" listener="#{bean.init}" />
You only need to make sure that you've at least a <f:viewParam>, otherwise JSF won't enter the invoked phase at all.
The JSF utility library OmniFaces already supports this event and the preInvokeAction event out the box. See also the showcase page which also demonstrates setting a facesmessage for redirect.
I want to know if there is an interceptor in JSF (like we use in Spring), and how to do we implement it?
You could implement a PhaseListener for this. You could program them to listen on a specific JSF phase which you specify in the overridden getPhaseId() method. You can intercept on the before and after phase events by beforePhase() and afterPhase() methods.
The below example listens on the render response phase:
public class RequestInterceptor implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
#Override
public void beforePhase(PhaseEvent event) {
// Do your job here which should run before the render response phase.
}
#Override
public void afterPhase(PhaseEvent event) {
// Do your job here which should run after the render response phase.
}
}
To get it to run, you need to register it as a <phase-listener> in the <life-cycle> section of the faces-config.xml file. You can have multiple <phase-listener>s.
<lifecycle>
<phase-listener>com.example.RequestInterceptor</phase-listener>
</lifecycle>
You can specify PhaseId.ANY_PHASE in getPhaseId() to let the phase listener run on every single JSF phase (note that not necessarily all of them will always be executed, that depends on the request type). You can if necessary get the current phase ID in the before and after phase methods by PhaseEvent#getPhaseId().
public class PhaseDebugger implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
#Override
public void beforePhase(PhaseEvent event) {
System.out.println("Before phase " + event.getPhaseId());
}
#Override
public void afterPhase(PhaseEvent event) {
System.out.println("After phase " + event.getPhaseId());
}
}
Alternatively, a Filter should work equally good if you want a more global hook (and thus you're not exactly interested in JSF requests/responses and you do not need anything from the FacesContext).
#WebFilter("/*")
public class RequestInterceptor implements Filter {
#Override
public void init(FilterConfig config) {
// Initialize global variables if necessary.
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// Do your job here which should run before the request processing.
chain.doFilter(request, response);
// Do your job here which should run after the request processing.
}
#Override
public void destroy() {
// Cleanup global variables if necessary.
}
}