I'm trying to implement exception handling for my Spring 3.2.0, Spring webflow, JSF 2.1.12 application.
So far I can catch most of the exceptions using an Exception handler flow:
<persistence-context/>
<decision-state id="handleException" >
<if test="deviceManager.isMobileDevice()" then="mobileException" else="generalException"/>
</decision-state>
<view-state id="generalException" view="../views/exception/generalException.xhtml">
<on-entry>
<evaluate expression="exceptionManager.extractMessages(flowExecutionException, rootCauseException)" result="viewScope.exc"/>
</on-entry>
</view-state>
<view-state id="mobileException" view="../views/exception/mobileException.xhtml">
<on-entry>
<evaluate expression="exceptionManager.extractMessages(flowExecutionException, rootCauseException)" result="viewScope.exc"/>
</on-entry>
</view-state>
<global-transitions>
<transition on-exception="java.lang.Exception" to="handleException"/>
</global-transitions>
But this can't handle NoSuchFlowDefinitionException and similar as, if I understood well, they happens outside the flow execution.
Anyone know how to handle these exceptions?
Also I would need to map the exception to a flow and not to a static view like, for example, error.html. This because my system requires to do some extra work in the background to load the page information.
Any help will be greatly appreciated, have a good day,
Mattia
<<<<< EDIT >>>>>
I added a SimpleMappingExceptionResolver to catch the NoSuchFlowDefinitionException:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException">
exceptionFlow
</prop>
</props>
</property>
</bean>
The Exception is catched but the resolver translate the target flow to a view, looking for a view: WEB-INF/exceptionFlow.xhtml This view does not exist as I would need to handle the exception with the flow exceptionFlow.xml
com.sun.faces.context.FacesFileNotFoundException: /WEB-INF/exceptionView.xhtml Not Found in ExternalContext as a Resource
How could I redirect that to a flow instead of a view? I tried adding a controller that maps on the view:
#Controller
#RequestMapping("/WEB-INF/exceptionFlow.xhtml")
public class ExceptionController {
#RequestMapping(method = RequestMethod.GET)
public String redirectToPublicFlowGet(ModelMap model) {
return "spring/flows/public";
}
#RequestMapping(method = RequestMethod.POST)
public String redirectToPublicFlowPost(ModelMap model) {
return "spring/flows/public";
}
}
But whatever I put on the request mapping (I tried "/WEB-INF/exceptionFlow.xhtml", "/exceptionFlow.xhtml", "exceptionFlow.xhtml". "exceptionFlow" The code never acces the methods (I put some debug breakpoint).
Anyone knows what I'm doing wrong?
Thanks have a good day
You can do it by changing
<prop key="org.springframework.webflow.definition.registry.NoSuchFlowDefinitionException">
exceptionFlow
</prop>
to
redirect:/exceptionFlow
this will create new flow execution for given flow
but you will lose information about the exception...
Related
I am not expert in Spring MVC and I'm have some dificulties to achieve the intented.
Basically, I need a Controller that will redirect to a web flow.
I know how to achieve this with the use of #Controller and #RequestMapping annotations. However, I cannot use spring-context as I'm restricted to version 2.5 (these annotations are present in 2.5 but application won't build with Java 8)
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/first/*">myMvcController</prop>
<prop key="/toflow/*">myFlowController</prop>
</props>
</property>
</bean>
<bean id="myMvcController" class="mypackage.controller.myController"/>
<bean id="myFlowController" class="org.springframework.webflow.executor.mvc.FlowController">
<property name="flowExecutor" ref="flowExecutor"/>
<property name="defaultFlowId" value="application-flow"/>
</bean>
And my controller looks something like:
public class myController extends Controller {
#Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView(forward:/toflow/variable)
Not sure if the forward is correct or if I should provide the url different, but the main problem is that the controller tries to resolve that as a view that doesn't exist. I just want to call /toflow/xxxx which I assume will be handle by my previous mapping and call my webflow.
Any suggestions?
Thanks
Use return new ModelAndView("redirect:/toflow/variable");
I have a base exception handler defined like this:
<bean id="exceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="errors.exception-handling" />
</bean>
and I have my controller methods with security like this:
#PreAuthorize("isAuthenticated()")
#RequestMapping(value = "/Home", method = RequestMethod.GET)
public String home(HttpServletRequest request, HttpSession sess) {
}
Now I have my NotAuthorized handler configured to redirect you to the login page if you hit /Home. However, with the exceptionResolver, I just get my page saying that there was an error. If I comment out the exceptionResolver it works fine.
How do I get it to ignore NotAuthroized and other such security exceptions?
You can add it like this
<property name="exceptionMappings">
<props>
<prop key="org.springframework.security.AccessDeniedException">loginPageUrl</prop>
</props>
</property>
UPDATE:
You can specify excludedExceptions as well to ignore NotAuthroized exception and let it be processed separately.
I'm facing a very similar problem to this: Yet another LazyInitializationException even with OpenSessionInViewFilter
I use Hibernate 4.2.7.Final. I have an entity which is mapped like this:
#Entity
#Table(...)
public class A {
...
#OneToMany(fetch=FetchType.LAZY, mappedBy="b")
private Set<B> bSet;
...
}
It loads a big amount of data, that's why I need to load it when it is required. So I load a page with this contoller request mapping:
#RequestMapping("/getDetails")
public ModelAndView showView(Model model) {
...
for(B b : myService.getBSet()) {...}
...
}
And the service is in transaction:
#Service
#Scope(value="session")
#Transactional("ora11transactionManager")
public class MyServiceImpl implements MyService {
private A a;
...
public Set<B> getBSet() {
return a.getBSet();
}
}
Transaction manager in the hibernate.cgf.xml:
<bean id="ora11sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="ora11source"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">${debug}</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.connection.characterEncoding">UTF-8</prop>
<prop key="hibernate.jdbc.use_get_generated_keys">true</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>mypackage</value>
</list>
</property>
</bean>
<bean id="ora11transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="ora11sessionFactory" />
</bean>
When I want to load the getDetails view, it throws the exception referencing that row in the service:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: <my-package>.A.bSet, could not initialize proxy - no Session
This is not the only lazy fetched collection I use, but anywhere else it works. The lazy loading must be in transaction, and it is in transaction (as you can see my service implementation)! I even added org.springframework.orm.hibernate4.support.OpenSessionInViewFilter in web.xml.
I can't find any solution for this, please advise!
UPDATE (The exact use of my entities):
I have a large set of As, and every A has a set of B. There is a view where I can show all As, they are in a list and showed in a datatable. At the end of every rows there is a button, which calls and action. In this action I save the selected A (in myService there is a setter for selected A). This action is in controller1. And when I want to show the Bs of an A, I set which is selected and redirect to an other view. This view is managed by an other controller, that's why I save the selected A to a service (session or singleton scoped).
#Controller
#Scope("session")
public class Controller1 {
...
public void setSelectedA(A selectedA) {
myService.setSelectedA(selectedA);
}
}
I tried to reach the set of B even in this method, but doesn't work (the whole service is transactional, I tried to set transactional annotation only to the setselectedA() and getBSet() method, but no success).
Your service is session scoped (#Scope(value="session")) but it does not make it automatically threadsafe. For example if you have a cart object(it's the same servlet session) the user may refresh his page and the page will be processed on the server from a different thread and it will be another Hibernate session but the same cart(and same Servlet session).
The problem is that entities which you cache in MySessionImpl require a live Hibernate session to trigger loading of B set - the session is closed after the first controller has finished processing.
Hibernate sessions are also not guaranteed to work properly when used from different threads so you can't extend their lives to provide lazy loading in controller B because it's processed in another thread.
So please avoid caching detached uninitialized object in your service class. So when you callreturn a.getBSet(); you are accessing session to which a was attached which does not exist in current thread.
I would refactor that code that all action is done in a threadsafe service with scope singleton(this scope is default in Spring) and it's methods should be coarse-grained - i.e. create a service that does as much as possible in single method call and that method is annotated as #Transactional.
If you need to keep a list of selected objects (for example the article IDs in a web store cart) you need only to store their identifiers(not the entities) in session scoped (per user) bean and then load them by IDs when needed in another controller/thread. To avoid extra database round trip for A entities you can enable second level cache in Hibernate.
Every documentation and examples that I read to be about validation form fields but I wish to show messages for others cases, for example to warn the view or flow have changed. So, that messages must be added on action methods.
In my case the app send to users a confirmation email, on flow there is a decision-state which redirect to views depending on link got declared a request parameter or not. Then I define a action-state which evaluate a method to confirm the email or warning to user about account's state (deleted or already enabled)
<on-start>
<set name="requestScope.code" value="requestParameters.code" />
<set name="requestScope.ln" value="requestParameters.ln" />
</on-start>
<decision-state id="checkConfirmation">
<if test="requestScope.code==null" then="login" else="confirmation" />
</decision-state>
<action-state id="confirmation">
<evaluate expression="login.confirmation(requestScope.ln,requestScope.code)" />
<transition on="yes" to="confirmationOk" />
<transition on="noUserFound" to="noUserFound" />
<transition on="userEnabled" to="userEnabled" />
<transition on="error" to="error" />
</action-state>
<view-state id="userEnabled" view="confirmation.xhtml">
<on-entry>
<set name="viewScope.operation" value="'enabled'" />
</on-entry>
<transition on="login" to="login" />
</view-state>
confirmation method
public String confirmation(String language,String emailCode){
logger.entry("Login.confirmation()");
String emailDecode=new String(Base64.decode(emailCode.getBytes()));
User user=userBo.getDao().findNamedQueryUnique("getUserByEmail",emailDecode);
if(user!=null){
if(!user.isEnabled()){
user.setEnabled(true);
try{
userBo.getDao().merge(user);
if(!EmailProvider.sendEmailEnabled(language,emailDecode)){
return "error";
}
}catch(DataAccessException e){
e.printStackTrace();
FlashMessages.addMessage(language,"es.project.properties.message.msg","unexpected.exception");
return "error";
}
}else{
// JSF way - FlashMessages.addMessage(language,"es.project.properties.message.msg","user.enable.confirmation");
//Spring webflow way - MessageContext context=new DefaultMessageContext();
//MessageBuilder builder=new MessageBuilder();
//context.addMessage(builder.code("user.enable.confirmation").build());
**********************************************************
*** I wish to add messages here and show in next view ****
**********************************************************
return "userEnabled";
}
}else{
//FlashMessages.addMessage(language,"es.project.properties.message.msg","user.validation.norfound.exception");
return "noUserFound";
}
return "yes";
}
I tried to follow spring webflow examples (booking-faces and booking-mvc) adding messages.properties on flow folder or creating a MessageSource bean, but I'm really lost on this matter.
I believe you are not having the messages.properties in classpath properly.
Suppose your file is in folder structure as:
src
|
|-resources
|
|-messages.properties
i.e make sure its in classpath.
You need to declare the ResourceBundleMessageSource with properties file in your servlet context xml file as:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="messages" />
</bean>
Then you can access the properties as:
<action-state id="confirmation">
...
<transition on="userEnabled" to="userEnabled">
<!--This is the property you want to set in else part of confirmation method - instead include here-->
<set name="flashScope.flashMessageKey" value="'es.project.properties.message.msg'" />
</transition>
</action-state>
Include this in view:
<%# taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>
Then you can view this message on view render by:
<fmt:message key="${flashMessageKey}"/>
I found a solution for Spring,JSF 2.2 and i18 in this post. Basically, you should define a spring message source and create a spring component which will be called for jsf outputtext tag. Then how #Prasad wrote it's necessary to set a key in a spring flow or programatically.
Properties resources are src/main/resources/error_en.properties and src/main/resources/error_es.properties
MessageSource Bean
#Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource msg=new ReloadableResourceBundleMessageSource();
msg.setBasename("classpath*:error");
return msg;
}
Spring component
#Component(value="message")
public class MessageSourceProvider extends HashMap {
private static final long serialVersionUID = 1L;
#Autowired
private MessageSource messageSource;
#Override
public String get(Object key) {
ServletRequest request = (ServletRequest) FacesContextWrapper.getCurrentInstance().getExternalContext().getRequest();
String message;
try {
message = messageSource.getMessage((String) key, null, request.getLocale());
}
catch (NoSuchMessageException e) {
message = "???" + key + "???";
}
return message;
}
}
JSF tag
<h:outputText value="#{message.unexpected}" />
I have used jasig (3.5.1) cas sever and successfully configured.It works fine. my project have another requirement. I mentioned it below
i need another login mechanism. it mean rather than using stand username password, i need corporate code,mobile number and password authentication for corporate users. so i have created another Credential class for that
public class CodeMobileNumberCredintials implements Credentials{
#NotNull
#Size(min=1,message = "required.code")
private String code;
#NotNull
#Size(min=1, message = "required.mobileNumber")
private String mobileNumber;
#NotNull
#Size(min=1, message = "required.password")
private String password;
...
}
Then i created a variable called "codeMobileNumberCredintials"in web-flow.
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
<var name="codeMobileNumberCredintials" class="org.jasig.cas.authentication.principal.CodeMobileNumberCredintials"/>
<view-state id="viewCorporateLoginForm" view="casCorporateLoginView" model="codeMobileNumberCredintials">
<binder>
<binding property="code" />
<binding property="mobileNumber" />
<binding property="password" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'codeMobileNumberCredintials'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="realCorporateSubmit">
<evaluate expression="authenticationViaFormAction.doCorporateBind(flowRequestContext, flowScope.codeMobileNumberCredintials)" />
</transition>
</view-state>
The issue is bean validation process not working for my custom login form.But normal username password form validated(when submiting form without givin username and password , It says "username and password blank"). But my custom autentication form not validated.It directly goes to controller class.
I have spent lot of time to do this. Can anyone help me to do my task.
Thank you
Amila
I had a similar problem. The easiest way to solve it, is to add new validator bean in
cas-servlet.xml Spring configuration file in cas-server-webapp module. In your case it will be:
<bean id="codeMobileNumberCredintialsValidator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"
p:messageInterpolator-ref="messageInterpolator" />
There was an issue with spring web flow, resulting an error during validation. It occurs with stack trace as below and error page displayed, instead redisplaying login form:
2013-10-04 09:48:31,683 TRACE [org.jasig.cas.web.init.SafeDispatcherServlet] - <Entering method [service with arguments [[org.apache.catalina.connector.RequestFacade#411c345a, org.apache.catalina.connector.ResponseFacade#22b1221b]]>
2013-10-04 09:48:31,686 DEBUG [org.jasig.cas.web.FlowExecutionExceptionResolver] - <Ignoring the received exception due to a type mismatch> org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException: Badly formatted flow execution key '[Ljava.lang.String;#58714e00', the expected format is 'e<executionId>s<snapshotId>'
The fix is described here CAS-1142. It help me, and it summarize with adding one line <webflow:redirect-in-same-state value="false" /> to cas-servlet.xml resulting as below:
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-attributes>
<webflow:always-redirect-on-pause value="false"/>
<webflow:redirect-in-same-state value="false" />
</webflow:flow-execution-attributes>
<webflow:flow-execution-listeners>
<webflow:listener ref="terminateWebSessionListener" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>