Bug 62353

Summary: 7.0.87: java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String
Product: Tomcat 7 Reporter: Konstantin Kolinko <knst.kolinko>
Component: CatalinaAssignee: Tomcat Developers Mailing List <dev>
Status: RESOLVED FIXED    
Severity: normal    
Priority: P2    
Version: trunk   
Target Milestone: ---   
Hardware: PC   
OS: All   
Attachments: TEST-org.apache.catalina.startup.TestContextConfig.APR.txt

Description Konstantin Kolinko 2018-05-04 12:18:53 UTC
Created attachment 35912 [details]
TEST-org.apache.catalina.startup.TestContextConfig.APR.txt

Running unit tests for Tomcat 7.0.87 (release candidate) with Java 6.

The following tests unexpectedly fail:
TEST-org.apache.catalina.startup.TestContextConfig.APR.txt
TEST-org.apache.catalina.startup.TestContextConfig.BIO.txt
TEST-org.apache.catalina.startup.TestContextConfig.NIO.txt

See attached log file.

Tomcat fails to start in testBug54448and54450 test. Stacktrace:

[[[
Caused by: java.lang.NoSuchMethodError: javax.annotation.Resource.lookup()Ljava/lang/String;
	at org.apache.catalina.startup.WebAnnotationSet.addResource(WebAnnotationSet.java:339)
	at org.apache.catalina.startup.WebAnnotationSet.loadFieldsAnnotation(WebAnnotationSet.java:274)
	at org.apache.catalina.startup.WebAnnotationSet.loadApplicationServletAnnotations(WebAnnotationSet.java:135)
	at org.apache.catalina.startup.WebAnnotationSet.loadApplicationAnnotations(WebAnnotationSet.java:67)
	at org.apache.catalina.startup.ContextConfig.applicationAnnotationsConfig(ContextConfig.java:417)
	at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:881)
	at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:388)
	at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
	at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5566)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
]]]


This is regression from code added in r1827367.
Comment 1 Konstantin Kolinko 2018-05-04 12:36:49 UTC
(In reply to Konstantin Kolinko from comment #0)
> Caused by: java.lang.NoSuchMethodError:
> javax.annotation.Resource.lookup()Ljava/lang/String;
> 
> This is regression from code added in r1827367.


1. The method javax.annotation.Resource.lookup() is documented as

@since Common Annotations 1.1

See
https://docs.oracle.com/javase/7/docs/api/javax/annotation/Resource.html#lookup()


2. jdk1.6.0_45 has a copy of this annotation class WITHOUT this method.

The source code for the class is present in src.zip bundled with JDK.

3. Tomcat 7 has its own copy of this annotation,
in \java\javax\annotation\Resource.java 

This copy has been updated to Commons Annotations 1.1 and has the lookup() method.


My guess is that thanks to (3.) the code compiles successfully, but (2.) causes it to failure at runtime.


A quick fix is to revert r1827367.

A bigger question is why do we bundle classes that are already provided by the JRE.

Comparing the classes in Java 6 and the classes in annotations-api.jar of Tomcat, a difference is that Tomcat has classes in the following package
- javax.annotation.security

The rest of differences are Common Annotations 1.0 vs 1.1.
Comment 2 Konstantin Kolinko 2018-05-04 12:40:52 UTC
(In reply to Konstantin Kolinko from comment #1)
> Comparing the classes in Java 6 and the classes in annotations-api.jar of
> Tomcat, a difference is that Tomcat has classes in the following package
> - javax.annotation.security
> 
> The rest of differences are Common Annotations 1.0 vs 1.1.

rt.jar of Java 6 has the following javax.annotation classes:

Generated.class
PostConstruct.class
PreDestroy.class
Resource.class
Resource$AuthenticationType.class
Resources.class

annotations-api.jar of Tomcat has the following additional classes and packages:

1) in javax.annotation: ManagedBean.class
- @since Common Annotations 1.1

2) package javax.annotation.security.
- I wonder why Java 6 is it. These classes are documented as @since Common Annotations 1.0.

3) package javax.annotation.sql
- @since Common Annotations 1.1
Comment 3 Konstantin Kolinko 2018-05-04 13:12:50 UTC
Looking into WebAnnotationSet class, the problematic code is triggered only when
- Annotation scanning has been performed
- It found a @Resource annotation and tries to process it.

A web application is not affected by this issue if any of the following is true:
- It does not have @Resource annotations.
- It does not perform annotation scanning (it is marked as metadata-complete).
- Tomcat runs on Java 7 or later.


E.g. standard applications bundled with Tomcat 7 (the examples app) are not affected.
Comment 4 Mark Thomas 2018-05-04 15:38:28 UTC
For reference:

Java EE 6 requires Common Annotations 1.1
Java SE 6 ships with Common Annotations 1.0

Still thinking about how this might be fixed.
Comment 5 Mark Thomas 2018-05-04 16:27:49 UTC
The expected way to enable Common Annotations 1.1 when running on Java 6 is via the Endorsed Standards Override Mechanism. I looked at various other ways to do this but they all broke for at least one use case.

Apart from BZ 50019, there has been no request for Common Annotations 1.1 support in Tomcat 7 and no problems reported, that I can recall, because it is not present.

I am therefore thinking along the the lines of making the changes in WebAnnotationSet that were introduced in r1827367 conditional on Tomcat running on Java 7 or Tomcat running on Java 6 that has been configured with Common Annotations 1.1.
Comment 6 Konstantin Kolinko 2018-05-04 17:06:10 UTC
(In reply to Konstantin Kolinko from comment #2)
> (In reply to Konstantin Kolinko from comment #1)
> > Comparing the classes in Java 6 and the classes in annotations-api.jar of
> > Tomcat, a difference is that Tomcat has classes in the following package
> > - javax.annotation.security
> > 
> > The rest of differences are Common Annotations 1.0 vs 1.1.
> 
> rt.jar of Java 6 has the following javax.annotation classes:
> 
> Generated.class
> PostConstruct.class
> PreDestroy.class
> Resource.class
> Resource$AuthenticationType.class
> Resources.class
> 
> annotations-api.jar of Tomcat has the following additional classes and
> packages:
> 
> 1) in javax.annotation: ManagedBean.class
> - @since Common Annotations 1.1
> 
> 2) package javax.annotation.security.
> - I wonder why Java 6 is it. These classes are documented as @since Common
> Annotations 1.0.
> 
> 3) package javax.annotation.sql
> - @since Common Annotations 1.1

Comparing Java 6 with Java 7 and 8:
the missing classes (ManagedBean) and the two packages (security, sql) are missing in Java 7 and Java 8 as well.

Java 6(.45)/7(.80)/8(.172) all have only the following classes:

> Generated.class
> PostConstruct.class
> PreDestroy.class
> Resource.class
> Resource$AuthenticationType.class
> Resources.class


The rest of Commons Annotations 1.1 classes are missing.


BTW, Commons Annotations 1.3 spec (latest), jsr-250.pdf:
[quote]
1.3 Compatibility

The annotations defined in this specification may be included individually as needed in products that make use of them. Other Java specifications will require support for subsets of these annotations. Products that support these Java specifications must include the required annotations.
[/quote]

That is why JRE only has a subset of the classes.
Comment 7 Mark Thomas 2018-05-04 17:35:53 UTC
Looking at the (lack of) dependencies between the annotations, it should be safe to load the Java SE provided ones from Java SE and the remainder from Tomcat's JAR without risk of conflict.

I've been testing the solution I proposed in comment #5 and it is looking good. I should be able to commit it shortly.
Comment 8 Konstantin Kolinko 2018-05-04 17:41:17 UTC
(In reply to Mark Thomas from comment #5)

I pondered about the Endorsed Standards Override Mechanism as well.

I think this means instructing users (via RUNNING.txt or via RELEASE-NOTES) to copy annotations-api.jar from "${catalina.home|/lib" into "${catalina.home|/endorsed" when running on Java 6.

Changing or distribution zip/tar archives (moving the file permanently / by default) does not make sense, as newer versions of Java can have newer versions of the classes, even newer that those bundled with Tomcat.
Comment 9 Mark Thomas 2018-05-04 18:42:38 UTC
Patch applied. Docs updated. Will be in 7.0.88.
Comment 10 Konstantin Kolinko 2018-05-05 13:47:31 UTC
(In reply to Mark Thomas from comment #9)
> Patch applied. Docs updated. Will be in 7.0.88.

Looks good. I amended the docs a bit.

Several notes, for a record:

1. Looking into commit history of javax.annotation classes in Tomcat 7:

- The commit that updates Common Annotations 1.0 -> 1.1 is r1521045 (fixed bug 55534).

The only change of existing annotation classes in that commit is adding lookup() element on @Resource annotation.

There is also r1797343 that added missing @Documented annotation. This is not a change between versions, as those annotations are already present in Commons Annotations 1.0 - as can be seen in Java 6 javadoc for javax.annotation classes.

Thus addition of "luokup" is the only difference.

2. When running with Java 6, the value of "luokup" element on a @Resource annotation, if present, will be silently ignored.

- This is covered by Java Language Specification,
13.5.7 Evolution of Annotation Types.

https://docs.oracle.com/javase/specs/jls/se6/html/binaryComp.html#13.5.7

- Technically: looking into source code for Open JDK 6,
I confirm that it is silently skipped.

Trace:
- java.lang.Class#initAnnotationsIfNecessary()
-> sun.reflect.annotation.AnnotationParser
-> AnnotationParser#parseAnnotation()
-> L237 "// Member is no longer present in annotation type; ignore it"

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/sun/reflect/annotation/AnnotationParser.java#236