Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tck: CdiOptionalInjectionTest - bean loadable from two places->CDI deployment error #145

Closed
hutchig opened this issue May 8, 2017 · 15 comments
Assignees

Comments

@hutchig
Copy link

hutchig commented May 8, 2017

The test code in CdiOptionalInjectionTest results in two copies of the OptionalValuesBean class being in the war.

One copy (oddly) is placed in the war:/WEB-INF/classes

$ tar -tf  ./_DEFAULT___DEFAULT__cdiOptionalInjectionTest.war
...
WEB-INF/classes/org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest$OptionalValuesBean.class
...
./WEB-INF/lib/cdiOptionalInjectionTest.jar

and another copy is in the jar:

org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest$OptionalValuesBean.class

This is very odd as the test code:

   @Deployment
    public static WebArchive deploy() {
        JavaArchive testJar = ShrinkWrap
                .create(JavaArchive.class, "cdiOptionalInjectionTest.jar")
                .addClasses(CdiOptionalInjectionTest.class, OptionalValuesBean.class)
                .addAsManifestResource(new StringAsset("my.optional.int.property=1234\nmy.optional.string.property=hello"),
                        "microprofile-config.properties")
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
                .as(JavaArchive.class);

        WebArchive war = ShrinkWrap
                .create(WebArchive.class, "cdiOptionalInjectionTest.war")
                .addAsLibrary(testJar);
        return war;
    }

does not appear to explicitly add the bean class to the war:/WEB-INF/classes??

Weld does not like this at all:

[08/05/17 14:32:48:224 BST] 00000033 com.ibm.ws.logging.internal.impl.IncidentImpl                I FFDC1015I: An FFDC Incident has been created: "com.ibm.ws.container.service.state.StateChangeException: org.jboss.weld.exceptions.DeploymentException: WELD-001409: Ambiguous dependencies for type OptionalValuesBean with qualifiers @Default
  at injection point [BackedAnnotatedField] @Inject private org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest.optionalValuesBean
  at org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest.optionalValuesBean(CdiOptionalInjectionTest.java:0)
  Possible dependencies:
  - Managed Bean [class org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest$OptionalValuesBean] with qualifiers [@Any @Default],
  - Managed Bean [class org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest$OptionalValuesBean] with qualifiers [@Any @Default]

I am no Arquillian expert but the error can be fixed by adding

.deleteClass(OptionalValuesBean.class)

to the jar construction. It would probably be better to have Arquillian config that prevented
the testcase having the class in the war's WEB-INF/classes but it does not do this as
currently written in my environment. Why?

@struberg struberg self-assigned this May 8, 2017
@struberg
Copy link
Member

struberg commented May 8, 2017

Please test with this fix - txs!

@hutchig
Copy link
Author

hutchig commented May 8, 2017

Not sure that will have any effect as the inner class will get added anyway (I had already played with that, so I know it will have no effect:-) ) See the addClasses javadoc in Shrinkwrap.

/**
* Adds the {@link Class}es, and all member (inner) {@link Class}es to the {@link Archive}.
*
* @param classes
* The classes to add to the Archive
* @return This archive
* @throws IllegalArgumentException
* If no classes were specified
*/
T addClasses(Class<?>... classes) throws IllegalArgumentException;

@struberg
Copy link
Member

struberg commented May 8, 2017

I think it should. Inner classes get automatically added with the top level class. Thus we did have it twice after manually adding it again. Now you should only see it once.

@hutchig
Copy link
Author

hutchig commented May 8, 2017

Apologies but I have just tried this now (again, I had done it before) and it does not change the war file so test still fails.

@struberg
Copy link
Member

struberg commented May 8, 2017

Weird, just checked with the debugger

That's the jar content:
0 = {java.util.LinkedHashMap$Entry@1810} "BasicPath [context=/]" -> "/"
1 = {java.util.LinkedHashMap$Entry@1811} "BasicPath [context=/org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest.class]" -> "/org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest.class"
2 = {java.util.LinkedHashMap$Entry@1812} "BasicPath [context=/org]" -> "/org"
3 = {java.util.LinkedHashMap$Entry@1813} "BasicPath [context=/org/eclipse]" -> "/org/eclipse"
4 = {java.util.LinkedHashMap$Entry@1814} "BasicPath [context=/org/eclipse/microprofile]" -> "/org/eclipse/microprofile"
5 = {java.util.LinkedHashMap$Entry@1815} "BasicPath [context=/org/eclipse/microprofile/config]" -> "/org/eclipse/microprofile/config"
6 = {java.util.LinkedHashMap$Entry@1816} "BasicPath [context=/org/eclipse/microprofile/config/tck]" -> "/org/eclipse/microprofile/config/tck"
7 = {java.util.LinkedHashMap$Entry@1817} "BasicPath [context=/org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest$OptionalValuesBean.class]" -> "/org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest$OptionalValuesBean.class"
8 = {java.util.LinkedHashMap$Entry@1818} "BasicPath [context=/META-INF/microprofile-config.properties]" -> "/META-INF/microprofile-config.properties"
9 = {java.util.LinkedHashMap$Entry@1819} "BasicPath [context=/META-INF]" -> "/META-INF"
10 = {java.util.LinkedHashMap$Entry@1820} "BasicPath [context=/META-INF/beans.xml]" -> "/META-INF/beans.xml"

Here is the content of the war:
0 = {java.util.LinkedHashMap$Entry@1929} "BasicPath [context=/]" -> "/"
1 = {java.util.LinkedHashMap$Entry@1930} "BasicPath [context=/WEB-INF/lib/cdiOptionalInjectionTest.jar]" -> "/WEB-INF/lib/cdiOptionalInjectionTest.jar"
2 = {java.util.LinkedHashMap$Entry@1931} "BasicPath [context=/WEB-INF]" -> "/WEB-INF"
3 = {java.util.LinkedHashMap$Entry@1932} "BasicPath [context=/WEB-INF/lib]" -> "/WEB-INF/lib"

As you can see, the OptionalValuesBean.class exists only once.
Anything I miss?

@hutchig
Copy link
Author

hutchig commented May 8, 2017

There must be something in my environment that is causing Shrinkwrap to behave differently. My war looks like this:
./WEB-INF/classes/org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest$OptionalValuesBean.class
./WEB-INF/classes/org/eclipse/microprofile/config/tck/CdiOptionalInjectionTest.class
./WEB-INF/lib/arquillian-core.jar
./WEB-INF/lib/arquillian-protocol.jar
./WEB-INF/lib/arquillian-testenricher-cdi.jar
./WEB-INF/lib/arquillian-testenricher-ejb.jar
./WEB-INF/lib/arquillian-testenricher-initialcontext.jar
./WEB-INF/lib/arquillian-testenricher-resource.jar
./WEB-INF/lib/arquillian-testng.jar
./WEB-INF/lib/cdiOptionalInjectionTest.jar

So you also have much
fewer jars than I do in the WEB-INF/lib it must be some difference in the env or config (no pun intended) I will investigate.

@struberg
Copy link
Member

struberg commented May 8, 2017

I guess the arquillian-connector-weld adds those files. That are just arquillian jars.

How does your cdiOptionalInjectionTest.jar file look like?

@hutchig
Copy link
Author

hutchig commented May 11, 2017

The jar looks really plain (just like yours above).
The addition jars in my war that get added to the war:/WEB-INF/lib
come from the Arquillian code that
ServiceLoads AuxiliaryArchiveAppenders in the Arquillian code stack:

private List<Archive<?>> loadAuxiliaryArchives(DeploymentDescription deployment) 
   {
      List<Archive<?>> archives = new ArrayList<Archive<?>>();

      // load based on the Containers ClassLoader
      Collection<AuxiliaryArchiveAppender> archiveAppenders = serviceLoader.get().all(AuxiliaryArchiveAppender.class);
      
for(AuxiliaryArchiveAppender archiveAppender : archiveAppenders)
      {
         Archive<?> auxiliaryArchive = archiveAppender.createAuxiliaryArchive();
         if(auxiliaryArchive != null)
         {
            archives.add(auxiliaryArchive);
         }
      }
      return archives;
...


from...
loadAuxiliaryArchives(DeploymentDescription deployment) 
buildTestableDeployments(DeploymentScenario scenario, TestClass testCase, ProtocolRegistry protoReg) 
createTestableDeployments(DeploymentScenario scenario, TestClass testCase)
generateDeployment(@Observes GenerateDeployment event)

and the testcase is added to the war (as well as already being in the jar) by the
Arquillian code in org.jboss.arquillian.container.test.impl.client.deployment.DeploymentGenerator:buildTestableDeployments in the code

try
         {
            // this should be made more reliable, does not work with e.g. a EnterpriseArchive
            if(ClassContainer.class.isInstance(applicationArchive)) 
            {
               ClassContainer<?> classContainer = ClassContainer.class.cast(applicationArchive);
               classContainer.addClass(testCase.getJavaClass());   //<<<<<<<<<<<<<<< here

I will have a bit more investigation on how to control this better...

@hutchig
Copy link
Author

hutchig commented May 11, 2017

The Arquillian code seems (to me) to always add the test class (and any inner classes) to the war in
buildTestableDeployments. If I look with the debugger, just after the war is created in the testcode I also see a clean war that looks just like yours, it is later that the war gets augmented by Arquillian prior to deployment and I wonder, Mark ( @struberg ), if you tar -tf the war in the ./target directory after the test is run I suspect you might see a war just like mine (at least with the testclass added to WEB-INF/classes). I think the reason it fails for me and not you might be due to the way that
Liberty plugs classloaders over the war into Weld - so in the Liberty environment there are two places to load the Bean from and not just a second one lower down the classpath of a classloader, or something similar.

@struberg
Copy link
Member

Hi @hutchig! If you see the class twice, then that might be a bug in the Arquillian adaptor of Liberty.
Does it run if you use weld alone or liberty-remote?

@hutchig
Copy link
Author

hutchig commented May 12, 2017

The Arquillian DeploymentGenerator is not container specific and the

buildTestableDeployments:classContainer.addClass(testCase.getJavaClass())

is not conditional (as far as I can tell) on any container attributes.

If I run the built war in wildfly 10 I get exactly the same deployment problem:

12:00:11,759 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-7) MSC000001: Failed to start service jboss.deployment.unit."_DEFAULT___DEFAULT__cdiOptionalInjectionTest.war".WeldStartService: org.jboss.msc.service.StartException in service jboss.deployment.unit."_DEFAULT___DEFAULT__cdiOptionalInjectionTest.war".WeldStartService: Failed to start service
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1904)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.jboss.weld.exceptions.DeploymentException: Exception List with 2 exceptions:
Exception 0 :
org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type Optional<Integer> with qualifiers @Default
  at injection point [BackedAnnotatedField] @Inject private org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest$OptionalValuesBean.notexistingProperty
  at org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest$OptionalValuesBean.notexistingProperty(OptionalValuesBean.java:0)
	at org.jboss.weld.bootstrap.Validator.validateInjectionPointForDeploymentProblems(Validator.java:359)
	at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:281)
	at org.jboss.weld.bootstrap.Validator.validateGeneralBean(Validator.java:134)
	at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:155)
	at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:518)
	at org.jboss.weld.bootstrap.ConcurrentValidator$1.doWork(ConcurrentValidator.java:68)
	at org.jboss.weld.bootstrap.ConcurrentValidator$1.doWork(ConcurrentValidator.java:66)
	at org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:63)
	at org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:56)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:748)
	at org.jboss.threads.JBossThread.run(JBossThread.java:320)
Exception 1 :
org.jboss.weld.exceptions.DeploymentException: WELD-001409: Ambiguous dependencies for type OptionalValuesBean with qualifiers @Default
  at injection point [BackedAnnotatedField] @Inject private org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest.optionalValuesBean
  at org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest.optionalValuesBean(CdiOptionalInjectionTest.java:0)
  Possible dependencies: 
  - Managed Bean [class org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest$OptionalValuesBean] with qualifiers [@Any @Default],
  - Managed Bean [class org.eclipse.microprofile.config.tck.CdiOptionalInjectionTest$OptionalValuesBean] with qualifiers [@Any @Default]

Does the way you run the tck leave wars in the filesystem (target dir) after you run the tests?

It is possible the way you run might be taking the other branch of the conditional

if(ClassContainer.class.isInstance(applicationArchive)) 

I will have a look if I can take the same branch.
An alternative is to move the bean out of the test class but that
does not solve the puzzle of why we get different behaviours which
I would like to resolve.

@struberg
Copy link
Member

@hutchig please do the following in your debugger

StringBuilder sb = new StringBuilder();
Enumeration urls = yourClassLoader.getResources("com/ibm/..../TheClass.class");
while (urls.hasMoreElements()) {
sb.apend("\n").apend(urs.nextElement());
}
sb.toString();

then check which resource paths you get for the class.

My guess is that isolation of the @deployment from your whole test classpath is not provided by your arquillian provider.

But just a blind guess of course...

@hutchig
Copy link
Author

hutchig commented May 15, 2017

Forgive me a little for veering off slightly Mark @struberg as I know this is QQ (or QS!) and not QAQA...

GIven the way the war as it is built on disk, I an sure it is correct that WELD throws a CDI error
on deployment. It does this for the war in both IBM and Redhat application servers even if this war is deployed outside Arquillian.

All the code the user requested to be in the archive is in the one jar and overall there is only one war - so WELD is correct to think app code could/should easily load from the two places the bean
is placed. We can't isolate a warX:/WEB-INF/lib/Y.jar from warX:/WEB-INF/classes.

Certainly, the thread context classloader that is part of the mp config spec as seen by the test code will be able to load from the same jar and the WEB-INF/lib of the war the jar is in - so I am certain that there is no relevant classloader here in JEE that should not be able to see both .class resources. For classloading, this is no problem at all as the first linkable .class resource found (in class/parent/diskloader_delegation_chainclasspath) will be used.

I think there are a few possibilities I can explore now that might resolve the issue given the current Arquillian core (i.e. not container specific connector) implementation:

  1. change the test case/setup/(perhaps container connector but I have not found this in the path yet) to take the other branch of if(ClassContainer.class.isInstance(applicationArchive))
    [I suspect this is why it runs in your single process environment as the ClassContainer is not a application archive (makes sense as it knows the test class is already visible so there is not need to add it)]
  2. change the testcase so that the injected bean is not a static nested class of the test class.
  3. [I currently think this third one is not possible] Construct some form of qualifier that can differentiate a class with identical source loaded from different locations (remembering we do not have control of either the initiating or defining classloader) [Edited to remove false comment about the bean not being used]

@hutchig
Copy link
Author

hutchig commented May 15, 2017

Test runs fine if the injected Bean is made a top level class (and the class added to the jar)

@Emily-Jiang
Copy link
Member

I'm going to move the bean class to its own class, which is nicer anymore and bypass the arquillian issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants