This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomee.git


The following commit(s) were added to refs/heads/main by this push:
     new fdc3be6500 TOMEE-4715 - Inject @Resource fields of the @Application 
descriptor and test instances in TomEEEmbeddedApplicationRunner (#2958)
fdc3be6500 is described below

commit fdc3be6500372e3c6bdb2f32190961a712c4ac82
Author: Richard Zowalla <[email protected]>
AuthorDate: Mon Sep 28 20:40:55 2026 +0200

    TOMEE-4715 - Inject @Resource fields of the @Application descriptor and 
test instances in TomEEEmbeddedApplicationRunner (#2958)
    
    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 0863805785..de2c20a551 100644
--- a/tomee/tomee-embedded/pom.xml
+++ b/tomee/tomee-embedded/pom.xml
@@ -227,6 +227,8 @@
                 <exclude>**/NoScannerSingleRunnerTest*</exclude>
                 <exclude>**/NoScannerSingleRunnerExtensionTest*</exclude>
                 <exclude>**/*RunnerExtensionModeTest*</exclude>
+                <exclude>**/ResourceInjectionExtensionTest*</exclude>
+                <exclude>**/ResourceByTypeInjectionExtensionTest*</exclude>
               </excludes>
             </configuration>
           </execution>
@@ -295,6 +297,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 3c1b170f58..13091edd63 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;
@@ -455,6 +468,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();
@@ -463,6 +478,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();
+        }
+    }
+}

Reply via email to