views:

390

answers:

2

I have a problem with Tapestry 5 and Spring integration. Problem occurs if I have a multiple beans that implement the same interface and I try to inject them with @Inject annotation. Of course I got an exception.

I found a tutorial that says that in that case I have to use @Service annotation too but now I'm getting

org.apache.tapestry5.internal.services.TransformationException
Error obtaining injected value for field 
com.foo.pages.Foo.testService: Service 
id 'someServiceIDeclaredInSpringContextFile' is not defined by any module...

Anyway, question is: How can I inject two different spring beans, that implement a same interface, into Tapestry 5 page?

A: 

It sounds like you either have a typo in the name give to the @Service annotation, or you have not actually defined the bean with the name that you are expecting. Without more information, it is hard to tell for sure, as there are also some other possibilities.

jsight
nope, double checked - no typos and in commonBeans.ctx.xml I have that bean too
vrm
+1  A: 

I solved this problem.

First I made a new annotation

public @interface Bean {
    String value();
}

and I use this wherever I have this one of multiple beans implementing same interface

@Inject
@Bean("springBeanName")
Service foo;

Then I changed org.apache.tapestry5.internal.spring.SpringModuleDef

private ContributionDef createContributionToMasterObjectProvider() {
  ....
  public void contribute(ModuleBuilderSource moduleSource, 
                ServiceResources resources,
                OrderedConfiguration configuration) {
    ....
    switch (beanMap.size()) {
           case 0:
             return null;
           case 1:
             Object bean = beanMap.values().iterator().next();
             return objectType.cast(bean);
           default:
             Bean annotation = annotationProvider.getAnnotation(Bean.class);
             Object springBean = null;
             String beanName = null;

             if (annotation != null) {
               beanName = annotation.value();
               springBean = beanMap.get(beanName);
             } else {
               String message = String.format(
                 "Spring context contains %d beans assignable to type %s: %s.",
                 beanMap.size(),
                 ClassFabUtils.toJavaClassName(objectType),
                 InternalUtils.joinSorted(beanMap.keySet()));
               throw new IllegalArgumentException(message);
             }
             if (springBean != null) {
               return objectType.cast(springBean);
             } else {
               String message = String.format(
                 "Bean [%s] of type %s doesn't exists. Available beans: %s",
                 beanName, ClassFabUtils.toJavaClassName(objectType),
                 InternalUtils.joinSorted(beanMap.keySet()));
               throw new IllegalArgumentException(message);
             }
           }
         }
       };
vrm
The behaviour of the Spring integration changed between version 5.0 and 5.1. In 5.0, every Spring bean was exposed as a Tapestry service, and if you had two beans implementing the same interface, you could always use `@Service` with the proper bean name to clarify which object you meant.In 5.1, that is no longer the case, rendering the `@Service` annotation useless for Spring beans. You're hosed unless you're ready to do some metaprogramming magic yourself.Good workaround, by the way.
Henning