0 Replies Latest reply on Sep 26, 2011 8:44 AM by stephane remillieux

    transaction-management-enabled and message propagation issue

    stephane remillieux Newbie

      Hi,


      I work on technical stack with Hibernate 3.4.0.GA for persistance layer (API JPA 1.0 when we could),
      Hibernate Validator 3.1.0.GA for domain validation, Spring 3.0.6 for service layer,
      SEAM 2.2.2.Final for presentation layer and conversation management(with extended persistence context) and
      JSF 1.2 (myfaces 1.2.10, tomahawk 1.1.11 et richfaces 3.3.3) for UI Layer.



      Currently, I've got a strange problem with error messages in JSF layer (h: message or h:messages).


      My main config for this problem, is :


      Into component.xml :




      <persistence:managed-persistence-context name="entityManagerManaged"
                                          entity-manager-factory="#{springEntityManagerFactory}"
                                          auto-create="true" />
      
          <factory name="hibernateSession"
               scope="STATELESS"
               auto-create="true"
               value="#{entityManagerManaged.delegate}"/>
      
          <spring:spring-transaction platform-transaction-manager="#{transactionManager}" />
          <core:init debug="false" transaction-management-enabled="false" security enabled="false"/>



      Into applicationContext.xml :




      <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="springEntityManagerFactory">
              <property name="persistenceUnitName" value="persistenceUnitRedaction"/>
              <property name="dataSource" ref="defaultDatasource"/>
                <property name="jpaVendorAdapter">
                     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
                </property>
          </bean>
      
          <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
              <property name="entityManagerFactory" ref="springEntityManagerFactory"/>
          </bean>
      
          <!-- transaction -->
           <tx:annotation-driven transaction-manager="transactionManager"/>
           
           <!-- ======================   DATASOURCE DEFINITION   ====================== -->
           
           <!--  Configuration de Seam Session Factory -->
           <bean id="seamSessionFactory" class="org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean">
                   <property name="sessionName" value="hibernateSession"/>
           </bean>
      
           <bean id="hibernateTemplateSeam"
                class="org.springframework.orm.hibernate3.HibernateTemplate">
                <property name="sessionFactory" ref="seamSessionFactory" />
                <!-- Config pour le flush mode MANUAL/NEVER -->
                <property name="checkWriteOperations" value="false" />
           </bean>



      Also this is my use cases :


      In my component.xml, I set this property transaction-management-enabled to false.


      I've got an object number property annotated like this :




      /**
       *
       *
       * @return the number
       */
       @Column( name="number", nullable = false, unique = true )
       @NotEmpty (message="Number must be completed" )
       @Length(min = 4, max = 21)
       public String getNumber(){
           return this.number;
       }



      Into page.xml, I've got this :




      <exception class="org.springframework.dao.DataIntegrityViolationException" log="true" log-level="WARN">
         <redirect >
              <message>Object already exists</message>
         </redirect>
      </exception>



      Into my xhtml pages, I've got this :




      ...
      
      <s:validateAll >
            <t:inputText id="number" value="#{controler.object.number}" label="Object number" required="true" readonly="#{!controler.activeEditMode}" forceId="true">
                f:converter converterId="EmptyToNullConverter" />
           </t:inputText>
           <t:message for="number" styleClass="error"  />
           <br />
           </s:validateAll>
           <br />
           <t:messages globalOnly="true" styleClass="error"  />
           <br />               
           // Button qui declenche le save     dans le component seam named controler     
           <t:commandButton  action="#{controler.validate()}" rendered="#{controler.activeEditMode}" value="Validate"/>
      ...




      I call save method (ou un saveOrUpdate) with one number already exist.


      I've got exception stack like this :



      javax.servlet.ServletException: Error calling action method of component with id j_id19:j_id75
           at javax.faces.webapp._ErrorPageWriter.throwException(_ErrorPageWriter.java:582)
           at javax.faces.webapp.FacesServlet.handleLifecycleException(FacesServlet.java:298)
           at javax.faces.webapp.FacesServlet.service(FacesServlet.java:192)
           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
           at org.apache.myfaces.webapp.filter.ExtensionsFilter.doFilter(ExtensionsFilter.java:392)
           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
           at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:530)
           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
           at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
           at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:90)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
           at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
           at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
           at org.jboss.seam.web.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:42)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
           at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:206)
           at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
           at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388)
           at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515)
           at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
           at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60)
           at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
           at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
           at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
           at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
           at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
           at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
           at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
           at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
           at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
           at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929)
           at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
           at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:405)
           at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:279)
           at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:515)
           at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302)
           at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
           at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
           at java.lang.Thread.run(Thread.java:662)
      Caused by: javax.faces.FacesException: Error calling action method of component with id j_id19:j_id75
           at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:72)
           at javax.faces.component.UICommand.broadcast(UICommand.java:127)
           at org.ajax4jsf.component.AjaxViewRoot.processEvents(AjaxViewRoot.java:329)
           at org.ajax4jsf.component.AjaxViewRoot.broadcastEventsForPhase(AjaxViewRoot.java:304)
           at org.ajax4jsf.component.AjaxViewRoot.processPhase(AjaxViewRoot.java:261)
           at org.ajax4jsf.component.AjaxViewRoot.processApplication(AjaxViewRoot.java:474)
           at org.apache.myfaces.lifecycle.InvokeApplicationExecutor.execute(InvokeApplicationExecutor.java:32)
           at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:103)
           at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:76)
           at javax.faces.webapp.FacesServlet.service(FacesServlet.java:183)
           ... 44 more
      Caused by: javax.faces.el.EvaluationException: javax.el.ELException: /pages/affaire.xhtml @124,124 action="#{controler.validate()}": org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [update Object set mailSuiEch=?, fraisRepro=?, number=?, objet=? where id=?]; constraint [REDACV3.SYS_C00395532]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
           at javax.faces.component._MethodExpressionToMethodBinding.invoke(_MethodExpressionToMethodBinding.java:82)
           at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:57)
           ... 53 more
      Caused by: javax.el.ELException: /pages/affaire.xhtml @124,124 action="#{controler.validate()}": org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [update Object set mailSuiEch=?, fraisRepro=?, number=?, objet=? where id=?]; constraint [REDACV3.SYS_C00395532]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
           at com.sun.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:74)
           at javax.faces.component._MethodExpressionToMethodBinding.invoke(_MethodExpressionToMethodBinding.java:78)
           ... 54 more
      Caused by: org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [update Object set mailSuiEch=?, fraisRepro=?, number=?, objet=? where id=?]; constraint [REDACV3.SYS_C00395532]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
           at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:637)
           at org.springframework.orm.hibernate3.SpringSessionSynchronization.translateException(SpringSessionSynchronization.java:160)
           at org.springframework.orm.hibernate3.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:148)
           at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:95)
           at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:927)
           at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:737)
           at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
           at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
           at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
           at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
           at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
           at $Proxy45.save(Unknown Source)
           at com.agysoft.marco.redaction.web.AffaireControler.valider(AffaireControler.java:69)
           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
           at java.lang.reflect.Method.invoke(Method.java:597)
           at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
           at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:32)
           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:56)
           at org.jboss.seam.transaction.RollbackInterceptor.aroundInvoke(RollbackInterceptor.java:28)
           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
           at org.jboss.seam.core.BijectionInterceptor.aroundInvoke(BijectionInterceptor.java:77)
           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
           at org.jboss.seam.core.ConversationInterceptor.aroundInvoke(ConversationInterceptor.java:65)
           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
           at org.jboss.seam.core.MethodContextInterceptor.aroundInvoke(MethodContextInterceptor.java:44)
           at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
           at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:107)
           at org.jboss.seam.intercept.JavaBeanInterceptor.interceptInvocation(JavaBeanInterceptor.java:185)
           at org.jboss.seam.intercept.JavaBeanInterceptor.invoke(JavaBeanInterceptor.java:103)
           at com.agysoft.marco.redaction.web.AffaireControler_$$_javassist_seam_3.valider(AffaireControler_$$_javassist_seam_3.java)
           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
           at java.lang.reflect.Method.invoke(Method.java:597)
           at org.jboss.el.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:335)
           at org.jboss.el.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:280)
           at org.jboss.el.parser.AstMethodSuffix.getValue(AstMethodSuffix.java:59)
           at org.jboss.el.parser.AstMethodSuffix.invoke(AstMethodSuffix.java:65)
           at org.jboss.el.parser.AstValue.invoke(AstValue.java:96)
           at org.jboss.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:276)
           at com.sun.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:68)
           ... 55 more
      Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
           at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94)
           at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
           at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
           at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
           at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
           at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
           at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
           at java.lang.reflect.Method.invoke(Method.java:597)
           at org.jboss.seam.persistence.HibernateSessionInvocationHandler.invoke(HibernateSessionInvocationHandler.java:82)
           at $Proxy68.flush(Unknown Source)
           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
           at java.lang.reflect.Method.invoke(Method.java:597)
           at org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean$SeamManagedSessionHandler.invoke(SeamManagedSessionFactoryBean.java:300)
           at $Proxy69.flush(Unknown Source)
           at org.springframework.orm.hibernate3.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:145)
           ... 95 more
      Caused by: java.sql.BatchUpdateException: ORA-00001: unique constraint (REDACV3.SYS_C00395532) violated
      
           at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10070)
           at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:213)
           at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)
           at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
           at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)




      In this case, Rollback is done correctly and I've got error message Object already exists displayed correctly to my user.
      I precise that in this config, hibernate validator messages works correctly.



      My problem is when I would rollback transaction into presentation layer (Seam component).


      I forced this behavior with  this lines :




            public String validate() {
                boolean test = true;
                this.objectService.save(this.object);
                if (test) {
                     throw new RuntimeException("Test runtime exception in presentation layer");
                }
                return "validate";
           }



      With this lines and config transaction-management-enabled to false , DataIntegrityViolationException is raised , Rollback is done and I've got error messages Object already exists correctly displayed.


      On the other side, if number filled by user is unique and I call my validate method also save method (spring and hibernate layer) do flush and commit and RuntimeException raised with redirect error message display but rollback comes too late (or maybe don't rollback) and my object is persist with my number update.


      I precise that in this alternate config, hibernate validator messages works also correctly.



      Consequently, I activate


      transaction-management-enabled="true".




      But I've got a very strange behavior :


      When DataIntegrityViolationException is raised (number already exists in DBRMS),
      Rollback is done but error message Object already exists DIDN'T DISPLAY to my user.


      On the other side, if number filled by user is unique and I call my validate method also save method (spring and hibernate layer) don't comit and RuntimeException raised with redirect error message display and THIS TIME rollback works fine and my object is not persist with my update.


      I test many things to resolve this issue:


      - Add into component.xml, this lines



      <component name="org.jboss.seam.transaction.facesTransactionEvents">
          <property name="transactionFailedMessageEnabled">false</property>
      </component>





      - Add into page.xml this line (to redirect to another page) :



      <exception class="org.springframework.dao.DataIntegrityViolationException" log="true" log-level="WARN">
                 <redirect view-id="/pages/errors.xhtml" >
                   <message>object already exists</message>
                 </redirect>
           </exception>



      - Replace into xhtml this lines :



      <t:messages globalOnly="true" styleClass="error"  />



      by



      <t:messages styleClass="error"  />




      I saw other ressources and JIRA bugs reports :


      - http://seamframework.org/Documentation/UnifiedErrorPageAndExceptionHandling


      - http://www.seamframework.org/Community/BugInFacesMessagePropagation


      - http://seamframework.org/Community/EndlessLoopInExceptionHandlingDuringCommit


      - https://issues.jboss.org/browse/JBSEAM-1855


      - https://issues.jboss.org/browse/JBSEAM-2139


      - https://issues.jboss.org/browse/JBSEAM-4168



      I didn't find any solution to my issue and many people don't talk about this property :



      transaction-management-enabled="true"




      I would really activate this property to manage exception and transaction in presentation/UI layer but I would also display error messages into below layer (service / dao ) like I don't active this property.


      I wouldn't add catch exception or something like this (transform exception, etc) to manage exception in my presentation layer.
      I find code very clean like this but if I must do...


      I rather not use


      @Begin(flushMode=manual)



      for other reason that above ( It's maybe not a solution :) ).


      Can you help me please to find a solution or a workaround ? Maybe is a bug ?


      Thanks Stephane.