Announcement Announcement Module
Collapse
No announcement yet.
Updating Last Login Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Updating Last Login

    I have a hibernate-spring-acegi application that is using annotations for transactions and security. My configuration was copied from the acegi sample app using the DefaultAdvisorAutoProxyCreator and the TransactionAttributeSourceAdvisor for transactions and the MethodDefinitionSourceAdvisor for security. My implementation of UserDetailsService uses Hibernate to get my Account domain object, and adapt it into a UserDetails implementation. Other than that, its all straight out of the box.

    And it all works great.

    Then I tried to add an ApplicationListener -- LastLoginListener -- that hears the
    AuthenticationSuccessEvent and gets the Account object in the passed authentication (from the UserDetails adapter), update lastLogin to now, and save it.

    And I get an exception that is killing me.

    Code:
    org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition
    	org.springframework.orm.hibernate3.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1098)
    	org.springframework.orm.hibernate3.HibernateTemplate$18.doInHibernate(HibernateTemplate.java:692)
    	org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:366)
    	org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:690)
    	org.osler.dao.hibernate.AbstractDaoHibernate.save(AbstractDaoHibernate.java:60)
    	org.osler.accounts.services.impl.AccountServiceImpl.updateLastLogin(AccountServiceImpl.java:52)
    	org.osler.security.acegi.LastLoginListener.onApplicationEvent(LastLoginListener.java:50)
    	org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:45)
    	org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:217)
    	org.acegisecurity.providers.ProviderManager.doAuthentication(ProviderManager.java:217)
    	org.acegisecurity.AbstractAuthenticationManager.authenticate(AbstractAuthenticationManager.java:49)
    	org.acegisecurity.ui.webapp.AuthenticationProcessingFilter.attemptAuthentication(AuthenticationProcessingFilter.java:90)
    	org.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:228)
    	org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
    	org.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter(HttpSessionContextIntegrationFilter.java:220)
    	org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:303)
    	org.acegisecurity.util.FilterChainProxy.doFilter(FilterChainProxy.java:173)
    	org.acegisecurity.util.FilterToBeanProxy.doFilter(FilterToBeanProxy.java:120)
    	org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:174)
    	org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:77)
    I figured that I had mistakenly marked the getAccountByUsername that is called by the loadUserByUsername method as a read-only transaction, so that the account loaded there could not be modified. But I didn't. And when I turn on logging for transactions it indicates that indeed, the account is not loaded as readOnly:

    Code:
    Adding transactional method [getAccountByUsername] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
    In addition, if I execute the same listener manually (i.e. not via the
    ApplicationListener), it works fine. I don't really even know where to start!

    Here s the LastLoginListener:
    Code:
    public void onApplicationEvent(ApplicationEvent event) {
    		if (event instanceof AuthenticationSuccessEvent) {
    			AuthenticationSuccessEvent successEvent = (AuthenticationSuccessEvent) event;
    			Authentication authentication = successEvent.getAuthentication();
    			UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    			String username = userDetails.getUsername();
    			accountService.updateLastLogin(username);
    		}
    	}
    and AccountServiceImpl (note that there are know annotations. there is
    a global @Transactional on the AccountServiceImpl class, but no specific
    attributes for individual methods):

    Code:
    public void updateLastLogin(String username) {
    		Account account = accountDao.getByUsername(username);
    		if (account != null) {
    			account.setLastLogin(new Date());
    			accountDao.save(account);
    		}
    	}
    The transaction/security interceptors and advisors:
    Code:
     <bean
      class="org.acegisecurity.intercept.method.MethodDefinitionAttributes" id="objectDefinitionSource">
      <property name="attributes">
       <bean class="org.acegisecurity.annotation.SecurityAnnotationAttributes"/>
      </property>
     </bean>
     <bean
      class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor" id="securityInterceptor">
      <property name="validateConfigAttributes" value="false"/>
      <property name="authenticationManager" ref="authenticationManager"/>
      <property name="accessDecisionManager" ref="accessDecisionManager"/>
      <property name="objectDefinitionSource"><ref bean="objectDefinitionSource" /></property>
     </bean>
    
     <bean autowire="constructor"
      class="org.acegisecurity.intercept.method.aopalliance.MethodDefinitionSourceAdvisor" id="methodSecurityAdvisor"/>
    
     <bean id="autoproxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
     	<property name="proxyTargetClass" value="true"></property>
     </bean>
        <bean id="transactionAdvisor" class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor" autowire="constructor"/>
     <bean
      class="org.springframework.transaction.interceptor.TransactionInterceptor" id="transactionInterceptor">
      <property name="transactionManager" ref="transactionManager"/> 
      <property name="transactionAttributeSource">
       <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
    
      </property>
     </bean>
    My event broadcaster in Spring is a simple, single threaded multicaster, so it's not that I'm accessing an object from a different Hibernate session.

    I'm really at a loss.

  • #2
    Solved

    This is tricky. I missed the following in my log:

    Code:
    Bean 'accountService' is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
    huh? So that definitely explains the problem. But why? Turns out that accountService was instantiated before advice could be applied auto-proxy.
    Why? because it is depended upon by my implementation of UserDetailsService,
    which in turn is used by the the daoAuthenticationProvider, which in turn is used by the authenticationManager, which is used by the securityFilter, which seems like it must be instantiated first.

    Fine, whatever. I just manually stick the transactional proxy into the accountService, and it all just works. 8 hours of my life I'm never getting back! Oh well.

    Code:
     <bean id="accountService"
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
      <property name="transactionManager" ref="transactionManager"/>
      <property name="target" >
       <bean class="org.osler.accounts.services.impl.AccountServiceImpl" id="accountServiceTarget">
      <property name="accountDao">
       <bean autowire="byType"
      class="org.osler.accounts.dao.hibernate.AccountDaoHibernate"/>
      </property>
     </bean>
      </property>
       <property name="transactionAttributeSource">
        <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
      </property>
    </bean>

    Comment

    Working...
    X