diff --git a/NOTICE b/NOTICE
index a1c5656ff47..1c13133b8f8 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
Apache KIE
-Copyright 2023 The Apache Software Foundation
+Copyright 2023-2024 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/calendar/BusinessCalendar.java b/api/kogito-api/src/main/java/org/kie/kogito/calendar/BusinessCalendar.java
index 6e15a6cc0e6..45ecce21bd4 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/calendar/BusinessCalendar.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/calendar/BusinessCalendar.java
@@ -27,18 +27,21 @@
public interface BusinessCalendar {
/**
- * Calculates given time expression into duration in milliseconds based on calendar configuration.
- *
+ * Returns the difference, in milliseconds, between the business date that matches the given
+ * timeExpression
, and the current time.
+ * See {@link #calculateBusinessTimeAsDate} for business date calculation
+ *
* @param timeExpression time expression that is supported by business calendar implementation.
* @return duration expressed in milliseconds
*/
- public long calculateBusinessTimeAsDuration(String timeExpression);
+ long calculateBusinessTimeAsDuration(String timeExpression);
/**
- * Calculates given time expression into target date based on calendar configuration.
+ * Returns the first Date
that matches the given timeExpression
and falls
+ * into the business calendar working hours.
*
* @param timeExpression time expression that is supported by business calendar implementation.
* @return date when given time expression will match in the future
*/
- public Date calculateBusinessTimeAsDate(String timeExpression);
+ Date calculateBusinessTimeAsDate(String timeExpression);
}
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/BusinessCalendarImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/BusinessCalendarImpl.java
index fbb650e3c35..280cc3f6e3f 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/BusinessCalendarImpl.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/BusinessCalendarImpl.java
@@ -18,48 +18,37 @@
*/
package org.jbpm.process.core.timer;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
-import java.util.Properties;
import java.util.TimeZone;
import java.util.regex.Matcher;
import org.jbpm.util.PatternConstants;
import org.kie.kogito.calendar.BusinessCalendar;
-import org.kie.kogito.timer.SessionClock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static org.jbpm.process.core.constants.CalendarConstants.BUSINESS_CALENDAR_PATH;
-
/**
* Default implementation of BusinessCalendar interface that is configured with properties.
* Following are supported properties:
*
- * - business.days.per.week - specifies number of working days per week (default 5)
- * - business.hours.per.day - specifies number of working hours per day (default 8)
- * - business.start.hour - specifies starting hour of work day (default 9AM)
- * - business.end.hour - specifies ending hour of work day (default 5PM)
+ * - business.start.hour - specifies starting hour of work day (mandatory)
+ * - business.end.hour - specifies ending hour of work day (mandatory)
* - business.holidays - specifies holidays (see format section for details on how to configure it)
* - business.holiday.date.format - specifies holiday date format used (default yyyy-MM-dd)
- * - business.weekend.days - specifies days of the weekend (default Saturday and Sunday)
+ * - business.weekend.days - specifies days of the weekend (default Saturday (7) and Sunday (1), use 0 to indicate no weekend days)
* - business.cal.timezone - specifies time zone to be used (if not given uses default of the system it runs on)
*
- *
+ *
* Format
- *
+ *
* Holidays can be given in two formats:
*
* - as date range separated with colon - for instance 2012-05-01:2012-05-15
@@ -68,28 +57,29 @@
* each holiday period should be separated from next one with comma: 2012-05-01:2012-05-15,2012-12-24:2012-12-27
*
* Holiday date format must be given in pattern that is supported by java.text.SimpleDateFormat
.
- *
- * Weekend days should be given as integer that corresponds to java.util.Calendar
constants.
+ *
+ * Weekend days should be given as integer that corresponds to java.util.Calendar
constants, use 0 to indicate no weekend days
*
- *
*/
public class BusinessCalendarImpl implements BusinessCalendar {
private static final Logger logger = LoggerFactory.getLogger(BusinessCalendarImpl.class);
- private Properties businessCalendarConfiguration;
-
private static final long HOUR_IN_MILLIS = 60 * 60 * 1000;
- private int daysPerWeek;
- private int hoursInDay;
- private int startHour;
- private int endHour;
- private String timezone;
+ private final int daysPerWeek;
+ private final int hoursInDay;
+ private final int startHour;
+ private final int endHour;
+ private final String timezone;
- private List holidays;
- private List weekendDays = new ArrayList<>();
- private SessionClock clock;
+ private final List holidays;
+ private final List weekendDays;
+
+ /**
+ * Testing calendar used only for testing purposes
+ */
+ private final Calendar testingCalendar;
private static final int SIM_WEEK = 3;
private static final int SIM_DAY = 5;
@@ -97,8 +87,6 @@ public class BusinessCalendarImpl implements BusinessCalendar {
private static final int SIM_MIN = 9;
private static final int SIM_SEC = 11;
- public static final String DAYS_PER_WEEK = "business.days.per.week";
- public static final String HOURS_PER_DAY = "business.hours.per.day";
public static final String START_HOUR = "business.start.hour";
public static final String END_HOUR = "business.end.hour";
// holidays are given as date range and can have more than one value separated with comma
@@ -108,95 +96,53 @@ public class BusinessCalendarImpl implements BusinessCalendar {
public static final String WEEKEND_DAYS = "business.weekend.days";
public static final String TIMEZONE = "business.cal.timezone";
- public BusinessCalendarImpl() {
- this(null);
+ public static Builder builder() {
+ return new Builder();
}
- public BusinessCalendarImpl(Properties configuration) {
- this(configuration, null);
+ /**
+ *
+ * @param testingCalendar is used only for testing purpose. It is null
in production and
+ * during normal execution
+ */
+ private BusinessCalendarImpl(Calendar testingCalendar) {
+ this(CalendarBeanFactory.createCalendarBean(), testingCalendar);
}
- public BusinessCalendarImpl(Properties configuration, SessionClock clock) {
- this.clock = clock;
- if (configuration == null) {
- businessCalendarConfiguration = new Properties();
- URL resource = Thread.currentThread().getContextClassLoader().getResource(BUSINESS_CALENDAR_PATH);
- if (Objects.nonNull(resource)) {
- try (InputStream is = resource.openStream()) {
- businessCalendarConfiguration.load(is);
- } catch (IOException e) {
- logger.error("Error while loading properties for business calendar", e);
- throw new RuntimeException("Error while loading properties for business calendar", e);
- }
- }
-
- } else {
- this.businessCalendarConfiguration = configuration;
- }
- init();
- }
-
- protected void init() {
- daysPerWeek = getPropertyAsInt(DAYS_PER_WEEK, "5");
- hoursInDay = getPropertyAsInt(HOURS_PER_DAY, "8");
- startHour = getPropertyAsInt(START_HOUR, "9");
- endHour = getPropertyAsInt(END_HOUR, "17");
- holidays = parseHolidays();
- parseWeekendDays();
- this.timezone = businessCalendarConfiguration.getProperty(TIMEZONE);
- }
-
- protected String adoptISOFormat(String timeExpression) {
-
- try {
- Duration p = null;
- if (DateTimeUtils.isPeriod(timeExpression)) {
- p = Duration.parse(timeExpression);
- } else if (DateTimeUtils.isNumeric(timeExpression)) {
- p = Duration.of(Long.valueOf(timeExpression), ChronoUnit.MILLIS);
- } else {
- OffsetDateTime dateTime = OffsetDateTime.parse(timeExpression, DateTimeFormatter.ISO_DATE_TIME);
- p = Duration.between(OffsetDateTime.now(), dateTime);
- }
-
- long days = p.toDays();
- long hours = p.toHours() % 24;
- long minutes = p.toMinutes() % 60;
- long seconds = p.getSeconds() % 60;
- long milis = p.toMillis() % 1000;
-
- StringBuffer time = new StringBuffer();
- if (days > 0) {
- time.append(days + "d");
- }
- if (hours > 0) {
- time.append(hours + "h");
- }
- if (minutes > 0) {
- time.append(minutes + "m");
- }
- if (seconds > 0) {
- time.append(seconds + "s");
- }
- if (milis > 0) {
- time.append(milis + "ms");
- }
-
- return time.toString();
- } catch (Exception e) {
- return timeExpression;
- }
+ private BusinessCalendarImpl(CalendarBean calendarBean, Calendar testingCalendar) {
+ holidays = calendarBean.getHolidays();
+ weekendDays = calendarBean.getWeekendDays();
+ daysPerWeek = calendarBean.getDaysPerWeek();
+ timezone = calendarBean.getTimezone();
+ startHour = calendarBean.getStartHour();
+ endHour = calendarBean.getEndHour();
+ hoursInDay = calendarBean.getHoursInDay();
+ this.testingCalendar = testingCalendar;
+ logger.debug("\tholidays: {},\n\tweekendDays: {},\n\tdaysPerWeek: {},\n\ttimezone: {},\n\tstartHour: {},\n\tendHour: {},\n\thoursInDay: {}",
+ holidays, weekendDays, daysPerWeek, timezone, startHour, endHour, hoursInDay);
}
+ /**
+ * @inheritDoc
+ */
+ @Override
public long calculateBusinessTimeAsDuration(String timeExpression) {
+ logger.trace("timeExpression {}", timeExpression);
timeExpression = adoptISOFormat(timeExpression);
Date calculatedDate = calculateBusinessTimeAsDate(timeExpression);
+ logger.debug("calculatedDate: {}, currentTime: {}, timeExpression: {}, Difference: {} ms",
+ calculatedDate, new Date(getCurrentTime()), timeExpression, calculatedDate.getTime() - getCurrentTime());
return (calculatedDate.getTime() - getCurrentTime());
}
+ /**
+ * @inheritDoc
+ */
+ @Override
public Date calculateBusinessTimeAsDate(String timeExpression) {
+ logger.trace("timeExpression {}", timeExpression);
timeExpression = adoptISOFormat(timeExpression);
String trimmed = timeExpression.trim();
@@ -206,7 +152,7 @@ public Date calculateBusinessTimeAsDate(String timeExpression) {
int min = 0;
int sec = 0;
- if (trimmed.length() > 0) {
+ if (!trimmed.isEmpty()) {
Matcher mat = PatternConstants.SIMPLE_TIME_DATE_MATCHER.matcher(trimmed);
if (mat.matches()) {
weeks = (mat.group(SIM_WEEK) != null) ? Integer.parseInt(mat.group(SIM_WEEK)) : 0;
@@ -216,115 +162,208 @@ public Date calculateBusinessTimeAsDate(String timeExpression) {
sec = (mat.group(SIM_SEC) != null) ? Integer.parseInt(mat.group(SIM_SEC)) : 0;
}
}
+ logger.trace("weeks: {}", weeks);
+ logger.trace("days: {}", days);
+ logger.trace("hours: {}", hours);
+ logger.trace("min: {}", min);
+ logger.trace("sec: {}", sec);
int time = 0;
- Calendar c = new GregorianCalendar();
+ Calendar calendar = getCalendar();
+ logger.trace("calendar selected for business calendar: {}", calendar.getTime());
if (timezone != null) {
- c.setTimeZone(TimeZone.getTimeZone(timezone));
- }
- if (this.clock != null) {
- c.setTimeInMillis(this.clock.getCurrentTime());
+ calendar.setTimeZone(TimeZone.getTimeZone(timezone));
}
// calculate number of weeks
int numberOfWeeks = days / daysPerWeek + weeks;
+ logger.trace("number of weeks: {}", numberOfWeeks);
if (numberOfWeeks > 0) {
- c.add(Calendar.WEEK_OF_YEAR, numberOfWeeks);
+ calendar.add(Calendar.WEEK_OF_YEAR, numberOfWeeks);
}
- handleWeekend(c, hours > 0 || min > 0);
+ logger.trace("calendar WEEK_OF_YEAR: {}", calendar.get(Calendar.WEEK_OF_YEAR));
+ rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(calendar, weekendDays, hours > 0 || min > 0);
hours += (days - (numberOfWeeks * daysPerWeek)) * hoursInDay;
// calculate number of days
int numberOfDays = hours / hoursInDay;
+ logger.trace("numberOfDays: {}", numberOfDays);
if (numberOfDays > 0) {
for (int i = 0; i < numberOfDays; i++) {
- c.add(Calendar.DAY_OF_YEAR, 1);
- handleWeekend(c, false);
- handleHoliday(c, hours > 0 || min > 0);
+ calendar.add(Calendar.DAY_OF_YEAR, 1);
+ boolean resetTime = false;
+ rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(calendar, weekendDays, resetTime);
+ logger.trace("calendar after rolling to next working day: {} when number of days > 0", calendar.getTime());
+ rollCalendarAfterHolidays(calendar, holidays, weekendDays, hours > 0 || min > 0);
+ logger.trace("calendar after holidays when number of days > 0: {}", calendar.getTime());
}
}
-
- int currentCalHour = c.get(Calendar.HOUR_OF_DAY);
- if (currentCalHour >= endHour) {
- c.add(Calendar.DAY_OF_YEAR, 1);
- c.add(Calendar.HOUR_OF_DAY, startHour - currentCalHour);
- c.set(Calendar.MINUTE, 0);
- c.set(Calendar.SECOND, 0);
- } else if (currentCalHour < startHour) {
- c.add(Calendar.HOUR_OF_DAY, startHour);
- }
+ int currentCalHour = calendar.get(Calendar.HOUR_OF_DAY);
+ boolean resetMinuteSecond = currentCalHour >= endHour || currentCalHour < startHour;
+ rollCalendarToWorkingHour(calendar, resetMinuteSecond);
+ logger.trace("calendar after rolling to working hour: {}", calendar.getTime());
// calculate remaining hours
time = hours - (numberOfDays * hoursInDay);
- c.add(Calendar.HOUR, time);
- handleWeekend(c, true);
- handleHoliday(c, hours > 0 || min > 0);
-
- currentCalHour = c.get(Calendar.HOUR_OF_DAY);
- if (currentCalHour >= endHour) {
- c.add(Calendar.DAY_OF_YEAR, 1);
- // set hour to the starting one
- c.set(Calendar.HOUR_OF_DAY, startHour);
- c.add(Calendar.HOUR_OF_DAY, currentCalHour - endHour);
- } else if (currentCalHour < startHour) {
- c.add(Calendar.HOUR_OF_DAY, startHour);
- }
+ calendar.add(Calendar.HOUR, time);
+ logger.trace("calendar after adding time {}: {}", time, calendar.getTime());
+ boolean resetTime = true;
+ rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(calendar, weekendDays, resetTime);
+ logger.trace("calendar after rolling to next working day: {}", calendar.getTime());
+ rollCalendarAfterHolidays(calendar, holidays, weekendDays, hours > 0 || min > 0);
+ logger.trace("calendar after holidays: {}", calendar.getTime());
+ rollCalendarToWorkingHour(calendar, false);
+ logger.trace("calendar after rolling to working hour: {}", calendar.getTime());
// calculate minutes
int numberOfHours = min / 60;
if (numberOfHours > 0) {
- c.add(Calendar.HOUR, numberOfHours);
+ calendar.add(Calendar.HOUR, numberOfHours);
min = min - (numberOfHours * 60);
}
- c.add(Calendar.MINUTE, min);
+ calendar.add(Calendar.MINUTE, min);
// calculate seconds
int numberOfMinutes = sec / 60;
if (numberOfMinutes > 0) {
- c.add(Calendar.MINUTE, numberOfMinutes);
+ calendar.add(Calendar.MINUTE, numberOfMinutes);
sec = sec - (numberOfMinutes * 60);
}
- c.add(Calendar.SECOND, sec);
+ calendar.add(Calendar.SECOND, sec);
+ logger.trace("calendar after adding {} hour, {} minutes and {} seconds: {}", numberOfHours, numberOfMinutes, sec, calendar.getTime());
+
+ rollCalendarToWorkingHour(calendar, false);
+ logger.trace("calendar after rolling to next working day: {}", calendar.getTime());
+
+ // take under consideration weekend
+ resetTime = false;
+ rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(calendar, weekendDays, resetTime);
+ logger.trace("calendar after rolling to next working day: {}", calendar.getTime());
+ // take under consideration holidays
+ rollCalendarAfterHolidays(calendar, holidays, weekendDays, resetTime);
+ logger.trace("calendar after holidays: {}", calendar.getTime());
+
+ return calendar.getTime();
+ }
+
+ /**
+ * Indirection used only for testing purposes
+ *
+ * @return
+ */
+ protected Calendar getCalendar() {
+ String debugMessage = testingCalendar != null ? "Returning clone of testingCalendar " : "Return new GregorianCalendar";
+ logger.trace(debugMessage);
+ return testingCalendar != null ? (Calendar) testingCalendar.clone() : new GregorianCalendar();
+ }
- currentCalHour = c.get(Calendar.HOUR_OF_DAY);
+ /**
+ * Rolls the HOUR_OF_DAY
of the given Calendar
depending on
+ * given currentCalHour
, instance endHour
, and instance startHour
+ *
+ * It also consider if the startHour < endHour (i.e. working daily hours) or startHour > endHour (i.e. nightly daily hours).
+ *
+ * The case where startHour = endHour is excluded by validation of the CalendarBean
+ *
+ * @param toRoll
+ * @param resetMinuteSecond if true
, set minutes and seconds to 0
+ */
+ protected void rollCalendarToWorkingHour(Calendar toRoll, boolean resetMinuteSecond) {
+ logger.trace("toRoll: {}", toRoll.getTime());
+ if (startHour < endHour) {
+ rollCalendarToDailyWorkingHour(toRoll, startHour, endHour);
+ } else {
+ throw new UnsupportedOperationException(String.format("This feature is not supported yet: %s should be greater than %s", END_HOUR, START_HOUR));
+ }
+ if (resetMinuteSecond) {
+ toRoll.set(Calendar.MINUTE, 0);
+ toRoll.set(Calendar.SECOND, 0);
+ }
+ }
+
+ /**
+ * Rolls the HOUR_OF_DAY
of the given Calendar
to the next "daily" working hour
+ *
+ * @param toRoll
+ * @param startHour
+ * @param endHour
+ */
+ static void rollCalendarToDailyWorkingHour(Calendar toRoll, int startHour, int endHour) {
+ logger.trace("toRoll: {}", toRoll.getTime());
+ logger.trace("startHour: {}", startHour);
+ logger.trace("endHour: {}", endHour);
+ int currentCalHour = toRoll.get(Calendar.HOUR_OF_DAY);
if (currentCalHour >= endHour) {
- c.add(Calendar.DAY_OF_YEAR, 1);
+ toRoll.add(Calendar.DAY_OF_YEAR, 1);
// set hour to the starting one
- c.set(Calendar.HOUR_OF_DAY, startHour);
- c.add(Calendar.HOUR_OF_DAY, currentCalHour - endHour);
+ toRoll.set(Calendar.HOUR_OF_DAY, startHour);
} else if (currentCalHour < startHour) {
- c.add(Calendar.HOUR_OF_DAY, startHour);
+ toRoll.add(Calendar.HOUR_OF_DAY, startHour - currentCalHour);
}
- // take under consideration weekend
- handleWeekend(c, false);
- // take under consideration holidays
- handleHoliday(c, false);
+ logger.trace("calendar after rolling to daily working hour: {}", toRoll.getTime());
+ }
- return c.getTime();
+ /**
+ * Rolls the HOUR_OF_DAY
of the given Calendar
to the next "nightly" working hour
+ *
+ * @param toRoll
+ * @param startHour
+ * @param endHour
+ */
+ static void rollCalendarToNightlyWorkingHour(Calendar toRoll, int startHour, int endHour) {
+ logger.trace("toRoll: {}", toRoll.getTime());
+ logger.trace("startHour: {}", startHour);
+ logger.trace("endHour: {}", endHour);
+ int currentCalHour = toRoll.get(Calendar.HOUR_OF_DAY);
+ if (currentCalHour < endHour) {
+ toRoll.set(Calendar.HOUR_OF_DAY, endHour);
+ } else if (currentCalHour >= startHour) {
+ toRoll.add(Calendar.DAY_OF_YEAR, 1);
+ toRoll.set(Calendar.HOUR_OF_DAY, endHour);
+ }
+ toRoll.set(Calendar.MINUTE, 0);
+ toRoll.set(Calendar.SECOND, 0);
+ logger.debug("calendar after rolling to nightly working hour: {}", toRoll.getTime());
}
- protected void handleHoliday(Calendar c, boolean resetTime) {
+ /**
+ * Rolls the given Calendar
to the first working day
+ * after configured holidays
, if provided.
+ *
+ * Set hour, minute, second and millisecond when
+ * resetTime
is true
+ *
+ * @param toRoll
+ * @param holidays
+ * @param resetTime
+ */
+ static void rollCalendarAfterHolidays(Calendar toRoll, List holidays, List weekendDays, boolean resetTime) {
+ logger.trace("toRoll: {}", toRoll.getTime());
+ logger.trace("holidays: {}", holidays);
+ logger.trace("weekendDays: {}", weekendDays);
+ logger.trace("resetTime: {}", resetTime);
if (!holidays.isEmpty()) {
- Date current = c.getTime();
+ Date current = toRoll.getTime();
for (TimePeriod holiday : holidays) {
// check each holiday if it overlaps current date and break after first match
if (current.after(holiday.getFrom()) && current.before(holiday.getTo())) {
- Calendar tmp = new GregorianCalendar();
- tmp.setTime(holiday.getTo());
+ Calendar lastHolidayDayTime = new GregorianCalendar();
+ lastHolidayDayTime.setTime(holiday.getTo());
- Calendar tmp2 = new GregorianCalendar();
- tmp2.setTime(current);
- tmp2.set(Calendar.HOUR_OF_DAY, 0);
- tmp2.set(Calendar.MINUTE, 0);
- tmp2.set(Calendar.SECOND, 0);
- tmp2.set(Calendar.MILLISECOND, 0);
+ Calendar currentDayTmp = new GregorianCalendar();
+ currentDayTmp.setTime(current);
+ currentDayTmp.set(Calendar.HOUR_OF_DAY, 0);
+ currentDayTmp.set(Calendar.MINUTE, 0);
+ currentDayTmp.set(Calendar.SECOND, 0);
+ currentDayTmp.set(Calendar.MILLISECOND, 0);
- long difference = tmp.getTimeInMillis() - tmp2.getTimeInMillis();
+ long difference = lastHolidayDayTime.getTimeInMillis() - currentDayTmp.getTimeInMillis();
+ int dayDifference = (int) Math.ceil(difference / (HOUR_IN_MILLIS * 24d));
- c.add(Calendar.HOUR_OF_DAY, (int) (difference / HOUR_IN_MILLIS));
+ toRoll.add(Calendar.DAY_OF_MONTH, dayDifference);
- handleWeekend(c, resetTime);
+ rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(toRoll, weekendDays, resetTime);
break;
}
}
@@ -332,122 +371,113 @@ protected void handleHoliday(Calendar c, boolean resetTime) {
}
- protected int getPropertyAsInt(String propertyName, String defaultValue) {
- String value = businessCalendarConfiguration.getProperty(propertyName, defaultValue);
+ /**
+ * Rolls the given Calendar
to the first working day
+ * Set hour, minute, second and millisecond when
+ * resetTime
is true
+ *
+ * @param toRoll
+ * @param resetTime
+ */
+ static void rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(Calendar toRoll, List weekendDays, boolean resetTime) {
+ logger.trace("toRoll: {}", toRoll.getTime());
+ logger.trace("weekendDays: {}", weekendDays);
+ logger.trace("resetTime: {}", resetTime);
+ int dayOfTheWeek = toRoll.get(Calendar.DAY_OF_WEEK);
+ logger.trace("dayOfTheWeek: {}", dayOfTheWeek);
+ while (!isWorkingDay(weekendDays, dayOfTheWeek)) {
+ toRoll.add(Calendar.DAY_OF_YEAR, 1);
+ if (resetTime) {
+ toRoll.set(Calendar.HOUR_OF_DAY, 0);
+ toRoll.set(Calendar.MINUTE, 0);
+ toRoll.set(Calendar.SECOND, 0);
+ toRoll.set(Calendar.MILLISECOND, 0);
+ }
+ dayOfTheWeek = toRoll.get(Calendar.DAY_OF_WEEK);
+ }
+ logger.trace("dayOfTheWeek after rolling calendar: {}", dayOfTheWeek);
+ }
- return Integer.parseInt(value);
+ static boolean isWorkingDay(List weekendDays, int day) {
+ logger.trace("weekendDays: {}", weekendDays);
+ logger.trace("day: {}", day);
+ return !weekendDays.contains(day);
}
- protected List parseHolidays() {
- String holidaysString = businessCalendarConfiguration.getProperty(HOLIDAYS);
- List holidays = new ArrayList<>();
- int currentYear = Calendar.getInstance().get(Calendar.YEAR);
- if (holidaysString != null) {
- String[] hPeriods = holidaysString.split(",");
- SimpleDateFormat sdf = new SimpleDateFormat(businessCalendarConfiguration.getProperty(HOLIDAY_DATE_FORMAT, "yyyy-MM-dd"));
- for (String hPeriod : hPeriods) {
- boolean addNextYearHolidays = false;
-
- String[] fromTo = hPeriod.split(":");
- if (fromTo[0].startsWith("*")) {
- addNextYearHolidays = true;
-
- fromTo[0] = fromTo[0].replaceFirst("\\*", currentYear + "");
- }
- try {
- if (fromTo.length == 2) {
- Calendar tmpFrom = new GregorianCalendar();
- if (timezone != null) {
- tmpFrom.setTimeZone(TimeZone.getTimeZone(timezone));
- }
- tmpFrom.setTime(sdf.parse(fromTo[0]));
-
- if (fromTo[1].startsWith("*")) {
-
- fromTo[1] = fromTo[1].replaceFirst("\\*", currentYear + "");
- }
-
- Calendar tmpTo = new GregorianCalendar();
- if (timezone != null) {
- tmpTo.setTimeZone(TimeZone.getTimeZone(timezone));
- }
- tmpTo.setTime(sdf.parse(fromTo[1]));
- Date from = tmpFrom.getTime();
-
- tmpTo.add(Calendar.DAY_OF_YEAR, 1);
-
- if ((tmpFrom.get(Calendar.MONTH) > tmpTo.get(Calendar.MONTH)) && (tmpFrom.get(Calendar.YEAR) == tmpTo.get(Calendar.YEAR))) {
- tmpTo.add(Calendar.YEAR, 1);
- }
-
- Date to = tmpTo.getTime();
- holidays.add(new TimePeriod(from, to));
-
- holidays.add(new TimePeriod(from, to));
- if (addNextYearHolidays) {
- tmpFrom = new GregorianCalendar();
- if (timezone != null) {
- tmpFrom.setTimeZone(TimeZone.getTimeZone(timezone));
- }
- tmpFrom.setTime(sdf.parse(fromTo[0]));
- tmpFrom.add(Calendar.YEAR, 1);
-
- from = tmpFrom.getTime();
- tmpTo = new GregorianCalendar();
- if (timezone != null) {
- tmpTo.setTimeZone(TimeZone.getTimeZone(timezone));
- }
- tmpTo.setTime(sdf.parse(fromTo[1]));
- tmpTo.add(Calendar.YEAR, 1);
- tmpTo.add(Calendar.DAY_OF_YEAR, 1);
-
- if ((tmpFrom.get(Calendar.MONTH) > tmpTo.get(Calendar.MONTH)) && (tmpFrom.get(Calendar.YEAR) == tmpTo.get(Calendar.YEAR))) {
- tmpTo.add(Calendar.YEAR, 1);
- }
-
- to = tmpTo.getTime();
- holidays.add(new TimePeriod(from, to));
- }
- } else {
-
- Calendar c = new GregorianCalendar();
- c.setTime(sdf.parse(fromTo[0]));
- c.add(Calendar.DAY_OF_YEAR, 1);
- // handle one day holiday
- holidays.add(new TimePeriod(sdf.parse(fromTo[0]), c.getTime()));
- if (addNextYearHolidays) {
- Calendar tmp = Calendar.getInstance();
- tmp.setTime(sdf.parse(fromTo[0]));
- tmp.add(Calendar.YEAR, 1);
-
- Date from = tmp.getTime();
- c.add(Calendar.YEAR, 1);
- holidays.add(new TimePeriod(from, c.getTime()));
- }
- }
- } catch (Exception e) {
- logger.error("Error while parsing holiday in business calendar", e);
- }
+ protected long getCurrentTime() {
+ String debugMessage = testingCalendar != null ? "Returning testingCalendar time " : "Return System time";
+ return testingCalendar != null ? testingCalendar.getTimeInMillis() : System.currentTimeMillis();
+ }
+
+ protected String adoptISOFormat(String timeExpression) {
+ logger.trace("timeExpression: {}", timeExpression);
+ try {
+ Duration p = null;
+ if (DateTimeUtils.isPeriod(timeExpression)) {
+ p = Duration.parse(timeExpression);
+ } else if (DateTimeUtils.isNumeric(timeExpression)) {
+ p = Duration.of(Long.valueOf(timeExpression), ChronoUnit.MILLIS);
+ } else {
+ OffsetDateTime dateTime = OffsetDateTime.parse(timeExpression, DateTimeFormatter.ISO_DATE_TIME);
+ p = Duration.between(OffsetDateTime.now(), dateTime);
+ }
+
+ long days = p.toDays();
+ long hours = p.toHours() % 24;
+ long minutes = p.toMinutes() % 60;
+ long seconds = p.getSeconds() % 60;
+ long milis = p.toMillis() % 1000;
+
+ StringBuffer time = new StringBuffer();
+ if (days > 0) {
+ time.append(days + "d");
+ }
+ if (hours > 0) {
+ time.append(hours + "h");
+ }
+ if (minutes > 0) {
+ time.append(minutes + "m");
}
+ if (seconds > 0) {
+ time.append(seconds + "s");
+ }
+ if (milis > 0) {
+ time.append(milis + "ms");
+ }
+
+ return time.toString();
+ } catch (Exception e) {
+ return timeExpression;
}
- return holidays;
}
- protected void parseWeekendDays() {
- String weekendDays = businessCalendarConfiguration.getProperty(WEEKEND_DAYS);
+ public static class Builder {
- if (weekendDays == null) {
- this.weekendDays.add(Calendar.SATURDAY);
- this.weekendDays.add(Calendar.SUNDAY);
- } else {
- String[] days = weekendDays.split(",");
- for (String day : days) {
- this.weekendDays.add(Integer.parseInt(day));
- }
+ private CalendarBean calendarBean;
+ private Calendar testingCalendar;
+
+ public Builder withCalendarBean(CalendarBean calendarBean) {
+ this.calendarBean = calendarBean;
+ return this;
+ }
+
+ /**
+ * Used only for testing purposes.
+ *
+ * @param testingCalendar
+ * @return
+ */
+ public Builder withTestingCalendar(Calendar testingCalendar) {
+ this.testingCalendar = testingCalendar;
+ return this;
+ }
+
+ public BusinessCalendarImpl build() {
+ return calendarBean == null ? new BusinessCalendarImpl(testingCalendar) : new BusinessCalendarImpl(calendarBean, testingCalendar);
}
}
- private class TimePeriod {
+ static class TimePeriod {
private Date from;
private Date to;
@@ -463,35 +493,27 @@ protected Date getFrom() {
protected Date getTo() {
return this.to;
}
- }
- protected long getCurrentTime() {
- if (clock != null) {
- return clock.getCurrentTime();
- } else {
- return System.currentTimeMillis();
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TimePeriod that)) {
+ return false;
+ }
+ return Objects.equals(from, that.from) && Objects.equals(to, that.to);
}
- }
- protected boolean isWorkingDay(int day) {
- if (weekendDays.contains(day)) {
- return false;
+ @Override
+ public int hashCode() {
+ return Objects.hash(from, to);
}
- return true;
- }
-
- protected void handleWeekend(Calendar c, boolean resetTime) {
- int dayOfTheWeek = c.get(Calendar.DAY_OF_WEEK);
- while (!isWorkingDay(dayOfTheWeek)) {
- c.add(Calendar.DAY_OF_YEAR, 1);
- if (resetTime) {
- c.set(Calendar.HOUR_OF_DAY, 0);
- c.set(Calendar.MINUTE, 0);
- c.set(Calendar.SECOND, 0);
- c.set(Calendar.MILLISECOND, 0);
- }
- dayOfTheWeek = c.get(Calendar.DAY_OF_WEEK);
+ @Override
+ public String toString() {
+ return "TimePeriod{" +
+ "from=" + from +
+ ", to=" + to +
+ '}';
}
}
+
}
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBean.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBean.java
new file mode 100644
index 00000000000..7ca90c904db
--- /dev/null
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBean.java
@@ -0,0 +1,399 @@
+/*
+ * 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.jbpm.process.core.timer;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.END_HOUR;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.HOLIDAYS;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.HOLIDAY_DATE_FORMAT;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.START_HOUR;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.TIMEZONE;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.WEEKEND_DAYS;
+
+public class CalendarBean {
+
+ // Default access for testing purpose
+ static final List DEFAULT_WEEKEND_DAYS = Arrays.asList(Calendar.SATURDAY, Calendar.SUNDAY);
+ static final String DEFAULT_WEEKENDS = DEFAULT_WEEKEND_DAYS.stream().map(String::valueOf).collect(Collectors.joining(","));
+ static final String DEFAULT_HOLIDAY_DATE_FORMAT = "yyyy-MM-dd";
+ static final String DEFAULT_TIMEZONE = TimeZone.getDefault().getID();
+
+ private static final Logger logger = LoggerFactory.getLogger(CalendarBean.class);
+ private static final Collection REQUIRED_PROPERTIES = Arrays.asList(START_HOUR, END_HOUR);
+
+ private static final Map> FORMAT_VALIDATOR_MAP;
+ private static final List> BUSINESS_VALIDATOR_LIST;
+
+ private static final int LOWER_HOUR_BOUND = 0;
+
+ private static final int UPPER_HOUR_BOUND = 24;
+
+ private static final String OUTSIDE_BOUNDARY_ERROR_MESSAGE = "%s %s outside expected boundaries %s";
+ private static final String INVALID_FORMAT_ERROR_MESSAGE = "%s is not valid: %s";
+ private static final String REPEATED_VALUES_ERROR_MESSAGE = "There are repeated values in the given %s %s";
+ private static final String OTHER_VALUES_ERR_MSG = "%s and other values provided in the given %s %s";
+ private static final String VALUES_SAME_ERR_MSG = "%s %s and %s %s must be different";
+ private static final String PROPERTY_REQUIRED_ERR_MSG = "Property %s is required";
+
+ private final Properties calendarConfiguration;
+
+ static {
+ FORMAT_VALIDATOR_MAP = new HashMap<>();
+ FORMAT_VALIDATOR_MAP.put(START_HOUR, (stringBuilder, properties) -> {
+ if (properties.containsKey(START_HOUR)) {
+ try {
+ int hour = getPropertyAsInt(START_HOUR, properties);
+ if (!isInsideValidRange(hour, LOWER_HOUR_BOUND, UPPER_HOUR_BOUND)) {
+ addMessageToStringBuilder(stringBuilder, String.format(OUTSIDE_BOUNDARY_ERROR_MESSAGE, START_HOUR, hour, "(0-24)"));
+ }
+ } catch (NumberFormatException e) {
+ addMessageToStringBuilder(stringBuilder, String.format(INVALID_FORMAT_ERROR_MESSAGE, START_HOUR, e.getMessage()));
+ }
+ }
+ });
+ FORMAT_VALIDATOR_MAP.put(END_HOUR, (stringBuilder, properties) -> {
+ if (properties.containsKey(END_HOUR)) {
+ try {
+ int hour = getPropertyAsInt(END_HOUR, properties);
+ if (!isInsideValidRange(hour, LOWER_HOUR_BOUND, UPPER_HOUR_BOUND)) {
+ addMessageToStringBuilder(stringBuilder, String.format(OUTSIDE_BOUNDARY_ERROR_MESSAGE, END_HOUR, hour, "(0-24)"));
+ }
+ } catch (NumberFormatException e) {
+ addMessageToStringBuilder(stringBuilder, String.format(INVALID_FORMAT_ERROR_MESSAGE, END_HOUR, e.getMessage()));
+ }
+ }
+ });
+ FORMAT_VALIDATOR_MAP.put(HOLIDAYS, (stringBuilder, properties) -> {
+ if (properties.containsKey(HOLIDAYS)) {
+ String originalData = properties.getProperty(HOLIDAYS);
+ String[] allHolidays = originalData.split(",");
+ for (String holiday : allHolidays) {
+ String[] ranges = holiday.split(":");
+ for (String range : ranges) {
+ try {
+ getFormattedDate(range, properties);
+ } catch (ParseException e) {
+ addMessageToStringBuilder(stringBuilder, String.format(INVALID_FORMAT_ERROR_MESSAGE, HOLIDAYS, e.getMessage()));
+ }
+ }
+ }
+ }
+ });
+ FORMAT_VALIDATOR_MAP.put(HOLIDAY_DATE_FORMAT, (stringBuilder, properties) -> {
+ if (properties.containsKey(HOLIDAY_DATE_FORMAT)) {
+ try {
+ getSimpleDateFormat((String) properties.get(HOLIDAY_DATE_FORMAT));
+ } catch (IllegalArgumentException e) {
+ addMessageToStringBuilder(stringBuilder, e.getMessage());
+ }
+ }
+ });
+ FORMAT_VALIDATOR_MAP.put(WEEKEND_DAYS, (stringBuilder, properties) -> {
+ if (properties.containsKey(WEEKEND_DAYS)) {
+ String originalData = properties.getProperty(WEEKEND_DAYS);
+ String[] weekendDays = originalData.split(",\\s?");
+ Set differentValues = Arrays.stream(weekendDays).collect(Collectors.toSet());
+ if (differentValues.size() < weekendDays.length) {
+ addMessageToStringBuilder(stringBuilder, String.format(REPEATED_VALUES_ERROR_MESSAGE, WEEKEND_DAYS, originalData));
+ }
+ if (differentValues.contains("0") && differentValues.size() > 1) {
+ addMessageToStringBuilder(stringBuilder, String.format(OTHER_VALUES_ERR_MSG, "0 (= no weekends)", WEEKEND_DAYS, originalData));
+ }
+ final List intValues = new ArrayList<>();
+ differentValues.forEach(s -> {
+ try {
+ intValues.add(getStringAsInt(s));
+ } catch (NumberFormatException e) {
+ addMessageToStringBuilder(stringBuilder, e.getMessage());
+ }
+ });
+ if (intValues.stream().anyMatch(value -> value < 0 || value > 7)) {
+ addMessageToStringBuilder(stringBuilder, String.format(OUTSIDE_BOUNDARY_ERROR_MESSAGE, WEEKEND_DAYS, intValues.stream().filter(value -> value < 0 || value > 7).toList(), "(0-7)"));
+ }
+ }
+ });
+ FORMAT_VALIDATOR_MAP.put(TIMEZONE, (stringBuilder, properties) -> {
+ if (properties.containsKey(TIMEZONE)) {
+ String originalData = properties.getProperty(TIMEZONE);
+ if (!Arrays.asList(TimeZone.getAvailableIDs()).contains(originalData)) {
+ addMessageToStringBuilder(stringBuilder, String.format(INVALID_FORMAT_ERROR_MESSAGE, TIMEZONE, originalData));
+ }
+ }
+ });
+ BUSINESS_VALIDATOR_LIST = new ArrayList<>();
+ BUSINESS_VALIDATOR_LIST.add((stringBuilder, properties) -> {
+ if (properties.containsKey(START_HOUR) && properties.containsKey(END_HOUR)) {
+ try {
+ int startHour = getPropertyAsInt(START_HOUR, properties);
+ int endHour = getPropertyAsInt(END_HOUR, properties);
+ if (startHour == endHour) {
+ addMessageToStringBuilder(stringBuilder, String.format(VALUES_SAME_ERR_MSG, START_HOUR, startHour, END_HOUR, endHour));
+ }
+ } catch (NumberFormatException nfe) {
+ logger.error("Number format exception while checking equality of start time and end time: {}", nfe.getMessage());
+ }
+ }
+ });
+ }
+
+ public CalendarBean(Properties calendarConfiguration) {
+ this.calendarConfiguration = calendarConfiguration;
+ setup();
+ }
+
+ static void formalValidation(StringBuilder errorMessage, Properties calendarConfiguration) {
+ requiredPropertyValidation(errorMessage, calendarConfiguration);
+ propertyFormatValidation(errorMessage, calendarConfiguration);
+ }
+
+ static void requiredPropertyValidation(StringBuilder errorMessage, Properties calendarConfiguration) {
+ REQUIRED_PROPERTIES.forEach(property -> validateRequiredProperty(property, errorMessage, calendarConfiguration));
+ }
+
+ static void propertyFormatValidation(StringBuilder errorMessage, Properties calendarConfiguration) {
+ FORMAT_VALIDATOR_MAP.values().forEach(stringBuilderPropertiesBiConsumer -> stringBuilderPropertiesBiConsumer.accept(errorMessage, calendarConfiguration));
+ }
+
+ static void businessValidation(StringBuilder errorMessage, Properties calendarConfiguration) {
+ BUSINESS_VALIDATOR_LIST.forEach(stringBuilderPropertiesBiConsumer -> stringBuilderPropertiesBiConsumer.accept(errorMessage, calendarConfiguration));
+ }
+
+ static void missingDataPopulation(Properties calendarConfiguration) {
+ if (!calendarConfiguration.containsKey(WEEKEND_DAYS)) {
+ calendarConfiguration.put(WEEKEND_DAYS, DEFAULT_WEEKENDS);
+ }
+ if (!calendarConfiguration.containsKey(HOLIDAY_DATE_FORMAT)) {
+ calendarConfiguration.put(HOLIDAY_DATE_FORMAT, DEFAULT_HOLIDAY_DATE_FORMAT);
+ }
+ if (!calendarConfiguration.containsKey(TIMEZONE)) {
+ calendarConfiguration.put(TIMEZONE, DEFAULT_TIMEZONE);
+ }
+ }
+
+ static int getPropertyAsInt(String propertyName, Properties calendarConfiguration) {
+ String value = calendarConfiguration.getProperty(propertyName);
+ return getStringAsInt(value);
+ }
+
+ static int getStringAsInt(String value) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException nfe) {
+ logger.error("Number format exception while parsing {} {}", value, nfe.getMessage());
+ throw nfe;
+ }
+ }
+
+ static Date getFormattedDate(String date, Properties businessCalendar) throws ParseException {
+ SimpleDateFormat sdf =
+ businessCalendar.containsKey(HOLIDAY_DATE_FORMAT) ? getSimpleDateFormat(businessCalendar.getProperty(HOLIDAY_DATE_FORMAT)) : getSimpleDateFormat(DEFAULT_HOLIDAY_DATE_FORMAT);
+ int currentYear = Calendar.getInstance().get(Calendar.YEAR);
+ if (date.startsWith("*")) {
+ date = date.replaceFirst("\\*", currentYear + "");
+ }
+ return sdf.parse(date);
+ }
+
+ static SimpleDateFormat getSimpleDateFormat(String format) throws IllegalArgumentException {
+ return new SimpleDateFormat(format);
+ }
+
+ static void validateRequiredProperty(String property, StringBuilder errorMessage, Properties calendarConfiguration) {
+ String value = calendarConfiguration.getProperty(property);
+ if (Objects.isNull(value)) {
+ addMessageToStringBuilder(errorMessage, String.format(PROPERTY_REQUIRED_ERR_MSG, property));
+ }
+ }
+
+ static boolean isInsideValidRange(int value, int lowerBound, int upperBound) {
+ return value >= lowerBound && value <= upperBound;
+ }
+
+ private static void addMessageToStringBuilder(StringBuilder stringBuilder, String message) {
+ stringBuilder.append(message);
+ stringBuilder.append("\n");
+ }
+
+ public List getHolidays() {
+ if (!calendarConfiguration.containsKey(HOLIDAYS)) {
+ return Collections.emptyList();
+ }
+ String timezone = calendarConfiguration.getProperty(TIMEZONE);
+
+ String holidaysString = calendarConfiguration.getProperty(HOLIDAYS);
+ List holidays = new ArrayList<>();
+ int currentYear = Calendar.getInstance().get(Calendar.YEAR);
+ String[] hPeriods = holidaysString.split(",");
+
+ for (String hPeriod : hPeriods) {
+ boolean addNextYearHolidays = false;
+
+ String[] fromTo = hPeriod.split(":");
+ if (fromTo[0].startsWith("*")) {
+ addNextYearHolidays = true;
+
+ fromTo[0] = fromTo[0].replaceFirst("\\*", currentYear + "");
+ }
+ try {
+ if (fromTo.length == 2) {
+ Calendar tmpFrom = new GregorianCalendar();
+ if (timezone != null) {
+ tmpFrom.setTimeZone(TimeZone.getTimeZone(timezone));
+ }
+ tmpFrom.setTime(getFormattedDate(fromTo[0], calendarConfiguration));
+
+ if (fromTo[1].startsWith("*")) {
+
+ fromTo[1] = fromTo[1].replaceFirst("\\*", currentYear + "");
+ }
+
+ Calendar tmpTo = new GregorianCalendar();
+ if (timezone != null) {
+ tmpTo.setTimeZone(TimeZone.getTimeZone(timezone));
+ }
+ tmpTo.setTime(getFormattedDate(fromTo[1], calendarConfiguration));
+ Date from = tmpFrom.getTime();
+
+ tmpTo.add(Calendar.DAY_OF_YEAR, 1);
+
+ if ((tmpFrom.get(Calendar.MONTH) > tmpTo.get(Calendar.MONTH)) && (tmpFrom.get(Calendar.YEAR) == tmpTo.get(Calendar.YEAR))) {
+ tmpTo.add(Calendar.YEAR, 1);
+ }
+
+ Date to = tmpTo.getTime();
+ holidays.add(new BusinessCalendarImpl.TimePeriod(from, to));
+
+ if (addNextYearHolidays) {
+ tmpFrom = new GregorianCalendar();
+ if (timezone != null) {
+ tmpFrom.setTimeZone(TimeZone.getTimeZone(timezone));
+ }
+ tmpFrom.setTime(getFormattedDate(fromTo[0], calendarConfiguration));
+ tmpFrom.add(Calendar.YEAR, 1);
+
+ from = tmpFrom.getTime();
+ tmpTo = new GregorianCalendar();
+ if (timezone != null) {
+ tmpTo.setTimeZone(TimeZone.getTimeZone(timezone));
+ }
+ tmpTo.setTime(getFormattedDate(fromTo[1], calendarConfiguration));
+ tmpTo.add(Calendar.YEAR, 1);
+ tmpTo.add(Calendar.DAY_OF_YEAR, 1);
+
+ if ((tmpFrom.get(Calendar.MONTH) > tmpTo.get(Calendar.MONTH)) && (tmpFrom.get(Calendar.YEAR) == tmpTo.get(Calendar.YEAR))) {
+ tmpTo.add(Calendar.YEAR, 1);
+ }
+
+ to = tmpTo.getTime();
+ holidays.add(new BusinessCalendarImpl.TimePeriod(from, to));
+ }
+ } else {
+
+ Calendar c = new GregorianCalendar();
+ c.setTime(getFormattedDate(fromTo[0], calendarConfiguration));
+ c.add(Calendar.DAY_OF_YEAR, 1);
+ // handle one day holiday
+ holidays.add(new BusinessCalendarImpl.TimePeriod(getFormattedDate(fromTo[0], calendarConfiguration), c.getTime()));
+ if (addNextYearHolidays) {
+ Calendar tmp = Calendar.getInstance();
+ tmp.setTime(getFormattedDate(fromTo[0], calendarConfiguration));
+ tmp.add(Calendar.YEAR, 1);
+
+ Date from = tmp.getTime();
+ c.add(Calendar.YEAR, 1);
+ holidays.add(new BusinessCalendarImpl.TimePeriod(from, c.getTime()));
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Error while parsing holiday in business calendar", e);
+ }
+ }
+ return holidays;
+ }
+
+ public List getWeekendDays() {
+ return parseWeekendDays(calendarConfiguration);
+ }
+
+ public int getDaysPerWeek() {
+ return 7 - parseWeekendDays(calendarConfiguration).size();
+ }
+
+ public String getTimezone() {
+ return calendarConfiguration.getProperty(TIMEZONE);
+ }
+
+ public int getStartHour() {
+ return getPropertyAsInt(START_HOUR);
+ }
+
+ public int getEndHour() {
+ return getPropertyAsInt(END_HOUR);
+ }
+
+ public int getHoursInDay() {
+ int startHour = getStartHour();
+ int endHour = getEndHour();
+ return startHour < endHour ? endHour - startHour : (24 - startHour) + endHour;
+ }
+
+ protected void setup() {
+ StringBuilder errorMessage = new StringBuilder();
+ formalValidation(errorMessage, calendarConfiguration);
+ missingDataPopulation(calendarConfiguration);
+ businessValidation(errorMessage, calendarConfiguration);
+ if (!errorMessage.isEmpty()) {
+ throw new IllegalArgumentException(errorMessage.toString());
+ }
+ }
+
+ protected List parseWeekendDays(Properties calendarConfiguration) {
+ String weekendDays = calendarConfiguration.getProperty(WEEKEND_DAYS);
+ String[] days = weekendDays.split(",");
+ return Arrays.stream(days).map(day -> Integer.parseInt(day.trim()))
+ .filter(intDay -> intDay != 0)
+ .collect(Collectors.toList());
+ }
+
+ protected int getPropertyAsInt(String propertyName) {
+ return getPropertyAsInt(propertyName, calendarConfiguration);
+ }
+}
\ No newline at end of file
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBeanFactory.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBeanFactory.java
new file mode 100644
index 00000000000..bf0eed797a5
--- /dev/null
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBeanFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.jbpm.process.core.timer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Objects;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.jbpm.process.core.constants.CalendarConstants.BUSINESS_CALENDAR_PATH;
+
+public class CalendarBeanFactory {
+
+ private static final Logger logger = LoggerFactory.getLogger(CalendarBeanFactory.class);
+
+ public static CalendarBean createCalendarBean() {
+ URL resource = Thread.currentThread().getContextClassLoader().getResource(BUSINESS_CALENDAR_PATH);
+ if (Objects.nonNull(resource)) {
+ logger.debug("URL resource: {}", resource);
+ Properties calendarConfiguration = new Properties();
+ try (InputStream is = resource.openStream()) {
+ calendarConfiguration.load(is);
+ return new CalendarBean(calendarConfiguration);
+ } catch (IOException e) {
+ String errorMessage = "Error while loading properties for business calendar";
+ logger.error(errorMessage, e);
+ throw new RuntimeException(errorMessage, e);
+ } catch (IllegalArgumentException e) {
+ String errorMessage = "Error while populating properties for business calendar";
+ logger.error(errorMessage, e);
+ throw e;
+ }
+ } else {
+ String errorMessage = String.format("Missing %s", BUSINESS_CALENDAR_PATH);
+ logger.error(errorMessage);
+ throw new RuntimeException(errorMessage);
+ }
+ }
+}
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
index aa68a09c331..ad63a5c3190 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java
@@ -565,6 +565,10 @@ public void configureTimers() {
}
public TimerInstance configureSLATimer(String slaDueDateExpression) {
+ return configureSLATimer(slaDueDateExpression, null);
+ }
+
+ public TimerInstance configureSLATimer(String slaDueDateExpression, String nodeInstanceId) {
// setup SLA if provided
slaDueDateExpression = resolveVariable(slaDueDateExpression).toString();
if (slaDueDateExpression == null || slaDueDateExpression.trim().isEmpty()) {
@@ -583,7 +587,7 @@ public TimerInstance configureSLATimer(String slaDueDateExpression) {
TimerInstance timerInstance = createDurationTimer(duration);
if (useTimerSLATracking()) {
- registerTimer(timerInstance);
+ registerTimer(timerInstance, nodeInstanceId);
}
return timerInstance;
}
@@ -598,6 +602,10 @@ private TimerInstance createDurationTimer(long duration) {
}
private TimerInstance registerTimer(TimerInstance timerInstance) {
+ return registerTimer(timerInstance, null);
+ }
+
+ private TimerInstance registerTimer(TimerInstance timerInstance, String nodeInstanceId) {
ProcessInstanceJobDescription description =
ProcessInstanceJobDescription.newProcessInstanceJobDescriptionBuilder()
.id(timerInstance.getId())
@@ -605,6 +613,7 @@ private TimerInstance registerTimer(TimerInstance timerInstance) {
.expirationTime(DurationExpirationTime.after(timerInstance.getDelay()))
.processInstanceId(getStringId())
.processId(getProcessId())
+ .nodeInstanceId(nodeInstanceId)
.build();
JobsService jobsService = InternalProcessRuntime.asKogitoProcessRuntime(getKnowledgeRuntime().getProcessRuntime()).getJobsService();
jobsService.scheduleJob(description);
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventNodeInstance.java
index bf4edf3b688..8506251a30d 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventNodeInstance.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/EventNodeInstance.java
@@ -93,7 +93,7 @@ public void internalTrigger(final KogitoNodeInstance from, String type) {
protected void configureSla() {
String slaDueDateExpression = (String) getNode().getMetaData().get("customSLADueDate");
if (slaDueDateExpression != null) {
- TimerInstance timer = ((WorkflowProcessInstanceImpl) getProcessInstance()).configureSLATimer(slaDueDateExpression);
+ TimerInstance timer = ((WorkflowProcessInstanceImpl) getProcessInstance()).configureSLATimer(slaDueDateExpression, this.getId());
if (timer != null) {
this.slaTimerId = timer.getId();
this.slaDueDate = new Date(System.currentTimeMillis() + timer.getDelay());
diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java
index c03ce5c4377..1489004472f 100755
--- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java
+++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/StateBasedNodeInstance.java
@@ -131,7 +131,7 @@ public void internalTrigger(KogitoNodeInstance from, String type) {
protected void configureSla() {
String slaDueDateExpression = (String) getNode().getMetaData().get("customSLADueDate");
if (slaDueDateExpression != null) {
- TimerInstance timer = ((WorkflowProcessInstanceImpl) getProcessInstance()).configureSLATimer(slaDueDateExpression);
+ TimerInstance timer = ((WorkflowProcessInstanceImpl) getProcessInstance()).configureSLATimer(slaDueDateExpression, this.getId());
if (timer != null) {
this.slaTimerId = timer.getId();
this.slaDueDate = new Date(System.currentTimeMillis() + timer.getDelay());
diff --git a/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/BusinessCalendarImplTest.java b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/BusinessCalendarImplTest.java
index 3d398d753d1..e1038c8b394 100755
--- a/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/BusinessCalendarImplTest.java
+++ b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/BusinessCalendarImplTest.java
@@ -18,419 +18,367 @@
*/
package org.jbpm.process.core.timer;
-import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
import java.util.Calendar;
+import java.util.Collections;
import java.util.Date;
+import java.util.List;
import java.util.Properties;
-import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
+import java.util.stream.IntStream;
import org.jbpm.test.util.AbstractBaseTest;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import org.kie.kogito.timer.SessionPseudoClock;
import org.slf4j.LoggerFactory;
+import static java.time.temporal.ChronoUnit.DAYS;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.END_HOUR;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.HOLIDAYS;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.HOLIDAY_DATE_FORMAT;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.START_HOUR;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.WEEKEND_DAYS;
-public class BusinessCalendarImplTest extends AbstractBaseTest {
+class BusinessCalendarImplTest extends AbstractBaseTest {
public void addLogger() {
logger = LoggerFactory.getLogger(this.getClass());
}
@Test
- public void testCalculateHours() {
- Properties config = new Properties();
- String expectedDate = "2012-05-04 16:45";
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-04 13:45").getTime());
-
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("3h");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
- }
-
- @Test
- public void testCalculateHoursCustomWorkingHours() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "6");
- String expectedDate = "2012-05-04 15:45";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-03 13:45").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("8h");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
- }
-
- @Test
- public void testCalculateHoursPassingOverWeekend() {
- Properties config = new Properties();
- String expectedDate = "2012-05-07 12:45";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-04 13:45").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("7h");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void instantiate() {
+ BusinessCalendarImpl retrieved = BusinessCalendarImpl.builder().build();
+ assertThat(retrieved).isNotNull();
+ retrieved = BusinessCalendarImpl.builder()
+ .withCalendarBean(CalendarBeanFactory.createCalendarBean())
+ .build();
+ assertThat(retrieved).isNotNull();
+
+ Properties calendarConfiguration = new Properties();
+ int startHour = 10;
+ int endHour = 16;
+ calendarConfiguration.put(START_HOUR, String.valueOf(startHour));
+ calendarConfiguration.put(END_HOUR, String.valueOf(endHour));
+ retrieved = BusinessCalendarImpl.builder()
+ .withCalendarBean(new CalendarBean(calendarConfiguration))
+ .build();
+ assertThat(retrieved).isNotNull();
}
@Test
- public void testCalculateHoursPassingOverCustomDefinedWeekend() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.WEEKEND_DAYS, Calendar.FRIDAY + "," + Calendar.SATURDAY);
- String expectedDate = "2012-05-06 12:45";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-03 13:45").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("7h");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateInsideDailyWorkingHourWithDelay() {
+ int daysToSkip = 0; // since executionHourDelay falls before endHOurGap
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(-4, 4, 0, 3, daysToSkip, null, null);
}
@Test
- public void testCalculateMinutesPassingOverWeekend() {
- Properties config = new Properties();
- String expectedDate = "2012-05-07 09:15";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-04 16:45").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("30m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateInsideDailyWorkingHourWithoutDelay() {
+ int daysToSkip = 0; // since executionHourDelay falls before endHOurGap
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(-4, 4, 0, 0, daysToSkip, null, null);
}
+ @Disabled("TO FIX https://github.com/apache/incubator-kie-issues/issues/1651")
@Test
- public void testCalculateMinutesPassingOverHoliday() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "2012-05-12:2012-05-19");
- String expectedDate = "2012-05-21 09:15";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-11 16:45").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("30m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateInsideNightlyWorkingHour() {
+ int daysToSkip = 0; // since executionHourDelay falls before endHOurGap
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(4, -4, 0, 3, daysToSkip, null, null);
}
@Test
- public void testCalculateDays() {
- Properties config = new Properties();
- String expectedDate = "2012-05-14 09:00";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDate("2012-05-04").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("6d");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateBeforeWorkingHourWithDelay() {
+ int daysToSkip = 0; // since executionHourDelay falls before endHOurGap
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(2, 4, -1, 1, daysToSkip, null, null);
}
@Test
- public void testCalculateDaysStartingInWeekend() {
+ void calculateBusinessTimeAsDateBeforeWorkingHourWithDelayFineGrained() {
+ // lets pretend 2024-11-28 10:48:33 is the current time
+ Calendar testingCalendar = Calendar.getInstance();
+ testingCalendar.set(Calendar.YEAR, 2024);
+ testingCalendar.set(Calendar.MONTH, Calendar.NOVEMBER);
+ testingCalendar.set(Calendar.DAY_OF_MONTH, 28);
+ testingCalendar.set(Calendar.HOUR_OF_DAY, 10);
+ testingCalendar.set(Calendar.MINUTE, 48);
+ testingCalendar.set(Calendar.SECOND, 33);
+
+ int startHour = 14;
Properties config = new Properties();
- String expectedDate = "2012-05-09 09:00";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDate("2012-05-05").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("2d");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
- }
-
- @Test
- public void testCalculateDaysCustomWorkingDays() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.DAYS_PER_WEEK, "4");
- config.setProperty(BusinessCalendarImpl.WEEKEND_DAYS, Calendar.FRIDAY + "," + Calendar.SATURDAY + "," + Calendar.SUNDAY);
- String expectedDate = "2012-05-15 14:30";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-03 14:30").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("6d");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
- }
-
- @Test
- public void testCalculateDaysMiddleDay() {
- Properties config = new Properties();
- String expectedDate = "2012-05-11 12:27";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-03 12:27").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("6d");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
- }
-
- @Test
- public void testCalculateDaysHoursMinutes() {
- Properties config = new Properties();
- String expectedDate = "2012-05-14 14:20";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDate("2012-05-04").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("6d4h80m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ config.setProperty(BusinessCalendarImpl.START_HOUR, String.valueOf(startHour));
+ config.setProperty(BusinessCalendarImpl.END_HOUR, "18");
+ config.setProperty(WEEKEND_DAYS, "0");
+
+ String delay = "10m";
+ BusinessCalendarImpl businessCal = BusinessCalendarImpl.builder().withCalendarBean(new CalendarBean(config))
+ .withTestingCalendar(testingCalendar)
+ .build();
+ Date retrieved = businessCal.calculateBusinessTimeAsDate(delay);
+ String expectedDate = "2024-11-28 14:10:00";
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String retrievedTime = sdf.format(retrieved);
+ assertThat(retrievedTime).isEqualTo(expectedDate);
+
+ delay = "10s";
+ retrieved = businessCal.calculateBusinessTimeAsDate(delay);
+ expectedDate = "2024-11-28 14:00:10";
+ retrievedTime = sdf.format(retrieved);
+ assertThat(retrievedTime).isEqualTo(expectedDate);
+
+ delay = "10m 10s";
+ retrieved = businessCal.calculateBusinessTimeAsDate(delay);
+ expectedDate = "2024-11-28 14:10:10";
+ retrievedTime = sdf.format(retrieved);
+ assertThat(retrievedTime).isEqualTo(expectedDate);
}
@Test
- public void testCalculateTimeDaysHoursMinutesHolidays() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "2012-05-10:2012-05-19");
- String expectedDate = "2012-05-21 14:20";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDate("2012-05-04").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("6d4h80m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateBeforeWorkingHourWithoutDelay() {
+ int daysToSkip = 0; // since executionHourDelay falls before endHOurGap
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(-1, 4, -2, 1, daysToSkip, null, null);
}
@Test
- public void testCalculateTimeDaysHoursMinutesSingleDayHolidays() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "2012-05-07");
- String expectedDate = "2012-05-08 13:20";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDate("2012-05-04").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("1d4h20m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateAfterWorkingHour() {
+ int daysToSkip = 1; // because the executionHourDelay is bigger to endHOurGap, so it goes to next day;
+ commonCalculateBusinessTimeAsDateAssertAtStartHour(-1, 2, 3, 3, daysToSkip, null, null);
+ commonCalculateBusinessTimeAsDateAssertAtStartHour(0, 6, 1, 5, daysToSkip, null, null);
}
@Test
- public void testCalculateTimeDaysHoursMinutesSingleDayHolidaysInMiddleOfWeek() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "2012-05-09");
- String expectedDate = "2012-05-10 15:30";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-08 11:10").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("1d4h20m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateWhenTodayAndTomorrowAreHolidays() {
+ String holidayDateFormat = "yyyy-MM-dd";
+ DateTimeFormatter sdf = DateTimeFormatter.ofPattern(holidayDateFormat);
+ LocalDate today = LocalDate.now();
+ LocalDate tomorrow = today.plusDays(1);
+ String holidays = sdf.format(today) + "," + sdf.format(tomorrow);
+ int daysToSkip = 2; // because both today and tomorrow are holiday
+ // endHOurGap and executionHourDelay are not relevant in this context
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(-4, 4, 0, 3, daysToSkip, holidayDateFormat, holidays);
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(-4, 4, 5, 3, daysToSkip, holidayDateFormat, holidays);
}
@Test
- public void testCalculateDaysPassingOverHolidayAtYearEnd() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "2012-12-31:2013-01-01");
- String expectedDate = "2013-01-04 09:15";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-12-28 16:45").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("2d30m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void calculateBusinessTimeAsDateWhenNextDayIsHoliday() {
+ String holidayDateFormat = "yyyy-MM-dd";
+ DateTimeFormatter sdf = DateTimeFormatter.ofPattern(holidayDateFormat);
+ LocalDate tomorrow = LocalDate.now().plusDays(1);
+ String holidays = sdf.format(tomorrow);
+ // 1 because the executionHourDelay is equal to endHOurGap, so it goes to next day;
+ // 1 because next day is holiday
+ int daysToSkip = 2;
+
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(-4, 4, 0, 4, daysToSkip, holidayDateFormat, holidays);
+ daysToSkip = 0; // since executionHourDelay falls before endHOurGap
+ commonCalculateBusinessTimeAsDateAssertBetweenHours(-4, 4, 0, 3, daysToSkip, holidayDateFormat, holidays);
}
@Test
- public void testCalculateDaysPassingOverHolidayAtYearEndWithWildcards() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "*-12-31:*-01-01");
- String expectedDate = "2013-01-02 09:15";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-12-28 16:45").getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("2d30m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void rollCalendarToDailyWorkingHour() {
+ int startHour = 14;
+ int endHour = 16;
+ Calendar toRoll = Calendar.getInstance();
+ int currentHour = 8;
+ toRoll.set(Calendar.HOUR_OF_DAY, currentHour);
+ int dayOfYear = toRoll.get(Calendar.DAY_OF_YEAR);
+ BusinessCalendarImpl.rollCalendarToDailyWorkingHour(toRoll, startHour, endHour);
+ assertThat(toRoll.get(Calendar.HOUR_OF_DAY)).isEqualTo(startHour);
+ assertThat(toRoll.get(Calendar.DAY_OF_YEAR)).isEqualTo(dayOfYear);
+
+ toRoll = Calendar.getInstance();
+ currentHour = 19;
+ toRoll.set(Calendar.HOUR_OF_DAY, currentHour);
+ dayOfYear = toRoll.get(Calendar.DAY_OF_YEAR);
+ BusinessCalendarImpl.rollCalendarToDailyWorkingHour(toRoll, startHour, endHour);
+ assertThat(toRoll.get(Calendar.HOUR_OF_DAY)).isEqualTo(startHour);
+ assertThat(toRoll.get(Calendar.DAY_OF_YEAR)).isEqualTo(dayOfYear + 1);
}
+ @Disabled("TO FIX https://github.com/apache/incubator-kie-issues/issues/1651")
@Test
- public void testCalculateISOHours() {
- Properties config = new Properties();
- String expectedDate = "2012-05-04 16:45";
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-04 13:45").getTime());
+ void rollCalendarToNightlyWorkingHour() {
+ int startHour = 20;
+ int endHour = 4;
+ Calendar toRoll = Calendar.getInstance();
+ int currentHour = 21;
+ toRoll.set(Calendar.HOUR_OF_DAY, currentHour);
+ int dayOfYear = toRoll.get(Calendar.DAY_OF_YEAR);
+ BusinessCalendarImpl.rollCalendarToNightlyWorkingHour(toRoll, startHour, endHour);
+ assertThat(toRoll.get(Calendar.HOUR_OF_DAY)).isEqualTo(startHour);
+ assertThat(toRoll.get(Calendar.DAY_OF_YEAR)).isEqualTo(dayOfYear);
+
+ toRoll = Calendar.getInstance();
+ currentHour = 3;
+ toRoll.set(Calendar.HOUR_OF_DAY, currentHour);
+ dayOfYear = toRoll.get(Calendar.DAY_OF_YEAR);
+ BusinessCalendarImpl.rollCalendarToNightlyWorkingHour(toRoll, startHour, endHour);
+ assertThat(toRoll.get(Calendar.HOUR_OF_DAY)).isEqualTo(startHour);
+ assertThat(toRoll.get(Calendar.DAY_OF_YEAR)).isEqualTo(dayOfYear + 1);
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("PT3H");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
}
@Test
- public void testCalculateISODaysAndHours() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "2012-05-09");
- String expectedDate = "2012-05-10 15:30";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-05-08 11:10").getTime());
-
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("P1DT4H20M");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void rollCalendarAfterHolidays() {
+ Instant now = Instant.now();
+ int holidayLeft = 4;
+ Instant startHolidayInstant = now.minus(2, DAYS);
+ Instant endHolidayInstant = now.plus(holidayLeft, DAYS);
+ Date startHoliday = Date.from(startHolidayInstant);
+ Date endHoliday = Date.from(endHolidayInstant);
+ List holidays = Collections.singletonList(new BusinessCalendarImpl.TimePeriod(startHoliday, endHoliday));
+ List weekendDays = Collections.emptyList();
+ Calendar calendar = Calendar.getInstance();
+ int currentDayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
+ BusinessCalendarImpl.rollCalendarAfterHolidays(calendar, holidays, weekendDays, false);
+ int expected = currentDayOfYear + holidayLeft + 1;
+ assertThat(calendar.get(Calendar.DAY_OF_YEAR)).isEqualTo(expected);
}
@Test
- public void testSingleHolidayWithinGivenTime() {
- final Properties props = new Properties();
- props.put(BusinessCalendarImpl.HOLIDAYS, "2015-01-13");
- String expectedDate = "2015-01-15 11:38";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis("2015-01-08 11:38:30.198").getTime());
-
- BusinessCalendarImpl businessCalendarImpl = new BusinessCalendarImpl(props, clock);
-
- Date result = businessCalendarImpl.calculateBusinessTimeAsDate("4d");
- assertThat(formatDate("yyyy-MM-dd HH:mm", result)).isEqualTo(expectedDate);
+ void rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking() {
+ List workingDays = IntStream.range(Calendar.MONDAY, Calendar.SATURDAY).boxed().toList();
+ List weekendDays = Arrays.asList(Calendar.SATURDAY, Calendar.SUNDAY);
+ boolean resetTime = false;
+ workingDays.forEach(workingDay -> {
+ Calendar calendar = getCalendarAtExpectedWeekDay(workingDay);
+ BusinessCalendarImpl.rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(calendar, weekendDays, resetTime);
+ assertThat(calendar.get(Calendar.DAY_OF_WEEK)).isEqualTo(workingDay);
+ });
+ weekendDays.forEach(weekendDay -> {
+ Calendar calendar = getCalendarAtExpectedWeekDay(weekendDay);
+ BusinessCalendarImpl.rollCalendarToNextWorkingDayIfCurrentDayIsNonWorking(calendar, weekendDays, resetTime);
+ assertThat(calendar.get(Calendar.DAY_OF_WEEK)).isEqualTo(Calendar.MONDAY);
+ });
}
@Test
- public void testCalculateMillisecondsAsDefault() {
- Properties config = new Properties();
- String expectedDate = "2012-05-04 16:45:10.000";
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis("2012-05-04 16:45:00.000").getTime());
-
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("10000");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm:ss.SSS", result)).isEqualTo(expectedDate);
+ void isWorkingDay() {
+ List workingDays = IntStream.range(Calendar.MONDAY, Calendar.SATURDAY).boxed().toList();
+ List weekendDays = Arrays.asList(Calendar.SATURDAY, Calendar.SUNDAY);
+ workingDays.forEach(workingDay -> assertThat(BusinessCalendarImpl.isWorkingDay(weekendDays, workingDay)).isTrue());
+ weekendDays.forEach(workingDay -> assertThat(BusinessCalendarImpl.isWorkingDay(weekendDays, workingDay)).isFalse());
}
- @Test
- public void testCalculateMinutesPassingAfterHour() {
- Properties config = new Properties();
- String currentDate = "2018-05-02 19:51:33";
- String expectedDate = "2018-05-03 09:01:00";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime(currentDate).getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate("1m");
-
- assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate);
+ private void commonCalculateBusinessTimeAsDateAssertBetweenHours(int startHourGap, int endHourGap, int testingCalendarHourGap, int executionHourDelay, int daysToSkip, String holidayDateFormat,
+ String holidays) {
+ BiFunction startBooleanCondition = (resultInstant, expectedStartTime) -> {
+ logger.debug("Check if {} is after or equal to {} ", resultInstant, expectedStartTime);
+ return !resultInstant.isBefore(expectedStartTime);
+ };
+ commonCalculateBusinessTimeAsDate(startHourGap,
+ endHourGap,
+ testingCalendarHourGap,
+ executionHourDelay,
+ daysToSkip,
+ holidayDateFormat,
+ holidays,
+ startBooleanCondition);
}
- @Test
- public void testBusinessCalendarWithoutProvidedConfiguration() {
- assertDoesNotThrow(() -> new BusinessCalendarImpl());
+ private void commonCalculateBusinessTimeAsDateAssertAtStartHour(int startHourGap, int endHourGap, int testingCalendarHourGap, int executionHourDelay, int daysToSkip, String holidayDateFormat,
+ String holidays) {
+ BiFunction startBooleanCondition = (resultInstant, expectedStartTime) -> {
+ logger.debug("Check if {} is equal to {} ", resultInstant, expectedStartTime);
+ return resultInstant.getEpochSecond() == expectedStartTime.getEpochSecond();
+ };
+ commonCalculateBusinessTimeAsDate(startHourGap,
+ endHourGap,
+ testingCalendarHourGap,
+ executionHourDelay,
+ daysToSkip,
+ holidayDateFormat,
+ holidays,
+ startBooleanCondition);
}
- @Test
- public void testCalculateMinutesPassingHoliday() {
- Properties config = new Properties();
- config.setProperty(BusinessCalendarImpl.DAYS_PER_WEEK, "5");
- config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "8");
- config.setProperty(BusinessCalendarImpl.START_HOUR, "9");
- config.setProperty(BusinessCalendarImpl.END_HOUR, "18");
- config.setProperty(BusinessCalendarImpl.WEEKEND_DAYS, "1,7"); // sun,sat
- config.setProperty(BusinessCalendarImpl.HOLIDAYS, "2018-04-30,2018-05-03:2018-05-05");
- config.setProperty(BusinessCalendarImpl.HOLIDAY_DATE_FORMAT, "yyyy-MM-dd");
- String currentDate = "2018-05-03 13:51:33";
- String duration = "10m";
- String expectedDate = "2018-05-07 09:10:00";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime(currentDate).getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate(duration);
-
- assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate);
- }
+ private void commonCalculateBusinessTimeAsDate(int startHourGap,
+ int endHourGap, int testingCalendarHourGap,
+ int executionHourDelay, int daysToSkip, String holidayDateFormat, String holidays,
+ BiFunction startBooleanCondition) {
+ logger.debug("startHourGap {}", startHourGap);
+ logger.debug("endHourGap {}", endHourGap);
+ logger.debug("testingCalendarHourGap {}", testingCalendarHourGap);
+ logger.debug("executionHourDelay {}", executionHourDelay);
+ logger.debug("numberOfHolidays {}", daysToSkip);
+ logger.debug("holidayDateFormat {}", holidayDateFormat);
+ logger.debug("holidays {}", holidays);
+
+ // lets pretend 12.00 is the current time
+ Calendar testingCalendar = Calendar.getInstance();
+ testingCalendar.set(Calendar.HOUR_OF_DAY, 12);
+ testingCalendar.set(Calendar.MINUTE, 0);
+ testingCalendar.set(Calendar.SECOND, 0);
+ logger.debug("testingCalendar {}", testingCalendar.getTime());
+ Calendar startCalendar = (Calendar) testingCalendar.clone();
+ startCalendar.add(Calendar.HOUR_OF_DAY, startHourGap);
+ logger.debug("startCalendar {}", startCalendar.getTime());
+ Calendar endCalendar = (Calendar) testingCalendar.clone();
+ endCalendar.add(Calendar.HOUR_OF_DAY, endHourGap);
+ logger.debug("endCalendar {}", endCalendar.getTime());
+
+ int startHour = startCalendar.get(Calendar.HOUR_OF_DAY);
+ int endHour = endCalendar.get(Calendar.HOUR_OF_DAY);
+
+ // We need to reconciliate for daily/working hours and daily/nightly hours
+ int hoursInDay = startHour < endHour ? endHour - startHour : 24 - (startHour - endHour);
+ int daysToAdd = daysToSkip;
+ logger.debug("daysToAdd (= numberOfHolidays) {}", daysToAdd);
+ if (executionHourDelay >= hoursInDay) {
+ daysToAdd += executionHourDelay / hoursInDay;
+ logger.debug("daysToAdd += (hourDelay / hoursInDay) {}", daysToAdd);
+ }
+ if (daysToAdd > 0) {
+ startCalendar.add(Calendar.DAY_OF_YEAR, daysToAdd);
+ endCalendar.add(Calendar.DAY_OF_YEAR, daysToAdd);
+ logger.debug("startCalendar (startCalendar + days to add) {}", startCalendar.getTime());
+ logger.debug("endCalendar (endCalendar + days to add) {}", endCalendar.getTime());
+ }
- @Test
- public void testCalculateMinutesPassingWeekend() {
Properties config = new Properties();
- String currentDate = "2018-05-06 13:51:33";
- String duration = "10m";
- String expectedDate = "2018-05-07 09:10:00";
-
- SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime(currentDate).getTime());
- BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock);
-
- Date result = businessCal.calculateBusinessTimeAsDate(duration);
-
- assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate);
- }
-
- private Date parseToDate(String dateString) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
-
- Date testTime;
- try {
- testTime = sdf.parse(dateString);
-
- return testTime;
- } catch (ParseException e) {
- return null;
+ config.setProperty(START_HOUR, String.valueOf(startHour));
+ config.setProperty(END_HOUR, String.valueOf(endHour));
+ config.setProperty(WEEKEND_DAYS, "0");
+ if (holidayDateFormat != null) {
+ config.setProperty(HOLIDAY_DATE_FORMAT, holidayDateFormat);
}
- }
-
- private Date parseToDateWithTime(String dateString) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
-
- Date testTime;
- try {
- testTime = sdf.parse(dateString);
-
- return testTime;
- } catch (ParseException e) {
- return null;
+ if (holidays != null) {
+ config.setProperty(HOLIDAYS, holidays);
}
- }
- private Date parseToDateWithTimeAndMillis(String dateString) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-
- Date testTime;
- try {
- testTime = sdf.parse(dateString);
-
- return testTime;
- } catch (ParseException e) {
- return null;
- }
- }
+ testingCalendar.add(Calendar.HOUR_OF_DAY, testingCalendarHourGap);
+ logger.debug("testingCalendar after testingCalendarHourGap {}", testingCalendar.getTime());
+ BusinessCalendarImpl businessCal = BusinessCalendarImpl.builder().withCalendarBean(new CalendarBean(config))
+ .withTestingCalendar(testingCalendar)
+ .build();
+ Date retrieved = businessCal.calculateBusinessTimeAsDate(String.format("%sh", executionHourDelay));
+ logger.debug("retrieved {}", retrieved);
- private String formatDate(String pattern, Date date) {
- SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+ Date expectedStart = startCalendar.getTime();
+ Date expectedEnd = endCalendar.getTime();
- String testTime = sdf.format(date);
+ Instant retrievedInstant = retrieved.toInstant();
+ Instant expectedStartTime = expectedStart.toInstant();
+ Instant expectedEndTime = expectedEnd.toInstant();
- return testTime;
+ logger.debug("retrievedInstant {}", retrievedInstant);
+ logger.debug("expectedStartTime {}", expectedStartTime);
+ logger.debug("expectedEndTime {}", expectedEndTime);
+ assertThat(startBooleanCondition.apply(retrievedInstant, expectedStartTime)).isTrue();
+ logger.debug("Check if {} is not after {} ", retrievedInstant, expectedEndTime);
+ assertThat(retrievedInstant.isAfter(expectedEndTime)).isFalse();
}
- private class StaticPseudoClock implements SessionPseudoClock {
-
- private long currentTime;
-
- private StaticPseudoClock(long currenttime) {
- this.currentTime = currenttime;
- }
-
- public long getCurrentTime() {
- return this.currentTime;
+ private Calendar getCalendarAtExpectedWeekDay(int weekDay) {
+ Calendar toReturn = Calendar.getInstance();
+ while (toReturn.get(Calendar.DAY_OF_WEEK) != weekDay) {
+ toReturn.add(Calendar.DAY_OF_YEAR, 1);
}
-
- public long advanceTime(long amount, TimeUnit unit) {
- throw new UnsupportedOperationException("It is static clock and does not allow advance time operation");
- }
-
+ return toReturn;
}
+
}
diff --git a/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanFactoryTest.java b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanFactoryTest.java
new file mode 100644
index 00000000000..0a4f59d59f7
--- /dev/null
+++ b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanFactoryTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.jbpm.process.core.timer;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CalendarBeanFactoryTest {
+
+ @Test
+ void testCreateCalendarBean() {
+ // This test relies on src/test/resources/calendar.properties:
+ // checked values comes from it
+ CalendarBean calendarBean = CalendarBeanFactory.createCalendarBean();
+ assertThat(calendarBean).isNotNull();
+ assertThat(calendarBean.getStartHour()).isEqualTo(10);
+ assertThat(calendarBean.getEndHour()).isEqualTo(16);
+ assertThat(calendarBean.getHoursInDay()).isEqualTo(6);
+ assertThat(calendarBean.getDaysPerWeek()).isEqualTo(5);
+ assertThat(calendarBean.getWeekendDays()).contains(Calendar.SATURDAY, Calendar.SUNDAY);
+ assertThat(calendarBean.getHolidays()).isEmpty();
+ assertThat(calendarBean.getTimezone()).isEqualTo(TimeZone.getDefault().getID());
+ }
+}
diff --git a/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanTest.java b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanTest.java
new file mode 100644
index 00000000000..64bf32194e6
--- /dev/null
+++ b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanTest.java
@@ -0,0 +1,297 @@
+/*
+ * 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.jbpm.process.core.timer;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.assertj.core.api.ThrowableAssert;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.END_HOUR;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.HOLIDAYS;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.HOLIDAY_DATE_FORMAT;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.START_HOUR;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.TIMEZONE;
+import static org.jbpm.process.core.timer.BusinessCalendarImpl.WEEKEND_DAYS;
+import static org.jbpm.process.core.timer.CalendarBean.DEFAULT_HOLIDAY_DATE_FORMAT;
+import static org.jbpm.process.core.timer.CalendarBean.DEFAULT_TIMEZONE;
+import static org.jbpm.process.core.timer.CalendarBean.DEFAULT_WEEKENDS;
+import static org.jbpm.process.core.timer.CalendarBean.DEFAULT_WEEKEND_DAYS;
+
+class CalendarBeanTest {
+
+ // Static validation methods
+ @ParameterizedTest
+ @MethodSource("getMissingPropertiesCalendar")
+ void requiredPropertyValidation(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonStaticMethodValidation(errorMessage -> CalendarBean.requiredPropertyValidation(errorMessage, calendarConfiguration), errorMessages);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getWronglyFormatPropertiesCalendar")
+ void propertyFormatValidation(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonStaticMethodValidation(errorMessage -> CalendarBean.propertyFormatValidation(errorMessage, calendarConfiguration), errorMessages);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getBusinessInvalidPropertiesCalendar")
+ void businessValidation(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonStaticMethodValidation(errorMessage -> CalendarBean.businessValidation(errorMessage, calendarConfiguration), errorMessages);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getPartialPropertiesCalendar")
+ void missingDataPopulation(Map propertyMap, Map defaultValuesMap) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ defaultValuesMap.keySet().forEach(key -> assertThat(calendarConfiguration.containsKey(key)).isFalse());
+ CalendarBean.missingDataPopulation(calendarConfiguration);
+ defaultValuesMap.forEach((key, value) -> {
+ assertThat(calendarConfiguration.containsKey(key)).isTrue();
+ assertThat(calendarConfiguration.getProperty(key)).isEqualTo(value);
+ });
+ }
+
+ @Test
+ void getPropertyAsInt() {
+ Properties calendarConfiguration = new Properties();
+ String propertyName = "propertyName";
+ int originalValue = 1;
+ String value = "" + originalValue;
+ calendarConfiguration.put(propertyName, value);
+ int retrieved = CalendarBean.getPropertyAsInt(propertyName, calendarConfiguration);
+ assertThat(retrieved).isEqualTo(originalValue);
+ value = "WRONG";
+ calendarConfiguration.put(propertyName, value);
+ String expectedMessage = "For input string: \"WRONG\"";
+ assertThatThrownBy(() -> CalendarBean.getPropertyAsInt(propertyName, calendarConfiguration))
+ .isInstanceOf(NumberFormatException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ @Test
+ void validateRequiredProperty() {
+ Properties calendarConfiguration = new Properties();
+ String propertyName = "propertyName";
+ String value = "propertyValue";
+ calendarConfiguration.put(propertyName, value);
+ StringBuilder errorMessage = new StringBuilder();
+ CalendarBean.validateRequiredProperty(propertyName, errorMessage, calendarConfiguration);
+ assertThat(errorMessage).isEmpty();
+ CalendarBean.validateRequiredProperty("missingProperty", errorMessage, calendarConfiguration);
+ String[] retrievedErrors = errorMessage.toString().split("\n");
+ assertThat(retrievedErrors).hasSize(1);
+ assertThat(retrievedErrors).contains("Property missingProperty is required");
+ }
+
+ @Test
+ void getFormattedDate() throws ParseException {
+ Properties calendarConfiguration = new Properties();
+ String dateFormat = "dd-MM-yyyy";
+ String date = "27-11-2024";
+ calendarConfiguration.put(HOLIDAY_DATE_FORMAT, dateFormat);
+ Date retrieved = CalendarBean.getFormattedDate(date, calendarConfiguration);
+ Date expected = CalendarBean.getSimpleDateFormat(dateFormat).parse(date);
+ assertThat(retrieved).isEqualTo(expected);
+
+ }
+
+ @Test
+ void getSimpleDateFormat() {
+ SimpleDateFormat retrieved = CalendarBean.getSimpleDateFormat(DEFAULT_HOLIDAY_DATE_FORMAT);
+ assertThat(retrieved).isNotNull();
+ retrieved = CalendarBean.getSimpleDateFormat("dd-MM-yyyy");
+ assertThat(retrieved).isNotNull();
+ String wrong = "WRONG";
+ String expectedMessage = "Illegal pattern character 'R'";
+ assertThatThrownBy(() -> CalendarBean.getSimpleDateFormat(wrong))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(expectedMessage);
+ }
+
+ // Instance methods
+ @ParameterizedTest
+ @MethodSource("getMissingPropertiesCalendar")
+ void requiredPropertyMissing(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonIllegalArgumentAssertion(() -> new CalendarBean(calendarConfiguration), errorMessages);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getWronglyFormatPropertiesCalendar")
+ void propertyWrongFormat(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonIllegalArgumentAssertion(() -> new CalendarBean(calendarConfiguration), errorMessages);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getBusinessInvalidPropertiesCalendar")
+ void businessInvalid(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonIllegalArgumentAssertion(() -> new CalendarBean(calendarConfiguration), errorMessages);
+ }
+
+ @Test
+ void instantiationFull() throws ParseException {
+ Properties calendarConfiguration = new Properties();
+ int startHour = 10;
+ int endHour = 16;
+ List weekendDays = Arrays.asList(3, 4);
+ String dateFormat = "dd-MM-yyyy";
+ String timezone = "ACT";
+ String holidays = "27-11-2024";
+ calendarConfiguration.put(START_HOUR, String.valueOf(startHour));
+ calendarConfiguration.put(END_HOUR, String.valueOf(endHour));
+ calendarConfiguration.put(WEEKEND_DAYS, weekendDays.stream().map(String::valueOf).collect(Collectors.joining(",")));
+ calendarConfiguration.put(HOLIDAY_DATE_FORMAT, dateFormat);
+ calendarConfiguration.put(TIMEZONE, timezone);
+ calendarConfiguration.put(HOLIDAYS, holidays);
+ CalendarBean retrieved = new CalendarBean(calendarConfiguration);
+
+ Date from = CalendarBean.getFormattedDate(holidays, calendarConfiguration);
+ Date to = CalendarBean.getFormattedDate("28-11-2024", calendarConfiguration);
+ assertThat(retrieved.getHolidays()).isEqualTo(List.of(new BusinessCalendarImpl.TimePeriod(from, to)));
+ assertThat(retrieved.getWeekendDays()).isEqualTo(weekendDays);
+ assertThat(retrieved.getDaysPerWeek()).isEqualTo(7 - weekendDays.size());
+ assertThat(retrieved.getTimezone()).isEqualTo(timezone);
+ assertThat(retrieved.getStartHour()).isEqualTo(startHour);
+ assertThat(retrieved.getEndHour()).isEqualTo(endHour);
+ assertThat(retrieved.getHoursInDay()).isEqualTo(endHour - startHour);
+ }
+
+ @Test
+ void instantiationPartial() {
+ Properties calendarConfiguration = new Properties();
+ int startHour = 10;
+ int endHour = 16;
+ calendarConfiguration.put(START_HOUR, String.valueOf(startHour));
+ calendarConfiguration.put(END_HOUR, String.valueOf(endHour));
+ CalendarBean retrieved = new CalendarBean(calendarConfiguration);
+ assertThat(retrieved.getHolidays()).isEqualTo(Collections.emptyList());
+ assertThat(retrieved.getWeekendDays()).isEqualTo(DEFAULT_WEEKEND_DAYS);
+ assertThat(retrieved.getDaysPerWeek()).isEqualTo(5);
+ assertThat(retrieved.getTimezone()).isEqualTo(DEFAULT_TIMEZONE);
+ assertThat(retrieved.getStartHour()).isEqualTo(startHour);
+ assertThat(retrieved.getEndHour()).isEqualTo(endHour);
+ assertThat(retrieved.getHoursInDay()).isEqualTo(endHour - startHour);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getMissingPropertiesCalendar")
+ void missingProperties(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonIllegalArgumentAssertion(() -> new CalendarBean(calendarConfiguration), errorMessages);
+ }
+
+ @ParameterizedTest
+ @MethodSource("getInvalidPropertiesCalendar")
+ public void invalidProperties(Map propertyMap, List errorMessages) {
+ Properties calendarConfiguration = new Properties();
+ calendarConfiguration.putAll(propertyMap);
+ commonIllegalArgumentAssertion(() -> new CalendarBean(calendarConfiguration), errorMessages);
+ }
+
+ // Let's avoid duplication
+ private void commonStaticMethodValidation(Consumer executedMethod,
+ List errorMessages) {
+ StringBuilder errors = new StringBuilder();
+ assertThat(errors).isEmpty();
+ executedMethod.accept(errors);
+ assertThat(errors).isNotEmpty();
+ String[] retrievedErrors = errors.toString().split("\n");
+ assertThat(retrievedErrors).hasSize(errorMessages.size());
+ errorMessages.forEach(msg -> assertThat(retrievedErrors).contains(msg));
+ }
+
+ private void commonIllegalArgumentAssertion(ThrowableAssert.ThrowingCallable executedMethod, List errorMessages) {
+ ThrowableAssert throwableAssert = (ThrowableAssert) assertThatThrownBy(executedMethod)
+ .isInstanceOf(IllegalArgumentException.class);
+ errorMessages.forEach(throwableAssert::hasMessageContaining);
+ }
+
+ private static Stream getMissingPropertiesCalendar() {
+ return Stream.of(
+ Arguments.of(Map.of(), List.of("Property " + START_HOUR + " is required", "Property " + END_HOUR + " is required")),
+ Arguments.of(Map.of(START_HOUR, "9"), List.of("Property " + END_HOUR + " is required")),
+ Arguments.of(Map.of(END_HOUR, "17"), List.of("Property " + START_HOUR + " is required")));
+ }
+
+ private static Stream getWronglyFormatPropertiesCalendar() {
+
+ return Stream.of(
+ Arguments.of(Map.of(START_HOUR, "9", END_HOUR, "25"), List.of(END_HOUR + " 25 outside expected boundaries (0-24)")),
+ Arguments.of(Map.of(START_HOUR, "26", END_HOUR, "-2"), List.of(START_HOUR + " 26 outside expected boundaries (0-24)", END_HOUR + " -2 outside expected boundaries (0-24)")),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "1,2,8,9"), List.of(WEEKEND_DAYS + " [8, 9] outside expected boundaries (0-7)")),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "0,1,2"), List.of("0 (= no weekends) and other values provided in the given " + WEEKEND_DAYS + " 0,1,2")),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "1,1,2"), List.of("There are repeated values in the given " + WEEKEND_DAYS + " 1,1,2")),
+ Arguments.of(Map.of(START_HOUR, "", END_HOUR, ""), List.of(START_HOUR + " is not valid: For input string: \"\"", END_HOUR + " is not valid: For input string: \"\"")));
+ }
+
+ private static Stream getBusinessInvalidPropertiesCalendar() {
+
+ return Stream.of(
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "10"), List.of(START_HOUR + " 10 and " + END_HOUR + " 10 must be different")));
+ }
+
+ private static Stream getPartialPropertiesCalendar() {
+
+ return Stream.of(
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", HOLIDAY_DATE_FORMAT, "dd-mm-YYYY", TIMEZONE, "ACT"), Map.of(WEEKEND_DAYS, DEFAULT_WEEKENDS)),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "5,6", TIMEZONE, "ACT"), Map.of(HOLIDAY_DATE_FORMAT, DEFAULT_HOLIDAY_DATE_FORMAT)),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "5,6", HOLIDAY_DATE_FORMAT, "dd-mm-YYYY"), Map.of(TIMEZONE, DEFAULT_TIMEZONE)));
+ }
+
+ private static Stream getInvalidPropertiesCalendar() {
+
+ return Stream.of(
+ Arguments.of(Map.of(START_HOUR, "9", END_HOUR, "25"), List.of(END_HOUR + " 25 outside expected boundaries (0-24)")),
+ Arguments.of(Map.of(START_HOUR, "26", END_HOUR, "-2"), List.of(START_HOUR + " 26 outside expected boundaries (0-24)", END_HOUR + " -2 outside expected boundaries (0-24)")),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "1,2,8,9"), List.of(WEEKEND_DAYS + " [8, 9] outside expected boundaries (0-7)")),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "10"), List.of(START_HOUR + " 10 and " + END_HOUR + " 10 must be different")),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "0,1,2"), List.of("0 (= no weekends) and other values provided in the given " + WEEKEND_DAYS + " 0,1,2")),
+ Arguments.of(Map.of(START_HOUR, "10", END_HOUR, "4", WEEKEND_DAYS, "1,1,2"), List.of("There are repeated values in the given " + WEEKEND_DAYS + " 1,1,2")),
+ Arguments.of(Map.of(START_HOUR, "", END_HOUR, ""), List.of(START_HOUR + " is not valid: For input string: \"\"", END_HOUR + " is not valid: For input string: \"\"")));
+ }
+}
diff --git a/jbpm/jbpm-flow/src/test/resources/calendar.properties b/jbpm/jbpm-flow/src/test/resources/calendar.properties
new file mode 100644
index 00000000000..9acb81c21e8
--- /dev/null
+++ b/jbpm/jbpm-flow/src/test/resources/calendar.properties
@@ -0,0 +1,20 @@
+# 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.
+
+business.end.hour=16
+business.start.hour=10
+
diff --git a/jbpm/jbpm-flow/src/test/resources/logback-test.xml b/jbpm/jbpm-flow/src/test/resources/logback-test.xml
index e2693493896..9a48cd358d7 100755
--- a/jbpm/jbpm-flow/src/test/resources/logback-test.xml
+++ b/jbpm/jbpm-flow/src/test/resources/logback-test.xml
@@ -29,6 +29,7 @@
+
diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ErrorEventTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ErrorEventTest.java
index 6840a87d13d..7d80ffd4b3e 100755
--- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ErrorEventTest.java
+++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ErrorEventTest.java
@@ -37,6 +37,8 @@
import org.jbpm.bpmn2.error.EndErrorProcess;
import org.jbpm.bpmn2.error.EndErrorWithEventSubprocessModel;
import org.jbpm.bpmn2.error.EndErrorWithEventSubprocessProcess;
+import org.jbpm.bpmn2.error.ErrorBoundaryEventInterruptingModel;
+import org.jbpm.bpmn2.error.ErrorBoundaryEventInterruptingProcess;
import org.jbpm.bpmn2.error.ErrorBoundaryEventOnServiceTaskModel;
import org.jbpm.bpmn2.error.ErrorBoundaryEventOnServiceTaskProcess;
import org.jbpm.bpmn2.error.ErrorVariableModel;
@@ -64,6 +66,7 @@
import org.jbpm.bpmn2.subprocess.ExceptionServiceProcessSignallingModel;
import org.jbpm.bpmn2.subprocess.ExceptionServiceProcessSignallingProcess;
import org.jbpm.process.instance.event.listeners.RuleAwareProcessEventListener;
+import org.jbpm.process.workitem.builtin.DoNothingWorkItemHandler;
import org.jbpm.process.workitem.builtin.SignallingTaskHandlerDecorator;
import org.jbpm.process.workitem.builtin.SystemOutWorkItemHandler;
import org.jbpm.test.utils.EventTrackerProcessListener;
@@ -89,6 +92,7 @@
import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager;
import org.kie.kogito.internal.process.workitem.WorkItemExecutionException;
import org.kie.kogito.internal.process.workitem.WorkItemTransition;
+import org.kie.kogito.process.Process;
import org.kie.kogito.process.ProcessInstance;
import org.kie.kogito.process.workitems.impl.DefaultKogitoWorkItemHandler;
@@ -231,6 +235,17 @@ public void afterNodeLeft(ProcessNodeLeftEvent event) {
}
+ @Test
+ public void testErrorBoundaryEvent() {
+ Application application = ProcessTestHelper.newApplication();
+ ProcessTestHelper.registerHandler(application, "MyTask",
+ new DoNothingWorkItemHandler());
+ Process process = ErrorBoundaryEventInterruptingProcess.newProcess(application);
+ ProcessInstance instance = process.createInstance(process.createModel());
+ instance.start();
+ assertThat(instance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
+ }
+
@Test
public void testErrorBoundaryEventOnTask() throws Exception {
kruntime = createKogitoProcessRuntime("org/jbpm/bpmn2/error/BPMN2-ErrorBoundaryEventOnTask.bpmn2");
diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java
index ac63c266f8b..4223c3ed323 100755
--- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java
+++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java
@@ -27,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -82,6 +83,8 @@
import org.jbpm.bpmn2.flow.MultiConnEnabledProcess;
import org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessModel;
import org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessProcess;
+import org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessWithORgatewayModel;
+import org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessWithORgatewayProcess;
import org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessWithOutputCmpCondModel;
import org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessWithOutputCmpCondProcess;
import org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessWithOutputModel;
@@ -104,10 +107,8 @@
import org.jbpm.test.utils.EventTrackerProcessListener;
import org.jbpm.test.utils.ProcessTestHelper;
import org.jbpm.workflow.instance.impl.NodeInstanceImpl;
-import org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl;
import org.jbpm.workflow.instance.node.CompositeContextNodeInstance;
import org.jbpm.workflow.instance.node.ForEachNodeInstance;
-import org.jbpm.workflow.instance.node.ForEachNodeInstance.ForEachJoinNodeInstance;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
@@ -122,6 +123,7 @@
import org.kie.internal.command.RegistryContext;
import org.kie.kogito.Application;
import org.kie.kogito.internal.process.event.DefaultKogitoProcessEventListener;
+import org.kie.kogito.internal.process.runtime.KogitoNodeInstance;
import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime;
import org.kie.kogito.internal.process.workitem.KogitoWorkItem;
@@ -896,69 +898,50 @@ public void beforeNodeTriggered(ProcessNodeTriggeredEvent event) {
}
@Test
- public void testMultiInstanceLoopCharacteristicsProcessWithORGateway()
- throws Exception {
- kruntime = createKogitoProcessRuntime("org/jbpm/bpmn2/flow/BPMN2-MultiInstanceLoopCharacteristicsProcessWithORgateway.bpmn2");
-
+ public void testMultiInstanceLoopCharacteristicsProcessWithORGateway() {
+ Application app = ProcessTestHelper.newApplication();
TestWorkItemHandler workItemHandler = new TestWorkItemHandler();
- kruntime.getKogitoWorkItemManager().registerWorkItemHandler("Human Task",
- workItemHandler);
- Map params = new HashMap<>();
+ ProcessTestHelper.registerHandler(app, "Human Task", workItemHandler);
+ org.kie.kogito.process.Process definition = MultiInstanceLoopCharacteristicsProcessWithORgatewayProcess.newProcess(app);
+ MultiInstanceLoopCharacteristicsProcessWithORgatewayModel model = definition.createModel();
List myList = new ArrayList<>();
myList.add(12);
myList.add(15);
- params.put("list", myList);
- KogitoProcessInstance processInstance = kruntime.startProcess(
- "MultiInstanceLoopCharacteristicsProcessWithORgateway", params);
-
+ model.setList(myList);
+ ProcessInstance processInstance = definition.createInstance(model);
+ processInstance.start();
List workItems = workItemHandler.getWorkItems();
assertThat(workItems).hasSize(4);
-
- Collection nodeInstances = ((WorkflowProcessInstanceImpl) processInstance)
- .getNodeInstances();
+ assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
+ Collection nodeInstances = processInstance.findNodes(node -> node instanceof ForEachNodeInstance);
assertThat(nodeInstances).hasSize(1);
- NodeInstance nodeInstance = nodeInstances.iterator().next();
+ KogitoNodeInstance nodeInstance = nodeInstances.iterator().next();
assertThat(nodeInstance).isInstanceOf(ForEachNodeInstance.class);
-
- Collection nodeInstancesChild = ((ForEachNodeInstance) nodeInstance)
- .getNodeInstances();
+ Collection nodeInstancesChild = ((ForEachNodeInstance) nodeInstance).getNodeInstances().stream()
+ .map(n -> (KogitoNodeInstance) n)
+ .collect(Collectors.toList());
assertThat(nodeInstancesChild).hasSize(2);
-
- for (NodeInstance child : nodeInstancesChild) {
+ for (KogitoNodeInstance child : nodeInstancesChild) {
assertThat(child).isInstanceOf(CompositeContextNodeInstance.class);
- assertThat(((CompositeContextNodeInstance) child)
- .getNodeInstances()).hasSize(2);
+ assertThat(((CompositeContextNodeInstance) child).getNodeInstances()).hasSize(2);
}
- kruntime.getKogitoWorkItemManager().completeWorkItem(
- workItems.get(0).getStringId(), null);
- kruntime.getKogitoWorkItemManager().completeWorkItem(
- workItems.get(1).getStringId(), null);
-
- processInstance = kruntime.getProcessInstance(processInstance.getStringId());
- nodeInstances = ((WorkflowProcessInstanceImpl) processInstance)
- .getNodeInstances();
+ processInstance.completeWorkItem(workItems.get(0).getStringId(), null);
+ processInstance.completeWorkItem(workItems.get(1).getStringId(), null);
+ nodeInstances = processInstance.findNodes(node -> node instanceof ForEachNodeInstance);
assertThat(nodeInstances).hasSize(1);
nodeInstance = nodeInstances.iterator().next();
assertThat(nodeInstance).isInstanceOf(ForEachNodeInstance.class);
-
- nodeInstancesChild = ((ForEachNodeInstance) nodeInstance)
- .getNodeInstances();
+ nodeInstancesChild = ((ForEachNodeInstance) nodeInstance).getNodeInstances().stream()
+ .map(n -> (KogitoNodeInstance) n)
+ .collect(Collectors.toList());
assertThat(nodeInstancesChild).hasSize(2);
-
- Iterator childIterator = nodeInstancesChild
- .iterator();
-
+ Iterator childIterator = nodeInstancesChild.iterator();
assertThat(childIterator.next()).isInstanceOf(CompositeContextNodeInstance.class);
- assertThat(childIterator.next()).isInstanceOf(ForEachJoinNodeInstance.class);
-
- kruntime.getKogitoWorkItemManager().completeWorkItem(
- workItems.get(2).getStringId(), null);
- kruntime.getKogitoWorkItemManager().completeWorkItem(
- workItems.get(3).getStringId(), null);
-
- assertProcessInstanceFinished(processInstance, kruntime);
-
+ assertThat(childIterator.next()).isInstanceOf(ForEachNodeInstance.ForEachJoinNodeInstance.class);
+ processInstance.completeWorkItem(workItems.get(2).getStringId(), null);
+ processInstance.completeWorkItem(workItems.get(3).getStringId(), null);
+ assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
}
@Test
diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTimerProcessTest.java
similarity index 82%
rename from jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTest.java
rename to jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTimerProcessTest.java
index 0ca0a9bde87..99704a6e186 100644
--- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTest.java
+++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/BusinessCalendarTimerProcessTest.java
@@ -28,6 +28,7 @@
import org.jbpm.bpmn2.objects.TestWorkItemHandler;
import org.jbpm.process.core.timer.BusinessCalendarImpl;
+import org.jbpm.process.core.timer.CalendarBean;
import org.jbpm.test.utils.ProcessTestHelper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -38,19 +39,20 @@
import static org.assertj.core.api.Assertions.assertThat;
-public class BusinessCalendarTest {
+public class BusinessCalendarTimerProcessTest {
- private static BusinessCalendar workingDayCalendar;
- private static BusinessCalendar notWorkingDayCalendar;
+ private static Properties notWorkingDayCalendarConfiguration;
+ private static Properties workingDayCalendarConfiguration;
@BeforeAll
public static void createCalendars() {
- workingDayCalendar = configureBusinessCalendar(true);
- notWorkingDayCalendar = configureBusinessCalendar(false);
+ workingDayCalendarConfiguration = configureBusinessCalendar(true);
+ notWorkingDayCalendarConfiguration = configureBusinessCalendar(false);
}
@Test
public void testTimerWithWorkingDayCalendar() throws InterruptedException {
+ BusinessCalendar workingDayCalendar = BusinessCalendarImpl.builder().withCalendarBean(new CalendarBean(workingDayCalendarConfiguration)).build();
Application app = ProcessTestHelper.newApplication(new MockProcessConfig(workingDayCalendar));
TestWorkItemHandler workItemHandler = new TestWorkItemHandler();
ProcessTestHelper.registerHandler(app, "Human Task", workItemHandler);
@@ -65,6 +67,7 @@ public void testTimerWithWorkingDayCalendar() throws InterruptedException {
@Test
public void testTimerWithNotWorkingDayCalendar() throws InterruptedException {
+ BusinessCalendar notWorkingDayCalendar = BusinessCalendarImpl.builder().withCalendarBean(new CalendarBean(notWorkingDayCalendarConfiguration)).build();
Application app = ProcessTestHelper.newApplication(new MockProcessConfig(notWorkingDayCalendar));
TestWorkItemHandler workItemHandler = new TestWorkItemHandler();
ProcessTestHelper.registerHandler(app, "Human Task", workItemHandler);
@@ -77,14 +80,12 @@ public void testTimerWithNotWorkingDayCalendar() throws InterruptedException {
assertThat(instance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE);
}
- private static BusinessCalendar configureBusinessCalendar(boolean isWorkingDayCalendar) {
+ private static Properties configureBusinessCalendar(boolean isWorkingDayCalendar) {
Properties businessCalendarConfiguration = new Properties();
if (isWorkingDayCalendar) {
businessCalendarConfiguration.setProperty(BusinessCalendarImpl.START_HOUR, "0");
businessCalendarConfiguration.setProperty(BusinessCalendarImpl.END_HOUR, "24");
- businessCalendarConfiguration.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "24");
- businessCalendarConfiguration.setProperty(BusinessCalendarImpl.DAYS_PER_WEEK, "7");
- businessCalendarConfiguration.setProperty(BusinessCalendarImpl.WEEKEND_DAYS, "8,9");
+ businessCalendarConfiguration.setProperty(BusinessCalendarImpl.WEEKEND_DAYS, "0");
} else {
Calendar currentCalendar = Calendar.getInstance();
Date today = new Date();
@@ -92,10 +93,13 @@ private static BusinessCalendar configureBusinessCalendar(boolean isWorkingDayCa
Date tomorrow = currentCalendar.getTime();
String dateFormat = "yyyy-MM-dd";
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
+ businessCalendarConfiguration.setProperty(BusinessCalendarImpl.START_HOUR, "9");
+ businessCalendarConfiguration.setProperty(BusinessCalendarImpl.END_HOUR, "17");
businessCalendarConfiguration.setProperty(BusinessCalendarImpl.HOLIDAYS, sdf.format(today) + "," + sdf.format(tomorrow));
+ businessCalendarConfiguration.setProperty(BusinessCalendarImpl.WEEKEND_DAYS, "1,2,3,4,5");
businessCalendarConfiguration.setProperty(BusinessCalendarImpl.HOLIDAY_DATE_FORMAT, dateFormat);
}
- return new BusinessCalendarImpl(businessCalendarConfiguration);
+ return businessCalendarConfiguration;
}
private static class MockProcessConfig extends AbstractProcessConfig {
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java
index 4504af36e14..ff375187a2e 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java
@@ -31,6 +31,6 @@ public class BusinessCalendarProducer {
@Produces
public BusinessCalendar createBusinessCalendar() {
- return new BusinessCalendarImpl();
+ return BusinessCalendarImpl.builder().build();
}
}
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java
index 8fd9f766a1f..2f0ca351f8b 100644
--- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java
+++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java
@@ -33,6 +33,6 @@ public class BusinessCalendarProducer {
@Bean
public BusinessCalendar createBusinessCalendar() {
- return new BusinessCalendarImpl();
+ return BusinessCalendarImpl.builder().build();
}
}
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java
deleted file mode 100644
index dd5e5f18ea9..00000000000
--- a/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 $Package$;
-
-import org.kie.kogito.calendar.BusinessCalendar;
-import org.kie.kogito.calendar.BusinessCalendarImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import jakarta.enterprise.inject.Produces;
-
-public class BusinessCalendarProducer {
-
- private static final Logger logger = LoggerFactory.getLogger(BusinessCalendarProducer.class);
-
- @Produces
- public BusinessCalendar createBusinessCalendar() {
- return new BusinessCalendarImpl();
- }
-}
\ No newline at end of file
diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java
deleted file mode 100644
index e80c7cb10b8..00000000000
--- a/kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 $Package$;
-
-import org.kie.kogito.calendar.BusinessCalendar;
-import org.kie.kogito.calendar.BusinessCalendarImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class BusinessCalendarProducer {
-
- private static final Logger logger = LoggerFactory.getLogger(BusinessCalendarProducer.class);
-
- @Bean
- public BusinessCalendar createBusinessCalendar() {
- return new BusinessCalendarImpl();
- }
-}
\ No newline at end of file
diff --git a/kogito-maven-plugin-test/pom.xml b/kogito-maven-plugin-test/pom.xml
index d830611e87d..b125e8a5252 100644
--- a/kogito-maven-plugin-test/pom.xml
+++ b/kogito-maven-plugin-test/pom.xml
@@ -1,4 +1,24 @@
+
diff --git a/kogito-maven-plugin-test/src/main/resources/TrafficViolation.dmn b/kogito-maven-plugin-test/src/main/resources/TrafficViolation.dmn
index daac1062afb..5d3ff539077 100644
--- a/kogito-maven-plugin-test/src/main/resources/TrafficViolation.dmn
+++ b/kogito-maven-plugin-test/src/main/resources/TrafficViolation.dmn
@@ -1,4 +1,22 @@
+
diff --git a/kogito-maven-plugin-test/src/main/resources/traffic-rules-dmn.bpmn b/kogito-maven-plugin-test/src/main/resources/traffic-rules-dmn.bpmn
index 1ddb14979f1..77014130335 100644
--- a/kogito-maven-plugin-test/src/main/resources/traffic-rules-dmn.bpmn
+++ b/kogito-maven-plugin-test/src/main/resources/traffic-rules-dmn.bpmn
@@ -1,4 +1,22 @@
+