This method is an equivalent of
* {@code org.ops4j.pax.web.extender.whiteboard.internal.WhiteboardContext#reRegisterWebElements()}.
*
- * {
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 extend
@Override
public 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}