This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch TOMEE-4715 in repository https://gitbox.apache.org/repos/asf/tomee.git
commit faada5547adbb80a099373821bf06a5b9258fa15 Author: Richard Zowalla <[email protected]> AuthorDate: Mon Sep 28 10:00:22 2026 +0200 TOMEE-4715 - Inject @Resource fields of the @Application descriptor and test instances in TomEEEmbeddedApplicationRunner The descriptor and the test instances are not components of the deployed application, so there are no resource references for them and the fields stayed null. Container resources are now linked like AutoConfig does it: by the explicit lookup or name, otherwise by the name of the field and then by the type. An explicit lookup or name which can't be resolved is logged and the field is left untouched, no other resource is injected instead. --- tomee/tomee-embedded/pom.xml | 30 ++++++ .../embedded/TomEEEmbeddedApplicationRunner.java | 110 +++++++++++++++++++++ .../ResourceByTypeInjectionExtensionTest.java | 82 +++++++++++++++ .../embedded/ResourceInjectionExtensionTest.java | 108 ++++++++++++++++++++ 4 files changed, 330 insertions(+) diff --git a/tomee/tomee-embedded/pom.xml b/tomee/tomee-embedded/pom.xml index 4641d6d2b2..668817fffa 100644 --- a/tomee/tomee-embedded/pom.xml +++ b/tomee/tomee-embedded/pom.xml @@ -226,6 +226,8 @@ <exclude>**/SingleInstanceRunnerExtensionTest*</exclude> <exclude>**/NoScannerSingleRunnerTest*</exclude> <exclude>**/NoScannerSingleRunnerExtensionTest*</exclude> + <exclude>**/ResourceInjectionExtensionTest*</exclude> + <exclude>**/ResourceByTypeInjectionExtensionTest*</exclude> </excludes> </configuration> </execution> @@ -257,6 +259,34 @@ </systemPropertyVariables> </configuration> </execution> + <execution> + <id>single-server-resource-by-type-injection</id> + <goals> + <goal>test</goal> + </goals> + <configuration> + <includes> + <include>**/ResourceByTypeInjectionExtensionTest*</include> + </includes> + <systemPropertyVariables> + <tomee.application-composer.application>org.apache.tomee.embedded.ResourceByTypeInjectionExtensionTest$TheApp</tomee.application-composer.application> + </systemPropertyVariables> + </configuration> + </execution> + <execution> + <id>single-server-resource-injection</id> + <goals> + <goal>test</goal> + </goals> + <configuration> + <includes> + <include>**/ResourceInjectionExtensionTest*</include> + </includes> + <systemPropertyVariables> + <tomee.application-composer.application>org.apache.tomee.embedded.ResourceInjectionExtensionTest$TheApp</tomee.application-composer.application> + </systemPropertyVariables> + </configuration> + </execution> </executions> </plugin> </plugins> diff --git a/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/TomEEEmbeddedApplicationRunner.java b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/TomEEEmbeddedApplicationRunner.java index 16a1082a1e..310c800340 100644 --- a/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/TomEEEmbeddedApplicationRunner.java +++ b/tomee/tomee-embedded/src/main/java/org/apache/tomee/embedded/TomEEEmbeddedApplicationRunner.java @@ -17,8 +17,13 @@ package org.apache.tomee.embedded; import org.apache.commons.lang3.text.StrSubstitutor; +import org.apache.openejb.assembler.classic.OpenEjbConfiguration; +import org.apache.openejb.assembler.classic.ResourceInfo; +import org.apache.openejb.config.AutoConfig; +import org.apache.openejb.config.ConfigurationFactory; import org.apache.openejb.config.DeploymentsResolver; import org.apache.openejb.loader.SystemInstance; +import org.apache.openejb.spi.ContainerSystem; import org.apache.openejb.testing.Application; import org.apache.openejb.testing.ApplicationComposers; import org.apache.openejb.testing.Classes; @@ -37,7 +42,10 @@ import org.apache.xbean.finder.archive.FileArchive; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; import jakarta.enterprise.inject.Vetoed; +import javax.naming.InitialContext; +import javax.naming.NamingException; import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -46,6 +54,7 @@ import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -62,7 +71,9 @@ import java.util.logging.Logger; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.util.logging.Level.FINE; import static java.util.logging.Level.SEVERE; +import static java.util.logging.Level.WARNING; import static org.apache.openejb.loader.JarLocation.jarLocation; import static org.apache.openejb.util.Classes.ancestors; @@ -86,6 +97,8 @@ public class TomEEEmbeddedApplicationRunner implements AutoCloseable { }); } + private static final Logger LOGGER = Logger.getLogger(TomEEEmbeddedApplicationRunner.class.getName()); + private volatile boolean started = false; private volatile Object app; private volatile Thread hook; @@ -454,6 +467,8 @@ public class TomEEEmbeddedApplicationRunner implements AutoCloseable { } final TomEEEmbeddedArgs args = SystemInstance.get().getComponent(TomEEEmbeddedArgs.class); f.set(target, args == null ? new String[0] : args.getArgs()); + } else if (f.isAnnotationPresent(Resource.class)) { + injectResource(target, f); } } aClass = aClass.getSuperclass(); @@ -462,6 +477,101 @@ public class TomEEEmbeddedApplicationRunner implements AutoCloseable { SystemInstance.get().fireEvent(new TomEEEmbeddedApplicationRunnerInjection(target)); } + // the descriptor and the test instances are not components of the deployed application so the container + // didn't create any resource-ref for them, we link the container resources the same way AutoConfig does it: + // explicit lookup or name, otherwise the field name and then the first resource matching the type. + // Only fields are supported and only container resources (openejb/Resource/<id>) are resolved. + private void injectResource(final Object target, final Field field) throws IllegalAccessException { + if (field.getType().isPrimitive() || Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) { + return; + } + if (!field.isAccessible()) { + field.setAccessible(true); + } + if (field.get(target) != null) { // already injected, don't override it + return; + } + + final Resource resource = field.getAnnotation(Resource.class); + final Class<?> type = field.getType(); + final boolean explicit = !resource.lookup().isEmpty() || !resource.name().isEmpty(); + final Object value; + if (!resource.lookup().isEmpty()) { + value = lookupResource(resource.lookup(), type); + } else if (!resource.name().isEmpty()) { + value = findResource(resource.name(), type, false); + } else { + value = findResource(field.getName(), type, true); + } + + if (value != null) { + field.set(target, value); + } else { // explicit ones are likely a mistake, the other ones can be something else than a container resource + LOGGER.log(explicit ? WARNING : FINE, "Can't find a resource to inject in " + field); + } + } + + private Object lookupResource(final String lookup, final Class<?> type) { + try { + final Object value = new InitialContext().lookup(lookup); + if (type.isInstance(value)) { + return value; + } + } catch (final NamingException | RuntimeException e) { + LOGGER.log(FINE, "Can't lookup " + lookup + ", trying the container resources", e); + } + return lookupContainerResource(AutoConfig.findResourceId(findResourceIds(null), lookup), type); + } + + // explicit names don't fallback on another resource of the same type to not inject an unexpected resource + private Object findResource(final String name, final Class<?> type, final boolean fallbackOnType) { + final List<String> typedIds = findResourceIds(type.getName()); + Object value = lookupContainerResource(AutoConfig.findResourceId(typedIds, name), type); + if (value == null) { // any type, can be a subtype + value = lookupContainerResource(AutoConfig.findResourceId(findResourceIds(null), name), type); + } + if (value == null && fallbackOnType) { // first one of the expected type + for (final String id : typedIds) { + value = lookupContainerResource(id, type); + if (value != null) { + break; + } + } + } + return value; + } + + private List<String> findResourceIds(final String type) { + final List<String> ids = new ArrayList<>(); + final OpenEjbConfiguration configuration = SystemInstance.get().getComponent(OpenEjbConfiguration.class); + if (configuration == null || configuration.facilities == null) { + return ids; + } + for (final ResourceInfo info : configuration.facilities.resources) { + if (type == null || type.equals(info.className) || ConfigurationFactory.isResourceType(info.service, info.types, type)) { + ids.add(info.id); + ids.addAll(info.aliases); + } + } + return ids; + } + + private Object lookupContainerResource(final String id, final Class<?> type) { + if (id == null) { + return null; + } + final ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class); + if (containerSystem == null) { + return null; + } + try { + final Object value = containerSystem.getJNDIContext().lookup("openejb/Resource/" + id); + return type.isInstance(value) ? value : null; + } catch (final NamingException e) { + return null; + } + } + @Retention(RUNTIME) @Target(TYPE) public @interface LifecycleTasks { diff --git a/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/ResourceByTypeInjectionExtensionTest.java b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/ResourceByTypeInjectionExtensionTest.java new file mode 100644 index 0000000000..634a8e5931 --- /dev/null +++ b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/ResourceByTypeInjectionExtensionTest.java @@ -0,0 +1,82 @@ +/* + * 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.tomee.embedded; + +import org.apache.openejb.testing.Application; +import org.apache.openejb.testing.Classes; +import org.apache.openejb.testng.PropertiesBuilder; +import org.apache.tomee.embedded.junit.jupiter.RunWithTomEEEmbedded; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import jakarta.annotation.Resource; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// the name of the fields doesn't match the single DataSource, so it is resolved by its type, +// needs its own surefire execution since the test jar contains several @Application, +// -Dtomee.application-composer.application=org.apache.tomee.embedded.ResourceByTypeInjectionExtensionTest$TheApp +@RunWithTomEEEmbedded +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ResourceByTypeInjectionExtensionTest { + @Application + private TheApp app; + + @Resource + private DataSource dataSource; + + @Test + public void descriptorInjection() throws SQLException { + assertNotNull(app); + assertUsable(app.dataSource); + } + + @Test + public void testInstanceInjection() throws SQLException { + assertUsable(dataSource); + } + + private static void assertUsable(final DataSource dataSource) throws SQLException { + assertNotNull(dataSource); + try (final Connection connection = dataSource.getConnection()) { + assertFalse(connection.isClosed()); + assertTrue(connection.getMetaData().getURL().startsWith("jdbc:hsqldb:mem:resource-by-type"), connection.getMetaData().getURL()); + } + } + + @Application + @Classes(cdi = true, context = "app") + public static class TheApp { + @Resource + private DataSource dataSource; + + @org.apache.openejb.testing.Configuration + public Properties config() { + return new PropertiesBuilder() + .p("db", "new://Resource?type=DataSource") + .p("db.JdbcDriver", "org.hsqldb.jdbcDriver") + .p("db.JdbcUrl", "jdbc:hsqldb:mem:resource-by-type") + .build(); + } + } +} diff --git a/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/ResourceInjectionExtensionTest.java b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/ResourceInjectionExtensionTest.java new file mode 100644 index 0000000000..cd55b3ea84 --- /dev/null +++ b/tomee/tomee-embedded/src/test/java/org/apache/tomee/embedded/ResourceInjectionExtensionTest.java @@ -0,0 +1,108 @@ +/* + * 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.tomee.embedded; + +import org.apache.openejb.testing.Application; +import org.apache.openejb.testing.Classes; +import org.apache.openejb.testng.PropertiesBuilder; +import org.apache.tomee.embedded.junit.jupiter.RunWithTomEEEmbedded; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import jakarta.annotation.Resource; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// needs its own surefire execution since the test jar contains several @Application, +// -Dtomee.application-composer.application=org.apache.tomee.embedded.ResourceInjectionExtensionTest$TheApp +@RunWithTomEEEmbedded +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ResourceInjectionExtensionTest { + @Application + private TheApp app; + + @Resource // matches the resource by the name of the field + private DataSource db; + + @Resource(name = "other") + private DataSource named; + + @Resource(name = "unknown") // no fallback on another DataSource for an explicit name + private DataSource unknown; + + @Resource(lookup = "openejb:Resource/other") + private DataSource lookedUp; + + @Resource(lookup = "openejb:Resource/missing") + private DataSource missing; + + @Test + public void descriptorInjection() throws SQLException { + assertNotNull(app); + assertNotNull(app.db); + assertUrl(app.db, "jdbc:hsqldb:mem:resource-injection"); + } + + @Test + public void testInstanceInjection() throws SQLException { + assertNotNull(db); + assertUrl(db, "jdbc:hsqldb:mem:resource-injection"); + } + + @Test + public void explicitResource() throws SQLException { + assertNotNull(named); + assertUrl(named, "jdbc:hsqldb:mem:resource-injection-other"); + assertNotNull(lookedUp); + assertUrl(lookedUp, "jdbc:hsqldb:mem:resource-injection-other"); + assertNull(missing); + assertNull(unknown); + } + + private static void assertUrl(final DataSource dataSource, final String expected) throws SQLException { + try (final Connection connection = dataSource.getConnection()) { + assertFalse(connection.isClosed()); + assertTrue(connection.getMetaData().getURL().startsWith(expected), connection.getMetaData().getURL()); + } + } + + @Application + @Classes(cdi = true, context = "app") + public static class TheApp { + @Resource + private DataSource db; + + @org.apache.openejb.testing.Configuration + public Properties config() { + return new PropertiesBuilder() + .p("db", "new://Resource?type=DataSource") + .p("db.JdbcDriver", "org.hsqldb.jdbcDriver") + .p("db.JdbcUrl", "jdbc:hsqldb:mem:resource-injection") + .p("other", "new://Resource?type=DataSource") + .p("other.JdbcDriver", "org.hsqldb.jdbcDriver") + .p("other.JdbcUrl", "jdbc:hsqldb:mem:resource-injection-other") + .build(); + } + } +}
