/*
* 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.
*/
package com.aliyun.odps.mapred.conf;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import com.alibaba.fastjson.JSON;
import com.aliyun.odps.Odps;
import com.aliyun.odps.OdpsHook;
import com.aliyun.odps.OdpsHooks;
import com.aliyun.odps.account.Account;
import com.aliyun.odps.account.Account.AccountProvider;
import com.aliyun.odps.account.AliyunAccount;
import com.aliyun.odps.utils.StringUtils;
/**
* SessionState指定了Odps MapReduce任务的会话上下文。
*
* SessionState保存的信息包括:
* <p>
* <li>{@link com.aliyun.odps.Odps}实例</li>
* <li>默认的{@link com.aliyun.odps.mapred.conf.JobConf}</li>
* <li>local run 开关</li>
* </p>
*
* 以下代码示例如何设置当前运行模式为local run:
*
* <pre>
* SessionState ss = SessionState.get();
* ss.setLocalRun(true);
* </pre>
*/
public class SessionState {
private static ThreadLocal<SessionState> tss = new InheritableThreadLocal<SessionState>() {
@Override
public SessionState initialValue() {
return new SessionState();
}
@Override
protected SessionState childValue(SessionState parentValue) {
return new SessionState(parentValue);
}
};
private Odps odps;
private JobConf defaultJob;
private boolean isLocalRun;
private boolean isCostMode;
private Map<String, String> aliases;
// if use internal cli
private boolean internalCli = false;
private String commandText = "";
private SessionState() {
try {
aliases = new HashMap<String, String>();
defaultJob = new JobConf(false);
String conf = System.getProperties().getProperty(MR_JOB_CONF);
if (conf != null && !conf.isEmpty()) {
try {
defaultJob.addResource(new FileInputStream(new File(conf)));
} catch (FileNotFoundException e) {
System.err.print("conf file " + conf + " not found, ignored!");
}
}
parseOldCli(defaultJob); // Try to be compatible with old console
} catch (AccessControlException e) {
// just ignore it for it maybe called by user code indirectly through JobConf constructors
}
}
public SessionState(SessionState ss) {
if (ss.odps != null) {
odps = new Odps(ss.odps);
}
if (ss.defaultJob != null) {
defaultJob = new JobConf(ss.defaultJob);
}
isLocalRun = ss.isLocalRun;
aliases = new HashMap<String, String>();
if (ss.aliases != null) {
aliases.putAll(ss.aliases);
}
}
// Copied from old OdpsConf.
private final static String OLD_PROJNAME_KEY = "odps.project.name";
private final static String OLD_ENDPOINT_KEY = "odps.end.point";
private final static String OLD_ACCESSID_KEY = "odps.access.id";
private final static String OLD_ACCESSKEY_KEY = "odps.access.key";
private final static String OLD_RUNMODE_KEY = "odps.runner.mode";
private final static String OLD_RESOURCE_KEY = "odps.cache.resources";
private final static String OLD_CP_RESOURCE_KEY = "odps.classpath.resources";
private final static String OLD_CONTEXT_FILE = "odps.exec.context.file";
private final static String OLD_ACCOUNT_PROVIDER_KEY = "odps.account.provider";
private final static String OLD_TAOBAO_TOKEN_KEY = "odps.taobao.token";
private final static String OLD_TAOBAO_ALGORITHM_KEY = "odps.taobao.algorithm";
// Local MR
private final static String LOCAL_TEMP_DIR = "odps.mapred.local.temp.dir";
private final static String LOCAL_TEMP_RETAIN = "odps.mapred.local.temp.retain";
private final static String LOCAL_RECORD_LIMIT = "odps.mapred.local.record.download.limit";
public static final String LOCAL_DOWNLOAD_MODE = "odps.mapred.local.download.mode"; //always|auto|never; default auto
//Local security
public static final String LOCAL_SECURITY_ENABLE = "odps.local.security.enable";
public static final String LOCAL_SECURITY_JNI_ENABLE = "odps.local.security.jni.enable";
public static final String LOCAL_USER_DEFINE_POLICY = "odps.local.user.define.policy";
public static final String COST = "cost";
public static final String ODPS_JOB_COST_ESTIMATE = "odps.task.cost.estimate";
private final static String
LOCAL_INPUT_COLUMN_SEPERATOR =
"odps.mapred.local.input.column.seperator";
private final static String
LOCAL_OUTPUT_COLUMN_SEPERATOR =
"odps.mapred.local.output.column.seperator";
private static final String MR_JOB_CONF = "odps.mr.job.conf";
/**
* From old Console load context file , include settings & aliases
*
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void loadContextFile(Properties prop) throws IOException {
String fileName = prop.getProperty(OLD_CONTEXT_FILE);
if (fileName == null) {
return;
}
String jsonStr = FileUtils.readFileToString(new File(fileName));
Map context = JSON.parseObject(jsonStr, Map.class);
if (context == null) {
return;
}
// client set ignore certs to true when request from internal-cli
internalCli = true;
getOdps().getRestClient().setIgnoreCerts(true);
if (context.containsKey("settings")) {
Map<String, String> settings = (Map<String, String>) context.get("settings");
for (Entry<String, String> setting : settings.entrySet()) {
defaultJob.set(setting.getKey(), setting.getValue());
}
}
if (context.containsKey("aliases")) {
aliases = (Map<String, String>) context.get("aliases");
}
if (context.containsKey("context")) {
// extract info from execution context
Map<String, Object> ctx = (Map<String, Object>) context.get("context");
defaultJob.setInstancePriority((Integer) ctx.get("priority"));
OdpsHooks.clearRegisteredHooks();
String hookString = (String) ctx.get("odpsHooks");
if (!StringUtils.isNullOrEmpty(hookString)) {
try {
String[] hooks = hookString.split(",");
List<Class<? extends OdpsHook>> hookList = new ArrayList<Class<? extends OdpsHook>>();
for (String hook : hooks) {
hookList.add((Class<? extends OdpsHook>) Class.forName(hook));
}
OdpsHooks.registerHooks(hookList);
} catch (ClassNotFoundException e) {
throw new IOException(e.getMessage(), e);
}
}
String runningCluster = (String) ctx.get("runningCluster");
if (!StringUtils.isNullOrEmpty(runningCluster)) {
odps.instances().setDefaultRunningCluster(runningCluster);
}
if (ctx.get("logViewHost") != null) {
String logViewHost = (String) ctx.get("logViewHost");
odps.setLogViewHost(logViewHost);
}
if (ctx.containsKey("https_check")) {
odps.getRestClient().setIgnoreCerts(!(Boolean) ctx.get("https_check"));
}
}
if (context.containsKey("commandText")) {
setCommandText((String) context.get("commandText"));
}
}
/**
* Parse old command line settings and set to new API accordingly.
*/
private void parseOldCli(JobConf conf) {
Properties prop = System.getProperties();
//check for cost mode
String cost = prop.getProperty(COST);
if (cost != null && "true".equals(cost)) {
defaultJob.setBoolean(ODPS_JOB_COST_ESTIMATE, true);
setCostMode(true);
}
if (prop.getProperty(OLD_ENDPOINT_KEY) != null) {
String endpoint = prop.getProperty(OLD_ENDPOINT_KEY);
String project = prop.getProperty(OLD_PROJNAME_KEY);
String accessId = prop.getProperty(OLD_ACCESSID_KEY);
String accessKey = prop.getProperty(OLD_ACCESSKEY_KEY);
String runmode = prop.getProperty(OLD_RUNMODE_KEY, "remote");
if (runmode.equalsIgnoreCase("local")) {
handleLocalMR(prop);
} else {
System.err.println("Running job in console.");
}
AccountProvider accountProvider = AccountProvider.ALIYUN;
String apStr = prop.getProperty(OLD_ACCOUNT_PROVIDER_KEY);
if (apStr != null) {
apStr = apStr.trim().toUpperCase();
try {
accountProvider = AccountProvider.valueOf(apStr);
} catch (Exception exception) {
throw new RuntimeException("Unsupport account provider:" + apStr);
}
}
Account account = null;
switch (accountProvider) {
case ALIYUN:
account = new AliyunAccount(accessId, accessKey);
break;
default:
throw new RuntimeException("unsupport account provider:" + accountProvider);
}
Odps odps = new Odps(account);
odps.setDefaultProject(project);
odps.setEndpoint(endpoint);
setOdps(odps);
setLocalRun(runmode.equalsIgnoreCase("local"));
String resources = prop.getProperty(OLD_RESOURCE_KEY, "");
String cpresources = prop.getProperty(OLD_CP_RESOURCE_KEY, "");
if (!resources.isEmpty() && !cpresources.isEmpty()) {
resources += ",";
}
resources += cpresources;
conf.setResources(resources);
}
try {
loadContextFile(prop);
} catch (IOException e) {
// Silently swallow it because adapter is not required to load context
// file.
}
}
/**
* 获取当前线程的SessionState
*
* @return 当前线程的SessionState
*/
public static SessionState get() {
return tss.get();
}
/**
* 获取{@link com.aliyun.odps.Odps}实例
*
* @return {@link com.aliyun.odps.Odps}实例
*/
public Odps getOdps() {
return odps;
}
/**
* 设置{@link com.aliyun.odps.Odps}实例
*
* @param odps
* {@link com.aliyun.odps.Odps}实例
*/
public void setOdps(Odps odps) {
this.odps = odps;
if (internalCli) {
odps.getRestClient().setIgnoreCerts(true);
}
}
/**
* 设置默认的默认的{@link com.aliyun.odps.mapred.conf.JobConf}。默认的
* {@link com.aliyun.odps.mapred.conf.JobConf}的默认构方法会载入这个默认配置。
*
* @return 默认的默认的{@link com.aliyun.odps.mapred.conf.JobConf}
*/
public JobConf getDefaultJob() {
return defaultJob;
}
/**
* 返回local run模式
*
* @return true如果是local run模式
*/
public boolean isLocalRun() {
return isLocalRun;
}
/**
* 设置local run模式
*
* @param b
* true如果需要local run
*/
public void setLocalRun(boolean b) {
isLocalRun = b;
}
/**
* 设置是否是计费预估模式
*
* @param b
* true如果是计费预估模式
*/
public void setCostMode(boolean b) {
isCostMode = b;
}
/**
* 返回是否是计费预估模式
*
* @return true如果是计费预估模式
*/
public boolean isCostMode() {
return isCostMode;
}
/**
* 设置默认的默认的{@link com.aliyun.odps.mapred.conf.JobConf}
*
* @param defaultJob
*/
public void setDefaultJob(JobConf defaultJob) {
this.defaultJob = defaultJob;
}
/**
* 设置Alias
*
* @param aliases
*/
public void setAliases(Map<String, String> aliases) {
this.aliases = aliases;
}
/**
* 返回Alias
*/
public Map<String, String> getAliases() {
return aliases;
}
private void handleLocalMR(Properties prop) {
String tempDir = prop.getProperty(LOCAL_TEMP_DIR);
if (tempDir != null && !tempDir.isEmpty()) {
defaultJob.set(LOCAL_TEMP_DIR, tempDir);
}
String tempRetain = prop.getProperty(LOCAL_TEMP_RETAIN);
if (tempRetain != null && tempRetain.trim().equalsIgnoreCase("false")) {
defaultJob.setBoolean(LOCAL_TEMP_RETAIN, false);
} else {
defaultJob.setBoolean(LOCAL_TEMP_RETAIN, true);
}
String limitDownloadCount = prop.getProperty(LOCAL_RECORD_LIMIT);
if (limitDownloadCount != null && !limitDownloadCount.isEmpty()) {
try {
int limit = Integer.parseInt(limitDownloadCount);
if (limit > 0) {
defaultJob.setInt(LOCAL_RECORD_LIMIT, limit);
}
} catch (Exception e) {
}
}
String downloadMode = prop.getProperty(LOCAL_DOWNLOAD_MODE, "AUTO");
defaultJob.set(LOCAL_DOWNLOAD_MODE, downloadMode);
String inputColumnSeperator = prop.getProperty(LOCAL_INPUT_COLUMN_SEPERATOR);
if (inputColumnSeperator != null && !inputColumnSeperator.isEmpty()) {
defaultJob.set(LOCAL_INPUT_COLUMN_SEPERATOR, inputColumnSeperator);
}
String outputColumnSeperator = prop.getProperty(LOCAL_OUTPUT_COLUMN_SEPERATOR);
if (outputColumnSeperator != null && !outputColumnSeperator.isEmpty()) {
defaultJob.set(LOCAL_OUTPUT_COLUMN_SEPERATOR, outputColumnSeperator);
}
boolean isSecurityEnabled =
prop.getProperty(LOCAL_SECURITY_ENABLE, "false").equalsIgnoreCase("true");
if (isSecurityEnabled) {
boolean isJNIEnabled =
prop.getProperty(LOCAL_SECURITY_JNI_ENABLE, "false").equalsIgnoreCase("true");
String userDefinePolicy = prop.getProperty(LOCAL_USER_DEFINE_POLICY, "");
defaultJob.setBoolean(LOCAL_SECURITY_ENABLE, isSecurityEnabled);
defaultJob.setBoolean(LOCAL_SECURITY_JNI_ENABLE, isJNIEnabled);
defaultJob.set(LOCAL_USER_DEFINE_POLICY, userDefinePolicy);
}
}
public String getCommandText() {
return commandText;
}
public void setCommandText(String commandText) {
this.commandText = stripNonValidXMLCharacters(commandText);
}
/**
* This method ensures that the output String has only valid XML unicode characters as specified
* by the XML 1.0 standard. For reference, please see <a
* href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the standard</a>. This method will
* return an empty String if the input is null or empty.
*
* @param in The String whose non-valid characters we want to remove.
* @return The in String, stripped of non-valid characters.
*/
private String stripNonValidXMLCharacters(String in) {
StringBuilder out = new StringBuilder();
char current;
if (in == null || ("".equals(in)))
return "";
for (int i = 0; i < in.length(); i++) {
current = in.charAt(i);
if ((current == 0x9) || (current == 0xA) || (current == 0xD)
|| ((current >= 0x20) && (current <= 0xD7FF))
|| ((current >= 0xE000) && (current <= 0xFFFD)))
out.append(current);
}
return out.toString();
}
}