View Javadoc

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