9 Replies Latest reply on Apr 10, 2008 2:52 PM by eirirlar

    StaleObjectStateException return to same page

    coralfe

      I am trying to handle concurrent modifications of an entity.


      The code in the seam samples (exception in pages.xml) works, however, it is not user friendly enough. We are using modal dialogs and the redirect to an error page is very inconvenient.


      Basically I would like to handle these exceptions by letting the user know that someone else has changed the entity and reloading the entity while remaining on (or reloading) the same page/modal.


      I would be able to handle this in a catch block, however seam intercepts the exception before that point.


      Does anyone have suggestions on how to handle this? 

        • 1. Re: StaleObjectStateException return to same page
          coralfe

          I've even tried the interceptor mentioned
          here
          to no avail!!


          • 2. Re: StaleObjectStateException return to same page
            coralfe

            For anyone else with this issue:-


            Solved it to a satisfactory level by calling
            Redirect.instance().captureCurrentView(); in the method that opens the dialog and putting



            <exception class="javax.persistence.OptimisticLockException">
                 <redirect view-id="#{redirect.viewId}">
                      <message>#{messages['error.OptimisticLockException']}
                      </message>
                 </redirect>
            </exception>
                 



            In pages.xml


            Wasn't quite the control I was hoping for but at least its usable.

            • 3. Re: StaleObjectStateException return to same page

              I'm having a similar problem, could you post some code samples?

              • 4. Re: StaleObjectStateException return to same page
                coralfe

                Not much else really...


                Method called when open modal:



                //manual so we dont get concurrentexception at odd times     
                @Begin(flushMode=FlushModeType.MANUAL) 
                
                public String prepAlterStrategy(Object whatever) {
                  //so concurrent calls errors end up in same place
                  // and pages.xml redirect.viewId is set     
                  // in this place remembers page before modal is opened     
                  Redirect.instance().captureCurrentView();
                  ...      
                  return "prepAlter";//faces navigation in modal
                }



                page opening modal:



                <rich:menuItem submitMode="ajax" value="edit" action="#{backingBean.prepAlterStrategy(stratBubble)}"  reRender="modalContent" oncomplete="javascript:Richfaces.showModalPanel('sm_modal')" />




                and the submit button on the modal calls and action with:



                object = entityManager.merge(object);
                entityManager.flush();



                Your viewid page also needs a h:messages
                Then pages.xml as above.


                Not sure if this best solution.





                • 5. Re: StaleObjectStateException return to same page

                  How do you handle the corrupt hibernate session? From the book Java Persistence with Hibernate:



                  All exceptions thrown by Hibernate are fatal. This means you have to roll back the database transaction and close the current Session. You arent't allowed to continue working with a Session that threw an exception.

                  I've handled it the following way, which is a bit hacky but working: In a stateful conversational sesssion bean with flushmode.manual, I have a private method called persist() which public methods of the bean calls when they need to flush to the database. The persist method takes care of any potential StaleObjectStateException. It does so by getting another pojo seamcomponent which I've called ConcurrencyResolver. ConcurrencyResolver has a method which is REQUIRES_NEW that replaces the hibernate session that resides in this conversation.



                  private void persist() {
                       try {
                            vrvaskSession.flush();
                       } catch (StaleObjectStateException sose) {
                            ConcurrencyResolver concurrencyResolver = (ConcurrencyResolver) Component
                                      .getInstance("concurrencyResolver");
                            hibernateSession = concurrencyResolver.replaceSession("hibernateSession");
                            object = (ObjectClass) hibernateSession.get(object
                                      .getClass(), object.getId());
                            facesMessages.add(FacesMessage.SEVERITY_WARN,
                                      "#{messages.staleObjectStateException}");
                       }
                  }




                  @Name("concurrencyResolver")
                  public class ConcurrencyResolver {
                       
                  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
                  public Session replasceSession(String sessionName) {
                       // Get session wrapper from conversation
                       ManagedHibernateSession currentSession = (ManagedHibernateSession) ScopeType.CONVERSATION
                                 .getContext().get(sessionName);
                       // End the transaction so 'sessionWillPassivate' can do it's job
                       UserTransaction transaction = Transaction.instance();
                       try {
                            if (transaction.isActive())
                                 transaction.rollback();
                            currentSession.getSession().clear();
                            // Closes the current session
                            currentSession.sessionWillPassivate(null);
                            // Creates a new session
                            return currentSession.getSession();
                  
                       } catch (Exception e) {
                            throw new RuntimeException(e);
                       }
                  }
                  
                  }



                  This solution would be less hacky if Seam had some api support for replacing the current hibernate session, or for optimistic locking exception handling in general.

                  • 6. Re: StaleObjectStateException return to same page

                    vrvaskSession.flush();

                    Should be hibernateSession.flush()



                    public Session replasceSession(String sessionName) {

                    Should be public Session replaceSession(String sessionName)

                    • 7. Re: StaleObjectStateException return to same page
                      christian.bauer

                      This has been long on the TODO list for Hibernate, sometimes disguised as Savepoints I think. From what I read it is one of the features for Hibernate 3.3.


                      In the book it's referred to as Merge Changes resolution of optimistic locking failures. We don't have great support for that in Hibernate or Seam.


                      The other useful option First Commit Wins is doable with exception handling in pages.xml and redirect/action/event/message, etc. What I am missing is handling it without a redirect but a partial page refresh, not sure that works.


                      So the best you can do right now without a lot of extra coding is tell your users Oops, somebody modified this while you worked on it and ask them to use the Back button and copy the stuff they want to rescue. Then they need to (somehow) call a reset action on the conversation and start over from the beginning. This is less than ideal than a nice merge dialog that opens without a redirect.

                      • 8. Re: StaleObjectStateException return to same page
                        coralfe

                        Our database calls are handled in a separate class which gets injected and that gets its entityManager injected with @In. Perhaps seam is reinjecting when hibernate's entity manager bombs, as I am able to continue without problem. I don't even lose my conversation.


                        This might also explain why I have never been able to actually catch the StaleObjectStateException!!!


                        I don't know seam's innards well enough to comment.
                        I am not doing anything specific with transactions. Seam is handling them and rolling back as required.

                        • 9. Re: StaleObjectStateException return to same page

                          Perhaps seam is reinjecting when hibernate's entity manager bombs.

                          Although it would be a great feature if seam could do this, unfortunately it's not what happens (check the object id of your session or entityManager in a debugger). That's why I had to write the code above to do it myself for a given conversation.


                          as I am able to continue without problem.

                          Although you haven't experienced problems yet, you might do so unexpectedly later in the conversation (according to the statement from the hibernate book above).


                          I don't even lose my conversation.

                          Unless you end it explicitly with an <end-conversation/> in pages.xml or something similar, you won't lose it. But your hibernate session will still be in bad shape.


                          This might also explain why I have never been able to actually catch the StaleObjectStateException!!!

                          I say you mentioned entityManager. If you're not using a hibernate session (but a JPA entitymanager instead), you won't get StaleObjectStateException but OptimisticLockException.