/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License, version 2 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*
* Copyright 2006 - 2008 Pentaho Corporation. All rights reserved.
*
* @created Mar 16, 2006
* @author James Dixon
*/
package org.pentaho.platform.engine.services.runtime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.commons.connection.IPentahoMetaData;
import org.pentaho.commons.connection.IPentahoResultSet;
import org.pentaho.platform.api.engine.IActionParameter;
import org.pentaho.platform.api.engine.IParameterManager;
import org.pentaho.platform.api.engine.IParameterProvider;
import org.pentaho.platform.api.engine.IParameterResolver;
import org.pentaho.platform.api.engine.IRuntimeContext;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.messages.Messages;
import org.pentaho.platform.util.DateMath;
public class TemplateUtil {
private final static String PARAMETER_PATTERN = "\\{([^\\}\\{$^]*)\\}"; //$NON-NLS-1$
private final static String DATE_EXPR_PATTERN = "([\\+\\-\\s]?(\\d)+:[YMWDhms][ES]?[\\s]?)+((\\s)?;.*)?"; //$NON-NLS-1$
// private final static String DATE_EXPR_PATTERN2 = "(DATEMATH\\(['\"])?([\\+\\-]?\\d:[YMWDhms][MS]?[\\s]?)+((\\s)?;.*)?.*"; //$NON-NLS-1$
private final static String DATEMATH_EXPR_PATTERN = "DATEMATH\\((\\s*)['\"].*['\"](\\s)*\\)"; //$NON-NLS-1$
private final static String DATEMATH_VAR_PATTERN = "DATEMATH:.*"; //$NON-NLS-1$
private final static String DATE_PATTERN = "\\d\\d\\d\\d-\\d\\d-\\d\\d"; //$NON-NLS-1$
private static final Pattern parameterExpressionPattern = Pattern.compile(TemplateUtil.PARAMETER_PATTERN);
private static final Pattern dateExpressionPattern = Pattern.compile(TemplateUtil.DATE_EXPR_PATTERN);
private static final Pattern dateMathExpressionPattern = Pattern.compile(TemplateUtil.DATEMATH_EXPR_PATTERN);
private static final Pattern dateMathVarPattern = Pattern.compile(TemplateUtil.DATEMATH_VAR_PATTERN);
private static final Pattern datePattern = Pattern.compile(TemplateUtil.DATE_PATTERN);
private static final List<String> SystemInputs = new ArrayList<String>();
private static final Log logger = LogFactory.getLog(TemplateUtil.class);
static {
TemplateUtil.SystemInputs.add("$user");//$NON-NLS-1$
TemplateUtil.SystemInputs.add("$url");//$NON-NLS-1$
TemplateUtil.SystemInputs.add("$solution"); //$NON-NLS-1$
}
public static String getSystemInput(final String inputName, final IRuntimeContext context) {
int i = TemplateUtil.SystemInputs.indexOf(inputName);
switch (i) {
case 0: { // User
return context.getSession().getName();
}
case 1: { // BaseURL
return PentahoSystem.getApplicationContext().getBaseUrl();
}
case 2: { // Solution
return PentahoSystem.getApplicationContext().getSolutionPath(""); //$NON-NLS-1$
}
}
return null;
}
public static String applyTemplate(final String template, final IRuntimeContext context,
final IParameterResolver resolver) {
return TemplateUtil.applyTemplate(template, new InputProperties(context), resolver);
}
public static String applyTemplate(final String template, final IRuntimeContext context) {
return TemplateUtil.applyTemplate(template, new InputProperties(context), null);
}
public static String applyTemplate(final String template, final IRuntimeContext context,
final String parameterPatternStr) {
Pattern pattern = Pattern.compile(parameterPatternStr);
return TemplateUtil.applyTemplate(template, new InputProperties(context), pattern, null);
}
public static String applyTemplate(final String template, final Properties inputs, final IParameterResolver resolver) {
return TemplateUtil.applyTemplate(template, inputs, TemplateUtil.parameterExpressionPattern, resolver);
}
/**
* Processes a template by processing the parameters declared in the
* template. The parameters to be replaced are enclosed in curly brackets.
* Parameters can be the input values (as specified by the name of the input
* value) or date expressions. Parameters that can not be processed are left
* in the template.
*
* @param template
* the template specification.
* @param input
* the input values communicated as a
* {@link java.util.Properties}.
* @param locale
* the locale to use for the formatting of date expression. If
* <tt>null</tt>, the locale for the thread is used. If no
* locale for the thread, then the default locale is used.
* @throws IllegalArgumentException
* if a date expression is illegal
* @see DateMath#calculateDateString(Calendar, String)
* @see PentahoSystem#getLocale()
* @see PentahoSystem#getDefaultLocale()
*/
public static String applyTemplate(final String template, final Properties inputs, final Pattern parameterPattern,
final IParameterResolver resolver) {
StringBuffer results = new StringBuffer();
Matcher parameterMatcher = parameterPattern.matcher(template);
int copyStart = 0;
while (parameterMatcher.find()) {
int start = parameterMatcher.start();
String parameter = parameterMatcher.group(1);
String value = null;
int colonPosition = parameter.indexOf(':');
boolean hasSpaces = parameter.indexOf(' ') != -1;
boolean isTableTemplate = !hasSpaces && parameter.indexOf(":col:") != -1; //$NON-NLS-1$
boolean isComponentResoved = !hasSpaces && colonPosition != -1;
boolean isNamedParameter = !hasSpaces;
boolean isDateParamter = hasSpaces;
if (isTableTemplate) {
TemplateUtil.applyTableTemplate(template, inputs, parameterPattern, results);
return results.toString();
}
if( isComponentResoved ) {
// Allow alternate parameter resolution to be provided by the
// component.
if (resolver != null) {
int newCopyStart = resolver.resolveParameter(template, parameter, parameterMatcher, copyStart, results);
if (newCopyStart >= 0) {
copyStart = newCopyStart;
continue;
}
}
StringTokenizer tokenizer = new StringTokenizer(parameter, ":"); //$NON-NLS-1$
if (tokenizer.countTokens() >= 5) {
// this looks like a data table key
parameter = tokenizer.nextToken();
String keyColumn = tokenizer.nextToken();
String keyValue = tokenizer.nextToken();
String valueColumn = tokenizer.nextToken();
StringBuffer defaultValue = new StringBuffer();
defaultValue.append(tokenizer.nextToken());
while (tokenizer.hasMoreTokens()) {
defaultValue.append(':').append(tokenizer.nextToken());
}
// see if we can find this in the data
if (inputs instanceof InputProperties) {
value = ((InputProperties) inputs).getProperty(parameter, keyColumn, keyValue, valueColumn, defaultValue
.toString());
}
}
}
else if (isNamedParameter) {
// TODO support type conversion
value = inputs.getProperty(parameter);
if (value == null) {
TemplateUtil.logger.warn(Messages.getInstance().getString("TemplateUtil.NOT_FOUND", parameter)); //$NON-NLS-1$
}
}
results.append(template.substring(copyStart, start));
copyStart = parameterMatcher.end();
if (isDateParamter || value == null) {
value = TemplateUtil.matchDateRegex( parameter, inputs );
}
if (value == null) {
results.append(parameterMatcher.group());
} else {
results.append(value);
}
}
if (copyStart < template.length()) {
results.append(template.substring(copyStart));
}
return results.toString();
}
public static void applyTableTemplate(final String template, final Properties inputs, final Pattern parameterPattern,
final StringBuffer results) {
Matcher parameterMatcher = parameterPattern.matcher(template);
ArrayList<String> partsList = new ArrayList<String>();
ArrayList<Integer> columnsList = new ArrayList<Integer>();
int idx = 0;
int lastEnd = 0;
IPentahoResultSet data = null;
while (parameterMatcher.find()) {
int start = parameterMatcher.start();
String parameter = parameterMatcher.group(1);
// pull out the repeating part
int pos1 = parameter.indexOf(":col:");//$NON-NLS-1$
if (pos1 > -1) {
String part = template.substring(lastEnd, start);
if (PentahoSystem.debug) {
TemplateUtil.logger.debug("parameter=" + parameter);//$NON-NLS-1$
TemplateUtil.logger.debug("part=" + part);//$NON-NLS-1$
}
String inputName = parameter.substring(0, pos1);
String columnNoStr = parameter.substring(pos1 + 5);
int columnNo = Integer.parseInt(columnNoStr);
if (PentahoSystem.debug) {
TemplateUtil.logger.debug("inputName=" + inputName);//$NON-NLS-1$
TemplateUtil.logger.debug("columnNoStr=" + columnNoStr);//$NON-NLS-1$
TemplateUtil.logger.debug("columnNo=" + columnNo);//$NON-NLS-1$
}
Object obj = null;
if (inputs instanceof InputProperties) {
obj = ((InputProperties) inputs).getInput(inputName);
}
if (obj == null) {
TemplateUtil.logger.warn(Messages.getInstance().getString("TemplateUtil.NOT_FOUND", inputName)); //$NON-NLS-1$
} else {
if (obj instanceof IPentahoResultSet) {
data = (IPentahoResultSet) obj;
if (columnNo < data.getColumnCount()) {
columnsList.add(new Integer(columnNo));
} else {
TemplateUtil.logger.warn(Messages.getInstance().getString("TemplateUtil.INVALID_COLUMN", String.valueOf(columnNo))); //$NON-NLS-1$
}
}
}
partsList.add(part);
lastEnd = parameterMatcher.end();
} else {
// don't support this yet
}
}
if (PentahoSystem.debug) {
TemplateUtil.logger.debug("partsList.size()=" + partsList.size());//$NON-NLS-1$
}
if (PentahoSystem.debug) {
TemplateUtil.logger.debug("columnsList.size()=" + columnsList.size());//$NON-NLS-1$
}
if (PentahoSystem.debug) {
TemplateUtil.logger.debug("data=" + data);//$NON-NLS-1$
}
if (partsList.size() > 0) {
partsList.add(template.substring(lastEnd));
} else {
TemplateUtil.logger.warn(Messages.getInstance().getString("TemplateUtil.NO_TOKEN")); //$NON-NLS-1$
}
if ((data != null) && (partsList.size() == columnsList.size() + 1)) {
// here we go
String parts[] = new String[partsList.size()];
partsList.toArray(parts);
Integer cols[] = new Integer[columnsList.size()];
columnsList.toArray(cols);
int rowNo = 0;
Object row[] = data.getDataRow(rowNo);
while (row != null) {
for (idx = 0; idx < cols.length; idx++) {
results.append(parts[idx]);
results.append(row[cols[idx].intValue()]);
}
results.append(parts[parts.length - 1]);
rowNo++;
row = data.getDataRow(rowNo);
}
}
if (PentahoSystem.debug) {
TemplateUtil.logger.debug("results=" + results.toString());//$NON-NLS-1$
}
}
public static String applyTemplate(final String template, final String name, final String value) {
String result = template;
result = result.replaceAll("\\{" + name + "\\}", value); //$NON-NLS-1$//$NON-NLS-2$
return result;
}
public static String applyTemplate(final String template, final String name, final String[] value) {
if (value == null) {
return (template);
}
if (value.length == 1) {
return (TemplateUtil.applyTemplate(template, name, value[0]));
}
int pos = template.indexOf("{" + name + "}"); //$NON-NLS-1$ //$NON-NLS-2$
if (pos == -1) {
return (template);
}
int startPos = template.substring(0, pos).lastIndexOf('&');
if (startPos < 0) {
startPos = template.substring(0, pos).lastIndexOf('?');
}
if (startPos < 0) {
startPos = 0;
} else {
startPos += 1;
}
int endPos = template.substring(pos + name.length() + 1).indexOf('&');
if (endPos < 0) {
endPos = template.substring(pos + name.length() + 1).indexOf('#');
}
if (endPos < 0) {
endPos = template.length();
} else {
endPos += pos + name.length() + 1;
}
String result = template.substring(0, startPos);
String replacePart = template.substring(startPos, endPos);
result += replacePart.replaceAll("\\{" + name + "\\}", value[0]); //$NON-NLS-1$ //$NON-NLS-2$
for (int i = 1; i < value.length; ++i) {
result += "&" + replacePart.replaceAll("\\{" + name + "\\}", value[i]); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
}
result += template.substring(endPos);
return result;
}
/**
* Uses regex matching to see if the input parameter appears to be a date expression
* @param parameter
* @return the value of the calculated date
*/
public static String matchDateRegex( String parameter ) {
return matchDateRegex( parameter, null );
}
/**
* Uses regex matching to see if the input parameter appears to be a date expression
* @param parameter
* @return the value of the calculated date
*/
public static String matchDateRegex( String parameter, final Properties inputs ) {
// try a 'expression' pattern
Matcher dateMatcher = TemplateUtil.dateExpressionPattern.matcher(parameter);
String value = null;
if (dateMatcher.matches()) {
if( parameter.indexOf(';') != -1 ) {
value = DateMath.calculateDateString(null, parameter);
} else {
// default to yyyy-MM-dd format for date strings
value = DateMath.calculateDateString(null, parameter+";yyyy-MM-dd"); //$NON-NLS-1$
}
}
if (value == null) {
// try a 'DATEMATH("expression")' pattern
dateMatcher = TemplateUtil.dateMathExpressionPattern.matcher(parameter);
if (dateMatcher.matches()) {
// remove the 'DATEMATH' part, look for the first single or double quote
int pos = parameter.indexOf( '\'' );
if( pos == -1 ) {
pos = parameter.indexOf( '"' );
}
if( pos != -1 ) {
parameter = parameter.substring( pos+1 );
// now look for the last one
pos = parameter.lastIndexOf( '\'' );
if( pos == -1 ) {
pos = parameter.lastIndexOf( '"' );
}
if( pos != -1 ) {
parameter = parameter.substring( 0, pos );
value = TemplateUtil.matchDateRegex(parameter, inputs);
}
}
}
}
if( value == null ) {
// try a DATEMATH:varname
dateMatcher = TemplateUtil.dateMathVarPattern.matcher(parameter);
if (dateMatcher.matches()) {
int pos = parameter.indexOf( ':' );
parameter = parameter.substring(pos+1);
parameter = inputs.getProperty( parameter );
if( parameter != null ) {
value = TemplateUtil.matchDateRegex(parameter, inputs);
}
}
}
if( value == null ) {
// try a date in yyyy-MM-dd format
dateMatcher = TemplateUtil.datePattern.matcher(parameter);
if (dateMatcher.matches()) {
value = parameter;
}
}
return value;
}
/**
* Acts as a facade for a {@link IRuntimeContext} to access the input values
* as from a {@link java.util.Properties Properties}. The class only
* overrides the {@link #getProperty(String)} method, as its is the only
* method used in
* {@link TemplateComponent#applyTemplate(String, IRuntimeContext) TemplateComponent.applyTemplate(String, IRuntimeContext)}.
*/
private static class InputProperties extends Properties {
private static final long serialVersionUID = 1L;
private IRuntimeContext context;
private Set<String> inputs;
private static final Log inputPropertiesLogger = LogFactory.getLog(InputProperties.class);
@SuppressWarnings({"unchecked"})
InputProperties(final IRuntimeContext context) {
this.context = context;
inputs = new HashSet<String>();
inputs.addAll(context.getInputNames());
inputs.addAll(context.getParameterManager().getCurrentInputNames());
inputs.add("$user"); //$NON-NLS-1$
inputs.add("$url"); //$NON-NLS-1$
inputs.add("$solution"); //$NON-NLS-1$
}
@Override
public int size() {
if (inputs == null) {
return 0;
} else {
return inputs.size();
}
}
public String getProperty(final String parameter, final String keyColumn, String keyValue,
final String valueColumn, final String defaultValue) {
if (!context.getInputNames().contains(parameter)) {
// leave the text alone
return "{" + parameter + ":" + keyColumn + ":" + keyValue + ":" + valueColumn + ":" + defaultValue + "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
}
Object valueObj = context.getInputParameterValue(parameter);
if (valueObj instanceof IPentahoResultSet) {
IPentahoResultSet data = (IPentahoResultSet) valueObj;
// this is slow
// TODO implement mapping or sorting here to improve performance
int keyColumnNo = data.getMetaData().getColumnIndex(keyColumn);
if (keyValue.indexOf('_') > 0) {
keyValue = keyValue.replace('_', ' ');
}
int valueColumnNo = data.getMetaData().getColumnIndex(valueColumn);
if ((keyColumnNo != -1) && (valueColumnNo != -1)) {
for (int row = 0; row < data.getRowCount(); row++) {
Object thisKey = data.getValueAt(row, keyColumnNo);
if (thisKey != null) {
if (keyValue.equals(thisKey.toString())) {
// we found the value
// TODO support typing here
return data.getValueAt(row, valueColumnNo).toString();
}
}
}
}
}
return defaultValue;
}
public Object getInput(final String name) {
Object value = null;
if (inputs == null) {
return null;
}
if (inputs.contains(name)) {
value = TemplateUtil.getSystemInput(name, context);
if (value != null) {
return value;
}
value = context.getInputParameterValue(name);
}
return value;
}
@Override
public String getProperty(final String name) {
String value = null;
if (inputs == null) {
return null;
}
if (inputs.contains(name)) {
value = TemplateUtil.getSystemInput(name, context);
if (value != null) {
return value;
}
IParameterManager paramMgr = context.getParameterManager();
Map allParams = paramMgr.getAllParameters();
Object valueObj;
if (allParams.containsKey(name)) {
IActionParameter param = (IActionParameter) allParams.get(name);
valueObj = param.getValue();
} else {
valueObj = context.getInputParameterValue(name);
}
if (valueObj instanceof String) {
value = (String) valueObj;
} else if (valueObj instanceof Object[]) {
Object values[] = (Object[]) valueObj;
StringBuffer valuesBuffer = new StringBuffer();
// TODO support non-string items
// TODO this is assuming that the surrounding 's exist
for (int i = 0; i < values.length; i++) {
if (i == 0) {
valuesBuffer.append("'").append(values[i].toString()).append("'"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
valuesBuffer.append(",'").append(values[i].toString()).append("'"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
String valueStr = valuesBuffer.toString();
value = valueStr.substring(1, valueStr.length() - 1);
} else if (valueObj instanceof IPentahoResultSet) {
IPentahoResultSet rs = (IPentahoResultSet) valueObj;
// See if we can find a column in the metadata with the same
// name as the input
IPentahoMetaData md = rs.getMetaData();
int columnIdx = -1;
if (md.getColumnCount() == 1) {
columnIdx = 0;
} else {
columnIdx = md.getColumnIndex(new String[] { name });
}
if (columnIdx < 0) {
InputProperties.inputPropertiesLogger.error(Messages.getInstance().getErrorString("Template.ERROR_0005_COULD_NOT_DETERMINE_COLUMN")); //$NON-NLS-1$
return null;
}
int rowCount = rs.getRowCount();
Object valueCell = null;
StringBuffer valuesBuffer = new StringBuffer();
// TODO support non-string columns
for (int i = 0; i < rowCount; i++) {
valueCell = rs.getValueAt(i, columnIdx);
if (i == 0) {
valuesBuffer.append("'").append(valueCell.toString()).append("'"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
valuesBuffer.append(",'").append(valueCell.toString()).append("'"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
String valueStr = valuesBuffer.toString();
// TODO Assumes that parameter is already surrounded by
// quotes.
value = valueStr.substring(1, valueStr.length() - 1);
} else if (valueObj != null) {
value = valueObj.toString();
}
// TODO add support for numeric classes
} else {
value = super.getProperty(name);
}
if (value == null) {
value = TemplateUtil.matchDateRegex( name, null );
} else {
value = TemplateUtil.matchDateRegex( value, null );
}
return value;
}
}
public static Properties parametersToProperties(final IParameterProvider parameterProvider) {
Properties properties = new Properties();
Iterator names = parameterProvider.getParameterNames();
while (names.hasNext()) {
String name = (String) names.next();
String value = parameterProvider.getStringParameter(name, null);
if (value != null) {
properties.put(name, value);
}
}
return properties;
}
}