Great news!

Ok, so I've made some good progress again after getting involved in this
thread:
http://tapestry.1045711.n5.nabble.com/Supporting-EJB-annotation-in-Tapestry-classes-td4696836.html

So the problem was:
When contributing to MasterObjectProvider my CDIObjectprovider will resolve
any tapestry services which also qualifies as a cdi bean.
This is because the MasterObjectProvider will ask Objectlocator for a
service after all providers have been asked.

I have instead contributed an CDIInjectionProvider.
*public static void contributeInjectionProvider(*
* OrderedConfiguration<InjectionProvider> configuration,*
* @Local CDIFactory cdiFactory) {*
* configuration.add("CDI", new CDIInjectionProvider(cdiFactory),
"after:*,before:Service");*
*}*

So attempts to find a cdi bean is no longer through the MasterObjectProvider
but directly by the InjectWorker (through the chain of command).

The conundrum is: I want to ask CDIInjectionProvider last,
but ServiceInjectionProvider will 'blow up' if it fails to resolve a
service/bean.
So what I've done for now is this 'hack':

*@Override*
*public boolean provideInjection(String fieldName, Class fieldType,*
* ObjectLocator locator, ClassTransformation transformation,*
* MutableComponentModel componentModel) {*
* *
* /***
* * Problem: in many cases a tapestry service will qualify as a cdi bean.*
* * In order to prevent cdi for managing a service that should be provided
by tapestry we check if locator has the service.*
* */*
* try {*
* if(locator.getService(fieldType)!=null)*
* return false;*
* } catch (RuntimeException e) { *
           //it blew up, which means there is a chance cdi can resolve it
* }*
* *
* TransformField field = transformation.getField(fieldName); *
* final Object injectionValue = cdiFactory.get(fieldType); *
* if(injectionValue!=null) {*
* field.inject(injectionValue);*
* return true;*
* }*
* return false;*
*}*

It checks to see if a service could be resolved and does nothing as it
should be handled by ServiceInjectionProvider which is next in chain.
It does the job. A bit hacky, but I cannot think of another way of doing it.

For Tapestry-CDI I've implemented two means of injection:
* using @inject (either tapestry's or jsr-330). This goes through the
Injectionprovider chain
* using @CDI - this is handled by own ComponentClassTransformWorker.

I'll host the code somewhere, perhaps github, in case others would be
interested in giving it a spin as well.

..and please let me know if you have any suggestion for a cleaner approach

--magnus

On Thu, Jun 9, 2011 at 10:10 AM, Magnus Kvalheim <mag...@kvalheim.dk> wrote:

> Thanks Howard, great to hear I'm on the right track :)
>
> I haven't given much thought to exposing tapestry services to cdi.
> It's not something we currently require for our projects, but if it's
> requested by others I could look into that as well!
>
> *Question*
> I have one issue related to the CDIObjectProvider though.
> While testing the quickstart (which uses the CDIModule) I created and
> registered a tapestry service - which accidentally also satisfies as a CDI
> managed bean.
> *From quickstart appmodule*
> *public static void bind(ServiceBinder binder){*
> * binder.bind(AnotherService.class).withId("AnotherSrv");    *
> *}*
>
> As posted earlier the CDIProvider is contributed to the
> MasterObjectProvider as last (after:*)?
> *configuration.add("cdiProvider", cdiProvider, "after:*");*
>
> AnotherService is injected into a component/page
> *@Inject private AnotherService another;*
> *
> *
> What I would want is the cdiprovider to be asked last - so that if Tapestry
> itself cannot provide the service it will eventually get delegated to CDI.
> In this case - tapestry should provide the service.
> However, the cdiprovider is asked to provide the bean. (This is why I've
> specifically test for scope now.)
>
> So the question is. *How can I make sure that the CDIProvider is asked
> last in chain?*
> Do I have to contribute to InjectionProvider as well?
>
> thanks in advance
> Magnus
>
> On Wed, Jun 8, 2011 at 6:47 PM, Howard Lewis Ship <hls...@gmail.com>wrote:
>
>> Nope, it's designed to be easy ... though contributions into
>> MasterObjectProvider are always tricky since you can get into
>> dependency cycles very easily. You've properly side-stepped this using
>> @InjectService (using the special @Local marker annotation is always
>> very important).
>>
>> The hard part, which is still difficult with Spring, is proper
>> co-dependence:  allowing Tapestry services to be injected into Spring
>> beans as well as the reverse.  It comes down to a turf battle about
>> which framework is "in charge".
>>
>> On Wed, Jun 8, 2011 at 4:56 AM, Magnus Kvalheim <mag...@kvalheim.dk>
>> wrote:
>> > Hi all,
>> >
>> > We're looking into moving our apps from a 'traditional' servlet
>> container
>> > with spring into a Java EE web profile server like glassfish 3.1.
>> > Motivations for doing this is to utilize cdi(jsr 299, 330), ejb3 and
>> more.
>> > Not just for the tapestry app, but also the other applications in
>> > our portfoleo which share common core business logic.
>> >
>> > For reference on previous discussions:
>> >
>> http://tapestry.1045711.n5.nabble.com/Java-based-spring-configuration-td3394086.html
>> > http://tapestry.1045711.n5.nabble.com/Discussion-td2421783i20.html
>> >
>> > Now, I've tried running the tapestry quickstart app in glassfish 3.1
>> (with
>> > the eclipse connector for publishing).
>> > This works ok - although I cannot make live class reloading work. :(
>> >
>> > Glassfish uses Weld, so the CDIModule is basically an objectprovider for
>> > injecting Weld managed beans.
>> > (As you probably know CDI/Weld can also be used outside jee as
>> alternative
>> > to tapestry-ioc, spring, etc)
>> >
>> > *CDIModule class*
>> > *public class CDIModule { *
>> > * public static void bind(ServiceBinder binder) {*
>> > *    binder.bind(ObjectProvider.class,
>> > CDIObjectProvider.class).withId("CDIObjectProvider");        *
>> > *    } *
>> > * public static BeanManager buildBeanManager(Logger log) { *
>> > * try {*
>> > * BeanManager beanManager = (BeanManager) new
>> > InitialContext().lookup("java:comp/BeanManager");*
>> > * return beanManager; *
>> > * } catch (NamingException e) {*
>> > * log.error("Could not lookup jndi resource: java:comp/BeanManager",
>> e);*
>> > * }*
>> > * return null;*
>> > * } *
>> > * public static void contributeMasterObjectProvider(*
>> > * @InjectService("CDIObjectProvider") ObjectProvider cdiProvider,*
>> > * OrderedConfiguration<ObjectProvider> configuration) { *
>> > *// configuration.add("cdiProvider", cdiProvider,
>> >
>> "after:Service,after:AnnotationBasedContributions,after:Alias,after:Autobuild");
>> > *
>> > * configuration.add("cdiProvider", cdiProvider, "after:*"); *
>> > * } *
>> > *}*
>> > *
>> > *
>> > The beanmanager is expected to be found in jndi. If the beans.xml is
>> present
>> > it will be available at this point.
>> > The BeanManager is also exposed as a service and injectable for other
>> > services or components.
>> > I've tested by adding the *@SubModule(CDIModule.class) *to my quickstart
>> > appmodule.
>> > *
>> > *
>> > *CDIObjectProvider class*
>> > *public class CDIObjectProvider implements ObjectProvider { *
>> > * private BeanManager beanManager;*
>> > * private Logger log;*
>> > * *
>> > * @SuppressWarnings({ "unchecked", "rawtypes" })*
>> > * private Set allowedScopes = CollectionFactory.newSet(*
>> > * ApplicationScoped.class,*
>> > * Singleton.class);*
>> > *
>> > *
>> > * public CDIObjectProvider(*
>> > * Logger log,*
>> > * @InjectService("BeanManager") BeanManager manager) {*
>> > * this.beanManager = manager;*
>> > * this.log = log;*
>> > * }*
>> > * @SuppressWarnings("unchecked")*
>> > * public <T> T provide(Class<T> objectType,*
>> > * AnnotationProvider annotationProvider, ObjectLocator locator) {*
>> > * Set<Bean<?>> beans =  beanManager.getBeans(objectType);*
>> > * if(beans!=null && beans.size()>0) {*
>> > * Bean<T> bean = (Bean<T>) beans.iterator().next(); *
>> > * if(hasValidScope(bean)) {*
>> > * CreationalContext<T> ctx = beanManager.createCreationalContext(bean);*
>> > * T o = (T) beanManager.getReference(bean, objectType, ctx); *
>> > * log.info("Found and returning: "+objectType.getCanonicalName());*
>> > * return o; *
>> > * }*
>> > * }*
>> > * return null;*
>> > * } *
>> > * protected <T> boolean hasValidScope(Bean<T> bean) {*
>> > * return bean!=null && allowedScopes.contains(bean.getScope());*
>> > * }*
>> > *}*
>> >
>> > I've limited the scope to singleton/applicationscoped. Perhaps also
>> Default
>> > could be accepted though.
>> > Until now I've only tested this with pojo's and not ejb's - but for that
>> > it's working as expected.
>> > I can inject CDI beans into pages and components using*
>> >  org.apache.tapestry5.ioc.annotations.Inject*
>> >
>> > I'm no expert to tapestry internals - so there could be
>> > other considerations that needs to be addressed.
>> > In fact in seemed just a little to easy to implement - so I must have
>> missed
>> > something. - Or perhaps it's just that easy to do in Tapestry :)
>> >
>> > Thoughts, comments?
>> >
>>
>>
>>
>> --
>> Howard M. Lewis Ship
>>
>> Creator of Apache Tapestry
>>
>> The source for Tapestry training, mentoring and support. Contact me to
>> learn how I can get you up and productive in Tapestry fast!
>>
>> (971) 678-5210
>> http://howardlewisship.com
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
>> For additional commands, e-mail: users-h...@tapestry.apache.org
>>
>>
>

Reply via email to