View Javadoc
1   package com.atlassian.plugin.servlet;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.Plugin;
5   import com.atlassian.plugin.PluginController;
6   import com.atlassian.plugin.PluginException;
7   import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
8   import com.atlassian.plugin.event.PluginEventListener;
9   import com.atlassian.plugin.event.PluginEventManager;
10  import com.atlassian.plugin.event.events.PluginDisabledEvent;
11  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
12  import com.atlassian.plugin.event.events.PluginFrameworkShuttingDownEvent;
13  import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
14  import com.atlassian.plugin.scope.ScopeManager;
15  import com.atlassian.plugin.servlet.descriptors.ServletContextListenerModuleDescriptor;
16  import com.atlassian.plugin.servlet.descriptors.ServletContextParamModuleDescriptor;
17  import com.atlassian.plugin.servlet.descriptors.ServletFilterModuleDescriptor;
18  import com.atlassian.plugin.servlet.descriptors.ServletModuleDescriptor;
19  import com.atlassian.plugin.servlet.filter.FilterDispatcherCondition;
20  import com.atlassian.plugin.servlet.filter.FilterLocation;
21  import com.atlassian.plugin.servlet.filter.PluginFilterConfig;
22  import com.atlassian.plugin.servlet.util.DefaultPathMapper;
23  import com.atlassian.plugin.servlet.util.PathMapper;
24  import com.atlassian.plugin.servlet.util.ServletContextServletModuleManagerAccessor;
25  import com.atlassian.plugin.util.ClassLoaderStack;
26  import com.google.common.annotations.VisibleForTesting;
27  import com.google.common.base.MoreObjects;
28  import com.google.common.collect.ImmutableMap;
29  import io.atlassian.util.concurrent.LazyReference;
30  import org.dom4j.Element;
31  import org.dom4j.dom.DOMElement;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.servlet.DispatcherType;
36  import javax.servlet.Filter;
37  import javax.servlet.FilterConfig;
38  import javax.servlet.ServletConfig;
39  import javax.servlet.ServletContext;
40  import javax.servlet.ServletContextEvent;
41  import javax.servlet.ServletContextListener;
42  import javax.servlet.ServletException;
43  import javax.servlet.http.HttpServlet;
44  import java.util.ArrayList;
45  import java.util.Collections;
46  import java.util.Enumeration;
47  import java.util.HashMap;
48  import java.util.HashSet;
49  import java.util.LinkedList;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.Set;
53  import java.util.concurrent.ConcurrentHashMap;
54  import java.util.concurrent.ConcurrentMap;
55  import java.util.concurrent.atomic.AtomicReference;
56  import java.util.stream.Collectors;
57  
58  import static com.atlassian.plugin.servlet.descriptors.ServletFilterModuleDescriptor.byWeight;
59  import static com.google.common.base.Preconditions.checkNotNull;
60  
61  /**
62   * A simple servletModuleManager to track and retrieve the loaded servlet plugin modules.
63   *
64   * @since 2.1.0
65   */
66  public class DefaultServletModuleManager implements ServletModuleManager {
67      private static final Logger log = LoggerFactory.getLogger(DefaultServletModuleManager.class);
68  
69      private final PathMapper servletMapper;
70      private final Map<String, ServletModuleDescriptor> servletDescriptors = new ConcurrentHashMap<>();
71      private final ConcurrentMap<String, LazyReference<HttpServlet>> servletRefs = new ConcurrentHashMap<>();
72  
73      private final PathMapper filterMapper;
74      private final Map<String, ServletFilterModuleDescriptor> filterDescriptors = new ConcurrentHashMap<>();
75      private final ConcurrentMap<String, LazyReference<Filter>> filterRefs = new ConcurrentHashMap<>();
76  
77      private final FilterFactory filterFactory;
78      private final ConcurrentMap<Plugin, ContextLifecycleReference> pluginContextRefs = new ConcurrentHashMap<>();
79  
80      private final AtomicReference<PluginController> pluginControllerRef = new AtomicReference<>();
81  
82      /**
83       * Constructor that sets itself in the servlet context for later use in dispatching servlets and filters.
84       *
85       * @param servletContext     The servlet context to store itself in
86       * @param pluginEventManager The plugin event manager
87       * @since 2.2.0
88       */
89      public DefaultServletModuleManager(final ServletContext servletContext, final PluginEventManager pluginEventManager) {
90          this(pluginEventManager, new DefaultPathMapper(), new DefaultPathMapper(), new FilterFactory());
91          ServletContextServletModuleManagerAccessor.setServletModuleManager(servletContext, this);
92      }
93  
94      /**
95       * @since 4.2
96       * @deprecated in 5.0 for removal in 6.0 when {@link ScopeManager} is removed
97       */
98      @Deprecated
99      public DefaultServletModuleManager(final ServletContext servletContext, final PluginEventManager pluginEventManager,
100                                        ScopeManager scopeManager) {
101         this(servletContext, pluginEventManager);
102     }
103 
104     /**
105      * Creates the servlet module manager, but assumes you will be calling {@link com.atlassian.plugin.servlet.util.ServletContextServletModuleManagerAccessor#setServletModuleManager(javax.servlet.ServletContext,
106      * ServletModuleManager)} yourself if you don't extend the dispatching servlet and filter classes to provide the servlet module
107      * manager instance.
108      *
109      * @param pluginEventManager The plugin event manager
110      */
111     public DefaultServletModuleManager(final PluginEventManager pluginEventManager) {
112         this(pluginEventManager, new DefaultPathMapper(), new DefaultPathMapper(), new FilterFactory());
113     }
114 
115     /**
116      * @since 4.2
117      * @deprecated in 5.0 for removal in 6.0 when {@link ScopeManager} is removed
118      */
119     @Deprecated
120     public DefaultServletModuleManager(final PluginEventManager pluginEventManager, ScopeManager scopeManager) {
121         this(pluginEventManager);
122     }
123 
124     /**
125      * Creates the servlet module manager, but assumes you will be calling {@link com.atlassian.plugin.servlet.util.ServletContextServletModuleManagerAccessor#setServletModuleManager(javax.servlet.ServletContext,
126      * ServletModuleManager)} yourself if you don't extend the dispatching servlet and filter classes to provide the servlet module
127      * manager instance.
128      *
129      * @param pluginEventManager The plugin event manager
130      * @param servletPathMapper  The path mapper used for mapping servlets to paths
131      * @param filterPathMapper   The path mapper used for mapping filters to paths
132      */
133     public DefaultServletModuleManager(final PluginEventManager pluginEventManager, final PathMapper servletPathMapper,
134                                        final PathMapper filterPathMapper) {
135         this(pluginEventManager, servletPathMapper, filterPathMapper, new FilterFactory());
136     }
137 
138     /**
139      * The broadest constructor should be the last one standing when: <ul> <li>dependency injection is fixed</li> <li>passing
140      * <code>this</code> before construction finishes is removed</li> </ul>
141      *
142      * @param pluginEventManager the event manager to register to
143      * @param servletMapper      maps servlet paths
144      * @param filterMapper       maps filter paths
145      * @param filterFactory      creates filters
146      * @since 4.2
147      */
148     public DefaultServletModuleManager(
149             final PluginEventManager pluginEventManager,
150             final PathMapper servletMapper,
151             final PathMapper filterMapper,
152             final FilterFactory filterFactory) {
153         this.servletMapper = servletMapper;
154         this.filterMapper = filterMapper;
155         this.filterFactory = filterFactory;
156         pluginEventManager.register(this);
157     }
158 
159     /**
160      * The broadest constructor should be the last one standing when: <ul> <li>dependency injection is fixed</li> <li>passing
161      * <code>this</code> before construction finishes is removed</li> </ul>
162      *
163      * @param pluginEventManager the event manager to register to
164      * @param servletMapper      maps servlet paths
165      * @param filterMapper       maps filter paths
166      * @param filterFactory      creates filters
167      * @param scopeManager       verifies scopes
168      * @since 4.2
169      * @deprecated in 5.0 for removal in 6.0 when {@link ScopeManager} is removed.
170      */
171     @Deprecated
172     public DefaultServletModuleManager(
173             final PluginEventManager pluginEventManager,
174             final PathMapper servletMapper,
175             final PathMapper filterMapper,
176             final FilterFactory filterFactory,
177             final ScopeManager scopeManager) {
178         this(pluginEventManager, servletMapper, filterMapper, filterFactory);
179     }
180 
181     /**
182      *
183      * @param servletContext servlet context to register with
184      * @param pluginEventManager the event manager to register to
185      * @param servletMapper      maps servlet paths
186      * @param filterMapper       maps filter paths
187      * @param filterFactory      creates filters
188      * @param scopeManager       verifies scopes
189      * @since 4.2
190      * @deprecated in 5.0 for removal in 6.0 when {@link ScopeManager} is removed
191      */
192     public DefaultServletModuleManager(
193             final ServletContext servletContext,
194             final PluginEventManager pluginEventManager,
195             final PathMapper servletMapper,
196             final PathMapper filterMapper,
197             final FilterFactory filterFactory,
198             final ScopeManager scopeManager) {
199         this(pluginEventManager, servletMapper, filterMapper, filterFactory, scopeManager);
200         ServletContextServletModuleManagerAccessor.setServletModuleManager(servletContext, this);
201     }
202 
203     public void addServletModule(final ServletModuleDescriptor descriptor) {
204         servletDescriptors.put(descriptor.getCompleteKey(), descriptor);
205 
206         // for some reason the JDK complains about getPaths not returning a
207         // List<String> ?!?!?
208         final List<String> paths = descriptor.getPaths();
209         for (final String path : paths) {
210             servletMapper.put(descriptor.getCompleteKey(), path);
211         }
212         final LazyReference<HttpServlet> servletRef = servletRefs.remove(descriptor.getCompleteKey());
213         if (servletRef != null) {
214             servletRef.get().destroy();
215         }
216     }
217 
218     public HttpServlet getServlet(final String path, final ServletConfig servletConfig) throws ServletException {
219         final String completeKey = servletMapper.get(path);
220         if (completeKey == null) {
221             return null;
222         }
223 
224         final ServletModuleDescriptor descriptor = servletDescriptors.get(completeKey);
225         if (descriptor == null) {
226             return null;
227         }
228 
229         final HttpServlet servlet = getServlet(descriptor, servletConfig);
230         if (servlet == null) {
231             servletRefs.remove(descriptor.getCompleteKey());
232         }
233         return servlet;
234     }
235 
236     public void removeServletModule(final ServletModuleDescriptor descriptor) {
237         servletDescriptors.remove(descriptor.getCompleteKey());
238         servletMapper.put(descriptor.getCompleteKey(), null);
239 
240         final LazyReference<HttpServlet> servletRef = servletRefs.remove(descriptor.getCompleteKey());
241         if (servletRef != null) {
242             servletRef.get().destroy();
243         }
244     }
245 
246     public void addFilterModule(final ServletFilterModuleDescriptor descriptor) {
247         filterDescriptors.put(descriptor.getCompleteKey(), descriptor);
248 
249         for (final String path : descriptor.getPaths()) {
250             filterMapper.put(descriptor.getCompleteKey(), path);
251         }
252         final LazyReference<Filter> filterRef = filterRefs.remove(descriptor.getCompleteKey());
253         if (filterRef != null) {
254             filterRef.get().destroy();
255         }
256     }
257 
258     public Iterable<Filter> getFilters(FilterLocation location, String path, FilterConfig filterConfig, FilterDispatcherCondition condition) {
259         return getFilters(location, path, filterConfig, condition.toDispatcherType());
260     }
261 
262     public Iterable<Filter> getFilters(FilterLocation location, String path, FilterConfig filterConfig, DispatcherType dispatcher) {
263         checkNotNull(dispatcher);
264         final List<ServletFilterModuleDescriptor> matchingFilterDescriptors = new ArrayList<>();
265 
266         for (final String completeKey : filterMapper.getAll(path)) {
267             final ServletFilterModuleDescriptor descriptor = filterDescriptors.get(completeKey);
268             if (!descriptor.getDispatcherTypes().contains(dispatcher)) {
269                 if (log.isTraceEnabled()) {
270                     log.trace("Skipping filter {} as dispatcher {} doesn't match list: {}",
271                             descriptor.getCompleteKey(), dispatcher, descriptor.getDispatcherTypes());
272                 }
273                 continue;
274             }
275 
276             if (location.equals(descriptor.getLocation())) {
277                 matchingFilterDescriptors.add(descriptor);
278             }
279         }
280 
281         final List<ServletFilterModuleDescriptor> scopedFilterDescriptors = matchingFilterDescriptors
282                 .stream()
283                 .sorted(byWeight)
284                 .collect(Collectors.toList());
285 
286         final List<Filter> filters = new LinkedList<>();
287         for (final ServletFilterModuleDescriptor descriptor : scopedFilterDescriptors) {
288             final Filter filter = getFilter(descriptor, filterConfig);
289             if (filter == null) {
290                 filterRefs.remove(descriptor.getCompleteKey());
291             } else {
292                 filters.add(filter);
293             }
294         }
295 
296         return filters;
297     }
298 
299 
300     public void removeFilterModule(final ServletFilterModuleDescriptor descriptor) {
301         filterDescriptors.remove(descriptor.getCompleteKey());
302         filterMapper.put(descriptor.getCompleteKey(), null);
303 
304         final LazyReference<Filter> filterRef = filterRefs.remove(descriptor.getCompleteKey());
305         if (filterRef != null) {
306             filterRef.get().destroy();
307         }
308     }
309 
310     @Override
311     public void addServlet(final Plugin plugin, final String servletName, final String className) {
312         // create a module descriptor descriptor with the required class to instantiate
313         final Element e = createServletModuleElement(servletName);
314         e.addAttribute("class", className);
315 
316         // create the module; servlet will be instantiated on first use
317         pluginControllerRef.get().addDynamicModule(plugin, e);
318     }
319 
320     @Override
321     public void addServlet(final Plugin plugin, final String servletName, final HttpServlet servlet, final ServletContext servletContext) {
322         // create a module descriptor descriptor with no class
323         final Element e = createServletModuleElement(servletName);
324 
325         // create the module
326         final ModuleDescriptor moduleDescriptor = pluginControllerRef.get().addDynamicModule(plugin, e);
327         if (!(moduleDescriptor instanceof ServletModuleDescriptor)) {
328             throw new PluginException("expected com.atlassian.plugin.PluginController#addDynamicModule(com.atlassian.plugin.Plugin, org.dom4j.Element)} to return an instance of com.atlassian.plugin.servlet.descriptors.ServletModuleDescriptor; a " + (moduleDescriptor == null ? null : moduleDescriptor.getClass()) + " was returned");
329         }
330 
331         // add a reference to the servlet given; will be initialised on first use
332         LazyLoadedServletReference servletRef = new LazyLoadedServletReference(servlet, (ServletModuleDescriptor) moduleDescriptor, servletContext);
333         if (servletRefs.putIfAbsent(moduleDescriptor.getCompleteKey(), servletRef) != null) {
334             pluginControllerRef.get().removeDynamicModule(plugin, moduleDescriptor);
335             throw new IllegalStateException("a servlet with atlassian-plugins module key '" + moduleDescriptor.getCompleteKey() + "' has already been registered");
336         }
337     }
338 
339     @PluginEventListener
340     public void onPluginFrameworkStartingEvent(final PluginFrameworkStartedEvent event) {
341         pluginControllerRef.set(event.getPluginController());
342     }
343 
344     @PluginEventListener
345     public void onPluginFrameworkShutdownEvent(final PluginFrameworkShutdownEvent event) {
346         if (pluginControllerRef.getAndSet(null) != event.getPluginController()) {
347             log.warn("PluginController passed via the PluginFrameworkShutdownEvent did not match that passed via PluginFrameworkStartedEvent");
348         }
349     }
350 
351     /**
352      * Call the plugins servlet context listeners contextDestroyed methods and cleanup any servlet contexts that are associated with
353      * the plugin that was disabled.
354      */
355     @PluginEventListener
356     public void onPluginDisabled(final PluginDisabledEvent event) {
357         final Plugin plugin = event.getPlugin();
358         final ContextLifecycleReference context = pluginContextRefs.remove(plugin);
359         if (context == null) {
360             return;
361         }
362 
363         context.get().contextDestroyed();
364     }
365 
366     @PluginEventListener
367     public void onPluginFrameworkBeforeShutdown(final PluginFrameworkShuttingDownEvent event) {
368         destroy();
369     }
370 
371     private void destroy() {
372         destroyModuleDescriptors(servletDescriptors);
373 
374         destroyModuleDescriptors(filterDescriptors);
375 
376         // Defensively copy the collection we are going to iterate over, because we call out to listeners, and they
377         // might call back (as happens with the ModuleDescriptor case).
378         for (final ContextLifecycleReference context : new ArrayList<>(pluginContextRefs.values())) {
379             if (context != null) {
380                 ContextLifecycleManager lifecycleManager = context.get();
381                 if (lifecycleManager != null) {
382                     lifecycleManager.contextDestroyed();
383                 }
384             }
385         }
386         pluginContextRefs.clear();
387     }
388 
389     private <T extends ModuleDescriptor> void destroyModuleDescriptors(Map<String, T> descriptors) {
390         // The ModuleDescriptor.destroy() can call back and modify the collection we wish to iterate over,
391         // so iterate over a copy. Since we need to be null safe (preserving historical behaviour), we use
392         // ArrayList rather than an ImmutableList.
393         for (final ModuleDescriptor moduleDescriptor : new ArrayList<>(descriptors.values())) {
394             if (moduleDescriptor != null) {
395                 moduleDescriptor.destroy();
396             }
397         }
398         descriptors.clear();
399     }
400 
401     /**
402      * Returns a wrapped Servlet for the servlet module. If a wrapped servlet for the module has not been created yet, we create one
403      * using the servletConfig.
404      * <p>
405      * Note: We use a map of lazily loaded references to the servlet so that only one can ever be created and initialized for each
406      * module descriptor.
407      */
408     HttpServlet getServlet(final ServletModuleDescriptor descriptor, final ServletConfig servletConfig) {
409         return getInstance(servletRefs, descriptor, new LazyLoadedServletReference(null, descriptor, servletConfig.getServletContext()));
410 
411     }
412 
413     /**
414      * Returns a wrapped Filter for the filter module. If a wrapped filter for the module has not been created yet, we create one
415      * using the filterConfig.
416      * <p>
417      * Note: We use a map of lazily loaded references to the filter so that only one can ever be created and initialized for each
418      * module descriptor.
419      *
420      * @return The filter, or null if the filter is invalid and should be removed
421      */
422     Filter getFilter(final ServletFilterModuleDescriptor descriptor, final FilterConfig filterConfig) {
423         return getInstance(filterRefs, descriptor, new LazyLoadedFilterReference(descriptor, filterConfig));
424     }
425 
426     private <T> T getInstance(ConcurrentMap<String, LazyReference<T>> refs, AbstractModuleDescriptor descriptor,
427                               LazyReference<T> newRef) {
428         try {
429             final LazyReference<T> oldRef = refs.putIfAbsent(descriptor.getCompleteKey(), newRef);
430             return oldRef != null ? oldRef.get() : newRef.get();
431         } catch (final RuntimeException ex) {
432             log.error("Unable to create new reference " + newRef, ex);
433             return null;
434         }
435 
436     }
437 
438     /**
439      * Returns a wrapped ServletContext for the plugin. If a wrapped servlet context for the plugin has not been created yet, we
440      * create using the baseContext, any context params specified in the plugin and initialize any context listeners the plugin may
441      * define.
442      * <p>
443      * Note: We use a map of lazily loaded references to the context so that only one can ever be created for each plugin.
444      *
445      * @param plugin      Plugin for whom we're creating a wrapped servlet context.
446      * @param baseContext The applications base servlet context which we will be wrapping.
447      * @return A wrapped, fully initialized servlet context that can be used for all the plugins filters and servlets.
448      */
449     private ServletContext getWrappedContext(final Plugin plugin, final ServletContext baseContext) {
450         ContextLifecycleReference pluginContextRef = pluginContextRefs.get(plugin);
451         if (pluginContextRef == null) {
452             pluginContextRef = new ContextLifecycleReference(this, plugin, baseContext);
453             if (pluginContextRefs.putIfAbsent(plugin, pluginContextRef) != null) {
454                 pluginContextRef = pluginContextRefs.get(plugin);
455             }
456         }
457         return pluginContextRef.get().servletContext;
458     }
459 
460     /**
461      * Create an element that may be used to create a servlet module descriptor.
462      * <p>
463      * The name passed is used for: <ul> <li>key, with "-servlet" appended</li> <li>name, with "Servlet" appended</li>
464      * <li>url-pattern, with "/" prepended</li> </ul>
465      *
466      * @param servletName mandatory
467      * @return valid Element
468      */
469     private Element createServletModuleElement(final String servletName) {
470         final Element e = new DOMElement("servlet");
471         e.addAttribute("key", servletName + "-servlet");
472         e.addAttribute("name", servletName + "Servlet");
473 
474         Element url = new DOMElement("url-pattern");
475         url.setText("/" + servletName);
476         e.add(url);
477 
478         return e;
479     }
480 
481     @VisibleForTesting
482     ImmutableMap<String, LazyReference<HttpServlet>> getServletRefs() {
483         return ImmutableMap.copyOf(servletRefs);
484     }
485 
486     private final class LazyLoadedFilterReference extends LazyReference<Filter> {
487         private final ServletFilterModuleDescriptor descriptor;
488         private final FilterConfig filterConfig;
489 
490         private LazyLoadedFilterReference(final ServletFilterModuleDescriptor descriptor, final FilterConfig filterConfig) {
491             this.descriptor = descriptor;
492             this.filterConfig = filterConfig;
493         }
494 
495         @Override
496         protected Filter create() throws Exception {
497             final Filter filter = filterFactory.newFilter(descriptor);
498             final ServletContext servletContext = getWrappedContext(descriptor.getPlugin(), filterConfig.getServletContext());
499             filter.init(new PluginFilterConfig(descriptor, servletContext));
500             return filter;
501         }
502 
503         @Override
504         public String toString() {
505             return MoreObjects.toStringHelper(this)
506                     .add("descriptor", descriptor)
507                     .add("filterConfig", filterConfig)
508                     .toString();
509         }
510     }
511 
512     @VisibleForTesting
513     final class LazyLoadedServletReference extends LazyReference<HttpServlet> {
514         private HttpServlet servlet;
515         private final ServletModuleDescriptor descriptor;
516         private final ServletContext servletContext;
517 
518         private LazyLoadedServletReference(final HttpServlet servlet, final ServletModuleDescriptor descriptor, final ServletContext servletContext) {
519             this.servlet = servlet;
520             this.descriptor = descriptor;
521             this.servletContext = servletContext;
522         }
523 
524         @Override
525         protected HttpServlet create() throws Exception {
526             // only create if a servlet wasn't supplied
527             if (servlet == null) {
528                 servlet = new DelegatingPluginServlet(descriptor);
529             }
530             final ServletContext wrappedContext = getWrappedContext(descriptor.getPlugin(), servletContext);
531             servlet.init(new PluginServletConfig(descriptor, wrappedContext));
532             return servlet;
533         }
534 
535         @Override
536         public String toString() {
537             return MoreObjects.toStringHelper(this)
538                     .add("descriptor", descriptor)
539                     .add("servletContext", servletContext)
540                     .toString();
541         }
542     }
543 
544     private static final class ContextLifecycleReference extends LazyReference<ContextLifecycleManager> {
545         private final ServletModuleManager servletModuleManager;
546         private final Plugin plugin;
547         private final ServletContext baseContext;
548 
549         private ContextLifecycleReference(final ServletModuleManager servletModuleManager, final Plugin plugin, final ServletContext baseContext) {
550             this.servletModuleManager = servletModuleManager;
551             this.plugin = plugin;
552             this.baseContext = baseContext;
553         }
554 
555         @Override
556         protected ContextLifecycleManager create() {
557             final ConcurrentMap<String, Object> contextAttributes = new ConcurrentHashMap<>();
558             final Map<String, String> initParams = mergeInitParams(baseContext, plugin);
559             final ServletContext context = new PluginServletContextWrapper(servletModuleManager, plugin, baseContext, contextAttributes, initParams);
560 
561             ClassLoaderStack.push(plugin.getClassLoader());
562             final List<ServletContextListener> listeners = new ArrayList<>();
563             try {
564                 for (final ServletContextListenerModuleDescriptor descriptor : findModuleDescriptorsByType(ServletContextListenerModuleDescriptor.class, plugin)) {
565                     listeners.add(descriptor.getModule());
566                 }
567             } finally {
568                 ClassLoaderStack.pop();
569             }
570 
571             return new ContextLifecycleManager(context, listeners);
572         }
573 
574         private Map<String, String> mergeInitParams(final ServletContext baseContext, final Plugin plugin) {
575             final Map<String, String> mergedInitParams = new HashMap<>();
576             @SuppressWarnings("unchecked")
577             final Enumeration<String> e = baseContext.getInitParameterNames();
578             while (e.hasMoreElements()) {
579                 final String paramName = e.nextElement();
580                 mergedInitParams.put(paramName, baseContext.getInitParameter(paramName));
581             }
582             for (final ServletContextParamModuleDescriptor descriptor : findModuleDescriptorsByType(ServletContextParamModuleDescriptor.class, plugin)) {
583                 mergedInitParams.put(descriptor.getParamName(), descriptor.getParamValue());
584             }
585             return Collections.unmodifiableMap(mergedInitParams);
586         }
587     }
588 
589     static <T extends ModuleDescriptor<?>> Iterable<T> findModuleDescriptorsByType(final Class<T> type, final Plugin plugin) {
590         final Set<T> descriptors = new HashSet<>();
591         for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors()) {
592             if (type.isAssignableFrom(descriptor.getClass())) {
593                 descriptors.add(type.cast(descriptor));
594             }
595         }
596         return descriptors;
597     }
598 
599     static final class ContextLifecycleManager {
600         private final ServletContext servletContext;
601         private final Iterable<ServletContextListener> listeners;
602 
603         ContextLifecycleManager(final ServletContext servletContext, final Iterable<ServletContextListener> listeners) {
604             this.servletContext = servletContext;
605             this.listeners = listeners;
606             for (final ServletContextListener listener : listeners) {
607                 listener.contextInitialized(new ServletContextEvent(servletContext));
608             }
609         }
610 
611         ServletContext getServletContext() {
612             return servletContext;
613         }
614 
615         void contextDestroyed() {
616             final ServletContextEvent event = new ServletContextEvent(servletContext);
617             for (final ServletContextListener listener : listeners) {
618                 listener.contextDestroyed(event);
619             }
620         }
621     }
622 }