cancel
Showing results for 
Search instead for 
Did you mean: 

Problem with Spring autowiring within a Service Task

gaston
Champ in-the-making
Champ in-the-making
Hi all,
I'm having problems calling a Spring autowired Repository from a task. The idea is to have the Service Task call one of the methods of a Service. Problem is, after executing the service task, I'm getting an "Unknown property in expression" if using a delegateExpression (similar to what's mentioned in this other thread), or a null pointer exception, as a result of the @Autowired Repository in myService not being properly instantiated).

The Service Task is being run as
 
   activiti:delegateExpression="${myBeanServiceImpl}")

My service bean is as follows:

   @Service("myBeanServiceImpl")
   public class MyBeanServiceImpl implements JavaDelegate {

      @Autowired
      private TestServiceDummy serviceDummy;

      @Override
      public void execute(DelegateExecution execution) {
         serviceDummy.save(new User()); // <– NPE here, the userRepository is null when called. ie: not being properly autowired.
           }

   // Getters and Setters for the testServiceDummy omitted for brevity

   }



Here's my TestServiceDummyImpl:


   @Service(value="testServiceDummyImpl")
   @Transactional(readOnly = true)
   public class TestServiceDummyImpl implements TestServiceDymmy {
      
      @Autowired
      private UserRepository userRepository;
      
      @Override
      public User save(User user) {
         userRepository.save(user);
      }
      
      // Getters && Setters for the repository
   
   }   

And a simple JPARepository from SpringData


   public interface UserRepository extends JPARepository<User, Long> {
   }
   
The same thing works without problems when called from our webapp (calling the service as a @ManagedProperty works ok) so the embedded project's config seems to be ok.

And here's Activiti Explorer's applicationContext file:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
   xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:jpa="http://www.springframework.org/schema/data/jpa"
   xmlns:jee="http://www.springframework.org/schema/jee"

   xsi:schemaLocation="
         http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                  http://www.springframework.org/schema/tx
                  http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context-3.1.xsd
         http://www.springframework.org/schema/data/jpa
         http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
         http://www.springframework.org/schema/jdbc
         http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
         http://www.springframework.org/schema/jee
         http://www.springframework.org/schema/jee/spring-jee-3.1.xsd">

   <context:property-placeholder location="classpath*:jdbc.properties" />

   <!– Scan this classpath for annotated components that will be auto-registered
      as Spring beans –>
   <context:annotation-config />

   <!– scan the embedded project's components –>
        <context:component-scan base-package="edu.bedelias.*" />

   <jpa:repositories base-package="edu.bedelias.repositories*" />

   <bean
      class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

   <!– Automatically translate hibernate/jpa exceptions into Spring's generic
      DataAccessException hierarchy for those classes annotated with Repository –>
   <bean
      class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

   <!– JPA Entity Manager Factory –>
   <bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
      <property name="packagesToScan">
         <list>
            <value>edu.bedelias.*</value>
         </list>
      </property>
      <property name="jpaProperties">
         <props>
            <!– set HibernateJpaVendorAdapter's behavior: 'create' = build a new
               DB on each run; 'update' = modify an existing database; 'create-drop' = 'create'
               and also drops tables when Hibernate closes; 'validate' = makes no changes
               to the database –>
            <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
         </props>
      </property>
   </bean>

   <bean id="hibernateJpaVendorAdapter"
      class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
      <property name="showSql" value="true" />
      <property name="generateDdl" value="false" />
      <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
   </bean>

   <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
      destroy-method="close">
      <property name="driverClass" value="${jdbc.driverClass}" />
      <property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
      <property name="user" value="${jdbc.user}" />
      <property name="password" value="${jdbc.password}" />
      <property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
      <property name="maxStatements" value="${jdbc.maxStatements}" />
      <property name="minPoolSize" value="${jdbc.minPoolSize}" />
   </bean>

   <!– Transaction Manager is defined –>
   <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="entityManagerFactory" ref="entityManagerFactory" />
      <property name="dataSource" ref="dataSource" />
   </bean>

   <!– Hijack the current @Session scope annotation on each @Service and make
      it last only for the duration of the thread –>
   <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
      <property name="scopes">
         <map>
            <entry key="session">
               <bean class="org.springframework.context.support.SimpleThreadScope" />
            </entry>
         </map>
      </property>
   </bean>

   <!– Enable the configuration of transactional behavior based on annotations –>
   <tx:annotation-driven />

   <bean id="dbProperties"
      class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="location" value="classpath:db.properties" />
      <!– Allow other PropertyPlaceholderConfigurer to run as well –>
      <property name="ignoreUnresolvablePlaceholders" value="true" />
   </bean>

   <bean id="demoDataGenerator" class="org.activiti.explorer.demo.DemoDataGenerator">
      <property name="processEngine" ref="processEngine" />
   </bean>

   <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
      <property name="dataSource" ref="dataSource" />
      <property name="transactionManager" ref="transactionManager" />
      <property name="databaseSchemaUpdate" value="true" />
      <property name="jobExecutorActivate" value="true" />
      <property name="customFormTypes">
         <list>
            <ref bean="userFormType" />
         </list>
      </property>
   </bean>

   <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"
      destroy-method="destroy">
      <property name="processEngineConfiguration" ref="processEngineConfiguration" />
   </bean>

   <bean id="repositoryService" factory-bean="processEngine"
      factory-method="getRepositoryService" />
   <bean id="runtimeService" factory-bean="processEngine"
      factory-method="getRuntimeService" />
   <bean id="taskService" factory-bean="processEngine"
      factory-method="getTaskService" />
   <bean id="historyService" factory-bean="processEngine"
      factory-method="getHistoryService" />
   <bean id="managementService" factory-bean="processEngine"
      factory-method="getManagementService" />
   <bean id="identityService" factory-bean="processEngine"
      factory-method="getIdentityService" />

   <bean id="activitiLoginHandler" class="org.activiti.explorer.ui.login.DefaultLoginHandler">
      <property name="identityService" ref="identityService" />
   </bean>

   <!– Include the UI-related wiring. This UI context will be used in the
      alfresco activiti admin UI –>
   <import resource="activiti-ui-context.xml" />

   <!– Custom form types –>
   <bean id="userFormType" class="org.activiti.explorer.form.UserFormType" />

</beans>
 

I've tried going for an "Expression" approach, as suggested [url=http://forums.activiti.org/en/viewtopic.php?f=8&t=3276&p=13020
]here and here to no avail.

If anyone is curious, the URL of the project is here: http://code.google.com/p/tecnoinf-activiti/source/browse/

Thanks in advance,

Gaston
18 REPLIES 18

jbarrez
Star Contributor
Star Contributor
Okay, that is pretty embarrassing ….

Anyway … I really don't see anything wrong: You've got a SpringEngineConfiguration, you've enabled component scan for the service. So it should just do what it's supposed to do.

Does the service task is in the right package (probably you've already checked that …).

We've got tests in our test suite (module activiti-spring) that test the delegateExpression in Spring. Since that test still runs green, it should work as advertised :s

gaston
Champ in-the-making
Champ in-the-making
Yeah, I'm at a loss here. Seems I can't get Vaadin to work nicely with the Spring servlet, just like this guy.

If everything else fails, I'll resort to RMI/Http Invoker to call my services from Activiti Explorer, as suggested here 😕

jbarrez
Star Contributor
Star Contributor
Would you be able to push your code somewhere (my preference goes to GitHub 😉 )?

That way, I can take a look, because it's an important thing of Explorer to be able to add in new service tasks on the fly.

gaston
Champ in-the-making
Champ in-the-making
Would you be able to push your code somewhere (my preference goes to GitHub 😉 )?

That way, I can take a look, because it's an important thing of Explorer to be able to add in new service tasks on the fly.

I'll make a "just the basics" sample with our configuration and push it to GitHub later today.

And again, I can't thank you enough for your time regarding this.

Regards,

gaston
Champ in-the-making
Champ in-the-making
Ok, sorry for the delay. This took me a while but finally was able to solve it.

This is how it ended up working:


ClassPathXmlApplicationContext cpx = new ClassPathXmlApplicationContext("classpath:applicationContextActiviti.xml");
TestServiceDummy = (TestServiceDummy) cpx.getBean("testServiceDummy");

I use this within the JavaDelegate/Class that gets called by the task, "applicationContextActiviti" is a custom appContext config file with the TestServiceDummy service manually declared.

This is because from Activiti Explorer I did not have visibility over Spring Beans (unless you get your hands dirty with Vaadin that is) so what I did was to skip the @Autowired on the class attribute and use the old manual approach to beans, then load them from within the caller class. From there on, all autowiring works.

Thanks so much for your time, please let me know if you still want that sample uploaded to GitHub.

Regards,

jbarrez
Star Contributor
Star Contributor
Ok, I understand how that works.

Still, I think we should support having beans defined in the applicationContext files that are already there.

wsalembi
Champ in-the-making
Champ in-the-making
Gaston is right, but I think he left out an important detail. Autowiring does not work within unit tests with org.activiti.engine.test.ActivitiRule. We had an issue with our spring beans not being autowired in our unit tests too.

Our activiti.cfg.xml file contains <code><context:annotation-config/></code>
We call a JavaDelegate class using activiti:delegateExpression

<code> <serviceTask id="checkValidName" name="CheckValidName" activiti:delegateExpression="${nameChecker}"></serviceTask></code>

The cause of the problem is ActivitiRule who delegates to org.activiti.engine.impl.cfg.BeansConfigurationHelper:Smiley TonguearseProcessEngineConfiguration to instantiate the spring context using DefaultListableBeanFactory. Because no ApplicationContext is used, the <code><context:annotation-config/></code> seems to be ignored.

(BeanFactory only supports the older Autowire collaborators mechanism. http://static.springsource.org/spring/docs/3.0.0.RELEASE/reference/htmlsingle/#beans-factory-autowir...)

If we instantiate the Spring context ourselves in the unit test using ClassPathXmlApplicationContext, we have no issues with autowiring.

Is it possible to review the implementation of BeansConfigurationHelper to use an application context instead of beanFactory?

frederikherema1
Star Contributor
Star Contributor
@wsalembi that definitely makes sense. Feel free to create a pull-request or share how you would handle the change for this on our codebase…

wsalembi
Champ in-the-making
Champ in-the-making
Hi Frederik, I fixed it in the activiti-engine module. New test created and no impact in the other tests detected

https://github.com/Activiti/Activiti/pull/100

Willem Salembier