Inability to Access Spring Beans in Input/Output Mapping Script

I stumbled upon an inability to access spring beans in an Input/Output Mapping Script. The example is as shown below:

Apparently I am not able to access the spring bean in an Input/Output mapping script, although the bean is wired correctly and I am able to access it throughout the process.

The process engine test is linked below:
Spring Bean Input/Output Access

Hi Robert,

I think accessing beans (CDI or Spring) from a script is a missing feature, see https://app.camunda.com/jira/browse/CAM-4222. You can consider implementing the interfaces org.camunda.bpm.engine.impl.scripting.engine.ResolverFactory and org.camunda.bpm.engine.impl.scripting.engine.Resolver in a way that accesses a Spring application context. Then register the resolver factory with the engine via the configuration property resolverFactories. If you like, you can also provide this feature as a pull request.

Cheers,
Thorben

Hi Thorben,

thanks, I will consider this solution.

Kind Regards
Robert

Here is a small example, for springboot, with an embedded process engine!
This is the bean for testing:

@Component
public class Testbean {
	private String name = "Hello from testbean!";
	public String getName() {
		return name;
	}
}

The resolver:

import org.camunda.bpm.engine.impl.scripting.engine.Resolver;
import org.springframework.context.ApplicationContext;

public class SpringResolver implements Resolver {

  protected ApplicationContext applicationContext;

  private static final Set<String> exposedBeans = new HashSet<String>(
      Arrays.asList("testbean"));
      
  public SpringResolver(ApplicationContext applicationContext) {
    ensureNotNull("applicationContext", applicationContext);
    this.applicationContext = applicationContext;
  }
  
  public boolean containsKey(Object key) {
	  return exposedBeans.contains((String)key);
  }
  
  public Object get(Object key) {
    return applicationContext.getBean((String)key);
  }
  
  public Set<String> keySet() {
	return exposedBeans;
  }
}

The resolver factory:

import org.camunda.bpm.engine.delegate.VariableScope;
import org.camunda.bpm.engine.impl.scripting.engine.Resolver;
import org.camunda.bpm.engine.impl.scripting.engine.ResolverFactory;
import org.springframework.context.ApplicationContext;

public class SpringResolverFactory implements ResolverFactory {

	private ApplicationContext applicationContext;

	public SpringResolverFactory(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	public Resolver createResolver(VariableScope variableScope) {
		return new SpringResolver(applicationContext);
	}
}

Configure the resolver factory with plugin-configuration:

public class SpringResolverPlugin implements ProcessEnginePlugin {
	private ApplicationContext applicationContext;

	public SpringBeanResolverPlugin(
	    ApplicationContext applicationContext) {
		super();
		this.applicationContext = applicationContext;
	}
	@Override
	public void preInit(
	    ProcessEngineConfigurationImpl processEngineConfiguration) {}

	@Override
	public void postInit(
	    ProcessEngineConfigurationImpl processEngineConfiguration) {
		processEngineConfiguration
			.getResolverFactories()
			.add(new SpringResolverFactory(applicationContext));
	}

	@Override
	public void postProcessEngineBuild(ProcessEngine processEngine) {}
}

And set the plugin into the spring context:

	@Bean
	public ProcessEnginePlugin springResolverPlugin(
	    ApplicationContext applicationContext) {
		return new SpringResolverPlugin(applicationContext);
	}

At last rty it out from javascript, from any script-task:

print(testbean.name)

It is better to enumerate only the needed beans in a HashSet (exposedBeans),
because the resolver’s

containsKey(Object key)

gets called very often by the system!
But if you really want get access to the ~600 beans in your container (slower because containsKey):

public class SpringResolver implements Resolver {

	protected ApplicationContext applicationContext;

	public SpringResolver(ApplicationContext applicationContext) {
		ensureNotNull("applicationContext", applicationContext);
		this.applicationContext = applicationContext;
	}

	public boolean containsKey(Object key) {
		try {
			return applicationContext.getBean((String) key) != null;
		} catch (Exception ex) {
			return false;
		}
	}

	public Object get(Object key) {
		return applicationContext.getBean((String) key);
	}

	public Set<String> keySet() {
		String[] beannames = applicationContext.getBeanDefinitionNames();
		return new HashSet<String>(Arrays.asList(beannames));
	}
}

Optimized :slight_smile:

public class SpringResolver implements Resolver {

	private ApplicationContext applicationContext;
	
	private Set<String> beannames;

	public SpringResolver(ApplicationContext applicationContext) {
		ensureNotNull("applicationContext", applicationContext);
		this.applicationContext = applicationContext;
		this.beannames = new HashSet<String>(
		    Arrays.asList(
		        applicationContext.getBeanDefinitionNames()));
	}

	public boolean containsKey(Object key) {
		return beannames.contains((String)key);
	}

	public Object get(Object key) {
		return applicationContext.getBean((String) key);
	}

	public Set<String> keySet() {
		return beannames;
	}
}
1 Like

This could be a great solution. I’m working on it. What is ‘ensureNotNull’ method ?

I notice this isn’t working for a shared process engine (the spring jars are not visible from tomcat lib folder). There’s some workaround for that?

Hallo!

ensureNotNull is a static import:

import static org.camunda.bpm.engine.impl.util.EnsureUtil.ensureNotNull;

It is in the spring jar’s, sorry, i have no experience with the shared engine, we are using embedded…