View Javadoc

1   package com.atlassian.johnson;
2   
3   import com.atlassian.johnson.config.ConfigurationJohnsonException;
4   import com.atlassian.johnson.config.DefaultJohnsonConfig;
5   import com.atlassian.johnson.config.JohnsonConfig;
6   import com.atlassian.johnson.config.XmlJohnsonConfig;
7   import com.atlassian.johnson.event.ApplicationEventCheck;
8   import com.atlassian.johnson.setup.ContainerFactory;
9   import org.apache.commons.lang.StringUtils;
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  
13  import javax.annotation.Nonnull;
14  import javax.annotation.Nullable;
15  import javax.servlet.ServletContext;
16  import java.util.List;
17  
18  import static com.google.common.base.Preconditions.checkNotNull;
19  import static com.google.common.base.Preconditions.checkState;
20  
21  /**
22   * Singleton class for controlling Johnson.
23   * <p>
24   * Because Johnson is intended to be a maintenance and application consistency layer around an application, its
25   * functionality is exposed statically. This class provides both the mechanisms for initialising and terminating
26   * Johnson, as well as for retrieving its configuration and the container of events.
27   * <p>
28   * Where possible to avoid synchronisation issues, it is preferable to bind Johnson to the lifecycle for the servlet
29   * container, using {@link com.atlassian.johnson.context.JohnsonContextListener JohnsonContextListener} to initialise
30   * and terminate Johnson.
31   *
32   * @since 2.0
33   */
34  public class Johnson {
35  
36      /**
37       * When used in a web environment, the {@link JohnsonConfig} will be exposed in the {@code ServletContext} under
38       * an attribute with this key.
39       *
40       * @since 3.0
41       */
42      public static final String ATTR_CONFIG = Johnson.class.getName() + ":Config";
43  
44      /**
45       * When used in a web environment, the {@link JohnsonEventContainer} will be exposed in the {@code ServletContext}
46       * under an attribute with this key.
47       *
48       * @since 3.0
49       */
50      public static final String ATTR_EVENT_CONTAINER = Johnson.class.getName() + ":EventContainer";
51  
52      /**
53       * During {@link #initialize(javax.servlet.ServletContext) initialisation}, the {@code ServletContext} is examined
54       * for an {@code init-param} with this name. If one is found, it controls the location from which the configuration
55       * is loaded. Otherwise, {@link XmlJohnsonConfig#DEFAULT_CONFIGURATION_FILE} is used as the default.
56       *
57       * @since 3.0
58       */
59      public static final String PARAM_CONFIG_LOCATION = "johnsonConfigLocation";
60  
61      private static final Logger LOG = LoggerFactory.getLogger(Johnson.class);
62  
63      private static JohnsonConfig config;
64      private static JohnsonEventContainer eventContainer;
65  
66      private Johnson() {
67          throw new UnsupportedOperationException(getClass().getName() + " should not be instantiated");
68      }
69  
70      /**
71       * Retrieves the statically-bound {@link JohnsonConfig}.
72       * <p>
73       * Note: If Johnson has not been {@link #initialize(String) initialised}, this method <i>will not</i> initialise
74       * it. Before attempting to use Johnson, it is left to the application developer to ensure it has been correctly
75       * initialised.
76       *
77       * @return the working configuration
78       * @throws IllegalStateException if {@link #initialize} has not been called
79       */
80      @Nonnull
81      public static JohnsonConfig getConfig() {
82          checkState(config != null, "Johnson.getConfig() was called before initialisation");
83  
84          return config;
85      }
86  
87      /**
88       * Attempts to retrieve the {@link JohnsonConfig} from the provided {@code ServletContext} under the key
89       * {@link #ATTR_CONFIG} before falling back on {@link #getConfig()}.
90       * <p>
91       * Note: If Johnson has not been {@link #initialize(ServletContext) initialised}, this method <i>will not</i>
92       * initialise it. Before attempting to use Johnson, it is left to the application developer to ensure it has
93       * been correctly initialised.
94       *
95       * @param context the servlet context
96       * @return the working configuration
97       * @throws IllegalStateException if {@link #initialize} has not been called
98       */
99      @Nonnull
100     public static JohnsonConfig getConfig(@Nonnull ServletContext context) {
101         Object attribute = checkNotNull(context, "context").getAttribute(ATTR_CONFIG);
102         if (attribute != null) {
103             return (JohnsonConfig) attribute;
104         }
105         return getConfig();
106     }
107 
108     /**
109      * Retrieves the statically-bound {@link JohnsonEventContainer}.
110      * <p>
111      * Note: If Johnson has not been {@link #initialize(String) initialised}, this method <i>will not</i> initialise
112      * it. Before attempting to use Johnson, it is left to the application developer to ensure it has been correctly
113      * initialised.
114      *
115      * @return the working event container
116      * @throws IllegalStateException if {@link #initialize} has not been called
117      * @since 3.0
118      */
119     @Nonnull
120     public static JohnsonEventContainer getEventContainer() {
121         checkState(eventContainer != null, "Johnson.getEventContainer() was called before initialisation");
122 
123         return eventContainer;
124     }
125 
126     /**
127      * Attempts to retrieve the {@link JohnsonEventContainer} from the provided {@code ServletContext} under the key
128      * {@link #ATTR_EVENT_CONTAINER} before falling back on the statically-bound instance.
129      * <p>
130      * Note: If Johnson has not been {@link #initialize(ServletContext) initialised}, this method <i>will not</i>
131      * initialise it. Before attempting to use Johnson, it is left to the application developer to ensure it has
132      * been correctly initialised.
133      *
134      * @param context the servlet context
135      * @return the working event container
136      * @throws IllegalStateException if {@link #initialize} has not been called
137      */
138     @Nonnull
139     public static JohnsonEventContainer getEventContainer(@Nonnull ServletContext context) {
140         Object attribute = checkNotNull(context, "context").getAttribute(ATTR_EVENT_CONTAINER);
141         if (attribute != null) {
142             return (JohnsonEventContainer) attribute;
143         }
144         return getEventContainer();
145     }
146 
147     /**
148      * Initialises Johnson, additionally binding the {@link JohnsonConfig} and {@link JohnsonEventContainer} to the
149      * provided {@code ServletContext} and performing any {@link ApplicationEventCheck}s which have been configured.
150      * <p>
151      * If the {@link #PARAM_CONFIG_LOCATION} {@code init-param} is set, it is used to determine the location of the
152      * Johnson configuration file. Otherwise, {@link XmlJohnsonConfig#DEFAULT_CONFIGURATION_FILE} is assumed.
153      * <p>
154      * Note: This method is <i>not synchronised</i> and <i>not thread-safe</i>. It is left to the <i>calling code</i>
155      * to ensure proper synchronisation. The easiest way to do this is to initialise Johnson by adding the
156      * {@link com.atlassian.johnson.context.JohnsonContextListener} to {@code web.xml}.
157      *
158      * @param context the servlet context
159      */
160     public static void initialize(@Nonnull ServletContext context) {
161         String location = StringUtils.defaultIfEmpty(
162                 checkNotNull(context, "context").getInitParameter(PARAM_CONFIG_LOCATION),
163                 XmlJohnsonConfig.DEFAULT_CONFIGURATION_FILE);
164 
165         initialize(location);
166 
167         context.setAttribute(ATTR_CONFIG, config);
168         context.setAttribute(ATTR_EVENT_CONTAINER, eventContainer);
169 
170         List<ApplicationEventCheck> checks = config.getApplicationEventChecks();
171         for (ApplicationEventCheck check : checks) {
172             check.check(eventContainer, context);
173         }
174     }
175 
176     /**
177      * Initialises Johnson, loading its configuration from the provided location, and sets the statically-bound
178      * instances of {@link JohnsonConfig} and {@link JohnsonEventContainer}.
179      * <p>
180      * If the location provided is {@code null} or empty, {@link XmlJohnsonConfig#DEFAULT_CONFIGURATION_FILE} is
181      * assumed.
182      * <p>
183      * Note: If the configuration fails to load, {@link DefaultJohnsonConfig} is used to provide defaults. For more
184      * information about what those defaults are, see the documentation for that class.
185      *
186      * @param location the location of the Johnson configuration file
187      */
188     public static void initialize(@Nullable String location) {
189         location = StringUtils.defaultIfEmpty(location, XmlJohnsonConfig.DEFAULT_CONFIGURATION_FILE);
190 
191         LOG.debug("Initialising Johnson with configuration from [{}]", location);
192         try {
193             config = XmlJohnsonConfig.fromFile(location);
194         } catch (ConfigurationJohnsonException e) {
195             LOG.warn("Failed to load configuration from [" + location + "]", e);
196             config = DefaultJohnsonConfig.getInstance();
197         }
198 
199         ContainerFactory containerFactory = config.getContainerFactory();
200         eventContainer = containerFactory.create();
201     }
202 
203     /**
204      * Terminates Johnson, clearing the statically-bound {@link JohnsonConfig} and {@link JohnsonEventContainer}.
205      */
206     public static void terminate() {
207         config = null;
208         eventContainer = null;
209     }
210 
211     /**
212      * Terminates Johnson, removing Johnson-related attributes from the provided {@code ServletContext} and clearing
213      * the statically-bound {@link JohnsonConfig} and {@link JohnsonEventContainer}.
214      *
215      * @param context the servlet context
216      */
217     public static void terminate(@Nonnull ServletContext context) {
218         checkNotNull(context, "context");
219 
220         terminate();
221 
222         context.removeAttribute(ATTR_CONFIG);
223         context.removeAttribute(ATTR_EVENT_CONTAINER);
224     }
225 }