/** * 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 org.apache.falcon.expression; import org.apache.commons.el.ExpressionEvaluatorImpl; import org.apache.falcon.FalconException; import org.apache.falcon.entity.common.FeedDataPath; import org.apache.falcon.util.CalendarUnit; import org.apache.falcon.util.DateUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.jsp.el.ELException; import javax.servlet.jsp.el.ExpressionEvaluator; import javax.servlet.jsp.el.FunctionMapper; import javax.servlet.jsp.el.VariableResolver; import java.lang.reflect.Method; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Properties; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper for evaluating expressions. */ public final class ExpressionHelper implements FunctionMapper, VariableResolver { private static final Logger LOG = LoggerFactory.getLogger(ExpressionHelper.class); private static final ExpressionHelper INSTANCE = new ExpressionHelper(); private static final ThreadLocal<Properties> THREAD_VARIABLES = new ThreadLocal<Properties>(); private static final Pattern SYS_PROPERTY_PATTERN = Pattern.compile("\\$\\{[A-Za-z0-9_.]+\\}"); private static final ExpressionEvaluator EVALUATOR = new ExpressionEvaluatorImpl(); private static final ExpressionHelper RESOLVER = ExpressionHelper.get(); public static final ThreadLocal<SimpleDateFormat> FORMATTER = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); format.setTimeZone(TimeZone.getTimeZone("UTC")); return format; } }; public static ExpressionHelper get() { return INSTANCE; } private enum DayOfWeek { SUN, MON, TUE, WED, THU, FRI, SAT } private ExpressionHelper() { } public <T> T evaluate(String expression, Class<T> clazz) throws FalconException { return evaluateFullExpression("${" + expression + "}", clazz); } @SuppressWarnings("unchecked") public <T> T evaluateFullExpression(String expression, Class<T> clazz) throws FalconException { try { return (T) EVALUATOR.evaluate(expression, clazz, RESOLVER, RESOLVER); } catch (ELException e) { throw new FalconException("Unable to evaluate " + expression, e); } } @Override public Method resolveFunction(String prefix, String name) { for (Method method : ExpressionHelper.class.getDeclaredMethods()) { if (method.getName().equals(name)) { return method; } } throw new UnsupportedOperationException("Not found " + prefix + ":" + name); } public void setPropertiesForVariable(Properties properties) { THREAD_VARIABLES.set(properties); } @Override public Object resolveVariable(String field) { return THREAD_VARIABLES.get().get(field); } private static ThreadLocal<Date> referenceDate = new ThreadLocal<Date>(); public static void setReferenceDate(Date date) { referenceDate.set(date); Properties variables = getTimeVariables(date, TimeZone.getTimeZone("UTC")); THREAD_VARIABLES.set(variables); } public static Properties getTimeVariables(Date date, TimeZone tz) { Properties vars = new Properties(); Calendar cal = Calendar.getInstance(tz); cal.setTime(date); vars.put(FeedDataPath.VARS.YEAR.name(), String.format("%04d", cal.get(Calendar.YEAR))); vars.put(FeedDataPath.VARS.MONTH.name(), String.format("%02d", (cal.get(Calendar.MONTH) + 1))); vars.put(FeedDataPath.VARS.DAY.name(), String.format("%02d", cal.get(Calendar.DAY_OF_MONTH))); vars.put(FeedDataPath.VARS.HOUR.name(), String.format("%02d", cal.get(Calendar.HOUR_OF_DAY))); vars.put(FeedDataPath.VARS.MINUTE.name(), String.format("%02d", cal.get(Calendar.MINUTE))); return vars; } private static int getDayOffset(String weekDayName) { int day; Calendar nominalTime = Calendar.getInstance(); nominalTime.setTimeZone(TimeZone.getTimeZone("UTC")); nominalTime.setTime(referenceDate.get()); int currentWeekDay = nominalTime.get(Calendar.DAY_OF_WEEK); int weekDay = DayOfWeek.valueOf(weekDayName).ordinal() + 1; //to map to Calendar.SUNDAY ... day = weekDay - currentWeekDay; if (weekDay > currentWeekDay) { day = day - 7; } return day; } @edu.umd.cs.findbugs.annotations.SuppressWarnings({"SF_SWITCH_FALLTHROUGH"}) private static Date getRelative(Date date, int boundary, int month, int day, int hour, int minute) { Calendar dsInstanceCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); dsInstanceCal.setTime(date); switch (boundary) { case Calendar.YEAR: dsInstanceCal.set(Calendar.MONTH, 0); case Calendar.MONTH: dsInstanceCal.set(Calendar.DAY_OF_MONTH, 1); case Calendar.DAY_OF_MONTH: dsInstanceCal.set(Calendar.HOUR_OF_DAY, 0); case Calendar.HOUR: dsInstanceCal.set(Calendar.MINUTE, 0); dsInstanceCal.set(Calendar.SECOND, 0); dsInstanceCal.set(Calendar.MILLISECOND, 0); break; case Calendar.SECOND: break; default: throw new IllegalArgumentException("Invalid boundary " + boundary); } dsInstanceCal.add(Calendar.YEAR, 0); dsInstanceCal.add(Calendar.MONTH, month); dsInstanceCal.add(Calendar.DAY_OF_MONTH, day); dsInstanceCal.add(Calendar.HOUR_OF_DAY, hour); dsInstanceCal.add(Calendar.MINUTE, minute); return dsInstanceCal.getTime(); } public static Date now(int hour, int minute) { return getRelative(referenceDate.get(), Calendar.SECOND, 0, 0, hour, minute); } public static Date today(int hour, int minute) { return getRelative(referenceDate.get(), Calendar.DAY_OF_MONTH, 0, 0, hour, minute); } public static Date yesterday(int hour, int minute) { return getRelative(referenceDate.get(), Calendar.DAY_OF_MONTH, 0, -1, hour, minute); } public static Date currentMonth(int day, int hour, int minute) { return getRelative(referenceDate.get(), Calendar.MONTH, 0, day, hour, minute); } public static Date lastMonth(int day, int hour, int minute) { return getRelative(referenceDate.get(), Calendar.MONTH, -1, day, hour, minute); } public static Date currentWeek(String weekDay, int hour, int minute) { int day = getDayOffset(weekDay); return getRelative(referenceDate.get(), Calendar.DAY_OF_MONTH, 0, day, hour, minute); } public static Date lastWeek(String weekDay, int hour, int minute) { int day = getDayOffset(weekDay); return getRelative(referenceDate.get(), Calendar.DAY_OF_MONTH, 0, day - 7, hour, minute); } public static Date currentYear(int month, int day, int hour, int minute) { return getRelative(referenceDate.get(), Calendar.YEAR, month, day, hour, minute); } public static Date lastYear(int month, int day, int hour, int minute) { return getRelative(referenceDate.get(), Calendar.YEAR, month - 12, day, hour, minute); } public static Date latest(int n) { //by pass Falcon validations return referenceDate.get(); } public static Date future(int n, int limit) { //by pass Falcon validations return referenceDate.get(); } public static long hours(int val) { return TimeUnit.HOURS.toMillis(val); } public static long minutes(int val) { return TimeUnit.MINUTES.toMillis(val); } public static long days(int val) { return TimeUnit.DAYS.toMillis(val); } public static long months(int val) { return val * days(31); } public static long years(int val) { return val * days(366); } public static String substitute(String originalValue) { return substitute(originalValue, System.getProperties()); } public static String substitute(String originalValue, Properties properties) { Matcher envVarMatcher = SYS_PROPERTY_PATTERN.matcher(originalValue); while (envVarMatcher.find()) { String envVar = originalValue.substring(envVarMatcher.start() + 2, envVarMatcher.end() - 1); String envVal = properties.getProperty(envVar, System.getenv(envVar)); envVar = "\\$\\{" + envVar + "\\}"; if (envVal != null) { originalValue = originalValue.replaceAll(envVar, Matcher.quoteReplacement(envVal)); envVarMatcher = SYS_PROPERTY_PATTERN.matcher(originalValue); } } return originalValue; } /** * Converts date string to required format. * @param dateTimeStr * @param format * @return * @throws ParseException */ public static String formatTime(String dateTimeStr, String format) throws ParseException { Date dateTime = DateUtil.parseDateFalconTZ(dateTimeStr); return DateUtil.formatDateCustom(dateTime, format); } /** * Formats the instance and return. * @return */ public static String instanceTime() { return DateUtil.formatDateFalconTZ(referenceDate.get()); } /** * EL function calculates date based on the following equation : newDate = baseDate + instance, * timeUnit. * @param strBaseDate * @param offset * @param unit * @return * @throws Exception */ public static String dateOffset(String strBaseDate, int offset, String unit) throws Exception { Calendar baseCalDate = DateUtil.getCalendar(strBaseDate); StringBuilder buffer = new StringBuilder(); baseCalDate.add(CalendarUnit.valueOf(unit).getCalendarUnit(), offset); buffer.append(DateUtil.formatDateFalconTZ(baseCalDate)); return buffer.toString(); } public static String user() { return "${user.name}"; } }