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      PathMapper servletMapper = new DefaultPathMapper();
51      Map<String, ServletModuleDescriptor> servletDescriptors = new HashMap<String, ServletModuleDescriptor>();
52      ConcurrentMap<String, LazyLoadedReference<HttpServlet>> servletRefs = new ConcurrentHashMap<String, LazyLoadedReference<HttpServlet>>();
53  
54      PathMapper filterMapper = new DefaultPathMapper();
55      Map<String, ServletFilterModuleDescriptor> filterDescriptors = new HashMap<String, ServletFilterModuleDescriptor>();
56      ConcurrentMap<String, LazyLoadedReference<Filter>> filterRefs = new ConcurrentHashMap<String, LazyLoadedReference<Filter>>();
57      
58      ConcurrentMap<Plugin, LazyLoadedReference<ServletContext>> pluginContextRefs = new ConcurrentHashMap<Plugin, LazyLoadedReference<ServletContext>>();
59      
60      public DefaultServletModuleManager(PluginEventManager pluginEventManager)
61      {
62          pluginEventManager.register(this);
63      }
64  
65      /* (non-Javadoc)
66       * @see com.atlassian.plugin.servlet.ServletModuleManager#addModule(com.atlassian.plugin.servlet.ServletModuleDescriptor)
67       */
68      public void addServletModule(ServletModuleDescriptor descriptor)
69      {
70          servletDescriptors.put(descriptor.getCompleteKey(), descriptor);
71  
72          for (String path : descriptor.getPaths())
73          {
74              servletMapper.put(descriptor.getCompleteKey(), path);
75          }
76      }
77  
78      /* (non-Javadoc)
79       * @see com.atlassian.plugin.servlet.ServletModuleManager#getServlet(java.lang.String, javax.servlet.ServletConfig)
80       */
81      public HttpServlet getServlet(String path, final ServletConfig servletConfig) throws ServletException
82      {
83          String completeKey = servletMapper.get(path);
84  
85          if (completeKey == null)
86          {
87              return null;
88          }
89          ServletModuleDescriptor descriptor = servletDescriptors.get(completeKey);
90          if (descriptor == null)
91          {
92              return null;
93          }
94  
95          return getServlet(descriptor, servletConfig);
96      }
97  
98      /* (non-Javadoc)
99       * @see com.atlassian.plugin.servlet.ServletModuleManager#removeModule(com.atlassian.plugin.servlet.ServletModuleDescriptor)
100      */
101     public void removeServletModule(ServletModuleDescriptor descriptor)
102     {
103         servletDescriptors.remove(descriptor.getCompleteKey());
104         servletMapper.put(descriptor.getCompleteKey(), null);
105 
106         LazyLoadedReference<HttpServlet> servletRef = servletRefs.remove(descriptor.getCompleteKey());
107         if (servletRef != null)
108         {
109             servletRef.get().destroy();
110         }
111     }
112 
113     public void addFilterModule(ServletFilterModuleDescriptor descriptor)
114     {
115         filterDescriptors.put(descriptor.getCompleteKey(), descriptor);
116 
117         for (String path : descriptor.getPaths())
118         {
119             filterMapper.put(descriptor.getCompleteKey(), path);
120         }
121     }
122 
123     /* (non-Javadoc)
124      * @see com.atlassian.plugin.servlet.ServletModuleManager#getFilters(com.atlassian.plugin.servlet.FilterLocation, java.lang.String, javax.servlet.FilterConfig)
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         return servletRef.get();
226     }
227     
228     /**
229      * Returns a wrapped Filter for the filter module.  If a wrapped filter for the module has not been 
230      * created yet, we create one using the filterConfig.
231      * <p/>
232      * Note: We use a map of lazily loaded references to the filter so that only one can ever be created and 
233      * initialized for each module descriptor.
234      * 
235      * @param descriptor
236      * @param filterConfig
237      * @return
238      */
239     private Filter getFilter(final ServletFilterModuleDescriptor descriptor, final FilterConfig filterConfig)
240     {
241         // check for an existing reference, if there is one it's either in the process of loading, in which case
242         // filterRef.get() below will block until it's available, otherwise we go about creating a new ref to use
243         LazyLoadedReference<Filter> filterRef = filterRefs.get(descriptor.getCompleteKey());
244         if (filterRef == null)
245         {
246             // if there isn't an existing reference, create one.
247             ServletContext servletContext = getWrappedContext(descriptor.getPlugin(), filterConfig.getServletContext());
248             filterRef = new LazyLoadedFilterReference(descriptor, servletContext);
249             
250             // check that another thread didn't beat us to the punch of creating a lazy reference.  if it did, we
251             // want to use that so there is only ever one reference 
252             if (filterRefs.putIfAbsent(descriptor.getCompleteKey(), filterRef) != null)
253             {
254                 filterRef = filterRefs.get(descriptor.getCompleteKey());
255             }
256         }
257         return filterRef.get();
258     }
259 
260     /**
261      * Returns a wrapped ServletContext for the plugin.  If a wrapped servlet context for the plugin has not been 
262      * created yet, we create using the baseContext, any context params specified in the plugin and initialize any
263      * context listeners the plugin may define.
264      * <p/>
265      * Note: We use a map of lazily loaded references to the context so that only one can ever be created for each
266      * plugin. 
267      *  
268      * @param plugin Plugin for whom we're creating a wrapped servlet context.
269      * @param baseContext The applications base servlet context which we will be wrapping.
270      * @return A wrapped, fully initialized servlet context that can be used for all the plugins filters and servlets.
271      */
272     private ServletContext getWrappedContext(Plugin plugin, ServletContext baseContext)
273     {
274         LazyLoadedReference<ServletContext> pluginContextRef = pluginContextRefs.get(plugin);
275         if (pluginContextRef == null)
276         {
277             pluginContextRef = new LazyLoadedContextReference(plugin, baseContext);
278             if (pluginContextRefs.putIfAbsent(plugin, pluginContextRef) != null)
279             {
280                 pluginContextRef = pluginContextRefs.get(plugin);
281             }
282         }
283         return pluginContextRef.get();
284     }
285 
286     private static final class LazyLoadedFilterReference extends LazyLoadedReference<Filter>
287     {
288         private final ServletFilterModuleDescriptor descriptor;
289         private final ServletContext servletContext;
290 
291         private LazyLoadedFilterReference(ServletFilterModuleDescriptor descriptor, ServletContext servletContext)
292         {
293             this.descriptor = descriptor;
294             this.servletContext = servletContext;
295         }
296 
297         @Override
298         protected Filter create() throws Exception
299         {
300             Filter filter = new DelegatingPluginFilter(descriptor);
301             filter.init(new PluginFilterConfig(descriptor, servletContext));
302             return filter;
303         }
304     }
305 
306     private static final class LazyLoadedServletReference extends LazyLoadedReference<HttpServlet>
307     {
308         private final ServletModuleDescriptor descriptor;
309         private final ServletContext servletContext;
310 
311         private LazyLoadedServletReference(ServletModuleDescriptor descriptor, ServletContext servletContext)
312         {
313             this.descriptor = descriptor;
314             this.servletContext = servletContext;
315         }
316 
317         @Override
318         protected HttpServlet create() throws Exception
319         {
320             HttpServlet servlet = new DelegatingPluginServlet(descriptor);
321             servlet.init(new PluginServletConfig(descriptor, servletContext));
322             return servlet;
323         }
324     }
325     
326     private static final class LazyLoadedContextReference extends LazyLoadedReference<ServletContext>
327     {
328         private final Plugin plugin;
329         private final ServletContext baseContext;
330         
331         private LazyLoadedContextReference(Plugin plugin, ServletContext baseContext)
332         {
333             this.plugin = plugin;
334             this.baseContext = baseContext;
335         }
336         
337         @Override
338         protected ServletContext create() throws Exception
339         {
340             ConcurrentMap<String, Object> contextAttributes = new ConcurrentHashMap<String, Object>();
341             Map<String, String> initParams = mergeInitParams(baseContext, plugin);
342             ServletContext context = new PluginServletContextWrapper(plugin, baseContext, contextAttributes, initParams);
343 
344             ClassLoaderStack.push(plugin.getClassLoader());
345             try
346             {
347                 for (ServletContextListenerModuleDescriptor descriptor : findModuleDescriptorsByType(ServletContextListenerModuleDescriptor.class, plugin))
348                 {
349                     descriptor.getModule().contextInitialized(new ServletContextEvent(context));
350                 }
351             } 
352             finally
353             {
354                 ClassLoaderStack.pop();
355             }
356             
357             return context;
358         }
359         
360         private Map<String, String> mergeInitParams(ServletContext baseContext, Plugin plugin)
361         {
362             Map<String, String> mergedInitParams = new HashMap<String, String>();
363             for (Enumeration<String> e = baseContext.getInitParameterNames(); e.hasMoreElements(); )
364             {
365                 String paramName = e.nextElement();
366                 mergedInitParams.put(paramName, baseContext.getInitParameter(paramName));
367             }
368             for (ServletContextParamModuleDescriptor descriptor : findModuleDescriptorsByType(ServletContextParamModuleDescriptor.class, plugin))
369             {
370                 mergedInitParams.put(descriptor.getParamName(), descriptor.getParamValue());
371             }
372             return Collections.unmodifiableMap(mergedInitParams);
373         }
374     }
375     
376     static <T extends ModuleDescriptor<?>> Iterable<T> findModuleDescriptorsByType(Class<T> type, Plugin plugin)
377     {
378         Set<T> descriptors = new HashSet<T>();
379         for (ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
380         {
381             if (type.isAssignableFrom(descriptor.getClass()))
382             {
383                 descriptors.add((T) descriptor);
384             }
385         }
386         return descriptors;
387     }
388 }