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
63
64
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
84
85
86
87
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
96
97
98 @Deprecated
99 public DefaultServletModuleManager(final ServletContext servletContext, final PluginEventManager pluginEventManager,
100 ScopeManager scopeManager) {
101 this(servletContext, pluginEventManager);
102 }
103
104
105
106
107
108
109
110
111 public DefaultServletModuleManager(final PluginEventManager pluginEventManager) {
112 this(pluginEventManager, new DefaultPathMapper(), new DefaultPathMapper(), new FilterFactory());
113 }
114
115
116
117
118
119 @Deprecated
120 public DefaultServletModuleManager(final PluginEventManager pluginEventManager, ScopeManager scopeManager) {
121 this(pluginEventManager);
122 }
123
124
125
126
127
128
129
130
131
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
140
141
142
143
144
145
146
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
161
162
163
164
165
166
167
168
169
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
184
185
186
187
188
189
190
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
207
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
313 final Element e = createServletModuleElement(servletName);
314 e.addAttribute("class", className);
315
316
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
323 final Element e = createServletModuleElement(servletName);
324
325
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
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
353
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
377
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
391
392
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
403
404
405
406
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
415
416
417
418
419
420
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
440
441
442
443
444
445
446
447
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
462
463
464
465
466
467
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
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 }