/** * Copyright 2016 vip.com. * <p> * 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. * </p> */ package com.vip.saturn.job.internal.config; import java.text.ParseException; import java.util.*; import com.vip.saturn.job.basic.SaturnConstant; import org.codehaus.jackson.map.type.MapType; import org.codehaus.jackson.map.type.TypeFactory; import org.quartz.CronExpression; import com.google.common.base.Strings; import com.vip.saturn.job.basic.AbstractSaturnService; import com.vip.saturn.job.basic.JobScheduler; import com.vip.saturn.job.exception.SaturnJobException; import com.vip.saturn.job.exception.ShardingItemParametersException; import com.vip.saturn.job.utils.JsonUtils; /** * 弹性化分布式作业配置服务. * * */ public class ConfigurationService extends AbstractSaturnService { private static final String DOUBLE_QUOTE = "\""; //参考http://stackoverflow.com/questions/17963969/java-regex-pattern-split-commna private static final String PATTERN = ",(?=(([^\"]*\"){2})*[^\"]*$)"; private MapType customContextType = TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, String.class); private TimeZone jobTimeZone; public ConfigurationService(JobScheduler jobScheduler) { super(jobScheduler); } /** * 获取作业分片总数. * * @return 作业分片总数 */ public int getShardingTotalCount() { return jobConfiguration.getShardingTotalCount(); } public boolean isLocalMode() { return jobConfiguration.isLocalMode(); } /** * 获取分片序列号和个性化参数对照表.<br> * 如果是本地模式的作业,则获取到[-1=xx] * * @return 分片序列号和个性化参数对照表 */ public Map<Integer, String> getShardingItemParameters() { Map<Integer, String> result = new HashMap<>(); String value = jobConfiguration.getShardingItemParameters(); if (Strings.isNullOrEmpty(value)) { return result; } //解释命令行参数 String[] shardingItemParameters = value.split(PATTERN); Map<String, String> result0 = new HashMap<>(shardingItemParameters.length); for (String each : shardingItemParameters) { String item = ""; String exec = ""; int index = each.indexOf("="); if (index > -1) { item = each.substring(0, index).trim(); exec = each.substring(index + 1, each.length()).trim(); //去掉前后的双引号" if (exec.startsWith(DOUBLE_QUOTE)) { exec = exec.substring(1); } if (exec.endsWith(DOUBLE_QUOTE)) { exec = exec.substring(0, exec.length() - 1); } } else { throw new ShardingItemParametersException("Sharding item parameters '%s' format error", value); } result0.put(item, exec); } if(isLocalMode()) { if(result0.containsKey("*")) { result.put(-1, result0.get("*")); } else { throw new ShardingItemParametersException("Sharding item parameters '%s' format error with local mode job, should be *=xx", value); } } else { Iterator<Map.Entry<String, String>> iterator = result0.entrySet().iterator(); while(iterator.hasNext()) { Map.Entry<String, String> next = iterator.next(); String item = next.getKey(); String exec = next.getValue(); try { result.put(Integer.parseInt(item), exec); } catch (final NumberFormatException ex) { throw new ShardingItemParametersException("Sharding item parameters key '%s' is not an integer.", item); } } } return result; } /** * 获取作业自定义参数. * * @return 作业自定义参数 */ public String getJobParameter() { return jobConfiguration.getJobParameter(); } /** * 获取作业时区字符串 */ public String getTimeZoneStr() { String timeZone = jobConfiguration.getTimeZone(); if(timeZone == null || timeZone.trim().isEmpty()) { return SaturnConstant.TIME_ZONE_ID_DEFAULT; } return timeZone; } /** * 获取作业时区对象 */ public TimeZone getTimeZone() { String timeZoneStr = jobConfiguration.getTimeZone(); if(timeZoneStr == null || timeZoneStr.trim().isEmpty()) { timeZoneStr = SaturnConstant.TIME_ZONE_ID_DEFAULT; } if(jobTimeZone != null && timeZoneStr.equals(jobTimeZone.getID())) { return jobTimeZone; } else { jobTimeZone = TimeZone.getTimeZone(timeZoneStr); return jobTimeZone; } } /** * 获取作业启动时间的cron表达式. * * @return 作业启动时间的cron表达式 */ public String getCron() { return jobConfiguration.getCron(); } /** * 更新作业的cron表达式 * @param jobName 作业名 * @param cron cron表达式 * @param customContext 自定义上下文 * @throws SaturnJobException 可能抛的异常有:type为0,表示cron表达式无效;type为1,表示作业名在这个namespace下不存在;type为3,表示customContext内容超出1M。 */ public void updateJobCron(String jobName, String cron, Map<String, String> customContext) throws SaturnJobException { String cron0 = cron; if(cron0 != null && !cron0.trim().isEmpty()) { try { cron0 = cron0.trim(); CronExpression.validateExpression(cron0); } catch (ParseException e) { throw new SaturnJobException(SaturnJobException.CRON_VALID, "The cron expression is valid: " + cron); } } else { cron0 = ""; } if(getJobNodeStorage().isJobExisted(jobName)) { String oldCustomContextStr = getJobNodeStorage().getJobNodeDataDirectly(jobName, ConfigurationNode.CUSTOME_CONTEXT); Map<String, String> oldCustomContextMap = toCustomContext(oldCustomContextStr); if(customContext != null && !customContext.isEmpty()) { oldCustomContextMap.putAll(customContext); String newCustomContextStr = toCustomContext(oldCustomContextMap); if(newCustomContextStr.getBytes().length > 1024 * 1024) { throw new SaturnJobException(SaturnJobException.OUT_OF_ZK_LIMIT_MEMORY, "The all customContext is out of zk limit memory(1M)"); } getJobNodeStorage().replaceJobNode(jobName, ConfigurationNode.CUSTOME_CONTEXT, newCustomContextStr); } String oldCron = getJobNodeStorage().getJobNodeDataDirectly(jobName, ConfigurationNode.CRON); if(cron0 != null && oldCron != null && !cron0.equals(oldCron.trim())) { getJobNodeStorage().updateJobNode(jobName, ConfigurationNode.CRON, cron0); } } else { throw new SaturnJobException(SaturnJobException.JOB_NOT_FOUND, "The job is not found: " + jobName); } } /** * 获取统计作业处理数据数量的间隔时间. * * @return 统计作业处理数据数量的间隔时间 */ public int getProcessCountIntervalSeconds() { return jobConfiguration.getProcessCountIntervalSeconds(); } /** * 本机当前时间是否在作业暂停时间段范围内。 * <p>特别的,无论pausePeriodDate,还是pausePeriodTime,如果解析发生异常,则忽略该节点,视为没有配置该日期或时分段。 * * @return 本机当前时间是否在作业暂停时间段范围内. */ public boolean isInPausePeriod() { return isInPausePeriod(new Date()); } /** * 该时间是否在作业暂停时间段范围内。 * <p>特别的,无论pausePeriodDate,还是pausePeriodTime,如果解析发生异常,则忽略该节点,视为没有配置该日期或时分段。 * * @param date 时间,本机时区的时间 * * @return 该时间是否在作业暂停时间段范围内。 */ public boolean isInPausePeriod(Date date) { Calendar calendar = Calendar.getInstance(getTimeZone()); calendar.setTime(date); int M = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH begin from 0. int d = calendar.get(Calendar.DAY_OF_MONTH); int h = calendar.get(Calendar.HOUR_OF_DAY); int m = calendar.get(Calendar.MINUTE); boolean dateIn = false; String pausePeriodDate = jobConfiguration.getPausePeriodDate(); boolean pausePeriodDateIsEmpty = (pausePeriodDate==null || pausePeriodDate.trim().isEmpty()); if(!pausePeriodDateIsEmpty){ String[] periodsDate = pausePeriodDate.split(","); if (periodsDate != null) { for (String period : periodsDate) { String[] tmp = period.trim().split("-"); if (tmp != null && tmp.length == 2) { String left = tmp[0].trim(); String right = tmp[1].trim(); String[] MdLeft = left.split("/"); String[] MdRight = right.split("/"); if (MdLeft != null && MdLeft.length == 2 && MdRight != null && MdRight.length == 2) { try { int MLeft = Integer.parseInt(MdLeft[0]); int dLeft = Integer.parseInt(MdLeft[1]); int MRight = Integer.parseInt(MdRight[0]); int dRight = Integer.parseInt(MdRight[1]); dateIn = (M > MLeft || M == MLeft && d >= dLeft) && (M < MRight || M == MRight && d <= dRight);//NOSONAR if (dateIn) { break; } } catch (NumberFormatException e) { dateIn = false; break; } } else { dateIn = false; break; } } else { dateIn = false; break; } } } } boolean timeIn = false; String pausePeriodTime = jobConfiguration.getPausePeriodTime(); boolean pausePeriodTimeIsEmpty = (pausePeriodTime==null||pausePeriodTime.trim().isEmpty()); if(!pausePeriodTimeIsEmpty){ String[] periodsTime = pausePeriodTime.split(","); if (periodsTime != null) { for (String period : periodsTime) { String[] tmp = period.trim().split("-"); if (tmp != null && tmp.length == 2) { String left = tmp[0].trim(); String right = tmp[1].trim(); String[] hmLeft = left.split(":"); String[] hmRight = right.split(":"); if (hmLeft != null && hmLeft.length == 2 && hmRight != null && hmRight.length == 2) { try { int hLeft = Integer.parseInt(hmLeft[0]); int mLeft = Integer.parseInt(hmLeft[1]); int hRight = Integer.parseInt(hmRight[0]); int mRight = Integer.parseInt(hmRight[1]); timeIn = (h > hLeft || h == hLeft && m >= mLeft) && (h < hRight || h == hRight && m <= mRight);//NOSONAR if (timeIn) { break; } } catch (NumberFormatException e) { timeIn = false; break; } } else { timeIn = false; break; } } else { timeIn = false; break; } } } } if(pausePeriodDateIsEmpty) { if(pausePeriodTimeIsEmpty) { return false; } else { return timeIn; } } else { if(pausePeriodTimeIsEmpty) { return dateIn; } else { return dateIn && timeIn; } } } /** * 获取是否开启失效转移. * * @return 是否开启失效转移 */ public boolean isFailover() { return jobConfiguration.isFailover(); } /** * 获取是否开启作业. * * @return 作业是否开启 */ public boolean isJobEnabled() { return jobConfiguration.isEnabled(); } /** * 获取超时时间 * * @return 超时时间 */ public int getTimeoutSeconds(){ return jobConfiguration.getTimeoutSeconds(); } /** * 获取是否显示正常日志 * @return 是否显示正常日志 */ public boolean showNormalLog() { return jobConfiguration.isShowNormalLog(); } /** * 获取自定义上下文 * @return 获取自定义上下文 */ public Map<String, String> getCustomContext() { String jobNodeData = getJobNodeStorage().getJobNodeData(ConfigurationNode.CUSTOME_CONTEXT); return toCustomContext(jobNodeData); } /** * 将str转为map * @param customContextStr str字符串 * @return 自定义上下文map */ private Map<String, String> toCustomContext(String customContextStr) { Map<String, String> customContext = null; if(customContextStr != null) { customContext = JsonUtils.fromJSON(customContextStr, customContextType); } if(customContext == null) { customContext = new HashMap<>(); } return customContext; } /** * 将map转为str字符串 * @param customContextMap 自定义上下文map * @return 自定义上下文str */ private String toCustomContext(Map<String, String> customContextMap) { String result = JsonUtils.toJSON(customContextMap); if(result == null) { result = ""; } return result.trim(); } public String getRawJobType(){ return jobConfiguration.getJobType(); } /** * 作业接收的queue名字 */ public String getQueueName() { return jobConfiguration.getQueueName(); } /** * 执行作业发送的channel名字 */ public String getChannelName() { return jobConfiguration.getChannelName(); } public List<String> getPreferList(){ List<String> executorList = new ArrayList<String>(); String prefer = jobConfiguration.getPreferList(); if(prefer == null || prefer.isEmpty()){ return executorList; } String[] executors = prefer.split(","); if(executors.length == 0){ return executorList; } for(String executor:executors){ executorList.add(executor); } return executorList; } public boolean isUseDispreferList() { return jobConfiguration.isUseDispreferList(); } }