From 71a7dd03cd88e78eed77420a82f476d9b4afefd5 Mon Sep 17 00:00:00 2001 From: Grzegorz Grzybek Date: Thu, 8 Jul 2021 08:17:14 +0200 Subject: [PATCH] [JSF, CDI, test, #1622] The best possible (for now) test with Aries-CDI, MyFaces and Weld. Works only under debugger. --- .../internal/model/BundleWebApplication.java | 8 ++- .../pax-web-fragment-myfaces-inject/pom.xml | 65 +++++++++++++++++++ pax-web-fragments/pom.xml | 1 + .../web/itest/AbstractControlledTestBase.java | 4 +- .../pax-web-itest-container-common/pom.xml | 4 ++ .../jsf/AbstractWarJsfCdiIntegrationTest.java | 2 - .../pax-web-itest-jetty/pom.xml | 13 ++++ .../war/jsf/WarJSFCdiIntegrationTest.java | 32 ++++++--- .../war/listeners/ListenerAddedInWebXml.java | 8 ++- .../SCIThatAddsServletContextListener.java | 1 + .../jetty/internal/JettyServerWrapper.java | 20 +++--- pom.xml | 53 +++++++++++++++ samples/samples-jsf/war-jsf23-cdi/pom.xml | 51 +++++++++++---- .../warjsf23cdi/FacesConfiguration.java | 30 +++++++++ .../pax/web/samples/warjsf23cdi/Hello.java | 17 +++-- 15 files changed, 267 insertions(+), 42 deletions(-) create mode 100644 pax-web-fragments/pax-web-fragment-myfaces-inject/pom.xml create mode 100644 samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/FacesConfiguration.java diff --git a/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplication.java b/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplication.java index 692a259eb2..bc940ffc63 100644 --- a/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplication.java +++ b/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplication.java @@ -673,6 +673,12 @@ private void deploy() { } } + // TODO: special situation for aries-cdi, which is also an extender. its two CDI extensions: + // - org.apache.aries.cdi.extension.servlet.weld.WeldServletExtension + // - org.apache.aries.cdi.extension.el.jsp.ELJSPExtension + // register two servlet context listeners via whiteboard + // We have to (somehow) wait for this extender before we actually register the web application... + state = deploymentState.get(); if (state == State.DEPLOYING) { LOG.debug("Registering {} in WebContainer", contextPath); @@ -1260,7 +1266,7 @@ private void buildModel() { ocm.getContextParams().putAll(mainWebXml.getContextParams()); // TODO: do it consistently using runtime configuration of temp directory File tmpLocation = new File(System.getProperty("java.io.tmpdir"), ocm.getTemporaryLocation()); - if (!tmpLocation.mkdirs()) { + if (!tmpLocation.exists() && !tmpLocation.mkdirs()) { LOG.warn("Can't create temporary directory for {}: {}", ocm, tmpLocation.getAbsolutePath()); } ocm.getInitialContextAttributes().put(ServletContext.TEMPDIR, tmpLocation); diff --git a/pax-web-fragments/pax-web-fragment-myfaces-inject/pom.xml b/pax-web-fragments/pax-web-fragment-myfaces-inject/pom.xml new file mode 100644 index 0000000000..9c5aab5764 --- /dev/null +++ b/pax-web-fragments/pax-web-fragment-myfaces-inject/pom.xml @@ -0,0 +1,65 @@ + + + + + 4.0.0 + + + org.ops4j.pax.web + pax-web-fragments + 8.0.0-SNAPSHOT + ../pom.xml + + + org.ops4j.pax.web + pax-web-fragment-myfaces-inject + bundle + + OPS4J Pax Web - myfaces-api tweak + + + This fragment bundle adds javax.inject import to myfaces-api + + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.apache.myfaces.core.api + javax.inject + + + + + + + + + + org.apache.myfaces.core + myfaces-impl + + + + + diff --git a/pax-web-fragments/pom.xml b/pax-web-fragments/pom.xml index ba7d6f4ed6..93cdd53d8b 100644 --- a/pax-web-fragments/pom.xml +++ b/pax-web-fragments/pom.xml @@ -44,6 +44,7 @@ pax-web-compatibility-interceptor12 pax-web-compatibility-annotation13 + pax-web-fragment-myfaces-inject pax-web-fragment-myfaces-spifly diff --git a/pax-web-itest/pax-web-itest-common/src/main/java/org/ops4j/pax/web/itest/AbstractControlledTestBase.java b/pax-web-itest/pax-web-itest-common/src/main/java/org/ops4j/pax/web/itest/AbstractControlledTestBase.java index 75fd6627a6..5ed83e8a61 100644 --- a/pax-web-itest/pax-web-itest-common/src/main/java/org/ops4j/pax/web/itest/AbstractControlledTestBase.java +++ b/pax-web-itest/pax-web-itest-common/src/main/java/org/ops4j/pax/web/itest/AbstractControlledTestBase.java @@ -475,7 +475,7 @@ protected Option[] paxCdiAndMyfaces(String containerCdiArtifact) { ); } - protected Option[] ariesCdiAndMyfaces(String containerCdiArtifact) { + protected Option[] ariesCdiAndMyfaces() { return new Option[]{ mavenBundle("jakarta.websocket", "jakarta.websocket-api") .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), @@ -492,6 +492,8 @@ protected Option[] ariesCdiAndMyfaces(String containerCdiArtifact) { .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), mavenBundle("org.apache.myfaces.core", "myfaces-impl") .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), + mavenBundle("org.ops4j.pax.web", "pax-web-fragment-myfaces-inject") + .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 2).noStart(), mavenBundle("org.ops4j.pax.web", "pax-web-fragment-myfaces-spifly") .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 2).noStart(), diff --git a/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/pom.xml b/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/pom.xml index 4f843e0d2a..b078720fee 100644 --- a/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/pom.xml +++ b/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/pom.xml @@ -177,6 +177,10 @@ org.ops4j.pax.web pax-web-compatibility-annotation13 + + org.ops4j.pax.web + pax-web-fragment-myfaces-inject + org.ops4j.pax.web pax-web-fragment-myfaces-spifly diff --git a/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/src/main/java/org/ops4j/pax/web/itest/container/war/jsf/AbstractWarJsfCdiIntegrationTest.java b/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/src/main/java/org/ops4j/pax/web/itest/container/war/jsf/AbstractWarJsfCdiIntegrationTest.java index 19bcd5dec5..276e53f62d 100644 --- a/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/src/main/java/org/ops4j/pax/web/itest/container/war/jsf/AbstractWarJsfCdiIntegrationTest.java +++ b/pax-web-itest/pax-web-itest-container/pax-web-itest-container-common/src/main/java/org/ops4j/pax/web/itest/container/war/jsf/AbstractWarJsfCdiIntegrationTest.java @@ -24,8 +24,6 @@ public abstract class AbstractWarJsfCdiIntegrationTest extends AbstractContainerTestBase { - protected abstract String containerSpecificCdiBundle(); - // only Jetty implementation for now, I need to prepare Pax CDI 1.2.x to integrate with new Pax Web... } diff --git a/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/pom.xml b/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/pom.xml index 94b3502dc9..410aaa362c 100644 --- a/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/pom.xml +++ b/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/pom.xml @@ -102,6 +102,19 @@ jakarta.el-api runtime + + + jakarta.enterprise + jakarta.enterprise.cdi-api + runtime + + + + + org.apache.myfaces.core + myfaces-api + runtime + diff --git a/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/src/test/java/org/ops4j/pax/web/itest/jetty/war/jsf/WarJSFCdiIntegrationTest.java b/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/src/test/java/org/ops4j/pax/web/itest/jetty/war/jsf/WarJSFCdiIntegrationTest.java index b99b318a3d..ccd6f469d6 100644 --- a/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/src/test/java/org/ops4j/pax/web/itest/jetty/war/jsf/WarJSFCdiIntegrationTest.java +++ b/pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/src/test/java/org/ops4j/pax/web/itest/jetty/war/jsf/WarJSFCdiIntegrationTest.java @@ -16,6 +16,7 @@ package org.ops4j.pax.web.itest.jetty.war.jsf; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Configuration; @@ -25,6 +26,8 @@ import org.ops4j.pax.web.itest.utils.client.HttpTestClientFactory; import org.osgi.framework.Bundle; +import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackage; +import static org.ops4j.pax.exam.CoreOptions.systemPackage; import static org.ops4j.pax.exam.OptionUtils.combine; @RunWith(PaxExam.class) @@ -35,10 +38,15 @@ public class WarJSFCdiIntegrationTest extends AbstractWarJsfCdiIntegrationTest { @Configuration public Option[] configure() { Option[] serverOptions = combine(baseConfigure(), paxWebJetty()); - Option[] osgiOptions = combine(serverOptions, configAdmin()); + // myfaces-core-api doesn't import javax.inject and I'm getting + // WELD-001111: interface javax.faces.annotation.FlowMap defined on org.apache.myfaces.cdi.JsfArtifactFlowMapProducer@2143ca58 is not a qualifier + // if javax.inject is available only as bundle (== is excluded from maven-failsafe-plugin test classpath) + Option[] tweakOptions1 = combine(serverOptions, bootDelegationPackage("javax.inject")); + Option[] tweakOptions2 = combine(tweakOptions1, systemPackage("javax.inject;version=\"1.0\"")); + Option[] osgiOptions = combine(tweakOptions2, configAdmin()); Option[] whiteboardOptions = combine(osgiOptions, paxWebExtenderWhiteboard()); Option[] jspOptions = combine(whiteboardOptions, paxWebJsp()); - Option[] cdiOptions = combine(jspOptions, ariesCdiAndMyfaces(containerSpecificCdiBundle())); + Option[] cdiOptions = combine(jspOptions, ariesCdiAndMyfaces()); return combine(cdiOptions, paxWebExtenderWar()); } @@ -50,16 +58,22 @@ public void setUp() throws Exception { } @Test + @Ignore public void testCdi() throws Exception { + // This test works fine when: + // - ARIES-2053 is fixed + // - I manually (in debugger) order the WAB deployment and calls to: + // - org.apache.aries.cdi.extension.servlet.weld.WeldServletExtension.afterDeploymentValidation() + // - org.apache.aries.cdi.extension.el.jsp.ELJSPExtension.afterDeploymentValidation() + // so the important aries-cdi/weld listeners are registered after the WAB is deployed (and its + // OsgiContextModel) + // - TODO: the listeners added after the WAB context (real Jetty/Tomcat/Undertow) has started are called + // (ServletContextListener.contextInitialized()) in separate thread to not block Aries CCR thread and + // pax-web-config thread. HttpTestClientFactory.createDefaultTestClient() - .withResponseAssertion("Response must contain '" + containerSpecificCdiBundle() + "'", - resp -> resp.contains(containerSpecificCdiBundle())) + .withResponseAssertion("Response must contain 'hello from working JSF 2.3/CDI 2.0 example, org.ops4j.pax.url.commons.handler.HandlerActivator$Handler'", + resp -> resp.contains("hello from working JSF 2.3/CDI 2.0 example, org.ops4j.pax.url.commons.handler.HandlerActivator$Handler")) .doGETandExecuteTest("http://127.0.0.1:8181/war-jsf23-cdi/"); } - @Override - protected String containerSpecificCdiBundle() { - return "pax-cdi-jetty-weld"; - } - } diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/listeners/ListenerAddedInWebXml.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/listeners/ListenerAddedInWebXml.java index c2cfd70fc2..ff9e3b474c 100644 --- a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/listeners/ListenerAddedInWebXml.java +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/listeners/ListenerAddedInWebXml.java @@ -24,7 +24,13 @@ public class ListenerAddedInWebXml implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { - StaticList.EVENTS.add("Listener-from-web.xml initialized"); + try { + sce.getServletContext().addListener(new ServletContextListener() { + }); + } catch (UnsupportedOperationException ignored) { + // it should not be possible to add a listener from a listener + StaticList.EVENTS.add("Listener-from-web.xml initialized"); + } } @Override diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/scis/SCIThatAddsServletContextListener.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/scis/SCIThatAddsServletContextListener.java index 01b6b6284e..bd9b66a4d4 100644 --- a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/scis/SCIThatAddsServletContextListener.java +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/scis/SCIThatAddsServletContextListener.java @@ -32,6 +32,7 @@ public class SCIThatAddsServletContextListener implements ServletContainerInitia @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { + // this is exactly how JasperInitializer (JSP SCI) adds context listeners found in TLDs ctx.addListener(new ServletContextListener() { @Override public void contextInitialized(ServletContextEvent sce) { diff --git a/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/JettyServerWrapper.java b/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/JettyServerWrapper.java index bb8ebab95a..c0bdd7861e 100644 --- a/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/JettyServerWrapper.java +++ b/pax-web-jetty/src/main/java/org/ops4j/pax/web/service/jetty/internal/JettyServerWrapper.java @@ -1305,18 +1305,16 @@ public void visit(EventListenerModelChange change) { // add the listener to real context - even ServletContextAttributeListener (but only once - even // if there are many OsgiServletContexts per ServletContext) -// boolean restartNeeded = false; -// if (servletContextHandler.isStarted()) { -// try { -// servletContextHandler.stop(); -// restartNeeded = true; -// } catch (Exception e) { -// LOG.error(e.getMessage(), e); -// } -// } servletContextHandler.addEventListener(eventListener); -// if (restartNeeded) { -// ensureServletContextStarted(servletContextHandler); + + // calling javax.servlet.ServletContextListener.contextInitialized() when server (context) is + // already started and doing it in separate thread is a tweak to make Aries-CDI + extensions + // work with CDI/JSF sample. I definitely have to solve it differently. + // The probelms are summarized in https://github.com/ops4j/org.ops4j.pax.web/issues/1622 +// if (servletContextHandler.isStarted() && ServletContextListener.class.isAssignableFrom(eventListener.getClass())) { +// new Thread(() -> { +// ((ServletContextListener) eventListener).contextInitialized(new ServletContextEvent(osgiServletContexts.get(context))); +// }).start(); // } }); } diff --git a/pom.xml b/pom.xml index 8e0361cae1..8843c7de22 100644 --- a/pom.xml +++ b/pom.xml @@ -229,6 +229,7 @@ 2.0.2 3.0.3 1.1 + 1.0.3 1.2.5 1.2 1.1.3 @@ -815,6 +816,11 @@ pax-web-compatibility-annotation13 ${project.version} + + org.ops4j.pax.web + pax-web-fragment-myfaces-inject + ${project.version} + org.ops4j.pax.web pax-web-fragment-myfaces-spifly @@ -1650,6 +1656,11 @@ + + jakarta.inject + jakarta.inject-api + ${dependency.jakarta.inject-api} + com.sun.activation javax.activation @@ -2663,16 +2674,34 @@ org.glassfish.hk2 hk2-api ${dependency.org.glassfish.hk2} + + + org.glassfish.hk2.external + jakarta.inject + + org.glassfish.hk2 hk2-locator ${dependency.org.glassfish.hk2} + + + org.glassfish.hk2.external + jakarta.inject + + org.glassfish.hk2 hk2-utils ${dependency.org.glassfish.hk2} + + + org.glassfish.hk2.external + jakarta.inject + + org.glassfish.hk2 @@ -2684,16 +2713,34 @@ org.glassfish.jersey.core jersey-common ${dependency.jersey.version} + + + org.glassfish.hk2.external + jakarta.inject + + org.glassfish.jersey.core jersey-server ${dependency.jersey.version} + + + org.glassfish.hk2.external + jakarta.inject + + org.glassfish.jersey.core jersey-client ${dependency.jersey.version} + + + org.glassfish.hk2.external + jakarta.inject + + org.glassfish.jersey.inject @@ -2704,6 +2751,12 @@ org.glassfish.jersey.containers jersey-container-servlet-core ${dependency.jersey.version} + + + org.glassfish.hk2.external + jakarta.inject + + org.glassfish.jersey.containers diff --git a/samples/samples-jsf/war-jsf23-cdi/pom.xml b/samples/samples-jsf/war-jsf23-cdi/pom.xml index c09d953176..dbded6e65d 100644 --- a/samples/samples-jsf/war-jsf23-cdi/pom.xml +++ b/samples/samples-jsf/war-jsf23-cdi/pom.xml @@ -47,32 +47,53 @@ + javax.enterprise.context, javax.inject, javax.servlet.*;version="[3.1,5)", + javax.faces.annotation, javax.faces.bean, + javax.faces.view, javax.faces.webapp, org.osgi.service.url, - org.osgi.service.cdi.annotations + org.osgi.service.cdi.annotations, + org.ops4j.pax.web.service.spi org.ops4j.pax.web.samples.warjsf23cdi <_wab>${project.build.directory}/${project.build.finalName} /war-jsf23-cdi - - org.apache.myfaces.*, - org.jboss.weld.*, - org.ops4j.pax.web.*, - org.apache.myfaces.cdi, - org.apache.myfaces.cdi.*, - org.apache.myfaces.flow.cdi, - org.apache.myfaces.flow.cdi.* - + + + + + + + + + - + + osgi.cdi.extension;filter:="(osgi.cdi.extension=aries.cdi.http)", + osgi.cdi.extension;filter:="(osgi.cdi.extension=aries.cdi.el.jsp)" - ]]> + @@ -125,6 +146,12 @@ provided + + org.apache.myfaces.core + myfaces-api + provided + + diff --git a/samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/FacesConfiguration.java b/samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/FacesConfiguration.java new file mode 100644 index 0000000000..f101527cdd --- /dev/null +++ b/samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/FacesConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021 OPS4J. + * + * 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 org.ops4j.pax.web.samples.warjsf23cdi; + +import org.osgi.service.cdi.annotations.Bean; + +import javax.enterprise.context.ApplicationScoped; +import javax.faces.annotation.FacesConfig; + +@ApplicationScoped +// see: +// - https://stackoverflow.com/a/47447140/250517 +// - https://github.com/ops4j/org.ops4j.pax.web/issues/1622 +@FacesConfig(version = FacesConfig.Version.JSF_2_3) +@Bean +public class FacesConfiguration { +} diff --git a/samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/Hello.java b/samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/Hello.java index cdb0f192e1..dc2e207467 100644 --- a/samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/Hello.java +++ b/samples/samples-jsf/war-jsf23-cdi/src/main/java/org/ops4j/pax/web/samples/warjsf23cdi/Hello.java @@ -21,16 +21,17 @@ import org.ops4j.pax.web.service.spi.ServerControllerFactory; import org.osgi.service.cdi.annotations.Bean; import org.osgi.service.cdi.annotations.Reference; -import org.osgi.service.cdi.annotations.SingleComponent; import org.osgi.service.url.URLStreamHandlerService; +import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; +import java.io.Serializable; @Bean -@SingleComponent @Named("hello") -public class Hello { +@RequestScoped +public class Hello implements Serializable { private String what; private String result; @@ -44,6 +45,10 @@ public class Hello { @Reference private ServerControllerFactory serverControllerFactory; + public Hello() { + System.out.println(""); + } + public void setWhat(String what) { this.what = what; } @@ -57,11 +62,13 @@ public String getResult() { } public String getTest() { - return String.format("test, %s, %s", handler, serverControllerFactory); + return String.format("%s, %s, %s", test, + handler.getClass().getName(), serverControllerFactory.getClass().getName()); } public void say() { - result = String.format("Hello %s! (mvn handler: %s, current web runtime: %s)", what, handler, serverControllerFactory); + result = String.format("Hello %s! (mvn handler: %s, current web runtime: %s)", what, + handler.getClass().getName(), serverControllerFactory.getClass().getName()); } }