3 Replies Latest reply on Apr 26, 2008 7:43 AM by matt.drees

    Spring Injected Models

    nealhaggard

      Spring Injected Models and Seam



      I'm trying to use a Spring-defined service and framework/model layer that I have created and jarred up from a separate project to be reused in the Web layer of my application. Using my service objects have worked flawlessly, I was able to retrieve objects from my service layer and show them in a table without an issue.  Now I'm trying to use Seam to create a new account for a user on the web layer by injecting an 'Account' model object from my Spring definition of a JPA/Hibernate POJO. 


      The problem I'm seeing is that Seam appears to be injecting a fresh account into my RegisterBean with every request.  How do I set up the RegisterBean with one Account object that'll stick around until I'm done registering the user?  (Note that I'm using a4j's support to re-validate the Account object with every onblur, so it'll need to stick through that).


      Am I going about this completely wrong?  I'm new to Seam, but not JSF, so I have enough knowledge to be dangerous, but really am still grasping to understand the state/stateless nature of doing this with POJOs instead of EJBs. I really don't want to have to rewrite my Model objects as EJBs as the JPA/Hibernate POJO is working great in the rest of the app. 


      I'm including what I feel are the relevant code pieces to help you guys answer my question.  If you need more info, please let me know and I'll post more of my config.


      components.xml


      <?xml version="1.0" encoding="UTF-8"?>
      <components xmlns="http://jboss.com/products/seam/components"
                  xmlns:core="http://jboss.com/products/seam/core"
                  xmlns:persistence="http://jboss.com/products/seam/persistence"
                  xmlns:drools="http://jboss.com/products/seam/drools"
                  xmlns:bpm="http://jboss.com/products/seam/bpm"
                  xmlns:security="http://jboss.com/products/seam/security"
                  xmlns:mail="http://jboss.com/products/seam/mail"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xmlns:spring="http://jboss.com/products/seam/spring"
                  xsi:schemaLocation=
                      "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.0.xsd 
                       http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.0.xsd 
                       http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.0.xsd
                       http://jboss.com/products/seam/bpm http://jboss.com/products/seam/bpm-2.0.xsd
                       http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.0.xsd
                       http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.0.xsd
                       http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.0.xsd
                       http://jboss.com/products/seam/spring
                       http://jboss.com/products/seam/spring-2.0.xsd">
      
         <core:init debug="@debug@" jndi-pattern="@jndiPattern@"/>
           
         <core:manager concurrent-request-timeout="500" 
                       conversation-timeout="120000" 
                       conversation-id-parameter="cid"/>
          
         <persistence:managed-persistence-context name="entityManager"
                                           auto-create="true"
                                entity-manager-factory="#{MyAppEntityManagerFactory}"/>
      
         <persistence:entity-manager-factory name="MyAppEntityManagerFactory" 
                            persistence-unit-name="MyApp"/>
         
         <drools:rule-base name="securityRules">
             <drools:rule-files><value>/security.drl</value></drools:rule-files>
         </drools:rule-base>
      
         <security:identity authenticate-method="#{authenticator.authenticate}"
                                 security-rules="#{securityRules}"
                                    remember-me="true"/>
                                    
         <event type="org.jboss.seam.security.notLoggedIn">
             <action execute="#{redirect.captureCurrentView}"/>
         </event>
         <event type="org.jboss.seam.security.loginSuccessful">
             <action execute="#{redirect.returnToCapturedView}"/>
         </event>
         
         <spring:context-loader>
           <spring:config-locations>
             <value>classpath:coreResourceContext.xml</value>
             <value>classpath:daoContext.xml</value>
             <value>classpath:modelContext.xml</value>
           </spring:config-locations>
         </spring:context-loader>
            
      </components>
      



      menu.xhtml


      <rich:toolBar
          xmlns="http://www.w3.org/1999/xhtml"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:s="http://jboss.com/products/seam/taglib"
          xmlns:rich="http://richfaces.org/rich">
          <rich:toolBarGroup>
              <h:outputText value="#{projectName}:"/>
              <s:link view="/home.xhtml" value="Home"/>
              <s:link view="/accountList.xhtml" value="Accounts" rendered="#{identity.loggedIn}" />
          </rich:toolBarGroup>
          <!-- @newMenuItem@ -->
          <rich:toolBarGroup location="right">
              <h:outputText value="Welcome, #{identity.username}!" rendered="#{identity.loggedIn}"/>
              <s:link view="/login.xhtml" value="Login" rendered="#{not identity.loggedIn}"/>
              <s:link view="/register.xhtml" value="Register" rendered="#{not identity.loggedIn}" conversationScope="begin" />
              <s:link view="/home.xhtml" action="#{identity.logout}" value="Logout" rendered="#{identity.loggedIn}"/>
          </rich:toolBarGroup>
      </rich:toolBar>
      



      RegisterBean.java


      @Name("registerAccount")
      @Scope(ScopeType.CONVERSATION)
      public class RegisterBean {
           @In("#{accountService}")
           private AccountService accountService;
      
           @In("#{account}")
           @Out
           private Account account;
      
           @In
           private FacesMessages facesMessages;
      
           private boolean registered = false;
      
           /**
            * Do the work of creating an account.
            * 
            * @return
            */
           @End
           public void register() {
                LogicMessages messages = new LogicMessages();
      
                try {
                     messages = accountService.create(account);
                     registered = true;
                } catch (LogicException e) {
                     messages.addAll(e.getMessages());
                }
                WebUtil.addLogicMessages(messages, facesMessages);
           }
      
           /**
            * @return the registered
            */
           public boolean isRegistered() {
                return registered;
           }
      }
      



      register.xhtml


      <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <ui:composition xmlns="http://www.w3.org/1999/xhtml"
           xmlns:s="http://jboss.com/products/seam/taglib"
           xmlns:ui="http://java.sun.com/jsf/facelets"
           xmlns:f="http://java.sun.com/jsf/core"
           xmlns:h="http://java.sun.com/jsf/html"
           xmlns:rich="http://richfaces.org/rich"
           xmlns:a4j="http://richfaces.org/a4j"
           template="layout/template.xhtml">
      
           <ui:define name="body">
                <h:form id="registerForm">
                     <h:messages globalOnly="true" />
      
                     <rich:panel>
                          <f:facet name="header">Register a new Account</f:facet>
      
                          <fieldset>
                               <s:decorate id="nameDecoration"     template="layout/edit.xhtml">
                                    <ui:define name="label">Name:</ui:define>
                                    <h:inputText value="#{account.name}" required="true">
                                         <a4j:support event="onblur" reRender="nameDecoration" />
                                    </h:inputText>
                               </s:decorate>
                               <s:decorate id="emailDecoration" template="../layout/edit.xhtml">
                                    <ui:define name="label">Email:</ui:define>
                                    <h:inputText value="#{account.email}" required="true">
                                         <a4j:support event="onblur" reRender="emailDecoration" />
                                    </h:inputText>
                               </s:decorate>
                               <s:decorate id="securityQuestionDecoration"     template="layout/edit.xhtml">
                                    <ui:define name="label">SecurityQuestion:</ui:define>
                                    <h:inputText value="#{account.securityQuestion}" required="true">
                                         <a4j:support event="onblur" reRender="securityQuestionDecoration" />
                                    </h:inputText>
                               </s:decorate>
                               <s:decorate id="securityAnswerDecoration" template="layout/edit.xhtml">
                                    <ui:define name="label">Security Answer:</ui:define>
                                    <h:inputText value="#{account.securityAnswer}" required="true">
                                         <a4j:support event="onblur" reRender="securityAnswerDecoration" />
                                    </h:inputText>
                               </s:decorate>
                          </fieldset>
                     </rich:panel>
      
                     <div class="actionButtons">
                          <h:commandButton value="Register!" action="#{registerAccount.register}" />
                     </div>
                </h:form>
           </ui:define>
      </ui:composition>
      



      daoContext.xml (relevant bit)


           <bean id="accountDao"
                class="myapp.AccountJpaDaoImpl">
                <property name="entityManagerFactory"
                     ref="entityManagerFactory" />
           </bean>
      
           <bean id="accountService"
                class="myapp.AccountServiceImpl">
                <constructor-arg>
                     <ref bean="accountDao" />
                </constructor-arg>
           </bean>
      



      modelContext.xml (relevant bit)


              <bean id="account"
                    class="myapp.AccountImpl"
                    scope="prototype" />
      
      

        • 1. Re: Spring Injected Models
          matt.drees

          Seam injects dependencies immediately before every method invocation, and it looks like you've configured #{account} to provide a new AccountImpl each time it's referenced (if I'm reading your modelContext.xml right).  So Seam is correctly giving you a new Account every invocation, which probably isn't what you want.


          If you're committed to using a spring factory for account, you might do something like this:


          @Name("registerAccount")
          @Scope(ScopeType.CONVERSATION)
          public class RegisterBean {
               @In("#{accountService}")
               private AccountService accountService;
          
               private Account account;
          
               @In
               private FacesMessages facesMessages;
          
               @In
               private Expressions expressions;
          
               private boolean registered = false;
          
               /**
                * Do the work of creating an account.
                * 
                * @return
                */
               @End
               public void register() {
                    account = (Account) expressions.createValueExpression("#{account}").getValue(); //get from Spring
                    LogicMessages messages = new LogicMessages();
          
                    try {
                         messages = accountService.create(account);
                         registered = true;
                    } catch (LogicException e) {
                         messages.addAll(e.getMessages());
                    }
                    WebUtil.addLogicMessages(messages, facesMessages);
               }
          
               /**
                * @return the registered
                */
               public boolean isRegistered() {
                    return registered;
               }
          }
          
          



          I definitely recommend that you avoid outjecting a variable with the same name as a Spring bean; I imagine one would always shadow the other. 

          • 2. Re: Spring Injected Models
            nealhaggard

            Excellent, thank you for the response.


            I actually got past this yesterday by doing the following:


            RegistrarBean.java


            @Name('registrar')
            @Scope(ScopeType.CONVERSATION)
            public class RegistrarBean implements Registrar {
                 @In("#{accountService}")
                 private AccountService accountService;
            
                 // this is used to pull a fresh account from Spring
                 @In("#{account}")
                 private Account account;
            
                 // hold the 'new' account here
                 private Account newAccount;
            
                 @In
                 private FacesMessages facesMessages;
            
                 private boolean registered = false;
            
                 @End
                 public void register() {
                      LogicMessages messages = new LogicMessages();
            
                      try {
                           messages = accountService.create(newAccount);
                           registered = true;
                      } catch (LogicException e) {
                           messages.addAll(e.getMessages());
                      }
                      WebUtil.addLogicMessages(messages, facesMessages);
                 }
            
                 public boolean isRegistered() {
                      return registered;
                 }
            
                 @Out
                 public Account getNewAccount() {
                      if (newAccount == null) {
                           newAccount = account;
                      }
                      return newAccount;
                 }
            
                 @In(required = false)
                 public void setNewAccount(Account account) {
                      this.newAccount = account;
                 }
            }
            



            Thank you very much for the code sample, I was looking for a way to do value bindings in Seam as I was hoping to do just what you did to retrieve the fresh account.


            I'm currently blocked trying to figure out the mess of my configuration files and how I tried to set up my entity manager factories with Spring and transactions.  For some reason transactions aren't starting naturally, and even trying to use RESOURCE LOCAL and starting transactions manually, it's failing.


            I'll get there eventually I'm sure.  Thanks again for the response.

            • 3. Re: Spring Injected Models
              matt.drees

              I'm afraid I can't help with Spring and transactions, but if you get stuck, someone here can, I'm sure.
              Good luck!