users@javaee-spec.java.net

[javaee-spec users] [jsr366-experts] Re: Compatibility Problems with MR Resource Annotation Widening

From: Jason Greene <jason.greene_at_redhat.com>
Date: Wed, 25 Feb 2015 16:08:16 -0600

> On Feb 24, 2015, at 6:07 PM, Bill Shannon <bill.shannon_at_oracle.com> wrote:
>
> In thinking about this a bit more, you bring up some good points.
>
> Part of what started us on this path of clarifying the expected behavior
> is that we discovered some bugs in this area in the RI, or at least, behavior
> that didn't match what we expected. That took us down some twisty paths
> that may not have ended up in the right place.
>
> Considering some of our history, it may be that some of this behavior is
> something we can't change. For example, in Java EE 5 we didn't have JNDI
> namespaces, but we did allow EJBs to be created by subclassing other
> classes that might have resource injection annotations. That seems to
> mean that the resource references have to be defined in the namespace
> of every component that subclasses the base class.

There’s one additional case I forgot to mention, where you can define new components in XML, and those can reference/share the same backing classes. Granted this is less common these days, but it runs into the same issue, and can affect your algorithm below.

>
> What we were trying to define is a simple processing model for annotations
> that would allow all processing to be done in a single pass over the classes.
>
> We expected every one of these resource annotations, on any class in the
> application, to define a resource reference entry in some JNDI namespace.
> The namespaces available to a given annotation on a given class were a
> function of where in the application the class was packaged. The deployment
> process that discovers these annotations could add the required information
> to the appropriate JNDI namespace without any information about how the
> class was used. And it could reject applications that attempted to
> declare resource references in namespaces that the class didn't have
> access to.
>
> What we're discovering is that this is too simplistic. We at least need
> to handle the case where a component class subclasses some other class.
> In this case, the base class may not have a component namespace of its
> own, but it needs to be able to define resources in the namespace of the
> component that subclasses it. Unfortunately, this means you need to know
> what all the "classes supporting injection" are, and you need to know the
> full class hierarchy for each of them, so that you can ensure the resource
> references from the superclasses appear in the component namespaces of all
> the component subclasses. Since "classes supporting injection" includes CDI
> managed beans, I don't know how you can know that at deployment time.

Weld is a bit special in that it performs its own EE Resource injection, using slightly different rules. However in our case we also determine whether or not a deployment contains a class that has “bean defining annotations” on it, to determine whether or not Weld needs to be involved (also whether or not a beans.xml has disabled weld processing).

>
> It seems that we have to handle at least some cases of superclasses defining
> resources in the same namespace as the subclass. Sadly, you may be right that
> we have to handle that even in the case where the superclass is in a library
>
> Let's look at the general algorithm for initializing the JNDI namespaces.
> (We'll ignore deployment descriptor overriding for now. We'll also
> ignore application clients for now.)

I’ll answer your question below first, by describing, very coarsely, how our processing works. Our processing is component centric. We have a first pass class structure and annotation scan which reads all the bytecode in a deployment, including all possible places in the deployment classes can exist, and constructs a set of shared indexes. We also do something similar wit XML metadata, although each EE component layer has to register the relevant parsing logic. From that information we have a number of fine grained deployment processors (you can relate it to something like an interceptor), installed by the various EE layers, which all collaborate to create a set of component definitions. Those component definitions are assembled with all of the JNDI binding information thats relevant to the component.

So in other words we notice every place that @Resource occurs, we just don’t do anything with that data until we process the component, and processing the component involves analyzing the resource data for all classes that make up the component.

>
> 1. Find all the classes in each war file.
>
> 2. Find all the superclasses of each class from table EE-5.1 in each
> war file.
>
> 3. For all these classes, add any resource references or resource
> definitions in the java:comp namespace to the corresponding
> war component's JNDI namespace.
>
> 4. For all these classes, add any resource references or resource
> definitions in the java:module namespace to the corresponding
> war component's JNDI namespace.
>
> 5. Find all the classes in each EJB jar file that define an EJB.
>
> 6. Find all the superclasses of each of these classes.
>
> 7. For all these classes, add any resource references or resource
> definitions in the java:comp namespace to the corresponding EJB
> component's JNDI namespace.
>
> 8. For all these classes, add any resource references or resource
> definitions in the java:module namespace to the corresponding EJB
> jar's module JNDI namespace.
>
> 9. Find all the classes in each EJB jar file.
>
> 10. For all these classes, add any resource references or resource
> definitions in the java:module namespace to the corresponding EJB
> jar's module JNDI namespace.
>
> 11. For all classes in the application package, add any resource
> references or resource definitions in the java:app namespace to the
> application's JNDI namespace.
>
> 12. For all classes in the application package, add any resource
> references or resource definitions in the java:global namespace to
> the global JNDI namespace.
>
>
> A difficulty with the algorithm above is that table EE-5.1 includes
> CDI managed beans. Without initializing the CDI runtime, it's impossible
> to know what all the CDI managed beans are. In steps 2 and 9, we could
> just consider every class to be a potential CDI managed bean, adding or
> changing these steps:

True, although CDI has a callback for this, since it discovers it for you.

>
> 2. Find every superclass of these classes.
>
> 9a. Find every superclass of these classes.
>
>
> Oh, but we forgot about interceptors, which have their own special rules.
> Add these steps:
>
> 2a. Find all the interceptors for each of these classes.
>
> 2b. Find all the superclasses of all these interceptor classes.
>
> 5a. Find all the interceptors for each of these classes.
>
> 5b. Find all the superclasses of all these interceptor classes.
>
> It's not clear from the Interceptors spec whether these special rules
> for interceptors apply to only those interceptors declared using
> @Interceptors, or also to those declared using interceptor bindings.
> Again, the latter are essentially impossible to determine without
> initializing CDI, so we'll assume only the former was intended for
> now. Since the genesis of this rule was from the EJB spec, we could
> further limit this to @Interceptors applied to EJBs if desired, which
> would remove steps 2a and 2b.
>
>
> What's missing from the algorithm is any attempt to detect the error
> where a class declares a resource in a namespace that's unavailable to
> the class. To handle that, we add these steps:
>
> 13. If any class in an ejb jar file and not in the set from step 6
> declares a resource in the java:comp namespace, it's an error.
>
> 14. If any class in a library and not in the sets from steps 2 and 6
> declares a resource in the java:comp or java:module namespace, it's
> an error.
>
> 15. If any class in a rar module and not in the sets from steps 2 and 6
> declares a resource in any java: namespace, it's an error.
>
> Ideally these would be deployment errors since it's better to detect
> application errors early, but clearly that comes at some cost.
>
> Does this algorithm better match what you're expecting, and hopefully
> implementing?

This certainly sounds better. Although, if I understand correctly, there is still some confusing behavior. For example, unqualified names (@Resource(name=“blah”)) will fail on a non-component class, unless extended by a component class (or defined in XML). If that component class is retired then we now have a failure.

I know we are supposed to be discussing the ideal behavior and not get into whether or not its an MR. However, I think its important to note there’s still that case where JSR-250 annotations are used by some third party framework, since they are generic, and now the EE container wants to take control of those. That’s probably ok for EE8. Although, at least, in our case we have already shipped an EE7 server (WildFly 8.0-8.2), so breaking them in a patch release (to conform to an MR) seems a bit unfair to them. Granted we could provide a flag for them to get them by if it was decided to go this route.

One thing I wonder though, is does this really bring enough value to justify the cost of the change? For example, it takes just one extra annotation to make a class a component (e.g. ManagedBean/Singleton/ApplicationScoped etc).

--
Jason T. Greene
WildFly Lead / JBoss EAP Platform Architect
JBoss, a division of Red Hat