diff --git a/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/ContainerUtil.java b/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/ContainerUtil.java index cc0764103..aa672c321 100644 --- a/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/ContainerUtil.java +++ b/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/ContainerUtil.java @@ -56,12 +56,11 @@ import org.openntf.xsp.jakartaee.util.ModuleUtil; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; -import org.osgi.framework.BundleReference; import org.osgi.framework.wiring.BundleWiring; import com.ibm.commons.util.StringUtil; import com.ibm.designer.runtime.domino.adapter.ComponentModule; -import com.ibm.domino.xsp.adapter.osgi.OSGIModule; +import com.ibm.domino.xsp.adapter.osgi.AbstractOSGIModule; import jakarta.el.ELResolver; import jakarta.el.ExpressionFactory; @@ -143,7 +142,7 @@ public static CDI getContainer(Bundle bundle) { Set bundleNames = new HashSet<>(); Set classNames = new HashSet<>(); try { - addBundleBeans(bundle, weld, bundleNames, classNames); + addBundleBeans(bundle, weld, bundleNames, classNames, true); } catch (BundleException e) { if(log.isLoggable(Level.WARNING)) { log.log(Level.WARNING, "Encountered exception loading bundle beans", e); @@ -199,14 +198,14 @@ public static CDI getContainerUnchecked(ComponentModule module) { return (CDI)module.getAttributes().get(ATTR_CONTEXTCONTAINER); } - private static void addBundleBeans(Bundle bundle, Weld weld, Set bundleNames, Set classNames) throws BundleException { + private static void addBundleBeans(Bundle bundle, Weld weld, Set bundleNames, Set classNames, boolean nonExported) throws BundleException { String symbolicName = bundle.getSymbolicName(); if(bundleNames.contains(symbolicName)) { return; } bundleNames.add(symbolicName); // Add classes from the bundle here - DiscoveryUtil.findBeanClasses(bundle) + DiscoveryUtil.findBeanClasses(bundle, nonExported) .filter(t -> !classNames.contains(t.getName())) .peek(t -> classNames.add(t.getName())) .distinct() @@ -221,7 +220,7 @@ private static void addBundleBeans(Bundle bundle, Weld weld, Set bundleN if(StringUtil.isNotEmpty(bundleName)) { Optional dependency = LibraryUtil.getBundle(bundleName); if(dependency.isPresent()) { - addBundleBeans(dependency.get(), weld, bundleNames, classNames); + addBundleBeans(dependency.get(), weld, bundleNames, classNames, false); } } } @@ -238,10 +237,12 @@ private static void addBundleBeans(Bundle bundle, Weld weld, Set bundleN public static CDI getContainer(ComponentModule module) { // OSGi Servlets use their containing bundle, and we have to assume // that it's from the current thread - if(module instanceof OSGIModule) { + if(module instanceof AbstractOSGIModule) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if(cl instanceof BundleReference) { - return getContainer(((BundleReference) cl).getBundle()); + Optional bundle = DiscoveryUtil.getBundleForClassLoader(cl); + if(bundle.isPresent()) { + return getContainer(bundle.get()); } } @@ -255,7 +256,7 @@ public static CDI getContainer(ComponentModule module) { long refresh = module.getLastRefresh(); - if(module instanceof OSGIModule || LibraryUtil.usesLibrary(CDILibrary.LIBRARY_ID, module)) { + if(module instanceof AbstractOSGIModule || LibraryUtil.usesLibrary(CDILibrary.LIBRARY_ID, module)) { String bundleId = getApplicationCDIBundle(module); if(StringUtil.isNotEmpty(bundleId)) { Optional bundle = LibraryUtil.getBundle(bundleId); @@ -292,7 +293,7 @@ public static CDI getContainer(ComponentModule module) { Set bundleNames = new HashSet<>(); Set classNames = new HashSet<>(); try { - addBundleBeans(bundle, weld, bundleNames, classNames); + addBundleBeans(bundle, weld, bundleNames, classNames, false); } catch (BundleException e) { if(log.isLoggable(Level.WARNING)) { log.log(Level.WARNING, "Encountered exception loading bundle beans", e); diff --git a/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/DiscoveryUtil.java b/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/DiscoveryUtil.java index 40dfcb7ea..4861a015d 100644 --- a/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/DiscoveryUtil.java +++ b/eclipse/bundles/org.openntf.xsp.cdi/src/org/openntf/xsp/cdi/util/DiscoveryUtil.java @@ -19,7 +19,10 @@ import java.io.InputStream; import java.io.UncheckedIOException; import java.lang.annotation.Annotation; +import java.lang.reflect.Field; import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; @@ -27,6 +30,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -38,6 +42,7 @@ import org.openntf.xsp.jakartaee.util.LibraryUtil; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; +import org.osgi.framework.BundleReference; import org.osgi.framework.Version; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -65,6 +70,7 @@ private enum ScanType { } private static final Map, Boolean> BEAN_DEFINING = new ConcurrentHashMap<>(); + private static Field WEBAPP_BUNDLE_FIELD = null; /** * Searches through the provided bundle to find all exported class names that may @@ -74,21 +80,25 @@ private enum ScanType { * the bundle's {@code Export-Package} listing.

* * @param bundle a {@link Bundle} instance to query + * @param nonExported {@code true} to include classes not marked as exported from the bundle * @return a {@link Stream} of discovered exported classes * @throws BundleException if there is a problem parsing the bundle manifest */ - public static Stream findCandidateBeanClassNames(Bundle bundle) throws BundleException { + public static Stream findCandidateBeanClassNames(Bundle bundle, boolean nonExported) throws BundleException { ScanType scanType = determineScanType(bundle); if(scanType == ScanType.ALL || scanType == ScanType.ANNOTATED) { String exportPackages = bundle.getHeaders().get("Export-Package"); //$NON-NLS-1$ - if(StringUtil.isNotEmpty(exportPackages)) { + if(StringUtil.isNotEmpty(exportPackages) || nonExported) { // Restrict to exported packages for sanity's sake - ManifestElement[] elements = ManifestElement.parseHeader("Export-Package", exportPackages); //$NON-NLS-1$ - Set packages = Arrays.stream(elements) - .map(ManifestElement::getValue) - .filter(StringUtil::isNotEmpty) - .collect(Collectors.toSet()); + Set packages = null; + if(!nonExported) { + ManifestElement[] elements = ManifestElement.parseHeader("Export-Package", exportPackages); //$NON-NLS-1$ + packages = Arrays.stream(elements) + .map(ManifestElement::getValue) + .filter(StringUtil::isNotEmpty) + .collect(Collectors.toSet()); + } URL jandexUrl = bundle.getResource("/META-INF/jandex.idx"); //$NON-NLS-1$ if(jandexUrl != null) { @@ -99,24 +109,32 @@ public static Stream findCandidateBeanClassNames(Bundle bundle) throws B throw new UncheckedIOException(MessageFormat.format("Encountered exception reading jandex.idx for {0}", bundle.getSymbolicName()), e); } - return packages.stream() - .map(p -> jandex.getClassesInPackage(p)) - .flatMap(Collection::stream) - .map(c -> c.name().toString()) - .distinct(); + if(packages != null) { + return packages.stream() + .map(p -> jandex.getClassesInPackage(p)) + .flatMap(Collection::stream) + .map(c -> c.name().toString()) + .distinct(); + } else { + return jandex.getKnownClasses() + .stream() + .map(c -> c.name().toString()) + .distinct(); + } } else { // Otherwise, do a manual crawl for class names String baseUrl = bundle.getEntry("/").toString(); //$NON-NLS-1$ List entries = Collections.list(bundle.findEntries("/", "*.class", true)); //$NON-NLS-1$ //$NON-NLS-2$ Set classNames = new HashSet<>(); + Set fpackages = packages; return entries.stream() .parallel() .map(String::valueOf) .map(url -> url.substring(baseUrl.length())) .map(LibraryUtil::toClassName) .filter(StringUtil::isNotEmpty) - .filter(className -> packages.contains(className.substring(0, className.lastIndexOf('.')))) + .filter(className -> fpackages == null || fpackages.contains(className.substring(0, className.lastIndexOf('.')))) .filter(className -> !classNames.contains(className)) .peek(classNames::add) .sequential(); @@ -135,15 +153,15 @@ public static Stream findCandidateBeanClassNames(Bundle bundle) throws B * the bundle's {@code Export-Package} listing.

* * @param bundle a {@link Bundle} instance to query - * @param force include classes even when there's no beans.xml + * @param nonExported {@code true} to include classes not marked as exported from the bundle * @return a {@link Stream} of discovered exported classes * @throws BundleException if there is a problem parsing the bundle manifest * @since 2.3.0 */ - public static Stream> findBeanClasses(Bundle bundle) throws BundleException { + public static Stream> findBeanClasses(Bundle bundle, boolean nonExported) throws BundleException { ScanType scanType = determineScanType(bundle); - return findCandidateBeanClassNames(bundle) + return findCandidateBeanClassNames(bundle, nonExported) .map(className -> { try { return bundle.loadClass(className); @@ -162,6 +180,30 @@ public static Stream> findBeanClasses(Bundle bundle) throws BundleExcep .map(c -> (Class)c); } + public static Optional getBundleForClassLoader(ClassLoader cl) { + // Equinox Servlets + if(cl instanceof BundleReference) { + return Optional.of(((BundleReference) cl).getBundle()); + } + + // Bundle webapps + if("com.ibm.pvc.internal.webcontainer.webapp.BundleWebAppClassLoader".equals(cl.getClass().getName())) { //$NON-NLS-1$ + return AccessController.doPrivileged((PrivilegedAction>)() -> { + try { + if(WEBAPP_BUNDLE_FIELD == null) { + WEBAPP_BUNDLE_FIELD = cl.getClass().getDeclaredField("bundle"); //$NON-NLS-1$ + WEBAPP_BUNDLE_FIELD.setAccessible(true); + } + return Optional.ofNullable((Bundle)WEBAPP_BUNDLE_FIELD.get(cl)); + } catch(Exception e) { + throw new RuntimeException(e); + } + }); + } + + return Optional.empty(); + } + private static ScanType determineScanType(Bundle bundle) { URL beansXml = bundle.getResource("/META-INF/beans.xml"); //$NON-NLS-1$ if(beansXml == null) { diff --git a/eclipse/bundles/org.openntf.xsp.jakartaee.commons/src/org/openntf/xsp/jakartaee/discovery/impl/ComponentModuleComponentEnabledLocator.java b/eclipse/bundles/org.openntf.xsp.jakartaee.commons/src/org/openntf/xsp/jakartaee/discovery/impl/ComponentModuleComponentEnabledLocator.java index e27dff0b5..2ba766e95 100644 --- a/eclipse/bundles/org.openntf.xsp.jakartaee.commons/src/org/openntf/xsp/jakartaee/discovery/impl/ComponentModuleComponentEnabledLocator.java +++ b/eclipse/bundles/org.openntf.xsp.jakartaee.commons/src/org/openntf/xsp/jakartaee/discovery/impl/ComponentModuleComponentEnabledLocator.java @@ -37,7 +37,6 @@ public boolean isActive() { @Override public boolean isComponentEnabled(String componentId) { - // TODO consider using an NSFComponentModule surrounding an OSGIModule ComponentModule module = ComponentModuleLocator.getDefault().get().getActiveModule(); if(module != null) { return LibraryUtil.usesLibrary(componentId, module); diff --git a/eclipse/bundles/org.openntf.xsp.microprofile.health/src/org/openntf/xsp/microprofile/health/HealthBeanContributor.java b/eclipse/bundles/org.openntf.xsp.microprofile.health/src/org/openntf/xsp/microprofile/health/HealthBeanContributor.java index 788e598b5..0f4bd91fd 100644 --- a/eclipse/bundles/org.openntf.xsp.microprofile.health/src/org/openntf/xsp/microprofile/health/HealthBeanContributor.java +++ b/eclipse/bundles/org.openntf.xsp.microprofile.health/src/org/openntf/xsp/microprofile/health/HealthBeanContributor.java @@ -41,8 +41,7 @@ public Collection> getBeanClasses() { // Look for annotated beans in io.smallrye.health Bundle bundle = FrameworkUtil.getBundle(ResponseProvider.class); try { - return DiscoveryUtil.findBeanClasses(bundle) - // TODO filter to only annotated + return DiscoveryUtil.findBeanClasses(bundle, false) .collect(Collectors.toList()); } catch (BundleException e) { throw new RuntimeException(e); diff --git a/eclipse/bundles/org.openntf.xsp.nosql/src/org/openntf/xsp/nosql/NoSQLBeanContributor.java b/eclipse/bundles/org.openntf.xsp.nosql/src/org/openntf/xsp/nosql/NoSQLBeanContributor.java index 8d649ebbe..e50d1b758 100644 --- a/eclipse/bundles/org.openntf.xsp.nosql/src/org/openntf/xsp/nosql/NoSQLBeanContributor.java +++ b/eclipse/bundles/org.openntf.xsp.nosql/src/org/openntf/xsp/nosql/NoSQLBeanContributor.java @@ -53,7 +53,7 @@ public Collection> getBeanClasses() { .map(FrameworkUtil::getBundle) .flatMap(t -> { try { - return DiscoveryUtil.findBeanClasses(t); + return DiscoveryUtil.findBeanClasses(t, false); } catch (BundleException e) { throw new RuntimeException(e); } diff --git a/eclipse/tests/it-xsp-jakartaee/src/test/java/it/org/openntf/xsp/jakartaee/webapp/cdi/TestWebappBeanResource.java b/eclipse/tests/it-xsp-jakartaee/src/test/java/it/org/openntf/xsp/jakartaee/webapp/cdi/TestWebappBeanResource.java new file mode 100644 index 000000000..1792e1d6b --- /dev/null +++ b/eclipse/tests/it-xsp-jakartaee/src/test/java/it/org/openntf/xsp/jakartaee/webapp/cdi/TestWebappBeanResource.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2018-2023 Contributors to the XPages Jakarta EE Support Project + * + * Licensed 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 it.org.openntf.xsp.jakartaee.webapp.cdi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.StringReader; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Response; + +import org.junit.jupiter.api.Test; + +import it.org.openntf.xsp.jakartaee.AbstractWebClientTest; +import it.org.openntf.xsp.jakartaee.TestDatabase; + +@SuppressWarnings({ "nls" }) +public class TestWebappBeanResource extends AbstractWebClientTest { + + @Test + public void testBundleBean() { + int expectedIdentity = 0; + // First NSF - uses .cdibundle + { + JsonObject obj = getBean(getRootUrl(null, TestDatabase.OSGI_WEBAPP) + "/cdiServlet"); + assertEquals("Hello from webappBean", obj.getString("hello")); + expectedIdentity = obj.getInt("identity"); + assertNotEquals(0, expectedIdentity); + } + // Call this again to ensure that it uses the same bean + { + JsonObject obj = getBean(getRootUrl(null, TestDatabase.OSGI_WEBAPP) + "/cdiServlet"); + assertEquals("Hello from webappBean", obj.getString("hello")); + int identity = obj.getInt("identity"); + assertEquals(expectedIdentity, identity); + } + } + + private JsonObject getBean(String base) { + Client client = getAnonymousClient(); + WebTarget target = client.target(base); + Response response = target.request().get(); + + checkResponse(200, response); + String output = response.readEntity(String.class); + try { + return Json.createReader(new StringReader(output)).readObject(); + } catch(Exception e) { + fail("Exception parsing JSON: " + output, e); + return null; + } + } +} diff --git a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/META-INF/MANIFEST.MF b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/META-INF/MANIFEST.MF index 8fc77255f..cee66da84 100644 --- a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/META-INF/MANIFEST.MF +++ b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/META-INF/MANIFEST.MF @@ -3,8 +3,18 @@ Bundle-ManifestVersion: 2 Bundle-Name: Example Jakarta EE Webapp Bundle-SymbolicName: org.openntf.xsp.jakarta.example.webapp;singleton:=true Automatic-Module-Name: org.openntf.xsp.jakarta.example.webapp +Bundle-ActivationPolicy: lazy +Bundle-Activator: org.openntf.xsp.jakarta.example.webapp.WebappActivator Bundle-Version: 2.13.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: org.openntf.xsp.jsp;bundle-version="2.8.0", org.openntf.xsp.jakarta.servlet;bundle-version="2.8.0" -Import-Package: com.ibm.designer.runtime.domino.adapter +Import-Package: com.ibm.designer.runtime.domino.adapter, + jakarta.enterprise.context;version="3.0.0", + jakarta.enterprise.inject;version="3.0.0", + jakarta.enterprise.inject.spi;version="3.0.0", + jakarta.inject;version="2.0.0", + jakarta.json.bind;version="2.0.0", + org.openntf.xsp.cdi.provider;version="2.13.0", + org.osgi.framework;version="1.8.0" +DynamicImport-Package: * diff --git a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/META-INF/beans.xml b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/META-INF/beans.xml new file mode 100644 index 000000000..a64451de7 --- /dev/null +++ b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/META-INF/beans.xml @@ -0,0 +1,23 @@ + + + \ No newline at end of file diff --git a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/WebContent/WEB-INF/web.xml b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/WebContent/WEB-INF/web.xml index a587fac4d..57ac381df 100644 --- a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/WebContent/WEB-INF/web.xml +++ b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/WebContent/WEB-INF/web.xml @@ -59,4 +59,18 @@ LocatorTestServlet /locatorTestServlet + + + CDIServlet + org.openntf.xsp.jakarta.servlet.webapp.JakartaServletFacade + + + org.openntf.xsp.jakarta.servlet.class + org.openntf.xsp.jakarta.example.webapp.CDIServlet + + + + CDIServlet + /cdiServlet + \ No newline at end of file diff --git a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/CDIServlet.java b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/CDIServlet.java new file mode 100644 index 000000000..baebf3970 --- /dev/null +++ b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/CDIServlet.java @@ -0,0 +1,24 @@ +package org.openntf.xsp.jakarta.example.webapp; + +import java.io.IOException; + +import org.openntf.xsp.jakarta.example.webapp.beans.WebappBean; + +import jakarta.enterprise.inject.spi.CDI; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class CDIServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + WebappBean bean = CDI.current().select(WebappBean.class).get(); + ServletOutputStream out = resp.getOutputStream(); + Jsonb jsonb = JsonbBuilder.create(); + jsonb.toJson(bean, out); + } +} diff --git a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/WebappActivator.java b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/WebappActivator.java new file mode 100644 index 000000000..ec00fb5b0 --- /dev/null +++ b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/WebappActivator.java @@ -0,0 +1,21 @@ +package org.openntf.xsp.jakarta.example.webapp; + +import org.openntf.xsp.cdi.provider.DominoCDIProvider; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import jakarta.enterprise.inject.spi.CDI; + +public class WebappActivator implements BundleActivator { + + @Override + public void start(BundleContext context) throws Exception { + CDI.setCDIProvider(new DominoCDIProvider()); + } + + @Override + public void stop(BundleContext context) throws Exception { + + } + +} diff --git a/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/beans/WebappBean.java b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/beans/WebappBean.java new file mode 100644 index 000000000..228434e93 --- /dev/null +++ b/eclipse/tests/org.openntf.xsp.jakarta.example.webapp/src/org/openntf/xsp/jakarta/example/webapp/beans/WebappBean.java @@ -0,0 +1,14 @@ +package org.openntf.xsp.jakarta.example.webapp.beans; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class WebappBean { + public String getHello() { + return "Hello from webappBean"; + } + + public int getIdentity() { + return System.identityHashCode(this); + } +}