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 e19228279c..692a259eb2 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 @@ -87,6 +87,7 @@ import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; +import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -226,7 +227,7 @@ public String toString() { /** *

A {@link BundleWebApplication} can be started only once. Even if "Figure 128.2 State diagram Web Application" * shows that WAB * can go from UNDEPLOYED to DEPLOYING state, we're using - * {@link org.apache.felix.utils.extender.AbstractExtender#destroyExtension}, so what will be started after + * {@code org.apache.felix.utils.extender.AbstractExtender#destroyExtension}, so what will be started after * undeployment is a new instance of {@link BundleWebApplication}. Again it's important to distinguish * {@link BundleWebApplication.State} and {@link WebApplicationEvent.State}.

* @@ -1172,6 +1173,8 @@ private void buildModel() { // very important step - we pass a classloader, which contains reachable bundles - bundles discovered when // WAB's metadata was parsed/processed ocm.setClassLoader(this.classLoader); + // this is important - we should be able to reference the context by path (not by name) + ocm.getContextRegistrationProperties().put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH, this.contextPath); // this is the best place to think about how to reference the underlying "context" // in HttpService and Whiteboard scenarios. @@ -1256,8 +1259,11 @@ private void buildModel() { // 1.4. Context initial parameters ocm.getContextParams().putAll(mainWebXml.getContextParams()); // TODO: do it consistently using runtime configuration of temp directory - ocm.getInitialContextAttributes().put(ServletContext.TEMPDIR, - new File(System.getProperty("java.io.tmpdir"), ocm.getTemporaryLocation())); + File tmpLocation = new File(System.getProperty("java.io.tmpdir"), ocm.getTemporaryLocation()); + if (!tmpLocation.mkdirs()) { + LOG.warn("Can't create temporary directory for {}: {}", ocm, tmpLocation.getAbsolutePath()); + } + ocm.getInitialContextAttributes().put(ServletContext.TEMPDIR, tmpLocation); wabBatch.addOsgiContextModel(ocm, scm); wabBatch.associateOsgiContextModel(httpContext, ocm); diff --git a/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplicationClassSpace.java b/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplicationClassSpace.java index 99404228ec..1f940a4bdd 100644 --- a/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplicationClassSpace.java +++ b/pax-web-extender-war/src/main/java/org/ops4j/pax/web/extender/war/internal/model/BundleWebApplicationClassSpace.java @@ -475,8 +475,7 @@ public void initialize(WebXml mainWebXml, OsgiServletContextClassLoader wabClass orderedFragments.put(fragment.getJarName(), fragment); } - List orderedLibsAttribute = (List) context.getAttribute(ServletContext.ORDERED_LIBS); - orderedLibs = orderedLibsAttribute == null ? Collections.emptyList() : orderedLibsAttribute; + orderedLibs = (List) context.getAttribute(ServletContext.ORDERED_LIBS); // After collecting the bundles associated with ordered web fragments, we can finish the "construction" // of WAB's classloader. diff --git a/pax-web-extender-whiteboard/src/main/java/org/ops4j/pax/web/extender/whiteboard/internal/WhiteboardExtenderContext.java b/pax-web-extender-whiteboard/src/main/java/org/ops4j/pax/web/extender/whiteboard/internal/WhiteboardExtenderContext.java index d6d0286b57..897afef001 100644 --- a/pax-web-extender-whiteboard/src/main/java/org/ops4j/pax/web/extender/whiteboard/internal/WhiteboardExtenderContext.java +++ b/pax-web-extender-whiteboard/src/main/java/org/ops4j/pax/web/extender/whiteboard/internal/WhiteboardExtenderContext.java @@ -238,7 +238,8 @@ public List resolveContexts(Bundle bundle, Filter selector) { // to work, we're explicitly skipping "shared" contexts - user will still be able to use such shared // HttpService contexts (specific to Pax Web), but with more effort. for (OsgiContextModel model : getBundleApplication(bundle).getWebContainerOsgiContextModels()) { - if (!model.isShared() && selector.matchCase(model.getContextRegistrationProperties())) { + if ((!model.isShared() || model.isWab()) + && selector.matchCase(model.getContextRegistrationProperties())) { targetContexts.add(model); } } @@ -396,6 +397,7 @@ private void reRegisterWebElements() { // 1. unregistration because of no matching contexts // TODO: DTOConstants.FAILURE_REASON_NO_SERVLET_CONTEXT_MATCHING if (newMatching.size() == 0) { + LOG.debug("Unregistering {} because its context selection filter doesn't match any context", webElement); if (view != null) { webElement.unregister(view); } @@ -407,6 +409,7 @@ private void reRegisterWebElements() { // TODO: get rid of existing DTOConstants.FAILURE_REASON_NO_SERVLET_CONTEXT_MATCHING if (oldMatching.size() == 0) { webElement.changeContextModels(newMatching); + LOG.debug("Registering {} because its context selection filter started matching existing contexts", webElement); if (view != null) { webElement.register(view); } @@ -427,10 +430,12 @@ private void reRegisterWebElements() { // so it's really easier - FULLY unregister the element from all current contexts and then // register to all the new contexts if (view != null) { + LOG.debug("Unregistering {} because its context selection filter matched new set of contexts", webElement); webElement.unregister(view); } webElement.changeContextModels(newMatching); if (view != null) { + LOG.debug("Registering {} again after its context selection filter matched new set of contexts", webElement); webElement.register(view); } } @@ -490,7 +495,7 @@ private BundleWhiteboardApplication getBundleApplication(final Bundle bundle) { /** * This method is invoked after checking that element model {@link ElementModel#isValid() is valid}. - * @param httpServiceRuntime + * @param webElement */ public void configureDTOs(ElementModel webElement) { // TODO: could result in DTOConstants.FAILURE_REASON_NO_SERVLET_CONTEXT_MATCHING @@ -499,21 +504,21 @@ public void configureDTOs(ElementModel webElement) { /** * This method is invoked after checking that element model {@link ElementModel#isValid() is not valid}. - * @param httpServiceRuntime + * @param webElement */ public void configureFailedDTOs(ElementModel webElement) { } /** * This method is invoked after checking that context model {@link OsgiContextModel#isValid() is valid}. - * @param httpServiceRuntime + * @param webContext */ public void configureDTOs(OsgiContextModel webContext) { } /** * This method is invoked after checking that context model {@link OsgiContextModel#isValid() is not valid}. - * @param httpServiceRuntime + * @param webContext */ public void configureFailedDTOs(OsgiContextModel webContext) { } diff --git a/pax-web-compatibility/pax-web-compatibility-annotation13/pom.xml b/pax-web-fragments/pax-web-compatibility-annotation13/pom.xml similarity index 97% rename from pax-web-compatibility/pax-web-compatibility-annotation13/pom.xml rename to pax-web-fragments/pax-web-compatibility-annotation13/pom.xml index fb74a881f4..6333baa45d 100644 --- a/pax-web-compatibility/pax-web-compatibility-annotation13/pom.xml +++ b/pax-web-fragments/pax-web-compatibility-annotation13/pom.xml @@ -22,7 +22,7 @@ org.ops4j.pax.web - pax-web-compatibility + pax-web-fragments 8.0.0-SNAPSHOT ../pom.xml diff --git a/pax-web-compatibility/pax-web-compatibility-cdi12/pom.xml b/pax-web-fragments/pax-web-compatibility-cdi12/pom.xml similarity index 92% rename from pax-web-compatibility/pax-web-compatibility-cdi12/pom.xml rename to pax-web-fragments/pax-web-compatibility-cdi12/pom.xml index 9eb858b5b5..d19a1fb287 100644 --- a/pax-web-compatibility/pax-web-compatibility-cdi12/pom.xml +++ b/pax-web-fragments/pax-web-compatibility-cdi12/pom.xml @@ -22,7 +22,7 @@ org.ops4j.pax.web - pax-web-compatibility + pax-web-fragments 8.0.0-SNAPSHOT ../pom.xml @@ -61,12 +61,12 @@ version:List="2.0,1.2,1.1,1.0" ]]> diff --git a/pax-web-compatibility/pax-web-compatibility-el2/pom.xml b/pax-web-fragments/pax-web-compatibility-el2/pom.xml similarity index 98% rename from pax-web-compatibility/pax-web-compatibility-el2/pom.xml rename to pax-web-fragments/pax-web-compatibility-el2/pom.xml index 89dbebdba9..6344c023c6 100644 --- a/pax-web-compatibility/pax-web-compatibility-el2/pom.xml +++ b/pax-web-fragments/pax-web-compatibility-el2/pom.xml @@ -22,7 +22,7 @@ org.ops4j.pax.web - pax-web-compatibility + pax-web-fragments 8.0.0-SNAPSHOT ../pom.xml diff --git a/pax-web-compatibility/pax-web-compatibility-interceptor12/pom.xml b/pax-web-fragments/pax-web-compatibility-interceptor12/pom.xml similarity index 97% rename from pax-web-compatibility/pax-web-compatibility-interceptor12/pom.xml rename to pax-web-fragments/pax-web-compatibility-interceptor12/pom.xml index 36fd8de1d7..b3c2264a1e 100644 --- a/pax-web-compatibility/pax-web-compatibility-interceptor12/pom.xml +++ b/pax-web-fragments/pax-web-compatibility-interceptor12/pom.xml @@ -22,7 +22,7 @@ org.ops4j.pax.web - pax-web-compatibility + pax-web-fragments 8.0.0-SNAPSHOT ../pom.xml diff --git a/pax-web-compatibility/pax-web-compatibility-servlet31/pom.xml b/pax-web-fragments/pax-web-compatibility-servlet31/pom.xml similarity index 98% rename from pax-web-compatibility/pax-web-compatibility-servlet31/pom.xml rename to pax-web-fragments/pax-web-compatibility-servlet31/pom.xml index eb5196b00d..656be77c23 100644 --- a/pax-web-compatibility/pax-web-compatibility-servlet31/pom.xml +++ b/pax-web-fragments/pax-web-compatibility-servlet31/pom.xml @@ -22,7 +22,7 @@ org.ops4j.pax.web - pax-web-compatibility + pax-web-fragments 8.0.0-SNAPSHOT ../pom.xml diff --git a/pax-web-fragments/pax-web-fragment-myfaces-spifly/pom.xml b/pax-web-fragments/pax-web-fragment-myfaces-spifly/pom.xml new file mode 100644 index 0000000000..1932fde9ff --- /dev/null +++ b/pax-web-fragments/pax-web-fragment-myfaces-spifly/pom.xml @@ -0,0 +1,70 @@ + + + + + 4.0.0 + + + org.ops4j.pax.web + pax-web-fragments + 8.0.0-SNAPSHOT + ../pom.xml + + + org.ops4j.pax.web + pax-web-fragment-myfaces-spifly + bundle + + OPS4J Pax Web - myfaces-impl SPI-FLY integration + + + This fragment bundle adds SPI-FLY requirements to myfaces-impl so it can be detected as CDI extension + + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.apache.myfaces.core.impl + =1.0)(!(version>=2.0)))" + ]]> + + osgi.serviceloader;osgi.serviceloader=javax.enterprise.inject.spi.Extension;aries.cdi.extension.mode=implicit + + + + + + + + + + + org.apache.myfaces.core + myfaces-impl + + + + + diff --git a/pax-web-compatibility/pom.xml b/pax-web-fragments/pom.xml similarity index 80% rename from pax-web-compatibility/pom.xml rename to pax-web-fragments/pom.xml index 5ea5f5a00c..ba7d6f4ed6 100644 --- a/pax-web-compatibility/pom.xml +++ b/pax-web-fragments/pom.xml @@ -28,17 +28,23 @@ org.ops4j.pax.web - pax-web-compatibility + pax-web-fragments pom - OPS4J Pax Web - Compatibility bundles + OPS4J Pax Web - support bundles + pax-web-compatibility-cdi12 pax-web-compatibility-servlet31 pax-web-compatibility-el2 pax-web-compatibility-interceptor12 pax-web-compatibility-annotation13 + + 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 c07ece47d3..75fd6627a6 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 @@ -404,9 +404,10 @@ protected Option[] myfaces() { .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), mavenBundle("org.ops4j.pax.web", "pax-web-compatibility-interceptor12") .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 2).noStart(), - // it has to be CDI 1.2 for Myfaces 2.3.x - mavenBundle("javax.enterprise", "cdi-api") - .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), + mavenBundle("jakarta.enterprise", "jakarta.enterprise.cdi-api") + .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1).noStart(), + mavenBundle("org.ops4j.pax.web", "pax-web-compatibility-cdi12") + .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 2).noStart(), mavenBundle("commons-collections", "commons-collections") .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), mavenBundle("commons-beanutils", "commons-beanutils") @@ -491,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-spifly") + .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 2).noStart(), // These 4 would be required because of osgi.contract capabilities. But Pax Web provides proper // compatibility bundles that fix _canonical_ jakarta API bundles @@ -529,6 +532,8 @@ protected Option[] ariesCdiAndMyfaces(String containerCdiArtifact) { .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), mavenBundle("org.apache.aries.cdi", "org.apache.aries.cdi.extension.servlet.weld") .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), + mavenBundle("org.apache.aries.cdi", "org.apache.aries.cdi.extension.el.jsp") + .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), mavenBundle("org.apache.aries.cdi", "org.apache.aries.cdi.extra") .versionAsInProject().startLevel(START_LEVEL_TEST_BUNDLE - 1), mavenBundle("org.jboss.weld", "weld-osgi-bundle") 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 eccec95344..4f843e0d2a 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-spifly + @@ -436,6 +440,10 @@ org.apache.aries.cdi org.apache.aries.cdi.extra + + org.apache.aries.cdi + org.apache.aries.cdi.extension.el.jsp + org.apache.aries.spifly 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 similarity index 90% rename from pax-web-itest/pax-web-itest-container/pax-web-itest-jetty/src/test/java/org/ops4j/pax/web/itest/jetty/war/jsf/WarJsfCdiIntegrationTest.java rename to 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 9e9c2277c0..b99b318a3d 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,7 +16,6 @@ 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; @@ -29,7 +28,7 @@ import static org.ops4j.pax.exam.OptionUtils.combine; @RunWith(PaxExam.class) -public class WarJsfCdiIntegrationTest extends AbstractWarJsfCdiIntegrationTest { +public class WarJSFCdiIntegrationTest extends AbstractWarJsfCdiIntegrationTest { private Bundle wab; @@ -37,7 +36,8 @@ public class WarJsfCdiIntegrationTest extends AbstractWarJsfCdiIntegrationTest { public Option[] configure() { Option[] serverOptions = combine(baseConfigure(), paxWebJetty()); Option[] osgiOptions = combine(serverOptions, configAdmin()); - Option[] jspOptions = combine(osgiOptions, paxWebJsp()); + Option[] whiteboardOptions = combine(osgiOptions, paxWebExtenderWhiteboard()); + Option[] jspOptions = combine(whiteboardOptions, paxWebJsp()); Option[] cdiOptions = combine(jspOptions, ariesCdiAndMyfaces(containerSpecificCdiBundle())); return combine(cdiOptions, paxWebExtenderWar()); } diff --git a/pax-web-itest/pax-web-itest-container/pom.xml b/pax-web-itest/pax-web-itest-container/pom.xml index cf243ca4bb..ebf4a8d9c1 100644 --- a/pax-web-itest/pax-web-itest-container/pom.xml +++ b/pax-web-itest/pax-web-itest-container/pom.xml @@ -63,7 +63,7 @@ **/*IntegrationTest.java - **/WarJsfCdiIntegrationTest.java + **/WarJSFCdiIntegrationTest.java diff --git a/pax-web-itest/pax-web-itest-osgi/pom.xml b/pax-web-itest/pax-web-itest-osgi/pom.xml index c0bf4de1a5..d839650617 100644 --- a/pax-web-itest/pax-web-itest-osgi/pom.xml +++ b/pax-web-itest/pax-web-itest-osgi/pom.xml @@ -159,6 +159,11 @@ test + + org.ops4j.pax.web + pax-web-compatibility-annotation13 + + diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/StaticList.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/StaticList.java new file mode 100644 index 0000000000..5ef9e1f4f4 --- /dev/null +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/StaticList.java @@ -0,0 +1,27 @@ +/* + * 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.itest.server.support.war; + +import java.util.LinkedList; +import java.util.List; + +public class StaticList { + + public static final List EVENTS = new LinkedList<>(); + + private StaticList() { } + +} 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 new file mode 100644 index 0000000000..c2cfd70fc2 --- /dev/null +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/listeners/ListenerAddedInWebXml.java @@ -0,0 +1,35 @@ +/* + * 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.itest.server.support.war.listeners; + +import org.ops4j.pax.web.itest.server.support.war.StaticList; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +public class ListenerAddedInWebXml implements ServletContextListener { + + @Override + public void contextInitialized(ServletContextEvent sce) { + StaticList.EVENTS.add("Listener-from-web.xml initialized"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + StaticList.EVENTS.add("Listener-from-web.xml destroyed"); + } + +} 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 new file mode 100644 index 0000000000..01b6b6284e --- /dev/null +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/scis/SCIThatAddsServletContextListener.java @@ -0,0 +1,50 @@ +/* + * 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.itest.server.support.war.scis; + +import org.ops4j.pax.web.itest.server.support.war.StaticList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import java.util.Set; + +public class SCIThatAddsServletContextListener implements ServletContainerInitializer { + + public static final Logger LOG = LoggerFactory.getLogger(SCIThatAddsServletContextListener.class); + + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException { + ctx.addListener(new ServletContextListener() { + @Override + public void contextInitialized(ServletContextEvent sce) { + LOG.info("Listener-from-SCI initialized", new Throwable()); + StaticList.EVENTS.add("Listener-from-SCI initialized"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + LOG.info("Listener-from-SCI destroyed", new Throwable()); + StaticList.EVENTS.add("Listener-from-SCI destroyed"); + } + }); + } + +} diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/servlets/ServletAddedInWebXml.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/servlets/ServletAddedInWebXml.java new file mode 100644 index 0000000000..62deb85362 --- /dev/null +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/servlets/ServletAddedInWebXml.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 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.itest.server.support.war.servlets; + +import org.ops4j.pax.web.itest.server.support.war.StaticList; + +import javax.servlet.http.HttpServlet; + +public class ServletAddedInWebXml extends HttpServlet { + + @Override + public void init() { + StaticList.EVENTS.add("Servlet-from-web.xml initialized"); + } + + @Override + public void destroy() { + StaticList.EVENTS.add("Servlet-from-web.xml destroyed"); + } + +} diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/SimplestServlet.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/servlets/SimplestServlet.java similarity index 95% rename from pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/SimplestServlet.java rename to pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/servlets/SimplestServlet.java index b6500ca23f..cc3b6bf83b 100644 --- a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/SimplestServlet.java +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/support/war/servlets/SimplestServlet.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.ops4j.pax.web.itest.server.support.war; +package org.ops4j.pax.web.itest.server.support.war.servlets; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarBasicTest.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarBasicTest.java index 61824250aa..c6d2a5d948 100644 --- a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarBasicTest.java +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarBasicTest.java @@ -37,7 +37,7 @@ protected boolean enableWhiteboardExtender() { } @Test - public void simplestWabWithoutWebXml() throws Exception { + public void simplestWabWithoutWebXml() { Bundle sample1 = mockBundle("sample1", "/sample"); installWab(sample1); diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarClassSpaceTest.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarClassSpaceTest.java index 54c5a5c1c3..fca03e7e66 100644 --- a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarClassSpaceTest.java +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarClassSpaceTest.java @@ -223,14 +223,14 @@ public void complexWab() throws Exception { when(wab.findEntries(wabClasses.getCanonicalPath() + "/", "*.class", true)).thenReturn( Collections.enumeration(Arrays.asList( getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/Cb1IFace3Impl.class"), - getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/SimplestServlet.class") + getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/servlets/SimplestServlet.class") )) ); // classes for WAB's fragment - accessible using WAB bundle itself, so it returns the above classes as well when(wab.findEntries("/", "*.class", true)).thenReturn( Collections.enumeration(Arrays.asList( getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/Cb1IFace3Impl.class"), - getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/SimplestServlet.class"), + getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/servlets/SimplestServlet.class"), getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/fragment/AnnotatedServlet1.class"), getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/fragment/AnnotatedServlet2.class"), getClass().getClassLoader().getResource("org/ops4j/pax/web/itest/server/support/war/fragment/AnnotatedServlet3.class") diff --git a/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarListenersTest.java b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarListenersTest.java new file mode 100644 index 0000000000..e69b947e31 --- /dev/null +++ b/pax-web-itest/pax-web-itest-server/src/test/java/org/ops4j/pax/web/itest/server/war/WarListenersTest.java @@ -0,0 +1,99 @@ +/* + * 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.itest.server.war; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.ops4j.pax.web.itest.server.MultiContainerTestSupport; +import org.ops4j.pax.web.itest.server.support.war.StaticList; +import org.osgi.framework.Bundle; + +import javax.servlet.ServletContainerInitializer; +import java.io.File; +import java.net.URL; +import java.util.Collections; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.ops4j.pax.web.itest.server.support.Utils.httpGET; + +@RunWith(Parameterized.class) +public class WarListenersTest extends MultiContainerTestSupport { + + @Override + protected boolean enableWarExtender() { + return true; + } + + @Test + public void wabWithListeners() throws Exception { + // 1. the WAB bundle + Bundle wab = mockBundle("wab-listeners", "/c"); + when(wab.getBundleId()).thenReturn(42L); + configureBundleClassPath(wab, "src/test/resources/bundles/wab-listeners", entries -> { + entries.add("WEB-INF/classes"); + }); + String webXmlLocation = String.format("bundle://42.0:0%s", + new File("src/test/resources/bundles/wab-listeners/WEB-INF/web.xml").getCanonicalPath()); + when(wab.findEntries("WEB-INF", "web.xml", false)) + .thenReturn(Collections.enumeration(Collections.singletonList(new URL(webXmlLocation)))); + when(wab.loadClass(anyString())) + .thenAnswer(i -> WarListenersTest.class.getClassLoader().loadClass(i.getArgument(0, String.class))); + + File wabServices = new File("src/test/resources/bundles/wab-listeners/WEB-INF/classes/META-INF/services/"); + when(wab.getResources("META-INF/services/" + ServletContainerInitializer.class.getName())).thenReturn( + Collections.enumeration(Collections.singletonList( + new File(wabServices, ServletContainerInitializer.class.getName()).toURI().toURL())) + ); + + // for proper 302 redirect - Undertow doesn't handle such redirect when accessing root of the context. + when(wab.getEntry("/")).thenReturn(new File("src/test/resources/bundles/wab-listeners/").toURI().toURL()); + + StaticList.EVENTS.clear(); + + installWab(wab); + + // there should be a /c context that's (by default) redirecting to /wab/ + assertThat(httpGET(port, "/c"), startsWith("HTTP/1.1 302")); + + assertThat(StaticList.EVENTS.size(), equalTo(3)); + assertThat(StaticList.EVENTS.get(0), equalTo("Listener-from-web.xml initialized")); + assertThat(StaticList.EVENTS.get(1), equalTo("Listener-from-SCI initialized")); + assertThat(StaticList.EVENTS.get(2), equalTo("Servlet-from-web.xml initialized")); + + uninstallWab(wab); + + assertThat(StaticList.EVENTS.size(), equalTo(6)); + assertThat(StaticList.EVENTS.get(3), equalTo("Servlet-from-web.xml destroyed")); + assertThat(StaticList.EVENTS.get(4), equalTo("Listener-from-SCI destroyed")); + assertThat(StaticList.EVENTS.get(5), equalTo("Listener-from-web.xml destroyed")); + + // there should be no /c context at all, so no redirect, just 404 + assertThat(httpGET(port, "/c"), startsWith("HTTP/1.1 404")); + + ServerModelInternals serverModelInternals = serverModelInternals(serverModel); + ServiceModelInternals serviceModelInternals = serviceModelInternals(wab); + + assertTrue(serverModelInternals.isClean(wab)); + assertTrue(serviceModelInternals.isEmpty()); + } + +} diff --git a/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/the-wab-itself/WEB-INF/web.xml b/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/the-wab-itself/WEB-INF/web.xml index db20e3f5ef..4bc3948450 100644 --- a/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/the-wab-itself/WEB-INF/web.xml +++ b/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/the-wab-itself/WEB-INF/web.xml @@ -33,7 +33,7 @@ simplest-servlet - org.ops4j.pax.web.itest.server.support.war.SimplestServlet + org.ops4j.pax.web.itest.server.support.war.servlets.SimplestServlet simplest-servlet diff --git a/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/wab-listeners/WEB-INF/classes/META-INF/services/javax.servlet.ServletContainerInitializer b/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/wab-listeners/WEB-INF/classes/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 0000000000..1100b5df2d --- /dev/null +++ b/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/wab-listeners/WEB-INF/classes/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1,17 @@ +# +# 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. +# + +org.ops4j.pax.web.itest.server.support.war.scis.SCIThatAddsServletContextListener diff --git a/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/wab-listeners/WEB-INF/web.xml b/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/wab-listeners/WEB-INF/web.xml new file mode 100644 index 0000000000..e89e89fe1f --- /dev/null +++ b/pax-web-itest/pax-web-itest-server/src/test/resources/bundles/wab-listeners/WEB-INF/web.xml @@ -0,0 +1,37 @@ + + + + + + org.ops4j.pax.web.itest.server.support.war.listeners.ListenerAddedInWebXml + + + + s1 + org.ops4j.pax.web.itest.server.support.war.servlets.ServletAddedInWebXml + 0 + + + s1 + /s + + + 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 dc56d6d82e..bb8ebab95a 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 @@ -761,6 +761,11 @@ public void visit(ServletContextModelChange change) { osgiContextModels.remove(contextPath); PaxWebServletContextHandler sch = contextHandlers.remove(contextPath); + // Note: for WAB deployments, this is the last operation of the undeployment batch and all web element + // removals are delayed until this step. + // This is important to ensure proper order of destruction ended with contextDestroyed() calls + // No need to clean anything, as the PaxWebServletContextHandler is not reused + if (sch.isStarted()) { LOG.info("Stopping Jetty context \"{}\"", contextPath); try { @@ -835,7 +840,6 @@ public void visit(OsgiContextModelChange change) { if (change.getKind() == OpCode.DELETE) { LOG.info("Removing {} from {}", osgiModel, sch); - // TOCHECK: are there web elements associated with removed mapping for OsgiServletContext? OsgiServletContext removedOsgiServletContext = osgiServletContexts.remove(osgiModel); osgiContextModels.get(contextPath).remove(osgiModel); @@ -860,11 +864,6 @@ public void visit(OsgiContextModelChange change) { // and the highest ranked context should be registered as OSGi service (if it wasn't registered) highestRankedContext.register(); - - // TODO: very important question - here the classloader of entire context should be changed to the one - // built around a bundle registering the highest ranked OsgiContextModel. But this class loader's - // "space" should also include (somehow) all the reachable bundles (for example the bundles - // that have registered any web elements into this context)... } else { // TOCHECK: there should be no more web elements in the context, no OSGi mechanisms, just 404 all the time ((PaxWebServletHandler) sch.getServletHandler()).setDefaultOsgiContextModel(null); @@ -973,7 +972,6 @@ public void visit(ServletModelChange change) { // proper order ensures that (assuming above scenario), for /c1, ocm2 will be chosen and ocm1 skipped change.getContextModels().forEach(osgiContextModel -> { String contextPath = osgiContextModel.getContextPath(); - if (!done.add(contextPath)) { // servlet was already added to given ServletContextHandler // in association with the highest ranked OsgiContextModel (and its supporting @@ -1057,7 +1055,6 @@ public void visit(ServletModelChange change) { if (!entry.getValue()) { continue; } - LOG.info("Removing servlet {}", model); Set done = new HashSet<>(); @@ -1068,6 +1065,12 @@ public void visit(ServletModelChange change) { return; } + if (pendingTransaction(contextPath)) { + LOG.debug("Delaying removal of servlet {}", model); + return; + } + + LOG.info("Removing servlet {}", model); LOG.debug("Removing servlet {} from context {}", model.getName(), contextPath); // there should already be a ServletContextHandler @@ -1125,7 +1128,6 @@ public void visit(FilterModelChange change) { if (change.getKind() == OpCode.ADD && model.isDynamic()) { for (OsgiContextModel ocm : change.getContextModels()) { String contextPath = ocm.getContextPath(); - if (!done.add(contextPath)) { continue; } @@ -1196,9 +1198,6 @@ public void visit(FilterStateChange change) { // For Pax Web purposes, we'll try to handle such scenario and all the filters in a chain without servlet // will use OsgiServletContext which is "best" (wrt service ranking) for given physical context path - FilterHolder[] filterHolders = sch.getServletHandler().getFilters(); - FilterMapping[] filterMappings = sch.getServletHandler().getFilterMappings(); - PaxWebFilterHolder[] newFilterHolders = new PaxWebFilterHolder[filters.size()]; @SuppressWarnings("unchecked") List[] newFilterMappings = (List[]) new LinkedList[filters.size()]; @@ -1284,42 +1283,6 @@ public void visit(FilterStateChange change) { } } - private List configureFilterMappings(FilterModel model) { - List mappings = new LinkedList<>(); - - if (model.getDynamicServletNames().size() > 0 || model.getDynamicUrlPatterns().size() > 0) { - // this FilterModel was created in SCI using ServletContext.addFilter(), so it has ONLY - // dynamic mappings (potentially more than one) - model.getDynamicServletNames().forEach(dm -> { - if (!dm.isAfter()) { - mappings.add(new PaxWebFilterMapping(model, dm)); - } - }); - model.getDynamicUrlPatterns().forEach(dm -> { - if (!dm.isAfter()) { - mappings.add(new PaxWebFilterMapping(model, dm)); - } - }); - model.getDynamicServletNames().forEach(dm -> { - if (dm.isAfter()) { - mappings.add(new PaxWebFilterMapping(model, dm)); - } - }); - model.getDynamicUrlPatterns().forEach(dm -> { - if (dm.isAfter()) { - mappings.add(new PaxWebFilterMapping(model, dm)); - } - }); - } else { - // normal OSGi mapping - for (FilterModel.Mapping map : model.getMappingsPerDispatcherTypes()) { - mappings.add(new PaxWebFilterMapping(model, map)); - } - } - - return mappings; - } - @Override public void visit(EventListenerModelChange change) { if (change.getKind() == OpCode.ADD) { @@ -1328,22 +1291,36 @@ public void visit(EventListenerModelChange change) { Set done = new HashSet<>(); contextModels.forEach((context) -> { String contextPath = context.getContextPath(); - ServletContextHandler servletContextHandler = contextHandlers.get(contextPath); + if (!done.add(contextPath)) { + return; + } + + PaxWebServletContextHandler servletContextHandler = contextHandlers.get(contextPath); EventListener eventListener = eventListenerModel.resolveEventListener(); if (eventListener instanceof ServletContextAttributeListener) { // add it to accessible list to fire per-OsgiContext attribute changes OsgiServletContext c = osgiServletContexts.get(context); c.addServletContextAttributeListener((ServletContextAttributeListener)eventListener); } - if (!done.add(contextPath)) { - return; - } // 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); +// } }); } + if (change.getKind() == OpCode.DELETE) { List eventListenerModels = change.getEventListenerModels(); for (EventListenerModel eventListenerModel : eventListenerModels) { @@ -1358,13 +1335,19 @@ public void visit(EventListenerModelChange change) { c.removeServletContextAttributeListener((ServletContextAttributeListener)eventListener); } } + + if (pendingTransaction(context.getContextPath())) { + LOG.debug("Delaying removal of event listener {}", eventListenerModel); + return; + } + if (servletContextHandler != null) { // remove the listener from real context - even ServletContextAttributeListener // this may be null in case of WAB where we keep event listeners so they get contextDestroyed // event properly servletContextHandler.removeEventListener(eventListener); } - eventListenerModel.ungetEventListener(eventListener); +// eventListenerModel.ungetEventListener(eventListener); }); } } @@ -1479,6 +1462,8 @@ public void visit(ContainerInitializerModelChange change) { if (sch.isStarted()) { // Jetty, Tomcat and Undertow all disable "addServlet()" method (and filter and listener // equivalents) after the context has started, so we'll just print an error here + // Also, SCis can be added only through pax-web-extender-war, so no way to start the context + // before adding any SCI LOG.warn("ServletContainerInitializer {} can't be added, as the context {} is already started", model.getContainerInitializer(), sch); } else { @@ -1486,7 +1471,6 @@ public void visit(ContainerInitializerModelChange change) { // ContainerInitializerModel, because (for now) there's no Whiteboard support, thus there can // be only one OsgiContextModel - ServletContainerInitializer initializer = model.getContainerInitializer(); // Jetty doesn't handle ServletContainerInitializers directly... There's something in // org.eclipse.jetty.annotations.AnnotationConfiguration and // org.eclipse.jetty.servlet.listener.ContainerInitializer which turns initializer into a @@ -1497,10 +1481,11 @@ public void visit(ContainerInitializerModelChange change) { DynamicRegistrations registrations = this.dynamicRegistrations.get(path); OsgiDynamicServletContext dynamicContext = new OsgiDynamicServletContext(osgiServletContexts.get(context), registrations); SCIWrapper wrapper = new SCIWrapper(dynamicContext, model); - initializers.get(path).put(System.identityHashCode(initializer), wrapper); + initializers.get(path).put(System.identityHashCode(model.getContainerInitializer()), wrapper); } }); } + if (change.getKind() == OpCode.DELETE) { List models = change.getContainerInitializerModels(); for (ContainerInitializerModel model : models) { @@ -1590,6 +1575,26 @@ private void ensureServletContextStarted(PaxWebServletContextHandler sch) { } } + // There's a deadlock, when starting the context in single paxweb-config thread: + // 1. org.apache.aries.cdi.weld.WeldCDIContainerInitializer.initialize() is called in Aries CCR thread + // 2. locking <0x1e70> (a org.jboss.weld.Container) in Aries CCR thread + // 3. org.apache.aries.cdi.extension.servlet.weld.WeldServletExtension.afterDeploymentValidation() is called in Aries CCR thread + // 4. org.jboss.weld.module.web.servlet.WeldInitialListener is whiteboard-registered in Aries CCR thread + // 5. Pax Web's listener tracker passes the registration to single paxweb-config thread + // 6. Pax Web's JettyServerWrapper registers the listener in already started context (so it restarts it) in + // paxweb-config thread + // 7. WeldInitialListener.contextInitialized() is called in paxweb-config thread + // 8. org.jboss.weld.module.web.servlet.HttpContextLifecycle#fireEventForApplicationScope() has + // synchronized block on the org.jboss.weld.Container instance - deadlock + + // TODO: do it better +// new Thread(() -> { +// try { +// sch.start(); +// } catch (Exception e) { +// LOG.error(e.getMessage(), e); +// } +// }).start(); sch.start(); } catch (Exception e) { LOG.error(e.getMessage(), e); @@ -1620,7 +1625,7 @@ private void configureErrorPages(String location, ErrorPageErrorHandler eph, Err * of current list of filters. This is quite special case, but not that uncommon - when new filters are * lower-ranked than all existing ones and there are no filters to be removed.

* - *

TODO: with {@link ServletHandler#insertFilterMapping(FilterMapping, int, boolean)} we could + *

TODO: with {@code ServletHandler#insertFilterMapping(FilterMapping, int, boolean)} we could * optimize even more

* * @param servletHandler @@ -1629,7 +1634,7 @@ private void configureErrorPages(String location, ErrorPageErrorHandler eph, Err * @return */ private boolean quickFilterChange(ServletHandler servletHandler, PaxWebFilterHolder[] newFilterHolders, List[] newFilterMappings) { - PaxWebFilterHolder[] existingFilterHolders = (PaxWebFilterHolder[]) servletHandler.getFilters(); + FilterHolder[] existingFilterHolders = servletHandler.getFilters(); int pos = 0; boolean quick = newFilterHolders.length >= existingFilterHolders.length; @@ -1639,7 +1644,7 @@ private boolean quickFilterChange(ServletHandler servletHandler, PaxWebFilterHol if (pos >= existingFilterHolders.length) { break; } - if (!existingFilterHolders[pos].getFilterModel().equals(newFilterHolders[pos].getFilterModel())) { + if (!((PaxWebFilterHolder)existingFilterHolders[pos]).getFilterModel().equals(newFilterHolders[pos].getFilterModel())) { quick = false; break; } @@ -1659,6 +1664,42 @@ private boolean quickFilterChange(ServletHandler servletHandler, PaxWebFilterHol return false; } + private List configureFilterMappings(FilterModel model) { + List mappings = new LinkedList<>(); + + if (model.getDynamicServletNames().size() > 0 || model.getDynamicUrlPatterns().size() > 0) { + // this FilterModel was created in SCI using ServletContext.addFilter(), so it has ONLY + // dynamic mappings (potentially more than one) + model.getDynamicServletNames().forEach(dm -> { + if (!dm.isAfter()) { + mappings.add(new PaxWebFilterMapping(model, dm)); + } + }); + model.getDynamicUrlPatterns().forEach(dm -> { + if (!dm.isAfter()) { + mappings.add(new PaxWebFilterMapping(model, dm)); + } + }); + model.getDynamicServletNames().forEach(dm -> { + if (dm.isAfter()) { + mappings.add(new PaxWebFilterMapping(model, dm)); + } + }); + model.getDynamicUrlPatterns().forEach(dm -> { + if (dm.isAfter()) { + mappings.add(new PaxWebFilterMapping(model, dm)); + } + }); + } else { + // normal OSGi mapping + for (FilterModel.Mapping map : model.getMappingsPerDispatcherTypes()) { + mappings.add(new PaxWebFilterMapping(model, map)); + } + } + + return mappings; + } + // PAXWEB-210: create security constraints // @Override public void addSecurityConstraintMappings( diff --git a/pax-web-jsp/pom.xml b/pax-web-jsp/pom.xml index 448caddb7e..7af6baedcd 100644 --- a/pax-web-jsp/pom.xml +++ b/pax-web-jsp/pom.xml @@ -215,6 +215,10 @@ <_nouses>true <_contract>!* + ="2.3,2.2,2.1,2" + ]]> diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/OsgiContextModel.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/OsgiContextModel.java index 318f85b362..287a80e247 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/OsgiContextModel.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/OsgiContextModel.java @@ -732,6 +732,10 @@ public int compareTo(OsgiContextModel o) { * @return */ public String getTemporaryLocation() { + if (isWab()) { + // When restarting, a WAB gets new OsgiContextModel, so we have to keep the path unique for the WAB + return "/".equals(contextPath) ? "ROOT" : contextPath; + } return String.format("%s/OCM%d", "/".equals(contextPath) ? "ROOT" : contextPath + "/", getNumericId()); } diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/ServiceModel.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/ServiceModel.java index fb65e04c77..170dcb375e 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/ServiceModel.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/ServiceModel.java @@ -434,8 +434,6 @@ public void visit(ErrorPageStateChange change) { *

This method is an equivalent of * {@code org.ops4j.pax.web.extender.whiteboard.internal.WhiteboardContext#reRegisterWebElements()}.

* - *

We don't have to

- * * @param oldContext * @param newContext may be null - in such case we have to determine new context * @param batch diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/elements/EventListenerModel.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/elements/EventListenerModel.java index 72c23dc2d8..29414e2720 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/elements/EventListenerModel.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/model/elements/EventListenerModel.java @@ -16,11 +16,11 @@ */ package org.ops4j.pax.web.service.spi.model.elements; -import java.util.EventListener; - import org.ops4j.pax.web.service.spi.model.events.EventListenerEventData; import org.ops4j.pax.web.service.spi.whiteboard.WhiteboardWebContainerView; +import java.util.EventListener; + public class EventListenerModel extends ElementModel { private EventListener eventListener; @@ -93,7 +93,7 @@ public EventListener getResolvedListener() { /** * When a listener is removed from native servlet container, it should be unget here - this - * is esiecially important with service references. + * is especially important with service references. * @param listener */ public void ungetEventListener(EventListener listener) { @@ -121,4 +121,14 @@ public Boolean performValidation() { return Boolean.TRUE; } + @Override + public String toString() { + return "EventListenerModel{id=" + getId() + + (eventListener != null ? ",listener='" + eventListener + "'" : "") + + (getElementSupplier() != null ? ",supplier='" + getElementSupplier() + "'" : "") + + (getElementReference() != null ? ",reference='" + getElementReference() + "'" : "") + + ",contexts=" + getContextModelsInfo() + + "}"; + } + } diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/DynamicRegistrations.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/DynamicRegistrations.java index d906b1bc8a..ac71ec0f3f 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/DynamicRegistrations.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/DynamicRegistrations.java @@ -51,7 +51,7 @@ * being registered by {@link javax.servlet.ServletContainerInitializer}s.

* *

Just as dynamic registration methods of {@link javax.servlet.ServletContext}, we don't bother with - * unregistration of the elements. We will eventually clean things up, but only after give context is somehow + * unregistration of the elements. We will eventually clean things up, but only after given context is somehow * destroyed/closed. Registration declarations stored in this class are cleared after invocation of * context's {@link javax.servlet.ServletContainerInitializer} ends.

*/ diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiDynamicServletContext.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiDynamicServletContext.java index edede96903..daa67a29b7 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiDynamicServletContext.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiDynamicServletContext.java @@ -35,6 +35,8 @@ import javax.servlet.descriptor.JspConfigDescriptor; import org.ops4j.pax.web.service.spi.model.OsgiContextModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

This class wraps {@link OsgiServletContext} and is used to perform "dynamic registration operations" of @@ -49,6 +51,8 @@ */ public class OsgiDynamicServletContext implements ServletContext { + public static final Logger LOG = LoggerFactory.getLogger(OsgiDynamicServletContext.class); + private final OsgiServletContext osgiContext; private final DynamicRegistrations registration; @@ -118,17 +122,29 @@ public ServletRegistration.Dynamic addServlet(String servletName, Class T createFilter(Class clazz) throws ServletException { - throw new UnsupportedOperationException("createFilter() is not supported."); + try { + return clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new ServletException(e.getMessage(), e); + } } @Override public T createListener(Class clazz) throws ServletException { - throw new UnsupportedOperationException("createListener() is not supported."); + try { + return clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new ServletException(e.getMessage(), e); + } } @Override public T createServlet(Class clazz) throws ServletException { - throw new UnsupportedOperationException("createServlet() is not supported."); + try { + return clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new ServletException(e.getMessage(), e); + } } @Override @@ -138,7 +154,9 @@ public void declareRoles(String... roleNames) { @Override public boolean setInitParameter(String name, String value) { - throw new UnsupportedOperationException("setInitParameter() is not supported."); + // called for example by + // org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration$JerseyWebApplicationInitializer.onStartup() + return osgiContext.getOsgiContextModel().getContextParams().putIfAbsent(name, value) == null; } @Override diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiScopedServletContext.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiScopedServletContext.java index 654940da21..6af0717011 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiScopedServletContext.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/OsgiScopedServletContext.java @@ -404,8 +404,7 @@ public ClassLoader getClassLoader() { // the servlet/filter, but in case of WAB, we'll return the WAB's classloader (delegating to // all reachable bundles) - if (getOsgiContextModel().getClassLoader() != null) { - // WAB case + if (getOsgiContextModel().isWab()) { return getOsgiContextModel().getClassLoader(); } diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicEventListenerRegistration.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicEventListenerRegistration.java index 2d6af0f810..56b40c3dae 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicEventListenerRegistration.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicEventListenerRegistration.java @@ -28,6 +28,8 @@ public class DynamicEventListenerRegistration { public DynamicEventListenerRegistration(EventListenerModel model, OsgiContextModel osgiContextModel) { this.model = model; + // "close" the context list + this.model.getContextModels(); this.osgiContextModel = osgiContextModel; } diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicFilterRegistration.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicFilterRegistration.java index 5f3d1f2214..78328be60b 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicFilterRegistration.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicFilterRegistration.java @@ -41,6 +41,8 @@ public class DynamicFilterRegistration implements FilterRegistration.Dynamic { public DynamicFilterRegistration(FilterModel model, OsgiContextModel osgiContextModel, DynamicRegistrations regs) { this.model = model; + // "close" the context list + this.model.getContextModels(); this.osgiContextModel = osgiContextModel; this.registrations = regs; } diff --git a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicServletRegistration.java b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicServletRegistration.java index e230cbcf24..564177330b 100644 --- a/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicServletRegistration.java +++ b/pax-web-spi/src/main/java/org/ops4j/pax/web/service/spi/servlet/dynamic/DynamicServletRegistration.java @@ -42,6 +42,8 @@ public class DynamicServletRegistration implements ServletRegistration.Dynamic { public DynamicServletRegistration(ServletModel model, OsgiContextModel osgiContextModel, ServletContextModel servletContextModel, DynamicRegistrations regs) { this.model = model; + // "close" the context list + this.model.getContextModels(); this.osgiContextModel = osgiContextModel; this.servletContextModel = servletContextModel; this.registrations = regs; @@ -90,6 +92,7 @@ public Set addMapping(String... urlPatterns) { // from existing registrations (except current registration) registrations.getDynamicServletRegistrations().values().forEach(r -> { if (r != DynamicServletRegistration.this) { + // tomcat additionally checks "if (wrapper.isOverridable())", so it's possible to alter "/" mapping existing.addAll(r.getMappings()); } }); @@ -119,7 +122,8 @@ public Set addMapping(String... urlPatterns) { @Override public Collection getMappings() { - return Collections.unmodifiableList(Arrays.asList(model.getUrlPatterns())); + return model.getUrlPatterns() == null ? Collections.emptyList() + : Collections.unmodifiableList(Arrays.asList(model.getUrlPatterns())); } @Override diff --git a/pax-web-tomcat/src/main/java/org/ops4j/pax/web/service/tomcat/internal/TomcatServerWrapper.java b/pax-web-tomcat/src/main/java/org/ops4j/pax/web/service/tomcat/internal/TomcatServerWrapper.java index dae10e5107..f9c20a5c0e 100644 --- a/pax-web-tomcat/src/main/java/org/ops4j/pax/web/service/tomcat/internal/TomcatServerWrapper.java +++ b/pax-web-tomcat/src/main/java/org/ops4j/pax/web/service/tomcat/internal/TomcatServerWrapper.java @@ -761,6 +761,10 @@ public void visit(ServletContextModelChange change) { osgiContextModels.remove(contextPath); PaxWebStandardContext context = contextHandlers.remove(contextPath); + // Note: for WAB deployments, this is the last operation of the undeployment batch and all web element + // removals are delayed until this step. + // This is important to ensure proper order of destruction ended with contextDestroyed() calls + if (isStarted(context)) { LOG.info("Stopping Tomcat context \"{}\"", contextPath); try { @@ -1012,7 +1016,6 @@ public void visit(ServletModelChange change) { if (!entry.getValue()) { continue; } - LOG.info("Removing servlet {}", model); Set done = new HashSet<>(); @@ -1023,6 +1026,12 @@ public void visit(ServletModelChange change) { return; } + if (pendingTransaction(contextPath)) { + LOG.debug("Delaying removal of servlet {}", model); + return; + } + + LOG.info("Removing servlet {}", model); LOG.debug("Removing servlet {} from context {}", model.getName(), contextPath); // there should already be a ServletContextHandler @@ -1039,7 +1048,6 @@ public void visit(ServletModelChange change) { // are there any error page declarations in the model? ErrorPageModel epm = model.getErrorPageModel(); if (epm != null) { - String location = model.getErrorPageModel().getLocation(); for (ErrorPage ep : realContext.findErrorPages()) { if (ep.getExceptionType() != null && epm.getExceptionClassNames().contains(ep.getExceptionType())) { realContext.removeErrorPage(ep); @@ -1066,7 +1074,6 @@ public void visit(FilterModelChange change) { if (change.getKind() == OpCode.ADD && model.isDynamic()) { for (OsgiContextModel ocm : change.getContextModels()) { String contextPath = ocm.getContextPath(); - if (!done.add(contextPath)) { continue; } @@ -1131,9 +1138,6 @@ public void visit(FilterStateChange change) { } } - PaxWebFilterDef[] newFilterDefs = new PaxWebFilterDef[filters.size() + 1]; - PaxWebFilterMap[] newFilterMaps = new PaxWebFilterMap[filters.size() + 1]; - for (FilterModel model : filters) { List contextModels = filtersMap.get(model) != null ? filtersMap.get(model) : model.getContextModels(); @@ -1158,6 +1162,10 @@ public void visit(EventListenerModelChange change) { Set done = new HashSet<>(); contextModels.forEach((context) -> { String contextPath = context.getContextPath(); + if (!done.add(contextPath)) { + return; + } + PaxWebStandardContext standardContext = contextHandlers.get(contextPath); EventListener eventListener = eventListenerModel.resolveEventListener(); if (eventListener instanceof ServletContextAttributeListener) { @@ -1165,9 +1173,6 @@ public void visit(EventListenerModelChange change) { OsgiServletContext c = osgiServletContexts.get(context); c.addServletContextAttributeListener((ServletContextAttributeListener)eventListener); } - if (!done.add(contextPath)) { - return; - } // add the listener to real context - even ServletContextAttributeListener (but only once - even // if there are many OsgiServletContexts per ServletContext) @@ -1179,6 +1184,7 @@ public void visit(EventListenerModelChange change) { } }); } + if (change.getKind() == OpCode.DELETE) { List eventListenerModels = change.getEventListenerModels(); for (EventListenerModel eventListenerModel : eventListenerModels) { @@ -1193,6 +1199,12 @@ public void visit(EventListenerModelChange change) { c.removeServletContextAttributeListener((ServletContextAttributeListener)eventListener); } } + + if (pendingTransaction(context.getContextPath())) { + LOG.debug("Delaying removal of event listener {}", eventListenerModel); + return; + } + // remove the listener from real context - even ServletContextAttributeListener if (standardContext != null) { // this may be null in case of WAB where we keep event listeners so they get contextDestroyed @@ -1211,10 +1223,10 @@ public void visit(EventListenerModelChange change) { newLListeners.add(l); } } - standardContext.setApplicationEventListeners(newEListeners.toArray(new Object[newEListeners.size()])); - standardContext.setApplicationLifecycleListeners(newLListeners.toArray(new Object[newLListeners.size()])); + standardContext.setApplicationEventListeners(newEListeners.toArray(new Object[0])); + standardContext.setApplicationLifecycleListeners(newLListeners.toArray(new Object[0])); } - eventListenerModel.ungetEventListener(eventListener); +// eventListenerModel.ungetEventListener(eventListener); }); } } @@ -1347,14 +1359,14 @@ public void visit(ContainerInitializerModelChange change) { // even if there's org.apache.catalina.core.StandardContext.addServletContainerInitializer(), // there's no "remove" equivalent and also we want to be able to pass correct implementation // of ServletContext there - ServletContainerInitializer initializer = model.getContainerInitializer(); DynamicRegistrations registrations = this.dynamicRegistrations.get(path); OsgiDynamicServletContext dynamicContext = new OsgiDynamicServletContext(osgiServletContexts.get(context), registrations); SCIWrapper wrapper = new SCIWrapper(dynamicContext, model); - initializers.get(path).put(System.identityHashCode(initializer), wrapper); + initializers.get(path).put(System.identityHashCode(model.getContainerInitializer()), wrapper); } }); } + if (change.getKind() == OpCode.DELETE) { List models = change.getContainerInitializerModels(); for (ContainerInitializerModel model : models) { @@ -1378,7 +1390,7 @@ public void visit(ContainerInitializerModelChange change) { *

This method is always (should be) called withing the "configuration thread" of Pax Web Runtime, because * it's called in visit() methods for servlets (including resources) and filters, so we can safely access * {@link org.ops4j.pax.web.service.spi.model.ServerModel}.

- * @param sch + * @param context */ private void ensureServletContextStarted(PaxWebStandardContext context) { String contextPath = context.getPath().equals("") ? "/" : context.getPath(); diff --git a/pax-web-undertow/src/main/java/org/ops4j/pax/web/service/undertow/internal/UndertowServerWrapper.java b/pax-web-undertow/src/main/java/org/ops4j/pax/web/service/undertow/internal/UndertowServerWrapper.java index af16e38f09..f61f4e4b14 100644 --- a/pax-web-undertow/src/main/java/org/ops4j/pax/web/service/undertow/internal/UndertowServerWrapper.java +++ b/pax-web-undertow/src/main/java/org/ops4j/pax/web/service/undertow/internal/UndertowServerWrapper.java @@ -991,6 +991,24 @@ public void visit(ServletContextModelChange change) { deploymentInfos.remove(contextPath); securityHandlers.remove(contextPath); wrappingHandlers.remove(contextPath); + + // Note: for WAB deployments, this is the last operation of the undeployment batch and all web element + // removals are delayed until this step. + // This is important to ensure proper order of destruction ended with contextDestroyed() calls + + DeploymentManager manager = getDeploymentManager(contextPath); + if (manager != null) { + LOG.info("Stopping Undertow context \"{}\"", contextPath); + try { + pathHandler.removePrefixPath(contextPath); + DeploymentInfo deploymentInfo = manager.getDeployment().getDeploymentInfo(); + manager.stop(); + manager.undeploy(); + servletContainer.removeDeployment(deploymentInfo); + } catch (ServletException e) { + LOG.warn("Error stopping Undertow context \"{}\": {}", contextPath, e.getMessage(), e); + } + } } } @@ -1242,7 +1260,6 @@ public void visit(ServletModelChange change) { if (!entry.getValue()) { continue; } - LOG.info("Removing servlet {}", model); Set done = new HashSet<>(); @@ -1253,8 +1270,14 @@ public void visit(ServletModelChange change) { return; } + if (pendingTransaction(contextPath)) { + LOG.debug("Delaying removal of servlet {}", model); + return; + } + // this time we just assume that the servlet context is started + LOG.info("Removing servlet {}", model); LOG.debug("Removing servlet {} from context {}", model.getName(), contextPath); // take existing deployment manager and the deployment info from its deployment @@ -1343,7 +1366,6 @@ public void visit(FilterModelChange change) { if (change.getKind() == OpCode.ADD && model.isDynamic()) { for (OsgiContextModel ocm : change.getContextModels()) { String contextPath = ocm.getContextPath(); - if (!done.add(contextPath)) { continue; } @@ -1363,7 +1385,6 @@ public void visit(FilterModelChange change) { OsgiServletContext osgiContext = osgiServletContexts.get(highestRankedModel); DeploymentManager manager = getDeploymentManager(contextPath); - DeploymentManager.State state = manager == null ? DeploymentManager.State.UNDEPLOYED : manager.getState(); DeploymentInfo deploymentInfo = manager == null ? deploymentInfos.get(contextPath) : manager.getDeployment().getDeploymentInfo(); Deployment deployment = manager == null ? null : manager.getDeployment(); @@ -1376,8 +1397,6 @@ public void visit(FilterModelChange change) { deployment.getFilters().addFilter(info); } - String filterName = model.getName(); - configureFilterMappings(model, deploymentInfo); } } @@ -1434,7 +1453,6 @@ public void visit(FilterStateChange change) { List added = new LinkedList<>(); - int pos = 1; for (FilterModel model : filters) { // we need highest ranked OsgiContextModel for current context path - chosen not among all // associated OsgiContextModels, but among OsgiContextModels of the FilterModel @@ -1483,8 +1501,6 @@ public void visit(FilterStateChange change) { if (!quick || added.size() > 0 || state != DeploymentManager.State.STARTED) { deploymentInfo.addFilter(info); - String filterName = model.getName(); - configureFilterMappings(model, deploymentInfo); } } @@ -1518,6 +1534,10 @@ public void visit(EventListenerModelChange change) { Set done = new HashSet<>(); contextModels.forEach((context) -> { String contextPath = context.getContextPath(); + if (!done.add(contextPath)) { + return; + } + DeploymentManager manager = getDeploymentManager(contextPath); DeploymentInfo deploymentInfo = manager == null ? deploymentInfos.get(context.getContextPath()) : manager.getDeployment().getDeploymentInfo(); @@ -1528,16 +1548,13 @@ public void visit(EventListenerModelChange change) { OsgiServletContext c = osgiServletContexts.get(context); c.addServletContextAttributeListener((ServletContextAttributeListener) eventListener); } - if (!done.add(contextPath)) { - return; - } // add the listener to real context - even ServletContextAttributeListener (but only once - even // if there are many OsgiServletContexts per ServlApplicationListenersetContext) // we have to wrap the listener, so proper OsgiServletContext is passed there EventListener wrapper = eventListener; if (eventListener instanceof ServletContextListener) { - ContextLinkingInvocationHandler handler = new ContextLinkingInvocationHandler((ServletContextListener)eventListener); + ContextLinkingInvocationHandler handler = new ContextLinkingInvocationHandler(eventListener); ClassLoader cl = context.getClassLoader(); if (cl == null) { // for test scenario @@ -1555,10 +1572,12 @@ public void visit(EventListenerModelChange change) { deploymentInfo.addListener(info); if (manager != null) { + // TODO: should be programmatic (true) for listeners added using ServletContext.addListeer() manager.getDeployment().getApplicationListeners().addListener(new ManagedListener(info, true)); } }); } + if (change.getKind() == OpCode.DELETE) { List eventListenerModels = change.getEventListenerModels(); for (EventListenerModel eventListenerModel : eventListenerModels) { @@ -1575,6 +1594,12 @@ public void visit(EventListenerModelChange change) { OsgiServletContext c = osgiServletContexts.get(context); c.removeServletContextAttributeListener((ServletContextAttributeListener) eventListener); } + + if (pendingTransaction(context.getContextPath())) { + LOG.debug("Delaying removal of event listener {}", eventListenerModel); + return; + } + // remove the listener from real context - even ServletContextAttributeListener // unfortunately, one does not simply remove EventListener from existing context in Undertow @@ -1607,7 +1632,7 @@ public void visit(EventListenerModelChange change) { } }); } - eventListenerModel.ungetEventListener(eventListener); +// eventListenerModel.ungetEventListener(eventListener); ensureServletContextStarted(contextPath); }); @@ -1765,21 +1790,16 @@ public void visit(ContainerInitializerModelChange change) { LOG.warn("ServletContainerInitializer {} can't be added, as the context \"{}\" is already started", model.getContainerInitializer(), path); } else { - // even if there's org.apache.catalina.core.StandardContext.addServletContainerInitializer(), - // there's no "remove" equivalent and also we want to be able to pass correct implementation - // of ServletContext there - ServletContainerInitializer initializer = model.getContainerInitializer(); - // because of the quirks related to Undertow's deploymentInfo vs. deployment (and their - // clones), we'll prepare specia SCIInfo here - OsgiServletContext osgiServletContext = osgiServletContexts.get(context); + // clones), we'll prepare special SCIInfo here DynamicRegistrations registrations = this.dynamicRegistrations.get(path); OsgiDynamicServletContext dynamicContext = new OsgiDynamicServletContext(osgiServletContexts.get(context), registrations); OsgiServletContainerInitializerInfo info = new OsgiServletContainerInitializerInfo(model, dynamicContext); - initializers.get(path).put(System.identityHashCode(initializer), info); + initializers.get(path).put(System.identityHashCode(model.getContainerInitializer()), info); } }); } + if (change.getKind() == OpCode.DELETE) { List models = change.getContainerInitializerModels(); for (ContainerInitializerModel model : models) { diff --git a/pom.xml b/pom.xml index e3ef37870a..8e0361cae1 100644 --- a/pom.xml +++ b/pom.xml @@ -305,7 +305,7 @@ 2.9.0 1.7.30 - 5.3.7 + 4.3.25.RELEASE 5.3.6_1 1.2.1 1.2.5 @@ -815,6 +815,11 @@ pax-web-compatibility-annotation13 ${project.version}
+ + org.ops4j.pax.web + pax-web-fragment-myfaces-spifly + ${project.version} + @@ -1608,6 +1613,29 @@ org.apache.aries.cdi.extra ${dependency.aries.cdi.version} + + org.apache.aries.cdi + org.apache.aries.cdi.extension.el.jsp + ${dependency.aries.cdi.version} + + + org.apache.geronimo.specs + geronimo-annotation_1.3_spec + + + org.apache.geronimo.specs + geronimo-el_2.2_spec + + + org.apache.geronimo.specs + geronimo-interceptor_1.2_spec + + + org.apache.geronimo.specs + geronimo-jcdi_2.0_spec + + + org.apache.aries.spifly @@ -2876,13 +2904,14 @@ pax-web-extender-war - + pax-web-fragments + + pax-web-jetty - pax-web-compatibility diff --git a/samples/samples-jsf/war-jsf23-cdi/pom.xml b/samples/samples-jsf/war-jsf23-cdi/pom.xml index fbc171911a..c09d953176 100644 --- a/samples/samples-jsf/war-jsf23-cdi/pom.xml +++ b/samples/samples-jsf/war-jsf23-cdi/pom.xml @@ -70,15 +70,19 @@ =1.0.0)(!(version>=2.0.0)))"; - beans:List=" - org.ops4j.pax.web.samples.warjsf23cdi.Hello, - org.apache.myfaces.flow.cdi.FlowBuilderFactoryBean", - osgi.cdi.extension;filter:="(osgi.cdi.extension=aries.cdi.http)" + osgi.cdi.extension;filter:="(osgi.cdi.extension=aries.cdi.http)", + osgi.cdi.extension;filter:="(osgi.cdi.extension=aries.cdi.el.jsp)" ]]> + + + + + + + WEB-INF/lib *;scope=compile - <_cdiannotations>*;discover=none + <_cdiannotations>*;discover=annotated diff --git a/samples/samples-war/war-spring/pom.xml b/samples/samples-war/war-spring/pom.xml index 68f8f1c0f2..26fee9e326 100644 --- a/samples/samples-war/war-spring/pom.xml +++ b/samples/samples-war/war-spring/pom.xml @@ -53,6 +53,12 @@ javax.naming, javax.xml.parsers, + javax.xml.stream, + javax.xml.transform, + javax.xml.transform.dom, + javax.xml.transform.sax, + javax.xml.transform.stax, + javax.xml.transform.stream, org.w3c.dom, org.xml.sax