This is an automated email from the ASF dual-hosted git repository. ggrzybek pushed a commit to branch karaf-4.2.x in repository https://gitbox.apache.org/repos/asf/karaf.git
The following commit(s) were added to refs/heads/karaf-4.2.x by this push: new 7fc877f [KARAF-7032] Provide a test for JTA API and change javax.transaction.* configuration in jre.properties and config.properties new 7a0092c Merge pull request #1294 from grgrzybek/KARAF-7032-42x 7fc877f is described below commit 7fc877f6789c987a4d65cc0201b4a58a41d122fb Author: Grzegorz Grzybek <gr.grzy...@gmail.com> AuthorDate: Thu Feb 11 10:05:52 2021 +0100 [KARAF-7032] Provide a test for JTA API and change javax.transaction.* configuration in jre.properties and config.properties --- .../resources/etc/config.properties | 9 +- .../resources/etc/jre.properties | 44 +++- .../karaf/instance/resources/etc/config.properties | 7 +- .../test/java/org/apache/karaf/itests/JtaTest.java | 277 +++++++++++++++++++++ 4 files changed, 331 insertions(+), 6 deletions(-) diff --git a/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties b/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties index 4d9a7cf..6b418f2 100644 --- a/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties +++ b/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties @@ -169,12 +169,17 @@ eecap-1.8= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8" # -# javax.transaction is needed to avoid class loader constraint violation when using javax.sql +# javax.transaction is needed ONLY for com.sun.corba.se.impl.javax.rmi.CORBA.Util.mapSystemException(). +# JDK8 and earlier provide only 3 exception classes in this package, so full JTA API bundles should always try the +# bootdelegation first - even if they also package (and export) javax.transaction package +# +# boot delegation of javax.transaction.xa is needed to avoid class loader constraint violation when using javax.sql +# and this package is always complete in all JDKs # org.osgi.framework.bootdelegation=\ com.sun.*, \ javax.transaction, \ - javax.transaction.*, \ + javax.transaction.xa, \ javax.xml.crypto, \ javax.xml.crypto.*, \ jdk.nashorn.*, \ diff --git a/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties b/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties index 0f408ab..e99d6e4 100644 --- a/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties +++ b/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties @@ -340,6 +340,39 @@ jre-1.7= \ org.xml.sax.helpers, \ com.sun.nio.sctp +# +# A note about javax.transaction and javax.transaction.xa packages in JDK8 and JDK9+ +# - javax.transaction package is not provided at all in JDK9+ because of the removal of Corba +# - javax.transaction package in JDK8 and earlier contains only 3 exception classes required to translate 3 Corba/OMG +# exception: org.omg.CORBA.TRANSACTION_REQUIRED, org.omg.CORBA.TRANSACTION_ROLLEDBACK, org.omg.CORBA.INVALID_TRANSACTION +# - javax.transaction.xa package should always be provided by JDK itself (thus exported from system bundle and bootdelegated) +# because of javax.sql.XAConnection interface relying on javax.sql.xa.XAResource interface +# - I decided to export javax.transaction.xa package with all the versions: 1.1, 1.2 and 1.3 just to satisfy all potential +# import version ranges (and emphasize the fact that JavaEE doesn't version packages at all) +# - javax.transaction package should be exported by JDK8 (but not JDK9+) with mandatory attribute ("partial" is an +# arbirtary name mentioned in "https://docs.osgi.org/specification/osgi.core/7.0.0/framework.module.html#framework.module.requirebundle" +# - javax.transaction exported with "partial=true;mandatory:=partial" prevents system bundle to be a wire candidate for +# bundles with just "Import-Package: javax.transaction" - actual JTA API bundle is needed to provide all the classes +# from this package (like javax.transaction.UserTransaction) +# - thus javax.transaction package is exported without a version - because each bundle with "Import-Package: javax.transaction" +# should always wire to full JTA API bundle. The fact that the JDK8 provided exception classes from this package +# are always loaded using boot class loader is an obvious, but internal consequence +# - the full trick mentioned in "3.13.1 Require-Bundle" requires another bundle that exports javax.transaction package +# without mandatory attribute and that has Require-Bundle requirement to a bundle that exports the package with mandatory +# attribute - and that's what javax.transaction/javax.transaction-api/1.2 does - it contains "Require-Bundle: system.bundle" +# - Require-Bundle in JTA API bundle is not needed if javax.transaction is boot-delegated - because failure to search +# boot-delegated javax.* packages doesn't stop the class loading process - local content is checked +# - jakarta.transaction/jakarta.transaction-api/1.3.x doesn't have (by mistake, see https://github.com/eclipse-ee4j/jta-api/issues/186) +# "Require-Bundle: system.bundle", but it still works thanks to boot-delegation +# And last, but important thing - DBCP2 (see DBCP-571) has "Import-Package: javax.transaction.xa;partial=true;mandatory:=partial" +# which is simply wrong (if anything, javax.transaction package should be imported this way, not javax.transaction.xa), +# but to allow DBCP2 to be resolved on Karaf, special export package is added just for DBCP2: +# Export-Pacakge: javax.transaction.xa;partial=true;mandatory:=partial;version="1.1" +# - mandatory "partial" attribute is added to javax.transaction export (JDK8) to prevent wiring to this package without +# full JTA API bundle +# - mandatory "partial" attribute is added to javax.transaction.xa export (all JDKs) to satisfy DBCP2 +# + jre-1.8= \ javax.accessibility, \ javax.activity, \ @@ -419,7 +452,11 @@ jre-1.8= \ javax.swing.tree, \ javax.swing.undo, \ javax.tools, \ - javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \ + javax.transaction;partial=true;mandatory:=partial, \ + javax.transaction.xa;version="1.1";partial=true;mandatory:=partial, \ + javax.transaction.xa;version="1.1", \ + javax.transaction.xa;version="1.2", \ + javax.transaction.xa;version="1.3", \ javax.xml, \ javax.xml.bind;version="2.2.8", \ javax.xml.bind.annotation;version="2.2.8", \ @@ -612,7 +649,10 @@ jre-9= \ javax.swing.tree, \ javax.swing.undo, \ javax.tools, \ - javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \ + javax.transaction.xa;version="1.1";partial=true;mandatory:=partial, \ + javax.transaction.xa;version="1.1", \ + javax.transaction.xa;version="1.2", \ + javax.transaction.xa;version="1.3", \ javax.xml, \ javax.xml.bind;version="2.3.0", \ javax.xml.bind.annotation;version="2.3.0", \ diff --git a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties index d50a4be..69313fd 100644 --- a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties +++ b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties @@ -173,12 +173,15 @@ eecap-1.8= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8" # -# javax.transaction is needed to avoid class loader constraint violation when using javax.sql +# javax.transaction is needed ONLY for com.sun.corba.se.impl.javax.rmi.CORBA.Util.mapSystemException(). JDK8 and earlier +# provide only 3 exception classes in this package, so full JTA bundles should always try first the bootdelegation +# javax.transaction.xa is needed to avoid class loader constraint violation when using javax.sql and this package +# is always complete in JDK8, earlier and older ones (including JPMS JDKs, a.k.a. "Jigsaw") # org.osgi.framework.bootdelegation = \ com.sun.*, \ javax.transaction, \ - javax.transaction.*, \ + javax.transaction.xa, \ javax.xml.crypto, \ javax.xml.crypto.*, \ jdk.nashorn.*, \ diff --git a/itests/test/src/test/java/org/apache/karaf/itests/JtaTest.java b/itests/test/src/test/java/org/apache/karaf/itests/JtaTest.java new file mode 100644 index 0000000..a8f0e3e --- /dev/null +++ b/itests/test/src/test/java/org/apache/karaf/itests/JtaTest.java @@ -0,0 +1,277 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.karaf.itests; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Properties; + +import aQute.lib.strings.Strings; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Configuration; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; +import org.ops4j.pax.exam.spi.reactors.PerMethod; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.framework.wiring.FrameworkWiring; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.ops4j.pax.exam.OptionUtils.combine; +import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut; + +@RunWith(PaxExam.class) +@ExamReactorStrategy(PerMethod.class) +public class JtaTest extends BaseTest { + + @Configuration + public Option[] config() { + File originalConfig = new File("../../assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties"); + Properties conf = new Properties(); + try (FileReader fr = new FileReader(originalConfig)) { + conf.load(fr); + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + String bd = conf.getProperty("org.osgi.framework.bootdelegation"); + String[] packages = bd.split("\\s*,\\s*"); + String[] filtered = Arrays.stream(packages).filter(p -> !p.contains("javax.transaction")).toArray(String[]::new); + + // unfortunately this doesn't work. editConfigurationFilePut() for config.properties is overwritten by + // bootDelegation() options which always come last + // so I can't perform tests without boot delegation of javax.transaction packages + return combine(super.config(), editConfigurationFilePut("etc/config.properties", "org.osgi.framework.bootdelegation", Strings.join(", ", filtered))); + } + + @Test + public void noSpecialFeatures() throws Exception { + ClassLoader cl = FrameworkUtil.getBundle(this.getClass()).adapt(BundleWiring.class).getClassLoader(); + + if (isJDK8OrEarlier()) { + // these classes should be boot delegated because they should be part of JDK8, all used ONLY + // in com.sun.corba.se.impl.javax.rmi.CORBA.Util.mapSystemException() + ensureLoadedFromSystem(cl, "javax.transaction.InvalidTransactionException"); + ensureLoadedFromSystem(cl, "javax.transaction.TransactionRequiredException"); + ensureLoadedFromSystem(cl, "javax.transaction.TransactionRolledbackException"); + } else { + // JDK9+ doesn't provide javax.transaction package at all + ensureNotFound(cl, "javax.transaction.InvalidTransactionException"); + ensureNotFound(cl, "javax.transaction.TransactionRequiredException"); + ensureNotFound(cl, "javax.transaction.TransactionRolledbackException"); + } + + // whatever the JDK, these classes should be available + ensureLoadedFromSystem(cl, "javax.transaction.xa.XAException"); + ensureLoadedFromSystem(cl, "javax.transaction.xa.XAResource"); + ensureLoadedFromSystem(cl, "javax.transaction.xa.Xid"); + + // whatever the JDK, these classes should NOT be available + ensureNotFound(cl, "javax.transaction.UserTransaction"); + ensureNotFound(cl, "javax.transaction.TransactionManager"); + } + + @Test + public void javaxTransaction1_2() throws Exception { + addFeaturesRepository("mvn:org.apache.karaf.features/enterprise/" + System.getProperty("karaf.version") + "/xml/features"); + // this feature installs javax.transaction/javax.transaction-api/1.2 with Require-Bundle: system.bundle + installAndAssertFeature("transaction-api"); + + ClassLoader myCl = FrameworkUtil.getBundle(this.getClass()).adapt(BundleWiring.class).getClassLoader(); + ClassLoader jtaCl = FrameworkUtil.getBundle(myCl.loadClass("javax.transaction.UserTransaction")).adapt(BundleWiring.class).getClassLoader(); + + if (isJDK8OrEarlier()) { + // these classes should be boot delegated + ensureLoadedFromSystem(myCl, "javax.transaction.InvalidTransactionException"); + ensureLoadedFromSystem(myCl, "javax.transaction.TransactionRequiredException"); + ensureLoadedFromSystem(myCl, "javax.transaction.TransactionRolledbackException"); + } else { + // these classes ARE boot delegated, but can't be found in JDK, so they're loaded from the API bundle + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.InvalidTransactionException"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionRequiredException"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionRolledbackException"); + } + + // whatever the JDK, these classes should be available from system CL, even if javax.transaction-api/1.2 + // exports javax.transaction.xa package + ensureLoadedFromSystem(myCl, "javax.transaction.xa.XAException"); + ensureLoadedFromSystem(myCl, "javax.transaction.xa.XAResource"); + ensureLoadedFromSystem(myCl, "javax.transaction.xa.Xid"); + ensureLoadedFromSystem(jtaCl, "javax.transaction.xa.XAException"); + ensureLoadedFromSystem(jtaCl, "javax.transaction.xa.XAResource"); + ensureLoadedFromSystem(jtaCl, "javax.transaction.xa.Xid"); + + // these classes should be loaded from JTA API bundle + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionManager"); + ensureLoadedFromCl(jtaCl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(jtaCl, jtaCl, "javax.transaction.TransactionManager"); + } + + @Test + public void javaxTransaction1_2AndDBCP2() throws Exception { + addFeaturesRepository("mvn:org.apache.karaf.features/enterprise/" + System.getProperty("karaf.version") + "/xml/features"); + // this feature installs javax.transaction/javax.transaction-api/1.2 with Require-Bundle: system.bundle + installAndAssertFeature("transaction-api"); + + Bundle pool2 = bundleContext.installBundle("mvn:org.apache.commons/commons-pool2/2.9.0"); + // DBCP2 has: + // - Import-Package: javax.transaction;version="1.1" + // - Import-Package: javax.transaction.xa;version="1.1";partial=true;mandatory:=partial + // Karaf provides special Export-Package: javax.transaction.xa;version="1.1";partial=true;mandatory:=partial + // from system bundle just for DBCP2 + // javax.transaction package is exported from system bundle (in JDK8) to prevent wiring this package + // requirement to system bundle - full JTA API is required and javax.transaction-api/1.2 does this + // using Require-Bundle: system.bundle + Bundle dbcp2 = bundleContext.installBundle("mvn:org.apache.commons/commons-dbcp2/2.8.0"); + boolean resolved = bundleContext.getBundle(0).adapt(FrameworkWiring.class).resolveBundles(Collections.singletonList(dbcp2)); + assertTrue(resolved); + + ClassLoader myCl = FrameworkUtil.getBundle(this.getClass()).adapt(BundleWiring.class).getClassLoader(); + ClassLoader dbcp2Cl = dbcp2.adapt(BundleWiring.class).getClassLoader(); + ClassLoader jtaCl = FrameworkUtil.getBundle(myCl.loadClass("javax.transaction.UserTransaction")).adapt(BundleWiring.class).getClassLoader(); + + if (isJDK8OrEarlier()) { + // these classes should be boot delegated + ensureLoadedFromSystem(myCl, "javax.transaction.InvalidTransactionException"); + ensureLoadedFromSystem(myCl, "javax.transaction.TransactionRequiredException"); + ensureLoadedFromSystem(myCl, "javax.transaction.TransactionRolledbackException"); + } else { + // these classes ARE boot delegated, but can't be found in JDK, so they're loaded from the API bundle + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.InvalidTransactionException"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionRequiredException"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionRolledbackException"); + } + + // These classes should come from system - whether loaded from my, jta or dbcp2 CL, even if dbcp2 wires + // directly to system through Import-Package: javax.transaction.xa;partial=true;mandatory:=partial + // It works because: + // - javax.transaction-api/1.2 has "Require-Bundle: system.bundle", which has priority over the fact that + // javax.transaction.xa package is also exported from this bundle + // - javax.transaction-api/1.3 has "Require-Bundle: system.bundle" and doesn't export javax.transaction.xa + // - jakarta.transaction-api/1.3 doesn't have "Require-Bundle: system.bundle" and doesn't export javax.transaction.xa + // see: + // - https://github.com/ops4j/org.ops4j.pax.transx/issues/33 + // - https://issues.apache.org/jira/browse/DBCP-571 + ensureLoadedFromSystem(myCl, "javax.transaction.xa.Xid"); + ensureLoadedFromSystem(jtaCl, "javax.transaction.xa.Xid"); + ensureLoadedFromSystem(dbcp2Cl, "javax.transaction.xa.Xid"); + + // these classes should be loaded from JTA API bundle + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionManager"); + ensureLoadedFromCl(jtaCl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(jtaCl, jtaCl, "javax.transaction.TransactionManager"); + ensureLoadedFromCl(dbcp2Cl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(dbcp2Cl, jtaCl, "javax.transaction.TransactionManager"); + } + + @Test + public void jakartaTransaction1_3AndDBCP2() throws Exception { + // this set of bundles matches Karaf's transaction-api feature, but uses jakarta.transaction-api/1.3 instead + // of javax.transaction-api/1.2 + bundleContext.installBundle("mvn:javax.interceptor/javax.interceptor-api/1.2"); + bundleContext.installBundle("mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.javax-inject/1_2"); + bundleContext.installBundle("mvn:javax.el/javax.el-api/3.0.0"); + bundleContext.installBundle("mvn:javax.enterprise/cdi-api/1.2"); + // this bundle doesn't have Require-Bundle: system.bundle, but javax.transaction package (the 3 exception + // classes provided by JDK8) is still bootdelegated by Karaf + Bundle jta = bundleContext.installBundle("mvn:jakarta.transaction/jakarta.transaction-api/1.3.3"); + + Bundle pool2 = bundleContext.installBundle("mvn:org.apache.commons/commons-pool2/2.9.0"); + Bundle dbcp2 = bundleContext.installBundle("mvn:org.apache.commons/commons-dbcp2/2.8.0"); + // this won't resolve if system bundle doesn't export javax.transaction.xa package without mandatory "partial" + // attribute because jakarta.transaction-api/1.3.x exporting javax.transaction is needed to resolve dbcp2 + boolean resolved = bundleContext.getBundle(0).adapt(FrameworkWiring.class).resolveBundles(Collections.singletonList(dbcp2)); + assertTrue(resolved); + + ClassLoader myCl = FrameworkUtil.getBundle(this.getClass()).adapt(BundleWiring.class).getClassLoader(); + ClassLoader dbcp2Cl = dbcp2.adapt(BundleWiring.class).getClassLoader(); + ClassLoader jtaCl = FrameworkUtil.getBundle(myCl.loadClass("javax.transaction.UserTransaction")).adapt(BundleWiring.class).getClassLoader(); + + if (isJDK8OrEarlier()) { + // these classes should be boot delegated + ensureLoadedFromSystem(myCl, "javax.transaction.InvalidTransactionException"); + ensureLoadedFromSystem(myCl, "javax.transaction.TransactionRequiredException"); + ensureLoadedFromSystem(myCl, "javax.transaction.TransactionRolledbackException"); + } else { + // these classes ARE boot delegated, but can't be found in JDK, so they're loaded from the API bundle + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.InvalidTransactionException"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionRequiredException"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionRolledbackException"); + } + + ensureLoadedFromSystem(jtaCl, "javax.transaction.xa.Xid"); + ensureLoadedFromSystem(myCl, "javax.transaction.xa.Xid"); + ensureLoadedFromSystem(dbcp2Cl, "javax.transaction.xa.Xid"); + + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(myCl, jtaCl, "javax.transaction.TransactionManager"); + ensureLoadedFromCl(jtaCl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(jtaCl, jtaCl, "javax.transaction.TransactionManager"); + ensureLoadedFromCl(dbcp2Cl, jtaCl, "javax.transaction.UserTransaction"); + ensureLoadedFromCl(dbcp2Cl, jtaCl, "javax.transaction.TransactionManager"); + } + + private void ensureLoadedFromSystem(ClassLoader cl, String className) { + try { + Class<?> c = cl.loadClass(className); + assertTrue(c != null && (c.getClassLoader() == null || c.getClassLoader().getClass().getName().contains("jdk.internal"))); + } catch (ClassNotFoundException e) { + fail("Can't load " + className); + } + } + + private void ensureLoadedFromCl(ClassLoader initiatingCl, ClassLoader loadingCl, String className) { + try { + Class<?> c = initiatingCl.loadClass(className); + assertTrue(c != null && c.getClassLoader() == loadingCl); + } catch (ClassNotFoundException e) { + fail("Can't load " + className); + } + } + + private void ensureNotFound(ClassLoader cl, String className) { + try { + Class<?> c = cl.loadClass(className); + fail("Class " + className + " should not be available"); + } catch (ClassNotFoundException ignored) { + } + } + + private boolean isJDK8OrEarlier() { + String v = System.getProperty("java.specification.version"); + try { + if (v.contains(".")) { + float f = Float.parseFloat(v); + return f < 1.9F; + } else { + int i = Integer.parseInt(v); + return i < 9; + } + } catch (NumberFormatException ignored) { + return true; + } + } + +}