blob: 69e55de3bf43d0daf60783ee560c8c8f6f8f63ba [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/**
* This is helper class for parameter substitution
*/
package org.apache.pig.tools.parameters;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Hashtable;
import java.util.List;
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.apache.hadoop.util.Shell;
import org.apache.pig.impl.PigContext;
import org.apache.pig.impl.logicalLayer.FrontendException;
import org.apache.pig.validator.BlackAndWhitelistFilter;
import org.apache.pig.validator.PigCommandFilter;
public class PreprocessorContext {
private int tableinitsize = 10;
private Deque<Map<String,String>> param_val_stack;
private PigContext pigContext;
public Map<String, String> getParamVal() {
Map <String, String> ret = new Hashtable <String, String>(tableinitsize);
//stack (deque) iterates LIFO
for (Map <String, String> map : param_val_stack ) {
for (Map.Entry<String, String> entry : map.entrySet()) {
if( ! ret.containsKey(entry.getKey()) ) {
ret.put(entry.getKey(), entry.getValue());
}
}
}
return ret;
}
private final Log log = LogFactory.getLog(getClass());
/**
* @param limit - max number of parameters. Passing
* smaller number only impacts performance
*/
public PreprocessorContext(int limit) {
tableinitsize = limit;
param_val_stack = new ArrayDeque<Map<String,String>> ();
param_val_stack.push(new Hashtable<String, String> (tableinitsize));
}
public void setPigContext(PigContext context) {
this.pigContext = context;
}
/**
* This method generates parameter value by running specified command
*
* @param key - parameter name
* @param val - string containing command to be executed
*/
public void processShellCmd(String key, String val) throws ParameterSubstitutionException, FrontendException {
processShellCmd(key, val, true);
}
/**
* This method generates value for the specified key by
* performing substitution if needed within the value first.
*
* @param key - parameter name
* @param val - value supplied for the key
*/
public void processOrdLine(String key, String val) throws ParameterSubstitutionException {
processOrdLine(key, val, true);
}
public void paramScopePush() {
param_val_stack.push( new Hashtable<String, String> (tableinitsize) );
}
public void paramScopePop() {
param_val_stack.pop();
}
public boolean paramval_containsKey(String key) {
for (Map <String, String> map : param_val_stack ) {
if( map.containsKey(key) ) {
return true;
}
}
return false;
}
public String paramval_get(String key) {
for (Map <String, String> map : param_val_stack ) {
if( map.containsKey(key) ) {
return map.get(key);
}
}
return null;
}
public void paramval_put(String key, String value) {
param_val_stack.peek().put(key, value);
}
/**
* This method generates parameter value by running specified command
*
* @param key - parameter name
* @param val - string containing command to be executed
*/
public void processShellCmd(String key, String val, Boolean overwrite) throws ParameterSubstitutionException, FrontendException {
if (pigContext != null) {
BlackAndWhitelistFilter filter = new BlackAndWhitelistFilter(pigContext);
filter.validate(PigCommandFilter.Command.SH);
}
if (paramval_containsKey(key) && !overwrite) {
return;
}
val = val.substring(1, val.length()-1); //to remove the backticks
String sub_val = substitute(val);
sub_val = executeShellCommand(sub_val);
if (paramval_containsKey(key) && !paramval_get(key).equals(sub_val) ) {
//(boolean overwrite is always true here)
log.warn("Warning : Multiple values found for " + key + " command `" + val + "`. "
+ "Previous value " + paramval_get(key) + ", now using value " + sub_val);
}
paramval_put(key, sub_val);
}
public void validate(String preprocessorCmd) throws FrontendException {
if (pigContext == null) {
return;
}
final BlackAndWhitelistFilter filter = new BlackAndWhitelistFilter(pigContext);
final String declareToken = "%declare";
final String defaultToken = "%default";
if (preprocessorCmd.toLowerCase().equals(declareToken)) {
filter.validate(PigCommandFilter.Command.DECLARE);
} else if (preprocessorCmd.toLowerCase().equals(defaultToken)) {
filter.validate(PigCommandFilter.Command.DEFAULT);
} else {
throw new IllegalArgumentException("Pig Internal Error. Invalid preprocessor command specified : "
+ preprocessorCmd);
}
}
/**
* This method generates value for the specified key by
* performing substitution if needed within the value first.
*
* @param key - parameter name
* @param val - value supplied for the key
* @param overwrite - specifies whether the value should be replaced if it already exists
*/
public void processOrdLine(String key, String val, Boolean overwrite) throws ParameterSubstitutionException {
String sub_val = substitute(val, key);
if (paramval_containsKey(key)) {
if (paramval_get(key).equals(sub_val) || !overwrite) {
return;
} else {
log.warn("Warning : Multiple values found for " + key
+ ". Previous value " + paramval_get(key)
+ ", now using value " + sub_val);
}
}
paramval_put(key, sub_val);
}
/*
* executes the 'cmd' in shell and returns result
*/
private String executeShellCommand (String cmd)
{
Process p;
String streamData="";
String streamError="";
try {
log.info("Executing command : " + cmd);
// we can't use exec directly since it does not handle
// case like foo -c "bar bar" correctly. It splits on white spaces even in presents of quotes
StringBuffer sb = new StringBuffer("");
String[] cmdArgs;
if (Shell.WINDOWS) {
cmd = cmd.replaceAll("/", "\\\\");
sb.append(cmd);
cmdArgs = new String[]{"cmd", "/c", sb.toString() };
} else {
sb.append("exec ");
sb.append(cmd);
cmdArgs = new String[]{"bash", "-c", sb.toString() };
}
p = Runtime.getRuntime().exec(cmdArgs);
} catch (IOException e) {
RuntimeException rte = new RuntimeException("IO Exception while executing shell command : "+e.getMessage() , e);
throw rte;
}
BufferedReader br = null;
try{
InputStreamReader isr = new InputStreamReader(p.getInputStream());
br = new BufferedReader(isr);
String line=null;
StringBuilder sb = new StringBuilder();
while ( (line = br.readLine()) != null){
sb.append(line);
sb.append("\n");
}
streamData = sb.toString();
} catch (IOException e){
RuntimeException rte = new RuntimeException("IO Exception while executing shell command : "+e.getMessage() , e);
throw rte;
} finally {
if (br != null) try {br.close();} catch(Exception e) {}
}
try {
InputStreamReader isr = new InputStreamReader(p.getErrorStream());
br = new BufferedReader(isr);
String line=null;
StringBuilder sb = new StringBuilder();
while ( (line = br.readLine()) != null ) {
sb.append(line);
sb.append("\n");
}
streamError = sb.toString();
log.debug("Error stream while executing shell command : " + streamError);
} catch (Exception e) {
RuntimeException rte = new RuntimeException("IO Exception while executing shell command : "+e.getMessage() , e);
throw rte;
} finally {
if (br != null) try {br.close();} catch(Exception e) {}
}
int exitVal;
try {
exitVal = p.waitFor();
} catch (InterruptedException e) {
RuntimeException rte = new RuntimeException("Interrupted Thread Exception while waiting for command to get over"+e.getMessage() , e);
throw rte;
}
if (exitVal != 0) {
RuntimeException rte = new RuntimeException("Error executing shell command: " + cmd + ". Command exit with exit code of " + exitVal );
throw rte;
}
return streamData.trim();
}
public void loadParamVal(List<String> params, List<String> paramFiles)
throws IOException, ParseException {
StringReader dummyReader = null; // ParamLoader does not have an empty contructor
ParamLoader paramLoader = new ParamLoader(dummyReader);
paramLoader.setContext(this);
if (paramFiles != null) {
for (String path : paramFiles) {
BufferedReader in = new BufferedReader(new FileReader(path));
paramLoader.ReInit(in);
while (paramLoader.Parse()) {}
in.close();
}
}
if (params != null) {
for (String param : params) {
paramLoader.ReInit(new StringReader(param));
paramLoader.Parse();
}
}
}
private Pattern bracketIdPattern = Pattern.compile("\\$\\{([_]*[a-zA-Z][a-zA-Z_0-9]*)\\}");
private Pattern id_pattern = Pattern.compile("\\$([_]*[a-zA-Z][a-zA-Z_0-9]*)");
public String substitute(String line) throws ParameterSubstitutionException {
return substitute(line, null);
}
public String substitute(String line, String parentKey) throws ParameterSubstitutionException {
int index = line.indexOf('$');
if (index == -1)
return line;
String replaced_line = line;
Matcher bracketKeyMatcher = bracketIdPattern.matcher(line);
String key="";
String val="";
while (bracketKeyMatcher.find()) {
if ( (bracketKeyMatcher.start() == 0) || (line.charAt( bracketKeyMatcher.start() - 1)) != '\\' ) {
key = bracketKeyMatcher.group(1);
if (!(paramval_containsKey(key))) {
String message;
if (parentKey == null) {
message = "Undefined parameter : " + key;
} else {
message = "Undefined parameter : " + key + " found when trying to find the value of " + parentKey + ".";
}
throw new ParameterSubstitutionException(message);
}
val = paramval_get(key);
if (val.contains("$")) {
val = val.replaceAll("(?<!\\\\)\\$", "\\\\\\$");
}
replaced_line = replaced_line.replaceFirst("\\$\\{"+key+"\\}", val);
}
}
Matcher keyMatcher = id_pattern.matcher( replaced_line );
key="";
val="";
while (keyMatcher.find()) {
// make sure that we don't perform parameter substitution
// for escaped vars of the form \$<id>
if ( (keyMatcher.start() == 0) || (line.charAt( keyMatcher.start() - 1)) != '\\' ) {
key = keyMatcher.group(1);
if (!(paramval_containsKey(key))) {
String message;
if (parentKey == null) {
message = "Undefined parameter : " + key;
} else {
message = "Undefined parameter : " + key + " found when trying to find the value of " + parentKey + ".";
}
throw new ParameterSubstitutionException(message);
}
val = paramval_get(key);
if (val.contains("$")) {
val = val.replaceAll("(?<!\\\\)\\$", "\\\\\\$");
}
replaced_line = replaced_line.replaceFirst("\\$"+key, val);
}
}
// unescape $<id>
replaced_line = replaced_line.replaceAll("\\\\\\$","\\$");
return replaced_line;
}
}