blob: ff3af4d21edaf0f542e5f1a51fc972b1721738c8 [file] [log] [blame]
/*
* Copyright 2012 International Business Machines Corp.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Licensed under the Apache License,
* Version 2.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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.batchee.container.modelresolver.impl;
import org.apache.batchee.container.modelresolver.PropertyResolver;
import org.apache.batchee.jaxb.Property;
import java.util.List;
import java.util.Properties;
public abstract class AbstractPropertyResolver<B> implements PropertyResolver<B> {
protected boolean isPartitionedStep = false;
public static final String UNRESOLVED_PROP_VALUE = ""; //Substitute empty String for unresolvable props
public AbstractPropertyResolver(boolean isPartitionStep) {
this.isPartitionedStep = isPartitionStep;
}
/*
* Convenience method that is the same as calling substituteProperties(job,
* submittedProps, null)
*/
public B substituteProperties(final B b, final Properties submittedProps) {
return this.substituteProperties(b, submittedProps, null);
}
private enum PROPERTY_TYPE {
JOB_PARAMETERS, SYSTEM_PROPERTIES, JOB_PROPERTIES, PARTITION_PROPERTIES
}
/**
* @param elementProperties xml properties that are direct children of the current element
* @param submittedProps submitted job properties
* @param parentProps resolved parent properties
* @return the properties associated with this elements scope
*/
protected Properties resolveElementProperties(
final List<Property> elementProperties,
final Properties submittedProps, final Properties parentProps) {
Properties currentXMLProperties = new Properties();
currentXMLProperties = this.inheritProperties(parentProps, currentXMLProperties);
for (final Property prop : elementProperties) {
String name = prop.getName();
name = this.replaceAllProperties(name, submittedProps, currentXMLProperties);
String value = prop.getValue();
value = this.replaceAllProperties(value, submittedProps, currentXMLProperties);
// add resolved properties to current properties
currentXMLProperties.setProperty(name, value);
// update JAXB model
prop.setName(name);
prop.setValue(value);
}
return currentXMLProperties;
}
/**
* Replace all the properties in String str.
*
* @param str
* @param submittedProps
* @param xmlProperties
* @return
*/
protected String replaceAllProperties(String str,
final Properties submittedProps, final Properties xmlProperties) {
int startIndex = 0;
NextProperty nextProp = this.findNextProperty(str, startIndex);
while (nextProp != null) {
// get the start index past this property for the next property in
// the string
//startIndex = this.getEndIndexOfNextProperty(str, startIndex);
startIndex = nextProp.endIndex;
// resolve the property
String nextPropValue = this.resolvePropertyValue(nextProp.propName, nextProp.propType, submittedProps, xmlProperties);
//if the property didn't resolve use the default value if it exists
if (nextPropValue.equals(UNRESOLVED_PROP_VALUE)) {
if (nextProp.defaultValueExpression != null) {
nextPropValue = this.replaceAllProperties(nextProp.defaultValueExpression, submittedProps, xmlProperties);
}
}
// After we get this value the lenght of the string might change so
// we need to reset the start index
int lengthDifference = 0;
switch (nextProp.propType) {
case JOB_PARAMETERS:
lengthDifference = nextPropValue.length() - (nextProp.propName.length() + "#{jobParameters['']}".length()); // this can be a negative value
startIndex = startIndex + lengthDifference; // move start index for next property
str = str.replace("#{jobParameters['" + nextProp.propName + "']}" + nextProp.getDefaultValExprWithDelimitersIfExists(), nextPropValue);
break;
case JOB_PROPERTIES:
lengthDifference = nextPropValue.length() - (nextProp.propName.length() + "#{jobProperties['']}".length()); // this can be a negative value
startIndex = startIndex + lengthDifference; // move start index for next property
str = str.replace("#{jobProperties['" + nextProp.propName + "']}" + nextProp.getDefaultValExprWithDelimitersIfExists(), nextPropValue);
break;
case SYSTEM_PROPERTIES:
lengthDifference = nextPropValue.length() - (nextProp.propName.length() + "#{systemProperties['']}".length()); // this can be a negative value
startIndex = startIndex + lengthDifference; // move start index for next property
str = str.replace("#{systemProperties['" + nextProp.propName + "']}" + nextProp.getDefaultValExprWithDelimitersIfExists(), nextPropValue);
break;
case PARTITION_PROPERTIES:
lengthDifference = nextPropValue.length() - (nextProp.propName.length() + "#{partitionPlan['']}".length()); // this can be a negative value
startIndex = startIndex + lengthDifference; // move start index for next property
str = str.replace("#{partitionPlan['" + nextProp.propName + "']}" + nextProp.getDefaultValExprWithDelimitersIfExists(), nextPropValue);
break;
default:
throw new IllegalStateException("unknown PROPERTY_TYPE " + nextProp.propType);
}
// find the next property
nextProp = this.findNextProperty(str, startIndex);
}
return str;
}
/**
* Gets the value of a property using the property type
* <p/>
* If the property 'propname' is not defined the String 'null' (without quotes) is returned as the
* value
*
* @param name
* @return
*/
private String resolvePropertyValue(final String name, PROPERTY_TYPE propType,
final Properties submittedProperties, final Properties xmlProperties) {
String value = null;
switch (propType) {
case JOB_PARAMETERS:
if (submittedProperties != null) {
value = submittedProperties.getProperty(name);
}
if (value != null) {
return value;
}
break;
case JOB_PROPERTIES:
if (xmlProperties != null) {
value = xmlProperties.getProperty(name);
}
if (value != null) {
return value;
}
break;
case SYSTEM_PROPERTIES:
value = System.getProperty(name);
if (value != null) {
return value;
}
break;
case PARTITION_PROPERTIES: //We are reusing the submitted props to carry the partition props
if (submittedProperties != null) {
value = submittedProperties.getProperty(name);
}
if (value != null) {
return value;
}
break;
default:
throw new IllegalStateException("unknown PROPERTY_TYPE " + propType);
}
return UNRESOLVED_PROP_VALUE;
}
/**
* Merge the parent properties that are already set into the child
* properties. Child properties always override parent values.
*
* @param parentProps A set of already resolved parent properties
* @param childProps A set of already resolved child properties
* @return
*/
private Properties inheritProperties(final Properties parentProps,
final Properties childProps) {
if (parentProps == null) {
return childProps;
}
if (childProps == null) {
return parentProps;
}
for (final String parentKey : parentProps.stringPropertyNames()) {
// Add the parent property to the child if the child does not
// already define it
if (!childProps.containsKey(parentKey)) {
childProps.setProperty(parentKey, parentProps
.getProperty(parentKey));
}
}
return childProps;
}
/**
* A helper method to the get the index of the '}' character in the given
* String str with a valid property substitution. A valid property looks
* like ${batch.property}
*
* @param str The string to search.
* @param startIndex The index in str to start the search.
* @return The index of the '}' character or -1 if no valid property is
* found in str.
*/
private int getEndIndexOfNextProperty(final String str, final int startIndex) {
if (str == null) {
return -1;
}
final int startPropIndex = str.indexOf("${", startIndex);
// we didn't find a property in this string
if (startPropIndex == -1) {
return -1;
}
final int endPropIndex = str.indexOf("}", startPropIndex);
// This check allows something like this "Some filename is ${}"
// Maybe we should require "${f}" ???
if (endPropIndex > startPropIndex) {
return endPropIndex;
}
// if no variables like ${prop1} are in string, return null
return -1;
}
/**
* A helper method to the get the next property in the given String str with
* a valid property substitution. A valid property looks like
* #{jobParameter['batch.property']}. This method will return only the name
* of the property found without the surrounding metadata.
* <p/>
* Example:
*
* @param str The string to search.
* @param startIndex The index in str to start the search.
* @return The name of the next property found without the starting
* #{<propertyType>[' or ending ']}
*/
private NextProperty findNextProperty(final String str, final int startIndex) {
if (str == null) {
return null;
}
final int startPropIndex = str.indexOf("#{", startIndex);
if (startPropIndex == -1) {
return null;
}
//FIXME We may want to throw a more helpful exception here to say there was probably a typo.
PROPERTY_TYPE type = null;
if (str.startsWith("#{jobParameters['", startPropIndex)) {
type = PROPERTY_TYPE.JOB_PARAMETERS;
} else if (str.startsWith("#{systemProperties['", startPropIndex)) {
type = PROPERTY_TYPE.SYSTEM_PROPERTIES;
} else if (str.startsWith("#{jobProperties['", startPropIndex)) {
type = PROPERTY_TYPE.JOB_PROPERTIES;
} else if (isPartitionedStep && str.startsWith("#{partitionPlan['", startPropIndex)) {
type = PROPERTY_TYPE.PARTITION_PROPERTIES;
}
if (type == null) {
return null;
}
final int endPropIndex = str.indexOf("']}");
// This check allows something like this "Some filename is ${jobParameters['']}"
// Maybe we should require "${f}" ???
String propName = null;
String defaultPropExpression = null;
if (endPropIndex > startPropIndex) {
//look for the ?:<default-value-expression>; syntax after the property to see if it has a default value
if (str.startsWith("?:", endPropIndex + "']}".length())) {
//find the end of the defaulting string
int tempEndPropIndex = str.indexOf(";", endPropIndex + "']}?:".length());
if (tempEndPropIndex == -1) {
throw new IllegalArgumentException("The default property expression is not properly terminated with ';'");
}
//this string does not include the ?: and ; It only contains the content in between
defaultPropExpression = str.substring(endPropIndex + "]}?:".length() + 1, tempEndPropIndex);
}
if (type.equals(PROPERTY_TYPE.JOB_PARAMETERS)) {
propName = str.substring(startPropIndex + "#{jobParameters['".length(), endPropIndex);
}
if (type.equals(PROPERTY_TYPE.JOB_PROPERTIES)) {
propName = str.substring(startPropIndex + "#{jobProperties['".length(), endPropIndex);
}
if (type.equals(PROPERTY_TYPE.SYSTEM_PROPERTIES)) {
propName = str.substring(startPropIndex + "#{systemProperties['".length(), endPropIndex);
}
if (type.equals(PROPERTY_TYPE.PARTITION_PROPERTIES)) {
propName = str.substring(startPropIndex + "#{partitionPlan['".length(), endPropIndex);
}
return new NextProperty(propName, type, startPropIndex, endPropIndex, defaultPropExpression);
}
// if no variables like #{jobProperties['prop1']} are in string, return null
return null;
}
class NextProperty {
final String propName;
final PROPERTY_TYPE propType;
final int startIndex;
final int endIndex;
final String defaultValueExpression;
NextProperty(String propName, PROPERTY_TYPE propType, int startIndex, int endIndex, String defaultValueExpression) {
this.propName = propName;
this.propType = propType;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.defaultValueExpression = defaultValueExpression;
}
String getDefaultValExprWithDelimitersIfExists() {
if (this.defaultValueExpression != null) {
return "?:" + this.defaultValueExpression + ";";
}
return "";
}
}
}