/** * 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.report; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.Cohort; import org.openmrs.Location; import org.openmrs.api.context.Context; /** * The EvaluationContext provides the following capabilities: - A baseCohort, i.e. the universe of * patients relevant to this context (defaults to all patients) - An in-memory cache which can be * used to persist and retrieve objects. Note that this cache is cleared whenever any changes are * made to baseCohort or any parameter values. - Capabilities to add, remove, and retrieve parameter * values - Capabilities to evaluate parametric expressions, e.g. ${someDateParameterName+30d} * * @deprecated see reportingcompatibility module */ @Deprecated public class EvaluationContext { protected Log log = LogFactory.getLog(getClass()); public static final String START_OF_EXPRESSION = "${"; public static final String END_OF_EXPRESSION = "}"; public static final Pattern DATE_OPERATION_PATTERN = Pattern .compile("(\\d{4}\\-\\d{2}\\-\\d{2}\\ \\d{2}:\\d{2}:\\d{2})(([+-])(\\d{1,})([dwmy]))?"); private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private Cohort baseCohort; private Map<Parameterizable, Map<Parameter, Object>> parameterValues = new HashMap<Parameterizable, Map<Parameter, Object>>(); private transient Map<String, Object> cache = new HashMap<String, Object>(); public EvaluationContext() { } public static boolean isExpression(String s) { return s != null && s.startsWith(START_OF_EXPRESSION) && s.endsWith(END_OF_EXPRESSION); } /** * Get the cache property * * @return Map<String, Object> */ public Map<String, Object> getCache() { return cache; } /** * Set the cache property * * @param cache */ public void setCache(Map<String, Object> cache) { this.cache = cache; } /** * Add a value to the cache with a given key */ public void addToCache(String key, Object value) { cache.put(key, value); } /** * Remove an entry cached with the given key * * @param key */ public void removeFromCache(String key) { cache.remove(key); } /** * Retrieve an entry from the cached with the given key * * @param key */ public Object getFromCache(String key) { return cache.get(key); } /** * Return true if a cache entry exists with the given key * * @param key */ public boolean isCached(String key) { return cache.get(key) != null; } /** * Clear the entire cache */ public void clearCache() { cache.clear(); } /** * Add a parameter to the context with the given value with global scope * * @param parameter * @param value */ public void addParameterValue(Parameter parameter, Object value) { addParameterValue(null, parameter, value); } /** * Add a parameter to the context with the given value in the scope of the passed * Parameterizable object * * @param obj <code>Parametrizable</code> object * @param parameter * @param value */ public void addParameterValue(Parameterizable obj, Parameter parameter, Object value) { clearCache(); Map<Parameter, Object> globalParams = parameterValues.get(obj); if (globalParams == null) { globalParams = new HashMap<Parameter, Object>(); parameterValues.put(obj, globalParams); } globalParams.put(parameter, value); } /** * Retrieve all parameter values * * @return Map */ public Map<Parameterizable, Map<Parameter, Object>> getParameterValues() { return parameterValues; } /** * Set all parameter values * * @param parameterValues */ public void setParameterValues(Map<Parameterizable, Map<Parameter, Object>> parameterValues) { clearCache(); this.parameterValues = parameterValues; } /** * Retrieve a Parameter by Name. If a parameterizable is passed in, it will check scope local to * the Parameterizable first, and if not found, check global scope It will return null if not * found in either scope * * @param obj <code>Parametrizable</code> object to get from * @param parameterName */ public Parameter getParameter(Parameterizable obj, String parameterName) { Map<Parameter, Object> params = parameterValues.get(obj); if (params != null) { for (Parameter parameter : params.keySet()) { if (parameterName != null && parameterName.equals(parameter.getName())) { return parameter; } if (obj != null) { return getParameter(null, parameterName); } } } return null; } /** * Retrieve Parameter from Global Scope only. It will return null if not found * * @param parameterName <code>String</code> name for parameter to get */ public Object getParameter(String parameterName) { return getParameter(null, parameterName); } /** * Retrieve parameter value by Parameter. If a parameterizable is passed in, it will check scope * local to the Parameterizable first, and if not found, check global scope It will return null * if not found in either scope * * @param obj <code>Parametrizable</code> object * @param parameter */ public Object getParameterValue(Parameterizable obj, Parameter parameter) { Map<Parameter, Object> params = parameterValues.get(obj); if (params != null) { Object localParam = params.get(parameter); if (localParam != null) { return localParam; } if (obj != null) { return getParameterValue(null, parameter); } } return null; } /** * Retrieve parameter value in Global Scope only. It will return null if not found * * @param parameter */ public Object getParameterValue(Parameter parameter) { return getParameterValue(null, parameter); } /** * Retrieve parameter value by parameter name. If a parameterizable is passed in, it will check * scope local to the Parameterizable first, and if not found, check global scope It will return * null if not found in either scope * * @param obj <code>Parametrizable</code> object * @param parameterName key of the parameter to look for * @return Object value of the parameter named by <code>parameterName</code> */ public Object getParameterValue(Parameterizable obj, String parameterName) { Parameter param = getParameter(obj, parameterName); if (param != null) { return getParameterValue(obj, param); } return null; } /** * Retrieve global parameter value by name * * @param parameterName key of the parameter to look for * @return Object value of the parameter named by <code>parameterName</code> */ public Object getParameterValue(String parameterName) { return getParameterValue(null, parameterName); } /** * This method will parse the passed expression and return a value based on the following * criteria:<br/> * <ul> * <li>Any string that matches a parameter within the EvaluationContext will be replaced by the * value of that parameter ** CURRENTLY REPLACEMENT PARAMETERS MUST EXIST IN THE GLOBAL SCOPE</li> * <li>If this date is followed by an expression, it will attempt to evaluate this by * incrementing/decrementing days/weeks/months/years as specified</li> * <li>Examples: Given 2 parameters: * <ul> * <li>report.startDate = java.util.Date with value of [2007-01-10] * <li>report.gender = "male" * </ul> * The following should result:<br/> * <br/> * * <pre> * evaluateExpression("${report.startDate}") -> "2007-01-10" as Date * evaluateExpression("${report.startDate+5d}") -> "2007-01-15" as Date * evaluateExpression("${report.startDate-1w}") -> "2007-01-03" as Date * evaluateExpression("${report.startDate+3m}") -> "2007-04-15" as Date * evaluateExpression("${report.startDate+1y}") -> "2008-01-10" as Date * * <pre> * </ul> * * @param expression * @return value for given expression, as an <code>Object</code> * @throws ParameterException */ public Object evaluateExpression(String expression) throws ParameterException { if (expression == null) { log.warn("evaluateExpression returning null."); return null; } log.debug("Starting expression: " + expression); boolean containsDate = false; while (expression.contains(START_OF_EXPRESSION) && expression.contains(END_OF_EXPRESSION)) { int startIndex = expression.indexOf(START_OF_EXPRESSION); int endIndex = expression.indexOf(END_OF_EXPRESSION); String toReplace = expression.substring(startIndex, endIndex + END_OF_EXPRESSION.length()); log.debug("Found expression to replace: " + toReplace); String replacement = expression.substring(startIndex + START_OF_EXPRESSION.length(), endIndex); log.debug("Stripped this down to: " + replacement); boolean found = false; // Iterate through each parameter and replace where appropriate in the expression string Map<Parameter, Object> globalParameters = parameterValues.get(null); if (globalParameters != null) { log.debug("Starting parameters: " + globalParameters); for (Parameter parameter : globalParameters.keySet()) { if (replacement.contains(parameter.getName())) { found = true; Object value = globalParameters.get(parameter); if (value == null) { // If parameter is required, but value is null, throw exception throw new ParameterException("Expression [" + replacement + "] requires parameter [" + parameter + "] which is null."); } log.debug("Starting evaluation of " + replacement + " with " + value); // Handle date parameters if (value instanceof Date) { containsDate = true; replacement = replacement.replace(parameter.getName(), df.format((Date) value)); log.debug("Modified to: " + replacement); // Attempt to evaluate any date arithmetic Matcher m = DATE_OPERATION_PATTERN.matcher(replacement); Calendar cal = Calendar.getInstance(); try { while (m.find()) { log.debug("Found date expression of: " + m.group()); String foundDate = m.group(1); if (m.group(2) != null) { int num = ("-".equals(m.group(3)) ? -1 : 1) * Integer.parseInt(m.group(4)); int field = Calendar.DATE; if ("w".equals(m.group(5))) { num *= 7; } else if ("m".equals(m.group(5))) { field = Calendar.MONTH; } else if ("y".equals(m.group(5))) { field = Calendar.YEAR; } cal.setTime(df.parse(foundDate)); cal.add(field, num); foundDate = df.format(cal.getTime()); log.debug("Calculated date of: " + foundDate); } replacement = replacement.replaceAll("\\Q" + m.group(0) + "\\E", foundDate); log.debug("Modified to: " + replacement); } } catch (Exception e) { log.debug(e.getMessage()); throw new ParameterException("Error parsing dates in expression: " + replacement); } } else if (value instanceof Location) { replacement = replacement.replace(parameter.getName(), ((Location) value).getLocationId() .toString()); } // Handle default parameters else { replacement = replacement.replace(parameter.getName(), value.toString()); } log.debug("Modified to: " + replacement); expression = expression.replace(toReplace, replacement); log.debug("Expression now: " + expression); } } } // By default, throw an exception if a parametric expression contains no parameters if (!found) { throw new ParameterException("Expression [" + expression + "] requires parameter [" + replacement + "] which is not found."); } } // If one of the parameters evaluated was a date, try casting this to a Date object if possible if (containsDate) { try { log.debug("Trying to parse back to a Date: " + expression); Date newDate = df.parse(expression); log.debug("Returning Date: " + newDate); return newDate; } catch (Exception e) { log.debug("Unable to parse into a Date."); } } log.debug("Returning String: " + expression); return expression; } public Cohort getBaseCohort() { if (baseCohort == null) { // Save this so we don't have to query the database next time. This doesn't clear the cache baseCohort = Context.getPatientSetService().getAllPatients(); } return baseCohort; } public void setBaseCohort(Cohort baseCohort) { clearCache(); this.baseCohort = baseCohort; } }