/**
* 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;
}
}