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 }