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