cancel
Showing results for 
Search instead for 
Did you mean: 

Activiti CDI module and Alternatives

ronnybr
Champ in-the-making
Champ in-the-making
Hello Activiti-Community,
I am using CDI in my application quite excessive and I am trying to write my JUnit-tests in a way, that most of my "real" CDI-Beans will be replaced by mocks.

Also, I am using Arquillian, so that the injection works.
The beans.xml for my tests looks like this:

<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="       http://java.sun.com/xml/ns/javaee        http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
  <class>de.data.experts.activiti.mock.DominoProducerMock</class>
  <class>de.data.experts.activiti.mock.PraxisberichtVeroeffentlichenMock</class>
  <class>de.data.experts.activiti.mock.ThemaVeroeffentlichenMock</class>
</alternatives>
</beans>

When I start my JUnit-test it passes a ServiceTask in which I reference a class:

<serviceTask activiti:expression="#{themaVeroeffentlichen.anlegen()}" completionQuantity="1" id="_30" implementation="##WebService" isForCompensation="false" name="Praxisberichtsthema veröffentlichen" startQuantity="1">
      <incoming>_31</incoming>
      <outgoing>_36</outgoing>
    </serviceTask>
The real class is annotated like this:

@Named("themaVeroeffentlichen")
@Dependent
public class ThemaVeroeffentlichen {
And the mock like this:

@Alternative
@Named("themaVeroeffentlichen")
@Dependent
public class ThemaVeroeffentlichenMock extends ThemaVeroeffentlichen {

As you can see in the beans.xml above, I want to replace the "themaVeroeffentlichen"-bean with a mock.
Surprisingly, my test sometimes passes and sometimes I get a NPE, because it calls the real bean and invokes the real method.
I debugged and found the CdiResolver and ProgrammaticBeanLookup classes.
In CdiResolver the method getValue(…) calls the lookup:

  public Object getValue(ELContext context, Object base, Object property) {
    try {
      Object result = getWrappedResolver().getValue(this.context, base, property);
      context.setPropertyResolved(result != null);
      return result;
    } catch (IllegalStateException e) {
      // dependent scoped / EJBs
      Object result = ProgrammaticBeanLookup.lookup(property.toString(), getBeanManager());
      context.setPropertyResolved(result != null);
      return result;
    }
  }

The lookup-method calls the BeanManager with the bean-name and gets a set. From the set the first bean is taken:

  public static Object lookup(String name, BeanManager bm) {
    Iterator<Bean< ? >> iter = bm.getBeans(name).iterator();
    if (!iter.hasNext()) {
      throw new IllegalStateException("CDI BeanManager cannot find an instance of requested type '" + name + "'");
    }
    Bean bean = iter.next();
    CreationalContext ctx = bm.createCreationalContext(bean);
    // select one beantype randomly. A bean has a non-empty set of beantypes.
    Type type = (Type) bean.getTypes().iterator().next();
    return bm.getReference(bean, type, ctx);
  }
And that's why my test sometimes passes and sometimes not. It depends on the order of the beans in the set. Sometimes the mock is first and sometimes the real class.

So, my question is, shouldn't the CdiResolver take another approach when trying to resolve the bean or shouldn't the ProgrammaticBeanLookup throw something like an AmbiguousResolutionException when finding more than one possibility?
7 REPLIES 7

frederikherema1
Star Contributor
Star Contributor
I'm not really sure what the behavior should be, when multiple beans are found for the given class-name OR bean-name. I guess it's okay for lookup based on class to ignore "duplicate" entries. But the lookup by name should indeed throw an exception, informing that multiple beans with the same name have been found.

I wonder how the "usual" way of looking up a CDI-bean handles the resolving of what alternatives to return, but I presume the order of the beans returned by the BeanManager reflect the way CDI itself resolves references to beans. Perhaps there is a better way than using the BeanManager to resolve beans, that actually takes into account the "alternatives".

Could you create an issue for this in Jira? Since you're a CDI-expert, can you perhaps add your recommendations on the way beans can be resolved, not using BeanManager? For now, you should override the CDIExpresionManager/CDI-ELResolver until this issue is cleared up…?

ronnybr
Champ in-the-making
Champ in-the-making
Thank you Frederik for your fast answer.
I'll create the JIRA issue later today and think about a possible (and elegant) solution.

Ususally, when CDI resolves alternatives for beans, activated alternatives always beat the normal class. And two possible, activated alternatives will result in an exception during deployment.

So far I solved the problem for me without changing Activiti-code. I am using delegates. The class, the BPMN-XML references, is now mostly empty, e.g.

@Named("praxisberichtVeroeffentlichen")
@Dependent
public class PraxisberichtVeroeffentlichen {

@Inject
private PraxisberichtVeroeffentlichenDelegate delegate;

public void veroeffentlichen(String student, String thema) {
  delegate.veroeffentlichen(student, thema);
}
}
and the delegate is being replaced by a mock durung JUnit-tests.
The delegate does all the work now.

frederikherema1
Star Contributor
Star Contributor
I see, so you're currently relying on CDI to injecting the alternative inside en in-the-middle-bean, resolved by Activiti, to overcome this issue?

ronnybr
Champ in-the-making
Champ in-the-making
Exactly. Activiti's CDIResolver get's one bean from the ProgrammaticBeanLookup and that bean has been initialized by CDI. That way, the alternative is taken, when running the JUnit-tests.

At least, that's how I understand all this CDI magic  Smiley Very Happy

ronald_van_kuij
Champ on-the-rise
Champ on-the-rise
Exactly. Activiti's CDIResolver get's one bean from the ProgrammaticBeanLookup and that bean has been initialized by CDI. That way, the alternative is taken, when running the JUnit-tests.

At least, that's how I understand all this CDI magic Smiley Very Happy
It is an option… An other option might be to have the resolver look at the @Alternative annotation and use that one instead of the normal bean. That requires a change to the code though… Not even sure it works….

ronnybr
Champ in-the-making
Champ in-the-making
An other option might be to have the resolver look at the @Alternative annotation…
For me, it doesn't seem right to try to implement something the CDI framework should already be able to provide, like privilige alternatives.

The resolve() method from BeanManager looks like a good solution.
http://www.grepcode.com/file/repo1.maven.org/maven2/javax.enterprise/cdi-api/1.0/javax/enterprise/in...

I changed some code in the ProgrammaticBeanLookup and had a short JUnit test check whether it works or not. And it seems to do what the signature promises  Smiley Wink
But I didn't have much time yesterday so I couldn't test it thoroughly.

frederikherema1
Star Contributor
Star Contributor
I'll have a deeper look into the resolve() alternative, see https://jira.codehaus.org/browse/ACT-1557