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