General Exception Mechanism
The application uses checked exceptions to communicate errors between the layers. The ApplicationException is the root of all possible business exceptions.
I am not going to jump in on checked vs. unchecked exceptions here (Google a bit about it if you are curious). I tend to use checked exceptions when the application has a chance to recover from the error. Unchecked are thrown when something happens which isn't recoverable. That is the reason, I am not happy with the exception handling mechanism build in at the moment. I am going to get into this a little later.
What is missing? ViewExpired and more.
Seems as if everything is handled right now. But only on the first impression. Open the login screen and wait a bit and let your http session timeout. You are now greeted with a not so nice ViewExpired exception screen.
If you are trying this as a loged-in user you are simply redirected to the login page. Anyway, the same error page could come up for some other unexpected conditions in the presentation layer. So, let's fix this. Most obvious thing to do is to simply introduce a dedicated error-page.
<error-page> <exception-type>javax.faces.application.ViewExpiredException</exception-type> <location>/viewExpired.xhtml</location> </error-page>Now you redirect your users to a dedicated page which could tell him/her something nice about workplace security and not leaving the app unattended for such a long time. This works for most of the applications out there. If you are willing to have some additional information on the page or simply want to catch more than one exception and handle them individually without having to configure them statically, you need something called an ExceptionHandler. This is new in JSF 2 and all you need to do is to implement an ExceptionHandler and it's factory. The factory itself is configured in the facex-config.xml because there isn't any annotation for it.
Open the faces-config.xml and add the following lines at the bottom:
<factory> <exception-handler-factory>info.galleria.handlers.GalleriaExceptionHandlerFactory</exception-handler-factory> </factory>Now we are going to implement the GalleriaExceptionHandlerFactory in the dedicated package. The interesting method here is the:
@Override public ExceptionHandler getExceptionHandler() { ExceptionHandler result = parent.getExceptionHandler(); result = new GalleriaExceptionHandler(result); return result; }This is called once per request must return a new ExceptionHandler instance each time it's called. Here the the real ExceptionHandlerFactory is called and asked to create the instance, which is then wrapped in the custom GalleriaExceptionHandler class. This is where the real interesting stuff happens.
@Override public void handle() throws FacesException { for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) { ExceptionQueuedEvent event = i.next(); ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource(); Throwable t = context.getException(); if (t instanceof ViewExpiredException) { ViewExpiredException vee = (ViewExpiredException) t; FacesContext fc = FacesContext.getCurrentInstance(); Map<String, Object> requestMap = fc.getExternalContext().getRequestMap(); NavigationHandler nav = fc.getApplication().getNavigationHandler(); try { // Push some stuff to the request scope for later use in the page requestMap.put("currentViewId", vee.getViewId()); nav.handleNavigation(fc, null, "viewExpired"); fc.renderResponse(); } finally { i.remove(); } } } // Let the parent handle all the remaining queued exception events. getWrapped().handle(); }Iterate over the unhandler exceptions using the iterator returned from getUnhandledExceptionQueuedEvents().iterator(). The ExeceptionQueuedEvent is a SystemEvent from which you can get the actual ViewExpiredException. Finally you extract some extra information from the exception and place it in request scope to access it via EL in the page later on. Last thing to do here for a ViewExpiredException is to use the JSF implicit navigation system ("viewExpired" is resolved to "viewExpired.xhtml") and navigate to the "viewExpired" page via the NavigationHandler. Don't forget to remove the handled exception in the finally block. You don't want this to be handled again by the parent exception handler. Now we have to create the viewExpired.xhtml page. Do this inside the galleria-jsf\src\main\webapp folder.
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" template="./templates/defaultLayout.xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" > <ui:define name="title"> <h:outputText value="#{msg['Exception.page.title']}" /> </ui:define> <ui:define name="content"> <h:form> <h:outputText value="#{msg['Exception.page.message']}" /> <p>You were on page #{currentViewId}. Maybe that's useful.</p> <p>Please re-login via the <h:outputLink styleClass="homepagelink" value="#{request.contextPath}/Index.xhtml" ><h:outputText value="Homepage" /></h:outputLink>.</p> </h:form> </ui:define> </ui:composition>Please note that I added new message properties here, so you need to make sure to place them in galleria-jsf\src\main\resources\resources\messages.properties and translations.
Until now this obviously only handles one special instance of exception. You could extend it to handle others as well. Now that we have the basic mechanism in place you are free to do this.
Refactoring the RuntimeException handling
As I said, I am not happy with the way the application is handling RuntimeExceptions. Now that we have a nice central exception handling in place we can move those stuff around a bit and refactor the *Manager classes. Delete all those catch (EJBException ejbEx) { blocks from all of them. We are going to take care of them in the GalleriaExceptionHandler in a minute. Simply add an another check to the GalleriaExceptionHandler and redirect the user to another page if any other exception than a ViewExpiredException is thrown.
// check for known Exceptions if (t instanceof ViewExpiredException) { ViewExpiredException vee = (ViewExpiredException) t; // Push some stuff to the request scope for later use in the page requestMap.put("currentViewId", vee.getViewId()); } else { forwardView = "generalError"; Locale locale = fc.getViewRoot().getLocale(); String key = "Excepetion.GeneralError"; logger.error(Messages.getLoggerString(key), t); String message = Messages.getString(key, locale); FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, message, null); fc.addMessage(null, facesMessage); }This approach has some advantages. It reduces the needed code in the *Manager classes and we finally have a central place to take care of those unrecoverable exceptions. This still is not very enterprise like. Imagine your first level support team needs to look after customers and they start complaining that the only message they get is a "GeneralError". That is not very helpful. You support team would need to escalate it and second or third level would need to check the logs and and and .. All this because of an error, that we could have know. First thing to do is to find out about the causing error. Parsing stack traces isn't big fun. Especially not of RuntimeExceptions that are wrapped in EJBExceptions and further on in FacesExceptions. Thank god for the Apache Commons ExceptionUtils. Open your galleria-jsf pom.xml and add them as dependency:
<dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency>Now you can start to examine the root cause:
} else { forwardView = "generalError"; // no known instance try to specify Throwable causingEx = ExceptionUtils.getRootCause(t); if (causingEx == null) { causingEx = t; }
//... logger.error(Messages.getLoggerString(key), t); requestMap.put("errorCode", errorCode);Don't forget to also log the complete stack-trace (t and not only causingEx) here. In general it's a bad thing to let users know about exceptions. Nobody really wants to see errors happen (because we do hate making mistakes) and after all exception stack-traces could disclose sensitive information which you wouldn't like to see on a screen somewhere. So you need to find a way to display something meaningful to the user without disclosing too much. That is where the famous error-codes come into play. Use the root-cause exception as message key or make your own decisions on what effort you are going to put in here. It might be a system of categories of errors (db, interface systems, etc.) which give the first-level support a good hint about what was causing the error. I would stick to a simpler solution from the beginning. Simply generate a UUID for every caught exception and trace it to both the log and the UI. A very simple one could be the following.
String errorCode = String.valueOf(Math.abs(new Date().hashCode()));
This should also be added to the message properties and don't forget that you need another one for the generalError template. If slf4j would use the same message format than jdk logging does you would only need one property .. anyway:
Exception.generalError.log=General Error logged: {}.
Exception.generalError.message=A general error with id {0} occured. Please call our hotline.
Add this to the generalError.xhtml and see how the error code is passed to the message template.
<h:outputFormat value="#{msg['Exception.generalError.message']}" > <f:param value="#{errorCode}"/> </h:outputFormat>
There is still a lot to improve on here. You could use the javax.faces.application.ProjectStage to lookup the current mode the application is running in. If you are running in ProjectStage.Development you could also put the complete stack-trace to the UI and make debugging live a bit easier. The following snippet is trying to get the ProjectStage from JNDI.
public static boolean isProduction() { ProjectStage stage = ProjectStage.Development; String stageValue = null; try { InitialContext ctx = new InitialContext(); stageValue = (String) ctx.lookup(ProjectStage.PROJECT_STAGE_JNDI_NAME); stage = ProjectStage.valueOf(stageValue); } catch (NamingException | IllegalArgumentException | NullPointerException e) { logger.error("Could not lookup JNDI object with name 'javax.faces.PROJECT_STAGE'. Using default 'production'"); } return ProjectStage.Production == stage; }
What about the 3-digit Http Error Pages?
That is another thing to take care of. All those remaining 3-digit http error codes which return one of those not nice looking error pages. The only thing to do this is to map them in the web.xml like shown in the following:
<error-page> <error-code>404</error-code> <location>/404.xhtml</location> </error-page>You should make sure to have those mappings in place and present a meaningful error to your users. It should become best practice to always offer a way to navigate further from there.
Hi Markus.
ReplyDeleteThank you for continuation of Galleria tutorial.
I couldn't find the code related to this part of tutorial in bitbucket!
Did you commit them to repository?
Thanks
Hi Heidarzadeh,
ReplyDeletethe code actually isn't in bitbucket. I tried to link the more complex files in the text. Vineet is integrating the changes if he finds them useful. That's completely fair to me, as I am only willing to show relevant parts that I find interesting which might not reflect his intention.
Thanks for reading,
-M
Hi y'all
ReplyDeleteI've read the tutorial and the blogs only a few days ago, so don't get bothered by my comments which actually are not concerned with exceptions and are a bit late ;-)
I like the tutorial - big thumbs up for Vineet - especially the part about testing.
What I would like to comment on is the use of CDI...or let's say the potential for improvement.
@EJB vs @Inject
----------------------------------
I would try to get rid of @EJB annotations completely. This is why JEE6 is sort of horrible - you have two or more ways to do the same thing (@EJB vs. @Inject). But I think CDIs annotations are more powerful than @EJB and should therefore be preferd.
What makes the situation complicated is that there are even Name-clashes (@Produces), so be carefull with the annotation you use actually - to an outsider this looks like JSR-Teams try to avoid to talk to each other sometimes ;-) But that is another storry.
@JSF powered by CDI
----------------------------------
However, there are other use cases where you can use CDI, i.e. @Producer and @Inject to tie things together in a clever way. Let's see, for instance if you do it like this in AlbumManager
@Produces @Named("petersCreate")
private CreateAlbumRequest createRequest = new ....
Then you can write your JSF CreateAlbum.xhtml like
And you can get rid of getters/setters for CreateAlbumRequest in AlbumManager
BUT stop this is only part of the story. If you try that then CDI creates more copies of CreateAlbumRequest. Why is that? Because currently AlbumManager is not CDI-Scoped, which makes the CDI-Runtime create an
AlbumManager for each Inject-Point...but this is CDI subtleties. To come around this and using CDI-out-of-the-box you should put AlbumManager in SessionScope for now...s. comment about CDI-ViewScope below.
@SessionScoped //prevents from multiple Copies
@Named //must not forget this to make it availible in JSF
public class AlbumManager implements Serializable
Or make it even more clever - probably we can even try to make the CreateRequest a @Model (@Model is a CDI Stereotype)?
That would then be very easy tied togehter
@Model
public class CreateAlbumRequest
@Inject
private CreateAlbumRequest createRequest;
This provides for pretty loose coupling I see some potential for reuse-patterns, because Views don't have to be tied to Managers directly.
I think in the future we will see even patterns which show some Produce-Inject-Cascades, I think about the use cases like this: Search -> Result-List -> Select Item From Result List -> Edit Details of selected Item.
If you have a BackingBean for each of the Steps than for instance the ResultList may CDI-Produce the selected Item which is then CDI-Injected into the Backing Bean of the EditDetailBackingBean
Currently you might run into some complications
1.) Trying to get rid of JSF-only Scopes because out-of-the-box CDI misses some Scopes like for instance ViewScope, but there are Protable CDI-Extensions which do help you to get around that...somewhere at jboss, maybe this http://seamframework.org/Seam3/Home
And actually doing an ViewScope on your own for JSF is straight forward.
2.) Don't forget to place "beans.xml" files ;-)
@Vineet - If you reead this, I can provide some patches, if you're interested in.
Cheers from another msg systems ag guy
Peter Huber
Correction to my original post- the JSF-Code did not show up. It should read...
DeleteThen you can write your JSF CreateAlbum.xhtml like
<h:inputText id="albumName" value="#{petersCreate.name}" />
Amazing bolg.. keep it up.
ReplyDelete