/*
* 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 - 2013 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.platform.engine.services.runtime;
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.PentahoRequestContextHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.services.messages.Messages;
import org.pentaho.platform.util.DateMath;
import java.util.ArrayList;
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;
public class TemplateUtil {
private static final String PARAMETER_PATTERN = "\\{([^\\}\\{$^]*)\\}"; //$NON-NLS-1$
private static final 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 static final String DATEMATH_EXPR_PATTERN = "DATEMATH\\((\\s*)['\"].*['\"](\\s)*\\)"; //$NON-NLS-1$
private static final String DATEMATH_VAR_PATTERN = "DATEMATH:.*"; //$NON-NLS-1$
private static final 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: { // Relative URL
return PentahoRequestContextHolder.getRequestContext().getContextPath();
}
case 2: { // Solution
return PentahoSystem.getApplicationContext().getSolutionPath( "" ); //$NON-NLS-1$
}
case 3: { // Fully Qualified Server URL
return PentahoSystem.getApplicationContext().getFullyQualifiedServerURL();
}
}
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 ) {
if ( TemplateUtil.logger.isDebugEnabled() ) {
TemplateUtil.logger.debug( 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 ) {
if ( TemplateUtil.logger.isDebugEnabled() ) {
TemplateUtil.logger.debug( 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();
}
}
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 {
String tempValue = TemplateUtil.matchDateRegex( value, null );
if ( tempValue != null ) {
value = tempValue;
}
}
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;
}
}