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 preferable 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 * @throws IllegalStateException if initialize has not been called 67 */ 68 public static JohnsonConfig getConfig() 69 { 70 if (config == null) 71 { 72 throw new IllegalStateException("Johnson.getConfig() was called before initialisation"); 73 } 74 return config; 75 } 76 77 /** 78 * Attempts to retrieve the {@link JohnsonConfig} from the provided {@code ServletContext} under the key 79 * {@link #CONFIG_ATTRIBUTE} before falling back on {@link #getConfig()}. 80 * <p/> 81 * Note: If Johnson has not been {@link #initialize(ServletContext) initialised}, this method <i>will not</i> 82 * initialise it. Before attempting to use Johnson, it is left to the application developer to ensure it has 83 * been correctly initialised. 84 * 85 * @param context the servlet context 86 * @return the working configuration 87 */ 88 public static JohnsonConfig getConfig(ServletContext context) 89 { 90 Object attribute = context.getAttribute(CONFIG_ATTRIBUTE); 91 if (attribute != null) 92 { 93 return (JohnsonConfig) attribute; 94 } 95 return config; 96 } 97 98 /** 99 * Attempts to retrieve the {@link JohnsonEventContainer} from the provided {@code ServletContext} under the key 100 * {@link #EVENT_CONTAINER_ATTRIBUTE} before falling back on the statically-bound instance. 101 * <p/> 102 * Note: If Johnson has not been {@link #initialize(ServletContext) initialised}, this method <i>will not</i> 103 * initialise it. Before attempting to use Johnson, it is left to the application developer to ensure it has 104 * been correctly initialised. 105 * 106 * @param context the servlet context 107 * @return the working event container 108 */ 109 public static JohnsonEventContainer getEventContainer(ServletContext context) 110 { 111 Object attribute = context.getAttribute(EVENT_CONTAINER_ATTRIBUTE); 112 if (attribute != null) 113 { 114 return (JohnsonEventContainer) attribute; 115 } 116 return eventContainer; 117 } 118 119 /** 120 * Initialises Johnson, additionally binding the {@link JohnsonConfig} and {@link JohnsonEventContainer} to the 121 * provided {@code ServletContext} and performing any {@link ApplicationEventCheck}s which have been configured. 122 * <p/> 123 * If the {@link #CONFIG_LOCATION_PARAM} {@code init-param} is set, it is used to determine the location of the 124 * Johnson configuration file. Otherwise, {@link XmlJohnsonConfig#DEFAULT_CONFIGURATION_FILE} is assumed. 125 * <p/> 126 * Note: This method is <i>not synchronised</i> and <i>not thread-safe</i>. It is left to the <i>calling code</i> 127 * to ensure proper synchronisation. The easiest way to do this is to initialise Johnson by adding the 128 * {@link com.atlassian.johnson.context.JohnsonContextListener} to {@code web.xml}. 129 * 130 * @param context the servlet context 131 */ 132 public static void initialize(ServletContext context) 133 { 134 String location = StringUtils.defaultIfEmpty(context.getInitParameter(CONFIG_LOCATION_PARAM), 135 XmlJohnsonConfig.DEFAULT_CONFIGURATION_FILE); 136 137 initialize(location); 138 139 context.setAttribute(CONFIG_ATTRIBUTE, config); 140 context.setAttribute(EVENT_CONTAINER_ATTRIBUTE, eventContainer); 141 142 List<ApplicationEventCheck> checks = config.getApplicationEventChecks(); 143 for (ApplicationEventCheck check : checks) 144 { 145 check.check(eventContainer, context); 146 } 147 } 148 149 /** 150 * Initialises Johnson, loading its configuration from the provided location, and sets the statically-bound 151 * instances of {@link JohnsonConfig} and {@link JohnsonEventContainer}. 152 * <p/> 153 * If the location provided is {@code null} or empty, {@link XmlJohnsonConfig#DEFAULT_CONFIGURATION_FILE} is 154 * assumed. 155 * <p/> 156 * Note: If the configuration fails to load, {@link DefaultJohnsonConfig} is used to provide defaults. For more 157 * information about what those defaults are, see the documentation for that class. 158 * 159 * @param location the location of the Johnson configuration file 160 */ 161 public static void initialize(String location) 162 { 163 location = StringUtils.defaultIfEmpty(location, XmlJohnsonConfig.DEFAULT_CONFIGURATION_FILE); 164 165 LOG.debug("Initialising Johnson with configuration from [{}]", location); 166 try 167 { 168 config = XmlJohnsonConfig.fromFile(location); 169 } 170 catch (ConfigurationJohnsonException e) 171 { 172 LOG.warn("Failed to load configuration from [" + location + "]", e); 173 config = DefaultJohnsonConfig.getInstance(); 174 } 175 176 ContainerFactory containerFactory = config.getContainerFactory(); 177 eventContainer = containerFactory.create(); 178 } 179 180 /** 181 * Terminates Johnson, clearing the statically-bound {@link JohnsonConfig} and {@link JohnsonEventContainer}. 182 */ 183 public static void terminate() 184 { 185 config = null; 186 eventContainer = null; 187 } 188 189 /** 190 * Terminates Johnson, removing Johnson-related attributes from the provided {@code ServletContext} and clearing 191 * the statically-bound {@link JohnsonConfig} and {@link JohnsonEventContainer}. 192 * 193 * @param context the servlet context 194 */ 195 public static void terminate(ServletContext context) 196 { 197 terminate(); 198 199 context.removeAttribute(CONFIG_ATTRIBUTE); 200 context.removeAttribute(EVENT_CONTAINER_ATTRIBUTE); 201 } 202 }