View Javadoc
1   package com.atlassian.plugin.servlet.descriptors;
2   
3   import com.atlassian.plugin.Plugin;
4   import com.atlassian.plugin.PluginParseException;
5   import com.atlassian.plugin.StateAware;
6   import com.atlassian.plugin.module.ModuleFactory;
7   import com.atlassian.plugin.servlet.ServletModuleManager;
8   import com.atlassian.plugin.servlet.filter.FilterDispatcherCondition;
9   import com.atlassian.plugin.servlet.filter.FilterLocation;
10  import com.atlassian.plugin.util.validation.ValidationPattern;
11  import com.google.common.annotations.VisibleForTesting;
12  import io.atlassian.util.concurrent.ResettableLazyReference;
13  import org.dom4j.Element;
14  
15  import javax.servlet.DispatcherType;
16  import javax.servlet.Filter;
17  import java.util.Arrays;
18  import java.util.Comparator;
19  import java.util.EnumSet;
20  import java.util.List;
21  import java.util.Objects;
22  import java.util.Set;
23  
24  import static com.atlassian.plugin.util.validation.ValidationPattern.test;
25  import static com.google.common.base.Preconditions.checkNotNull;
26  import static java.util.stream.Collectors.toSet;
27  
28  /**
29   * A module descriptor that allows plugin developers to define servlet filters. Developers can define what urls the
30   * filter should be applied to by defining one or more <url-pattern> elements and they can decide where in the
31   * filter stack a plugin filter should go by defining the "location" and "weight" attributes.
32   * <p>
33   * The location attribute can have one of four values:
34   *
35   * <ul>
36   * <li>after-encoding - after the character encoding filter</li>
37   * <li>before-login - before the login filter</li>
38   * <li>before-decoration - before any global decoration like sitemesh</li>
39   * <li>before-dispatch - before any dispatching filters or servlets</li>
40   * </ul>
41   * The default for the location attribute is "before-dispatch".
42   * <p>
43   * The weight attribute can have any integer value. Filters with lower values of the weight attribute will come before
44   * those with higher values within the same location.
45   *
46   * @since 2.1.0
47   */
48  public class ServletFilterModuleDescriptor extends BaseServletModuleDescriptor<Filter> implements StateAware {
49      /**
50       * Set to make all filters apply to the async dispatcher chain.
51       * @since 4.6.0
52       */
53      @VisibleForTesting
54      static final String FORCE_ASYNC_DISPATCHER_SYSPROP = "atlassian.plugins.filter.force.async.dispatcher";
55      @VisibleForTesting
56      static final ResettableLazyReference<Boolean> FORCE_ASYNC = new ResettableLazyReference<Boolean>() {
57          @Override
58          protected Boolean create() {
59              return Boolean.getBoolean(FORCE_ASYNC_DISPATCHER_SYSPROP);
60          }
61      };
62  
63      static final String DEFAULT_LOCATION = FilterLocation.BEFORE_DISPATCH.name();
64      static final String DEFAULT_WEIGHT = "100";
65  
66      private FilterLocation location;
67  
68      private int weight;
69      private final ServletModuleManager servletModuleManager;
70  
71      private Set<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
72  
73      /**
74       * Creates a descriptor that uses a module class factory to create instances.
75       *
76       * @param moduleFactory        The module factory
77       * @param servletModuleManager The module manager
78       * @since 2.5.0
79       */
80      public ServletFilterModuleDescriptor(ModuleFactory moduleFactory, ServletModuleManager servletModuleManager) {
81          super(moduleFactory);
82          this.servletModuleManager = checkNotNull(servletModuleManager);
83      }
84  
85      public static final Comparator<ServletFilterModuleDescriptor> byWeight =
86              Comparator.comparingInt(ServletFilterModuleDescriptor::getWeight);
87  
88      @SuppressWarnings("unchecked")
89      public void init(Plugin plugin, Element element) throws PluginParseException {
90          super.init(plugin, element);
91          try {
92              location = FilterLocation.parse(element.attributeValue("location", DEFAULT_LOCATION));
93              weight = Integer.valueOf(element.attributeValue("weight", DEFAULT_WEIGHT));
94          } catch (IllegalArgumentException ex) {
95              throw new PluginParseException(ex);
96          }
97  
98          List<Element> dispatcherElements = element.elements("dispatcher");
99          if (!dispatcherElements.isEmpty()) {
100             dispatcherTypes.clear();
101             for (Element dispatcher : dispatcherElements) {
102                 // already been validated via the validation rules
103                 dispatcherTypes.add(DispatcherType.valueOf(dispatcher.getTextTrim()));
104             }
105         }
106         if (FORCE_ASYNC.get()) {
107             dispatcherTypes.add(DispatcherType.ASYNC);
108         }
109     }
110 
111     @Override
112     protected void provideValidationRules(ValidationPattern pattern) {
113         super.provideValidationRules(pattern);
114         StringBuilder conditionRule = new StringBuilder();
115         conditionRule.append("dispatcher[");
116         DispatcherType[] dispatcherTypes = DispatcherType.values();
117         for (int x = 0; x < dispatcherTypes.length; x++) {
118             conditionRule.append(". != '").append(dispatcherTypes[x]).append("'");
119             if (x + 1 < dispatcherTypes.length) {
120                 conditionRule.append(" and ");
121             }
122         }
123         conditionRule.append("]");
124         pattern.rule(conditionRule.toString(),
125                 test("dispatcher").withError("The dispatcher value must be one of the following only " + Arrays.asList(DispatcherType.values())),
126                 test("@class").withError("The class is required"));
127     }
128 
129     public void enabled() {
130         super.enabled();
131         servletModuleManager.addFilterModule(this);
132     }
133 
134     public void disabled() {
135         servletModuleManager.removeFilterModule(this);
136         super.disabled();
137     }
138 
139     @Override
140     public Filter getModule() {
141         return moduleFactory.createModule(moduleClassName, this);
142     }
143 
144     public FilterLocation getLocation() {
145         return location;
146     }
147 
148     public int getWeight() {
149         return weight;
150     }
151 
152     /**
153      * Returns a set of dispatcher conditions that have been set for this filter, these conditions
154      * will be one of the following: <code>REQUEST, FORWARD, INCLUDE or ERROR</code>.
155      *
156      * @return A set of dispatcher conditions that have been set for this filter.
157      * @since 2.5.0
158      * @deprecated since 4.6.0. Use {@link #getDispatcherTypes()} instead.
159      */
160     public Set<FilterDispatcherCondition> getDispatcherConditions() {
161         return dispatcherTypes.stream()
162                 .map(FilterDispatcherCondition::fromDispatcherType)
163                 .filter(Objects::nonNull)
164                 .collect(toSet());
165     }
166 
167     /**
168      * Returns a set of dispatcher types that have been set for this filter. These can be any of the values supported by
169      * {@link DispatcherType}.
170      *
171      * @return A set of dispatcher types that have been set for this filter.
172      * @since 4.6.0
173      */
174     public Set<DispatcherType> getDispatcherTypes() {
175         return dispatcherTypes;
176     }
177 }