/** * The contents of this file are subject to the OpenMRS Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.web.filter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.log.CommonsLogLogChute; import org.openmrs.util.OpenmrsUtil; import org.openmrs.web.WebConstants; import org.openmrs.web.WebUtil; import org.openmrs.web.filter.initialization.InitializationFilter; import org.openmrs.web.filter.update.UpdateFilter; import org.springframework.web.util.JavaScriptUtils; /** * Abstract class used when a small wizard is needed before Spring, jsp, etc has been started up. * * @see UpdateFilter * @see InitializationFilter */ public abstract class StartupFilter implements Filter { protected final Log log = LogFactory.getLog(getClass()); protected static VelocityEngine velocityEngine = null; /** * Set by the {@link #init(FilterConfig)} method so that we have access to the current * {@link ServletContext} */ protected FilterConfig filterConfig = null; /** * Records errors that will be displayed to the user */ protected List<String> errors = new ArrayList<String>(); /** * The web.xml file sets this {@link StartupFilter} to be the first filter for all requests. * * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, * javax.servlet.ServletResponse, javax.servlet.FilterChain) */ public final void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (skipFilter((HttpServletRequest) request)) { chain.doFilter(request, response); } else { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; String servletPath = httpRequest.getServletPath(); // for all /images and /initfilter/scripts files, write the path // (the "/initfilter" part is needed so that the openmrs_static_context-servlet.xml file doesn't // get instantiated early, before the locale messages are all set up) if (servletPath.startsWith("/images") || servletPath.startsWith("/initfilter/scripts")) { servletPath = servletPath.replaceFirst("/initfilter", "/WEB-INF/view"); // strip out the /initfilter part // writes the actual image file path to the response File file = new File(filterConfig.getServletContext().getRealPath(servletPath)); if (httpRequest.getPathInfo() != null) file = new File(file, httpRequest.getPathInfo()); try { InputStream imageFileInputStream = new FileInputStream(file); OpenmrsUtil.copyFile(imageFileInputStream, httpResponse.getOutputStream()); imageFileInputStream.close(); } catch (FileNotFoundException e) { log.error("Unable to find file: " + file.getAbsolutePath()); } } else if (servletPath.startsWith("/scripts")) { 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" + servletPath + "' instead."); } // for anything but /initialsetup else if (!httpRequest.getServletPath().equals("/" + WebConstants.SETUP_PAGE_URL)) { // send the user to the setup page httpResponse.sendRedirect("/" + WebConstants.WEBAPP_NAME + "/" + WebConstants.SETUP_PAGE_URL); } else { if (httpRequest.getMethod().equals("GET")) { doGet(httpRequest, httpResponse); } else if (httpRequest.getMethod().equals("POST")) { // only clear errors before POSTS so that redirects can show errors too. errors.clear(); doPost(httpRequest, httpResponse); } } // Don't continue down the filter chain otherwise Spring complains // that it hasn't been set up yet. // The jsp and servlet filter are also on this chain, so writing to // the response directly here is the only option } } /** * Convenience method to set up the velocity context properly */ private void initializeVelocity() { if (velocityEngine == null) { velocityEngine = new VelocityEngine(); Properties props = new Properties(); props.setProperty(RuntimeConstants.RUNTIME_LOG, "startup_wizard_vel.log"); // Linux requires setting logging properties to initialize Velocity Context. props.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.CommonsLogLogChute"); props.setProperty(CommonsLogLogChute.LOGCHUTE_COMMONS_LOG_NAME, "initial_wizard_velocity"); // so the vm pages can import the header/footer props.setProperty(RuntimeConstants.RESOURCE_LOADER, "class"); props.setProperty("class.resource.loader.description", "Velocity Classpath Resource Loader"); props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); try { velocityEngine.init(props); } catch (Exception e) { log.error("velocity init failed, because: " + e); } } } /** * Called by {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} on GET requests * * @param httpRequest * @param httpResponse */ protected abstract void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException; /** * Called by {@link #doFilter(ServletRequest, ServletResponse, FilterChain)} on POST requests * * @param httpRequest * @param httpResponse * @throws Exception */ protected abstract void doPost(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException; /** * All private attributes on this class are returned to the template via the velocity context * and reflection * * @param templateName the name of the velocity file to render. This name is prepended with * {@link #getTemplatePrefix()} * @param referenceMap * @param writer */ protected void renderTemplate(String templateName, Map<String, Object> referenceMap, HttpServletResponse httpResponse) throws IOException { VelocityContext velocityContext = new VelocityContext(); if (referenceMap != null) { for (Map.Entry<String, Object> entry : referenceMap.entrySet()) { velocityContext.put(entry.getKey(), entry.getValue()); } } Object model = getModel(); // put each of the private varibles into the template for convenience for (Field field : model.getClass().getDeclaredFields()) { try { velocityContext.put(field.getName(), field.get(model)); } catch (IllegalArgumentException e) { log.error("Error generated while getting field value: " + field.getName(), e); } catch (IllegalAccessException e) { log.error("Error generated while getting field value: " + field.getName(), e); } } String fullTemplatePath = getTemplatePrefix() + templateName; InputStream templateInputStream = getClass().getClassLoader().getResourceAsStream(fullTemplatePath); if (templateInputStream == null) { throw new IOException("Unable to find " + fullTemplatePath); } velocityContext.put("errors", errors); // explicitly set the content type for the response because some servlet containers are assuming text/plain httpResponse.setContentType("text/html"); try { velocityEngine.evaluate(velocityContext, httpResponse.getWriter(), this.getClass().getName(), new InputStreamReader(templateInputStream)); } catch (Exception e) { throw new RuntimeException("Unable to process template: " + fullTemplatePath, e); } } /** * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; initializeVelocity(); } /** * @see javax.servlet.Filter#destroy() */ public void destroy() { } /** * This string is prepended to all templateNames passed to * {@link #renderTemplate(String, Map, HttpServletResponse)} * * @return string to prepend as the path for the templates */ protected String getTemplatePrefix() { return "org/openmrs/web/filter/"; } /** * The model that is used as the backer for all pages in this startup wizard. Should never * return null. * * @return the stored formbacking/model object */ protected abstract Object getModel(); /** * If this returns true, this filter fails early and quickly. All logic is skipped and startup * and usage continue normally. * * @return true if this filter can be skipped */ public abstract boolean skipFilter(HttpServletRequest request); /** * Convert a map of strings to objects to json * * @param map object to convert * @param escapeJavascript specifies if javascript special characters should be escaped * @param sb StringBuffer to append to */ private void toJSONString(Map<String, Object> map, StringBuffer sb, boolean escapeJavascript) { boolean first = true; sb.append('{'); for (Map.Entry<String, Object> entry : map.entrySet()) { if (first) first = false; else sb.append(','); sb.append('"'); if (entry.getKey() == null) sb.append("null"); else { if (escapeJavascript) sb.append(JavaScriptUtils.javaScriptEscape(entry.getKey())); else sb.append(WebUtil.escapeQuotesAndNewlines(entry.getKey())); } sb.append('"').append(':'); sb.append(toJSONString(entry.getValue(), escapeJavascript)); } sb.append('}'); } /** * Convert a list of objects to json * * @param list object to convert * @param escapeJavascript specifies if javascript special characters should be escaped * @param sb StringBuffer to append to */ private void toJSONString(List<Object> list, StringBuffer sb, boolean escapeJavascript) { boolean first = true; sb.append('['); for (Object listItem : list) { if (first) first = false; else sb.append(','); sb.append(toJSONString(listItem, escapeJavascript)); } sb.append(']'); } /** * Convert all other objects to json * * @param object object to convert * @param sb StringBuffer to append to * @param escapeJavascript specifies if javascript special characters should be escaped */ private void toJSONString(Object object, StringBuffer sb, boolean escapeJavascript) { if (object == null) sb.append("null"); else { if (escapeJavascript) sb.append('"').append(JavaScriptUtils.javaScriptEscape(object.toString())).append('"'); else sb.append('"').append(WebUtil.escapeQuotesAndNewlines(object.toString())).append('"'); } } /** * Convenience method to convert the given object to a JSON string. Supports Maps, Lists, * Strings, Boolean, Double * * @param object object to convert to json * @param escapeJavascript specifies if javascript special characters should be escaped * @return JSON string to be eval'd in javascript */ protected String toJSONString(Object object, boolean escapeJavascript) { StringBuffer sb = new StringBuffer(); if (object instanceof Map) toJSONString((Map<String, Object>) object, sb, escapeJavascript); else if (object instanceof List) toJSONString((List) object, sb, escapeJavascript); else if (object instanceof Boolean) sb.append(object.toString()); else toJSONString(object, sb, escapeJavascript); return sb.toString(); } }