/web/src/main/java/org/openmrs/web/filter/StartupFilter.java

https://gitlab.com/namratanehete/lh-toolkit · Java · 388 lines · 221 code · 38 blank · 129 comment · 32 complexity · d00f79a1200832ccabda3692e4283afc MD5 · raw file

  1. /**
  2. * This Source Code Form is subject to the terms of the Mozilla Public License,
  3. * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  4. * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
  5. * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
  6. *
  7. * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
  8. * graphic logo is a trademark of OpenMRS Inc.
  9. */
  10. package org.openmrs.web.filter;
  11. import java.io.File;
  12. import java.io.FileInputStream;
  13. import java.io.FileNotFoundException;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.io.InputStreamReader;
  17. import java.lang.annotation.Annotation;
  18. import java.lang.reflect.Field;
  19. import java.util.HashMap;
  20. import java.util.Locale;
  21. import java.util.Map;
  22. import java.util.Properties;
  23. import javax.servlet.Filter;
  24. import javax.servlet.FilterChain;
  25. import javax.servlet.FilterConfig;
  26. import javax.servlet.ServletContext;
  27. import javax.servlet.ServletException;
  28. import javax.servlet.ServletRequest;
  29. import javax.servlet.ServletResponse;
  30. import javax.servlet.http.HttpServletRequest;
  31. import javax.servlet.http.HttpServletResponse;
  32. import org.apache.commons.lang.ArrayUtils;
  33. import org.apache.commons.logging.Log;
  34. import org.apache.commons.logging.LogFactory;
  35. import org.apache.velocity.VelocityContext;
  36. import org.apache.velocity.app.VelocityEngine;
  37. import org.apache.velocity.runtime.RuntimeConstants;
  38. import org.apache.velocity.runtime.log.CommonsLogLogChute;
  39. import org.apache.velocity.tools.Scope;
  40. import org.apache.velocity.tools.ToolContext;
  41. import org.apache.velocity.tools.ToolManager;
  42. import org.apache.velocity.tools.config.DefaultKey;
  43. import org.apache.velocity.tools.config.FactoryConfiguration;
  44. import org.apache.velocity.tools.config.ToolConfiguration;
  45. import org.apache.velocity.tools.config.ToolboxConfiguration;
  46. import org.codehaus.jackson.map.ObjectMapper;
  47. import org.openmrs.OpenmrsCharacterEscapes;
  48. import org.openmrs.api.APIException;
  49. import org.openmrs.api.context.Context;
  50. import org.openmrs.util.LocaleUtility;
  51. import org.openmrs.util.OpenmrsUtil;
  52. import org.openmrs.web.WebConstants;
  53. import org.openmrs.web.filter.initialization.InitializationFilter;
  54. import org.openmrs.web.filter.update.UpdateFilter;
  55. import org.openmrs.web.filter.util.FilterUtil;
  56. import org.openmrs.web.filter.util.LocalizationTool;
  57. /**
  58. * Abstract class used when a small wizard is needed before Spring, jsp, etc has been started up.
  59. *
  60. * @see UpdateFilter
  61. * @see InitializationFilter
  62. */
  63. public abstract class StartupFilter implements Filter {
  64. protected final Log log = LogFactory.getLog(getClass());
  65. protected static VelocityEngine velocityEngine = null;
  66. public static final String AUTO_RUN_OPENMRS = "auto_run_openmrs";
  67. /**
  68. * Set by the {@link #init(FilterConfig)} method so that we have access to the current
  69. * {@link ServletContext}
  70. */
  71. protected FilterConfig filterConfig = null;
  72. /**
  73. * Records errors that will be displayed to the user
  74. */
  75. protected Map<String, Object[]> errors = new HashMap<String, Object[]>();
  76. /**
  77. * Messages that will be displayed to the user
  78. */
  79. protected Map<String, Object[]> msgs = new HashMap<String, Object[]>();
  80. /**
  81. * Used for configuring tools within velocity toolbox
  82. */
  83. private ToolContext toolContext = null;
  84. /**
  85. * The web.xml file sets this {@link StartupFilter} to be the first filter for all requests.
  86. *
  87. * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
  88. * javax.servlet.ServletResponse, javax.servlet.FilterChain)
  89. */
  90. public final void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
  91. ServletException {
  92. if (skipFilter((HttpServletRequest) request)) {
  93. chain.doFilter(request, response);
  94. } else {
  95. HttpServletRequest httpRequest = (HttpServletRequest) request;
  96. HttpServletResponse httpResponse = (HttpServletResponse) response;
  97. String servletPath = httpRequest.getServletPath();
  98. // for all /images and /initfilter/scripts files, write the path
  99. // (the "/initfilter" part is needed so that the openmrs_static_context-servlet.xml file doesn't
  100. // get instantiated early, before the locale messages are all set up)
  101. if (servletPath.startsWith("/images") || servletPath.startsWith("/initfilter/scripts")) {
  102. servletPath = servletPath.replaceFirst("/initfilter", "/WEB-INF/view"); // strip out the /initfilter part
  103. // writes the actual image file path to the response
  104. File file = new File(filterConfig.getServletContext().getRealPath(servletPath));
  105. if (httpRequest.getPathInfo() != null) {
  106. file = new File(file, httpRequest.getPathInfo());
  107. }
  108. InputStream imageFileInputStream = null;
  109. try {
  110. imageFileInputStream = new FileInputStream(file);
  111. OpenmrsUtil.copyFile(imageFileInputStream, httpResponse.getOutputStream());
  112. }
  113. catch (FileNotFoundException e) {
  114. log.error("Unable to find file: " + file.getAbsolutePath());
  115. }
  116. finally {
  117. if (imageFileInputStream != null) {
  118. try {
  119. imageFileInputStream.close();
  120. }
  121. catch (IOException io) {
  122. log.warn("Couldn't close imageFileInputStream: " + io);
  123. }
  124. }
  125. }
  126. } else if (servletPath.startsWith("/scripts")) {
  127. log.error("Calling /scripts during the initializationfilter pages will cause the openmrs_static_context-servlet.xml to initialize too early and cause errors after startup. Use '/initfilter"
  128. + servletPath + "' instead.");
  129. }
  130. // for anything but /initialsetup
  131. else if (!httpRequest.getServletPath().equals("/" + WebConstants.SETUP_PAGE_URL)
  132. && !httpRequest.getServletPath().equals("/" + AUTO_RUN_OPENMRS)) {
  133. // send the user to the setup page
  134. httpResponse.sendRedirect("/" + WebConstants.WEBAPP_NAME + "/" + WebConstants.SETUP_PAGE_URL);
  135. } else {
  136. if (httpRequest.getMethod().equals("GET")) {
  137. doGet(httpRequest, httpResponse);
  138. } else if (httpRequest.getMethod().equals("POST")) {
  139. // only clear errors before POSTS so that redirects can show errors too.
  140. errors.clear();
  141. msgs.clear();
  142. doPost(httpRequest, httpResponse);
  143. }
  144. }
  145. // Don't continue down the filter chain otherwise Spring complains
  146. // that it hasn't been set up yet.
  147. // The jsp and servlet filter are also on this chain, so writing to
  148. // the response directly here is the only option
  149. }
  150. }
  151. /**
  152. * Convenience method to set up the velocity context properly
  153. */
  154. private void initializeVelocity() {
  155. if (velocityEngine == null) {
  156. velocityEngine = new VelocityEngine();
  157. Properties props = new Properties();
  158. props.setProperty(RuntimeConstants.RUNTIME_LOG, "startup_wizard_vel.log");
  159. // Linux requires setting logging properties to initialize Velocity Context.
  160. props.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
  161. "org.apache.velocity.runtime.log.CommonsLogLogChute");
  162. props.setProperty(CommonsLogLogChute.LOGCHUTE_COMMONS_LOG_NAME, "initial_wizard_velocity");
  163. // so the vm pages can import the header/footer
  164. props.setProperty(RuntimeConstants.RESOURCE_LOADER, "class");
  165. props.setProperty("class.resource.loader.description", "Velocity Classpath Resource Loader");
  166. props.setProperty("class.resource.loader.class",
  167. "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
  168. try {
  169. velocityEngine.init(props);
  170. }
  171. catch (Exception e) {
  172. log.error("velocity init failed, because: " + e);
  173. }
  174. }
  175. }
  176. /**
  177. * Called by {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} on GET requests
  178. *
  179. * @param httpRequest
  180. * @param httpResponse
  181. */
  182. protected abstract void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException,
  183. ServletException;
  184. /**
  185. * Called by {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} on POST requests
  186. *
  187. * @param httpRequest
  188. * @param httpResponse
  189. */
  190. protected abstract void doPost(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException,
  191. ServletException;
  192. /**
  193. * All private attributes on this class are returned to the template via the velocity context
  194. * and reflection
  195. *
  196. * @param templateName the name of the velocity file to render. This name is prepended with
  197. * {@link #getTemplatePrefix()}
  198. * @param referenceMap
  199. * @param httpResponse
  200. */
  201. protected void renderTemplate(String templateName, Map<String, Object> referenceMap, HttpServletResponse httpResponse)
  202. throws IOException {
  203. // first we should get velocity tools context for current client request (within
  204. // his http session) and merge that tools context with basic velocity context
  205. if (referenceMap == null) {
  206. return;
  207. }
  208. Object locale = referenceMap.get(FilterUtil.LOCALE_ATTRIBUTE);
  209. ToolContext toolContext = getToolContext(locale != null ? locale.toString() : Context.getLocale().toString());
  210. VelocityContext velocityContext = new VelocityContext(toolContext);
  211. for (Map.Entry<String, Object> entry : referenceMap.entrySet()) {
  212. velocityContext.put(entry.getKey(), entry.getValue());
  213. }
  214. Object model = getModel();
  215. // put each of the private varibles into the template for convenience
  216. for (Field field : model.getClass().getDeclaredFields()) {
  217. try {
  218. field.setAccessible(true);
  219. velocityContext.put(field.getName(), field.get(model));
  220. }
  221. catch (IllegalArgumentException e) {
  222. log.error("Error generated while getting field value: " + field.getName(), e);
  223. }
  224. catch (IllegalAccessException e) {
  225. log.error("Error generated while getting field value: " + field.getName(), e);
  226. }
  227. }
  228. String fullTemplatePath = getTemplatePrefix() + templateName;
  229. InputStream templateInputStream = getClass().getClassLoader().getResourceAsStream(fullTemplatePath);
  230. if (templateInputStream == null) {
  231. throw new IOException("Unable to find " + fullTemplatePath);
  232. }
  233. velocityContext.put("errors", errors);
  234. velocityContext.put("msgs", msgs);
  235. // explicitly set the content type for the response because some servlet containers are assuming text/plain
  236. httpResponse.setContentType("text/html");
  237. try {
  238. velocityEngine.evaluate(velocityContext, httpResponse.getWriter(), this.getClass().getName(),
  239. new InputStreamReader(templateInputStream));
  240. }
  241. catch (Exception e) {
  242. throw new RuntimeException("Unable to process template: " + fullTemplatePath, e);
  243. }
  244. }
  245. /**
  246. * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
  247. */
  248. public void init(FilterConfig filterConfig) throws ServletException {
  249. this.filterConfig = filterConfig;
  250. initializeVelocity();
  251. }
  252. /**
  253. * @see javax.servlet.Filter#destroy()
  254. */
  255. public void destroy() {
  256. }
  257. /**
  258. * This string is prepended to all templateNames passed to
  259. * {@link #renderTemplate(String, Map, HttpServletResponse)}
  260. *
  261. * @return string to prepend as the path for the templates
  262. */
  263. protected String getTemplatePrefix() {
  264. return "org/openmrs/web/filter/";
  265. }
  266. /**
  267. * The model that is used as the backer for all pages in this startup wizard. Should never
  268. * return null.
  269. *
  270. * @return the stored formbacking/model object
  271. */
  272. protected abstract Object getModel();
  273. /**
  274. * If this returns true, this filter fails early and quickly. All logic is skipped and startup
  275. * and usage continue normally.
  276. *
  277. * @return true if this filter can be skipped
  278. */
  279. public abstract boolean skipFilter(HttpServletRequest request);
  280. /**
  281. * Convenience method to convert the given object to a JSON string. Supports Maps, Lists,
  282. * Strings, Boolean, Double
  283. *
  284. * @param object object to convert to json
  285. * @return JSON string to be eval'd in javascript
  286. */
  287. protected String toJSONString(Object object) {
  288. ObjectMapper mapper = new ObjectMapper();
  289. mapper.getJsonFactory().setCharacterEscapes(new OpenmrsCharacterEscapes());
  290. try {
  291. return mapper.writeValueAsString(object);
  292. }
  293. catch (IOException e) {
  294. log.error("Failed to convert object to JSON");
  295. throw new APIException(e);
  296. }
  297. }
  298. /**
  299. * Gets tool context for specified locale parameter. If context does not exists, it creates new
  300. * context, configured for that locale. Otherwise, it changes locale property of
  301. * {@link LocalizationTool} object, that is being contained in tools context
  302. *
  303. * @param locale the string with locale parameter for configuring tools context
  304. * @return the tool context object
  305. */
  306. public ToolContext getToolContext(String locale) {
  307. Locale systemLocale = LocaleUtility.fromSpecification(locale);
  308. //Defaults to en if systemLocale is null or invalid e.g en_GBs
  309. if (systemLocale == null || !ArrayUtils.contains(Locale.getAvailableLocales(), systemLocale)) {
  310. systemLocale = Locale.ENGLISH;
  311. }
  312. // If tool context has not been configured yet
  313. if (toolContext == null) {
  314. // first we are creating manager for tools, factory for configuring tools
  315. // and empty configuration object for velocity tool box
  316. ToolManager velocityToolManager = new ToolManager();
  317. FactoryConfiguration factoryConfig = new FactoryConfiguration();
  318. // since we are using one tool box for all request within wizard
  319. // we should propagate toolbox's scope on all application
  320. ToolboxConfiguration toolbox = new ToolboxConfiguration();
  321. toolbox.setScope(Scope.APPLICATION);
  322. // next we are directly configuring custom localization tool by
  323. // setting its class name, locale property etc.
  324. ToolConfiguration localizationTool = new ToolConfiguration();
  325. localizationTool.setClassname(LocalizationTool.class.getName());
  326. localizationTool.setProperty(ToolContext.LOCALE_KEY, systemLocale);
  327. localizationTool.setProperty(LocalizationTool.BUNDLES_KEY, "messages");
  328. // and finally we are adding just configured tool into toolbox
  329. // and creating tool context for this toolbox
  330. toolbox.addTool(localizationTool);
  331. factoryConfig.addToolbox(toolbox);
  332. velocityToolManager.configure(factoryConfig);
  333. toolContext = velocityToolManager.createContext();
  334. toolContext.setUserCanOverwriteTools(true);
  335. } else {
  336. // if it already has been configured, we just pull out our custom localization tool
  337. // from tool context, then changing its locale property and putting this tool back to the context
  338. // First, we need to obtain the value of default key annotation of our localization tool
  339. // class using reflection
  340. Annotation annotation = LocalizationTool.class.getAnnotation(DefaultKey.class);
  341. DefaultKey defaultKeyAnnotation = (DefaultKey) annotation;
  342. String key = defaultKeyAnnotation.value();
  343. //
  344. LocalizationTool localizationTool = (LocalizationTool) toolContext.get(key);
  345. localizationTool.setLocale(systemLocale);
  346. toolContext.put(key, localizationTool);
  347. }
  348. return toolContext;
  349. }
  350. }