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();
+ }
+ }
+}