View Javadoc

1   package com.atlassian.plugin.servlet;
2   
3   import static com.atlassian.plugin.servlet.descriptors.ServletFilterModuleDescriptor.byWeight;
4   
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.Comparator;
8   import java.util.Enumeration;
9   import java.util.HashMap;
10  import java.util.HashSet;
11  import java.util.LinkedList;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.concurrent.ConcurrentHashMap;
16  import java.util.concurrent.ConcurrentMap;
17  
18  import javax.servlet.Filter;
19  import javax.servlet.FilterConfig;
20  import javax.servlet.ServletConfig;
21  import javax.servlet.ServletContext;
22  import javax.servlet.ServletContextEvent;
23  import javax.servlet.ServletException;
24  import javax.servlet.http.HttpServlet;
25  
26  import com.atlassian.plugin.ModuleDescriptor;
27  import com.atlassian.plugin.Plugin;
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.servlet.descriptors.ServletContextListenerModuleDescriptor;
32  import com.atlassian.plugin.servlet.descriptors.ServletContextParamModuleDescriptor;
33  import com.atlassian.plugin.servlet.descriptors.ServletFilterModuleDescriptor;
34  import com.atlassian.plugin.servlet.descriptors.ServletModuleDescriptor;
35  import com.atlassian.plugin.servlet.filter.DelegatingPluginFilter;
36  import com.atlassian.plugin.servlet.filter.FilterLocation;
37  import com.atlassian.plugin.servlet.filter.PluginFilterConfig;
38  import com.atlassian.plugin.servlet.util.ClassLoaderStack;
39  import com.atlassian.plugin.servlet.util.DefaultPathMapper;
40  import com.atlassian.plugin.servlet.util.LazyLoadedReference;
41  import com.atlassian.plugin.servlet.util.PathMapper;
42  
43  /**
44   * A simple servletModuleManager to track and retrieve the loaded servlet plugin modules.
45   * 
46   * @since 2.1.0
47   */
48  public class DefaultServletModuleManager implements ServletModuleManager
49  {
50      private final PathMapper servletMapper = new DefaultPathMapper();
51      private final Map<String, ServletModuleDescriptor> servletDescriptors = new HashMap<String, ServletModuleDescriptor>();
52      private final ConcurrentMap<String, LazyLoadedReference<HttpServlet>> servletRefs = new ConcurrentHashMap<String, LazyLoadedReference<HttpServlet>>();
53  
54      private final PathMapper filterMapper = new DefaultPathMapper();
55      private final Map<String, ServletFilterModuleDescriptor> filterDescriptors = new HashMap<String, ServletFilterModuleDescriptor>();
56      private final ConcurrentMap<String, LazyLoadedReference<Filter>> filterRefs = new ConcurrentHashMap<String, LazyLoadedReference<Filter>>();
57      
58      private final ConcurrentMap<Plugin, LazyLoadedReference<ServletContext>> pluginContextRefs = new ConcurrentHashMap<Plugin, LazyLoadedReference<ServletContext>>();
59      
60      public DefaultServletModuleManager(PluginEventManager pluginEventManager)
61      {
62          pluginEventManager.register(this);
63      }
64  
65      public void addServletModule(ServletModuleDescriptor descriptor)
66      {
67          servletDescriptors.put(descriptor.getCompleteKey(), descriptor);
68  
69          @SuppressWarnings("unchecked") // for some reason the JDK complains about getPaths not returning a List<String> ?!?!?
70          final List<String> paths = descriptor.getPaths();
71          for (String path : paths)
72          {
73              servletMapper.put(descriptor.getCompleteKey(), path);
74          }
75          LazyLoadedReference<HttpServlet> servletRef = servletRefs.remove(descriptor.getCompleteKey());
76          if (servletRef != null)
77          {
78              servletRef.get().destroy();
79          }
80      }
81  
82      public HttpServlet getServlet(String path, final ServletConfig servletConfig) throws ServletException
83      {
84          String completeKey = servletMapper.get(path);
85  
86          if (completeKey == null)
87          {
88              return null;
89          }
90          ServletModuleDescriptor descriptor = servletDescriptors.get(completeKey);
91          if (descriptor == null)
92          {
93              return null;
94          }
95  
96          return getServlet(descriptor, servletConfig);
97      }
98  
99      public void removeServletModule(ServletModuleDescriptor descriptor)
100     {
101         servletDescriptors.remove(descriptor.getCompleteKey());
102         servletMapper.put(descriptor.getCompleteKey(), null);
103 
104         LazyLoadedReference<HttpServlet> servletRef = servletRefs.remove(descriptor.getCompleteKey());
105         if (servletRef != null)
106         {
107             servletRef.get().destroy();
108         }
109     }
110 
111     public void addFilterModule(ServletFilterModuleDescriptor descriptor)
112     {
113         filterDescriptors.put(descriptor.getCompleteKey(), descriptor);
114 
115         for (String path : descriptor.getPaths())
116         {
117             filterMapper.put(descriptor.getCompleteKey(), path);
118         }
119         LazyLoadedReference<Filter> filterRef = filterRefs.remove(descriptor.getCompleteKey());
120         if (filterRef != null)
121         {
122             filterRef.get().destroy();
123         }
124     }
125 
126     public Iterable<Filter> getFilters(FilterLocation location, String path, final FilterConfig filterConfig) throws ServletException
127     {
128         List<ServletFilterModuleDescriptor> matchingFilterDescriptors = new ArrayList<ServletFilterModuleDescriptor>();
129         for (String completeKey : filterMapper.getAll(path))
130         {
131             final ServletFilterModuleDescriptor descriptor = filterDescriptors.get(completeKey);
132             if (location.equals(descriptor.getLocation()))
133             {
134                 sortedInsert(matchingFilterDescriptors, descriptor, byWeight);
135             }
136         }
137         List<Filter> filters = new LinkedList<Filter>();
138         for (final ServletFilterModuleDescriptor descriptor : matchingFilterDescriptors)
139         {
140             filters.add(getFilter(descriptor, filterConfig));
141         }
142         return filters;
143     }
144 
145     static <T> void sortedInsert(List<T> list, final T e, Comparator<T> comparator)
146     {
147         int insertIndex = Collections.binarySearch(list, e, comparator);
148         if (insertIndex < 0)
149         {
150             // no entry already there, so the insertIndex is the negative value of where it should be inserted 
151             insertIndex = -insertIndex - 1;
152         }
153         else
154         {
155             // there is already a value at that position, so we need to find the next available spot for it
156             while (insertIndex < list.size() && comparator.compare(list.get(insertIndex), e) == 0)
157             {
158                 insertIndex++;
159             }
160         }
161         list.add(insertIndex, e);
162     }
163 
164     public void removeFilterModule(ServletFilterModuleDescriptor descriptor)
165     {
166         filterDescriptors.remove(descriptor.getCompleteKey());
167         filterMapper.put(descriptor.getCompleteKey(), null);
168 
169         LazyLoadedReference<Filter> filterRef = filterRefs.remove(descriptor.getCompleteKey());
170         if (filterRef != null)
171         {
172             filterRef.get().destroy();
173         }
174     }
175     
176     /**
177      * Call the plugins servlet context listeners contextDestroyed methods and cleanup any servlet contexts that are 
178      * associated with the plugin that was disabled.
179      */
180     @PluginEventListener
181     public void onPluginDisabled(PluginDisabledEvent event)
182     {
183         Plugin plugin = event.getPlugin();
184         LazyLoadedReference<ServletContext> context = pluginContextRefs.remove(plugin);
185         if (context == null)
186         {
187             return;
188         }
189         
190         for (ServletContextListenerModuleDescriptor descriptor : findModuleDescriptorsByType(ServletContextListenerModuleDescriptor.class, plugin))
191         {
192             descriptor.getModule().contextDestroyed(new ServletContextEvent(context.get()));
193         }
194     }
195 
196     /**
197      * Returns a wrapped Servlet for the servlet module.  If a wrapped servlet for the module has not been 
198      * created yet, we create one using the servletConfig.
199      * <p/>
200      * Note: We use a map of lazily loaded references to the servlet so that only one can ever be created and 
201      * initialized for each module descriptor.
202      * 
203      * @param descriptor
204      * @param servletConfig
205      * @return
206      */
207     private HttpServlet getServlet(final ServletModuleDescriptor descriptor, final ServletConfig servletConfig)
208     {
209         // check for an existing reference, if there is one it's either in the process of loading, in which case
210         // servletRef.get() below will block until it's available, otherwise we go about creating a new ref to use
211         LazyLoadedReference<HttpServlet> servletRef = servletRefs.get(descriptor.getCompleteKey());
212         if (servletRef == null)
213         {
214             // if there isn't an existing reference, create one.
215             ServletContext servletContext = getWrappedContext(descriptor.getPlugin(), servletConfig.getServletContext());
216             servletRef = new LazyLoadedServletReference(descriptor, servletContext);
217             
218             // check that another thread didn't beat us to the punch of creating a lazy reference.  if it did, we
219             // want to use that so there is only ever one reference 
220             if (servletRefs.putIfAbsent(descriptor.getCompleteKey(), servletRef) != null)
221             {
222                 servletRef = servletRefs.get(descriptor.getCompleteKey());
223             }
224         }
225         HttpServlet servlet = servletRef.get();
226         return servlet;
227     }
228     
229     /**
230      * Returns a wrapped Filter for the filter module.  If a wrapped filter for the module has not been 
231      * created yet, we create one using the filterConfig.
232      * <p/>
233      * Note: We use a map of lazily loaded references to the filter so that only one can ever be created and 
234      * initialized for each module descriptor.
235      * 
236      * @param descriptor
237      * @param filterConfig
238      * @return
239      */
240     private Filter getFilter(final ServletFilterModuleDescriptor descriptor, final FilterConfig filterConfig)
241     {
242         // check for an existing reference, if there is one it's either in the process of loading, in which case
243         // filterRef.get() below will block until it's available, otherwise we go about creating a new ref to use
244         LazyLoadedReference<Filter> filterRef = filterRefs.get(descriptor.getCompleteKey());
245         if (filterRef == null)
246         {
247             // if there isn't an existing reference, create one.
248             ServletContext servletContext = getWrappedContext(descriptor.getPlugin(), filterConfig.getServletContext());
249             filterRef = new LazyLoadedFilterReference(descriptor, servletContext);
250             
251             // check that another thread didn't beat us to the punch of creating a lazy reference.  if it did, we
252             // want to use that so there is only ever one reference 
253             if (filterRefs.putIfAbsent(descriptor.getCompleteKey(), filterRef) != null)
254             {
255                 filterRef = filterRefs.get(descriptor.getCompleteKey());
256             }
257         }
258         return filterRef.get();
259     }
260 
261     /**
262      * Returns a wrapped ServletContext for the plugin.  If a wrapped servlet context for the plugin has not been 
263      * created yet, we create using the baseContext, any context params specified in the plugin and initialize any
264      * context listeners the plugin may define.
265      * <p/>
266      * Note: We use a map of lazily loaded references to the context so that only one can ever be created for each
267      * plugin. 
268      *  
269      * @param plugin Plugin for whom we're creating a wrapped servlet context.
270      * @param baseContext The applications base servlet context which we will be wrapping.
271      * @return A wrapped, fully initialized servlet context that can be used for all the plugins filters and servlets.
272      */
273     private ServletContext getWrappedContext(Plugin plugin, ServletContext baseContext)
274     {
275         LazyLoadedReference<ServletContext> pluginContextRef = pluginContextRefs.get(plugin);
276         if (pluginContextRef == null)
277         {
278             pluginContextRef = new LazyLoadedContextReference(plugin, baseContext);
279             if (pluginContextRefs.putIfAbsent(plugin, pluginContextRef) != null)
280             {
281                 pluginContextRef = pluginContextRefs.get(plugin);
282             }
283         }
284         return pluginContextRef.get();
285     }
286 
287     private static final class LazyLoadedFilterReference extends LazyLoadedReference<Filter>
288     {
289         private final ServletFilterModuleDescriptor descriptor;
290         private final ServletContext servletContext;
291 
292         private LazyLoadedFilterReference(ServletFilterModuleDescriptor descriptor, ServletContext servletContext)
293         {
294             this.descriptor = descriptor;
295             this.servletContext = servletContext;
296         }
297 
298         @Override
299         protected Filter create() throws Exception
300         {
301             Filter filter = new DelegatingPluginFilter(descriptor);
302             filter.init(new PluginFilterConfig(descriptor, servletContext));
303             return filter;
304         }
305     }
306 
307     private static final class LazyLoadedServletReference extends LazyLoadedReference<HttpServlet>
308     {
309         private final ServletModuleDescriptor descriptor;
310         private final ServletContext servletContext;
311 
312         private LazyLoadedServletReference(ServletModuleDescriptor descriptor, ServletContext servletContext)
313         {
314             this.descriptor = descriptor;
315             this.servletContext = servletContext;
316         }
317 
318         @Override
319         protected HttpServlet create() throws Exception
320         {
321             HttpServlet servlet = new DelegatingPluginServlet(descriptor);
322             servlet.init(new PluginServletConfig(descriptor, servletContext));
323             return servlet;
324         }
325     }
326     
327     private static final class LazyLoadedContextReference extends LazyLoadedReference<ServletContext>
328     {
329         private final Plugin plugin;
330         private final ServletContext baseContext;
331         
332         private LazyLoadedContextReference(Plugin plugin, ServletContext baseContext)
333         {
334             this.plugin = plugin;
335             this.baseContext = baseContext;
336         }
337         
338         @Override
339         protected ServletContext create() throws Exception
340         {
341             ConcurrentMap<String, Object> contextAttributes = new ConcurrentHashMap<String, Object>();
342             Map<String, String> initParams = mergeInitParams(baseContext, plugin);
343             ServletContext context = new PluginServletContextWrapper(plugin, baseContext, contextAttributes, initParams);
344 
345             ClassLoaderStack.push(plugin.getClassLoader());
346             try
347             {
348                 for (ServletContextListenerModuleDescriptor descriptor : findModuleDescriptorsByType(ServletContextListenerModuleDescriptor.class, plugin))
349                 {
350                     descriptor.getModule().contextInitialized(new ServletContextEvent(context));
351                 }
352             } 
353             finally
354             {
355                 ClassLoaderStack.pop();
356             }
357             
358             return context;
359         }
360         
361         private Map<String, String> mergeInitParams(ServletContext baseContext, Plugin plugin)
362         {
363             Map<String, String> mergedInitParams = new HashMap<String, String>();
364             for (Enumeration<String> e = baseContext.getInitParameterNames(); e.hasMoreElements(); )
365             {
366                 String paramName = e.nextElement();
367                 mergedInitParams.put(paramName, baseContext.getInitParameter(paramName));
368             }
369             for (ServletContextParamModuleDescriptor descriptor : findModuleDescriptorsByType(ServletContextParamModuleDescriptor.class, plugin))
370             {
371                 mergedInitParams.put(descriptor.getParamName(), descriptor.getParamValue());
372             }
373             return Collections.unmodifiableMap(mergedInitParams);
374         }
375     }
376     
377     static <T extends ModuleDescriptor<?>> Iterable<T> findModuleDescriptorsByType(Class<T> type, Plugin plugin)
378     {
379         Set<T> descriptors = new HashSet<T>();
380         for (ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
381         {
382             if (type.isAssignableFrom(descriptor.getClass()))
383             {
384                 descriptors.add((T) descriptor);
385             }
386         }
387         return descriptors;
388     }
389 }