From e56ad87d04c2a4897cf2aade920c4711d2ce1a6f Mon Sep 17 00:00:00 2001 From: Gabriele Cardosi Date: Tue, 3 Dec 2024 07:56:51 +0100 Subject: [PATCH 1/7] [NO_ISSUE] Fix license headers (#3802) Co-authored-by: Gabriele-Cardosi --- kogito-maven-plugin-test/pom.xml | 20 +++++++++++++++++++ .../src/main/resources/TrafficViolation.dmn | 18 +++++++++++++++++ .../src/main/resources/traffic-rules-dmn.bpmn | 18 +++++++++++++++++ 3 files changed, 56 insertions(+) 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 @@ + From 40507e911a78858838e4bca342cbfd167264e37f Mon Sep 17 00:00:00 2001 From: Martin Weiler Date: Tue, 3 Dec 2024 08:03:57 -0700 Subject: [PATCH 2/7] [incubator-kie-issues#1648] Fix BusinessCalendarImpl time calculation when currentCalHour < startHour (#3795) * [incubator-kie-issues#1648] Fix BusinessCalendarImpl time calculation when currentCalHour < startHour * Test case for BusinessCalendar time calculation when currentCalHour < endHour * Use parseToDateWithTimeAndMillis to make sure calculation is exact * Additional tests to verify correct handling of seconds --- .../core/timer/BusinessCalendarImpl.java | 16 +++-- .../core/timer/BusinessCalendarImplTest.java | 72 +++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) 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..b22f48cbb35 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 @@ -193,7 +193,9 @@ public long calculateBusinessTimeAsDuration(String timeExpression) { Date calculatedDate = calculateBusinessTimeAsDate(timeExpression); - return (calculatedDate.getTime() - getCurrentTime()); + long calculatedDurationInMs = (calculatedDate.getTime() - getCurrentTime()); + logger.debug("calculateBusinessTimeAsDuration for expression {} returns {} seconds", timeExpression, (calculatedDurationInMs / 1000)); + return calculatedDurationInMs; } public Date calculateBusinessTimeAsDate(String timeExpression) { @@ -251,7 +253,9 @@ public Date calculateBusinessTimeAsDate(String timeExpression) { c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); } else if (currentCalHour < startHour) { - c.add(Calendar.HOUR_OF_DAY, startHour); + c.add(Calendar.HOUR_OF_DAY, startHour - currentCalHour); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); } // calculate remaining hours @@ -267,7 +271,9 @@ public Date calculateBusinessTimeAsDate(String timeExpression) { 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); + c.add(Calendar.HOUR_OF_DAY, startHour - currentCalHour); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); } // calculate minutes @@ -293,7 +299,9 @@ public Date calculateBusinessTimeAsDate(String timeExpression) { 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); + c.add(Calendar.HOUR_OF_DAY, startHour - currentCalHour); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); } // take under consideration weekend handleWeekend(c, false); 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..40ad261bddf 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 @@ -368,6 +368,78 @@ public void testCalculateMinutesPassingWeekend() { assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate); } + @Test + public void testCalculateMinutesBeforeStartHour() { + Properties config = new Properties(); + config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); + config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); + config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); + String currentDate = "2024-11-28 10:48:33.000"; + String duration = "10m"; + String expectedDate = "2024-11-28 14:10:00"; + + SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(currentDate).getTime()); + BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock); + + Date result = businessCal.calculateBusinessTimeAsDate(duration); + + assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate); + } + + @Test + public void testCalculateSecondsBeforeStartHour() { + Properties config = new Properties(); + config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); + config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); + config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); + String currentDate = "2024-11-28 10:48:33.000"; + String duration = "10s"; + String expectedDate = "2024-11-28 14:00:10"; + + SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(currentDate).getTime()); + BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock); + + Date result = businessCal.calculateBusinessTimeAsDate(duration); + + assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate); + } + + @Test + public void testCalculateMinutesBeforeEndHour() { + Properties config = new Properties(); + config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); + config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); + config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); + String currentDate = "2024-11-28 17:58:33.000"; + String duration = "10m"; + String expectedDate = "2024-11-29 14:08:33"; + + SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(currentDate).getTime()); + BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock); + + Date result = businessCal.calculateBusinessTimeAsDate(duration); + + assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate); + } + + @Test + public void testCalculateSecondsBeforeEndHour() { + Properties config = new Properties(); + config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); + config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); + config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); + String currentDate = "2024-11-28 17:59:33.000"; + String duration = "50s"; + String expectedDate = "2024-11-29 14:00:23"; + + SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(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"); From 59e24683cbe9e54e75e8024e0ae3710b8a288c5e Mon Sep 17 00:00:00 2001 From: Toshiya Kobayashi Date: Wed, 4 Dec 2024 14:31:18 +0900 Subject: [PATCH 3/7] [incubator-kie-issues-1618] Fix NOTICE year (#3803) --- NOTICE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE b/NOTICE index a1c5656ff47..c4276f2d54e 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache KIE -Copyright 2023 The Apache Software Foundation +Copyright 2024 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). From 1250509a231bd08275cb0990e7d2331f6ff5c7d0 Mon Sep 17 00:00:00 2001 From: Martin Weiler Date: Wed, 4 Dec 2024 11:06:30 -0700 Subject: [PATCH 4/7] [incubator-kie-issues#1650] Add nodeInstanceId reference to node SLA timers (#3799) * [incubator-kie-issues#1650] Add nodeInstanceId reference to node SLA timers * Minor refactor --- .../instance/impl/WorkflowProcessInstanceImpl.java | 11 ++++++++++- .../workflow/instance/node/EventNodeInstance.java | 2 +- .../instance/node/StateBasedNodeInstance.java | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) 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()); From d55b9e39ae55aef1227716b9261a792551752d00 Mon Sep 17 00:00:00 2001 From: Toshiya Kobayashi Date: Thu, 5 Dec 2024 11:19:53 +0900 Subject: [PATCH 5/7] [incubator-kie-issues-1618] Fix NOTICE year with range (#3806) --- NOTICE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE b/NOTICE index c4276f2d54e..1c13133b8f8 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache KIE -Copyright 2024 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/). From 4b0fce530de9cd09345237dbdc709ac8d5d52dae Mon Sep 17 00:00:00 2001 From: Abhiram Gundala <164050036+Abhitocode@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:11:42 -0500 Subject: [PATCH 6/7] [incubator-kie-issues-1131] v7 migration to code generation (#3649) --- .../java/org/jbpm/bpmn2/ErrorEventTest.java | 15 ++++ .../test/java/org/jbpm/bpmn2/FlowTest.java | 79 ++++++++----------- 2 files changed, 46 insertions(+), 48 deletions(-) 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 From 1346b0e580f8104bde12feeee659494c4dae1fd0 Mon Sep 17 00:00:00 2001 From: Abhiram Gundala <164050036+Abhitocode@users.noreply.github.com> Date: Mon, 9 Dec 2024 06:30:05 -0500 Subject: [PATCH 7/7] [incubator-kie-issues#1612] Fix BusinessCalendar behavior with inconsistent (full/partial) set of properties on `calendar.properties` (#3788) * incubator-kie-issues-1612 * incubator-kie-issues-1612 * incubator-kie-issues-1612 * [incubator-kie-issues#1612] TODO setup * [incubator-kie-issues#1612] Separating different concerns in different classes: CalendarBean is responsible of calendar.properties file. BusinessCalendarImpl is responsible of working time evaluation * incubator-kie-issues-1612 * [incubator-kie-issues#1612] Example test * incubator-kie-issues-1612 * incubator-kie-issues-1612 * [incubator-kie-issues#1612] Extend test coverage. Minor refactoring related to it. * incubator-kie-issues-1612 * [incubator-kie-issues#1612] Comment on tests * [incubator-kie-issues#1612] Minor fixes * [incubator-kie-issues#1612] Extend test coverage. Minor refactoring related to it. * [incubator-kie-issues#1612] Minor refactoring * incubator-kie-issues-1612 * [incubator-kie-issues#1612] Fixing tests. Including incubator-kie-issues#1648 fix * [incubator-kie-issues#1612] WIP - implementing nightly hour * [incubator-kie-issues#1612] WIP - Simplify test - moving tested methods to static * [incubator-kie-issues#1612] WIP - Cleanup * [incubator-kie-issues#1612] Working tests. * incubator-kie-issues-1612 * incubator-kie-issues-1612 * incubator-kie-issues-1612 * [incubator-kie-issues#1612] Fixed logging * [incubator-kie-issues#1612] Fixed minute/second reset on calendarRolling * [incubator-kie-issues#1612] Fixed assertiont JUnit5 -> assertj * [incubator-kie-issues#1612] Fixed test * incubator-kie-issues-1612 * incubator-kie-issues-1612 * [incubator-kie-issues#1612] Avoid minute/second reset on rolling hour, since the minute/second management is based on "add" operation * [incubator-kie-issues#1612] Fix naming * [incubator-kie-issues#1612] Add minute / second test/fix * [incubator-kie-issues#1612] Extend test coverage * updated logging * logger update * logger update --------- Co-authored-by: Gabriele-Cardosi --- .../kie/kogito/calendar/BusinessCalendar.java | 13 +- .../core/timer/BusinessCalendarImpl.java | 622 ++++++++-------- .../jbpm/process/core/timer/CalendarBean.java | 399 ++++++++++ .../core/timer/CalendarBeanFactory.java | 60 ++ .../core/timer/BusinessCalendarImplTest.java | 696 +++++++----------- .../core/timer/CalendarBeanFactoryTest.java | 45 ++ .../process/core/timer/CalendarBeanTest.java | 297 ++++++++ .../src/test/resources/calendar.properties | 20 + .../src/test/resources/logback-test.xml | 1 + ... => BusinessCalendarTimerProcessTest.java} | 24 +- ...sinessCalendarProducerQuarkusTemplate.java | 2 +- ...usinessCalendarProducerSpringTemplate.java | 2 +- ...sinessCalendarProducerQuarkusTemplate.java | 36 - ...usinessCalendarProducerSpringTemplate.java | 37 - 14 files changed, 1450 insertions(+), 804 deletions(-) create mode 100644 jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBean.java create mode 100644 jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/timer/CalendarBeanFactory.java create mode 100644 jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanFactoryTest.java create mode 100644 jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/CalendarBeanTest.java create mode 100644 jbpm/jbpm-flow/src/test/resources/calendar.properties rename jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/calendar/{BusinessCalendarTest.java => BusinessCalendarTimerProcessTest.java} (82%) delete mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerQuarkusTemplate.java delete mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/test/resources/class-templates/producer/BusinessCalendarProducerSpringTemplate.java 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 b22f48cbb35..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,97 +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()); - long calculatedDurationInMs = (calculatedDate.getTime() - getCurrentTime()); - logger.debug("calculateBusinessTimeAsDuration for expression {} returns {} seconds", timeExpression, (calculatedDurationInMs / 1000)); - return calculatedDurationInMs; + return (calculatedDate.getTime() - getCurrentTime()); } + /** + * @inheritDoc + */ + @Override public Date calculateBusinessTimeAsDate(String timeExpression) { + logger.trace("timeExpression {}", timeExpression); timeExpression = adoptISOFormat(timeExpression); String trimmed = timeExpression.trim(); @@ -208,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; @@ -218,121 +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 - currentCalHour); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - } + 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 - currentCalHour); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - } + 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 - currentCalHour); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); + 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; } } @@ -340,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; @@ -471,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/test/java/org/jbpm/process/core/timer/BusinessCalendarImplTest.java b/jbpm/jbpm-flow/src/test/java/org/jbpm/process/core/timer/BusinessCalendarImplTest.java index 40ad261bddf..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,491 +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); - } - - @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); - } - - @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); - } - - @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); - } - - @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 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 testCalculateDaysStartingInWeekend() { - 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); + void calculateBusinessTimeAsDateInsideDailyWorkingHourWithDelay() { + int daysToSkip = 0; // since executionHourDelay falls before endHOurGap + commonCalculateBusinessTimeAsDateAssertBetweenHours(-4, 4, 0, 3, daysToSkip, null, null); } @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); + 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 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); + void calculateBusinessTimeAsDateInsideNightlyWorkingHour() { + int daysToSkip = 0; // since executionHourDelay falls before endHOurGap + commonCalculateBusinessTimeAsDateAssertBetweenHours(4, -4, 0, 3, daysToSkip, null, null); } @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); + void calculateBusinessTimeAsDateBeforeWorkingHourWithDelay() { + int daysToSkip = 0; // since executionHourDelay falls before endHOurGap + commonCalculateBusinessTimeAsDateAssertBetweenHours(2, 4, -1, 1, daysToSkip, null, null); } @Test - public void testCalculateTimeDaysHoursMinutesHolidays() { + 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(); - 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); - } - - @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); - } - - @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); - } - - @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); - } + config.setProperty(BusinessCalendarImpl.START_HOUR, String.valueOf(startHour)); + config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); + config.setProperty(WEEKEND_DAYS, "0"); - @Test - public void testCalculateDaysPassingOverHolidayAtYearEndWithWildcards() { - Properties config = new Properties(); - config.setProperty(BusinessCalendarImpl.HOLIDAYS, "*-12-31:*-01-01"); - String expectedDate = "2013-01-02 09:15"; + 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"; - SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTime("2012-12-28 16:45").getTime()); - BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String retrievedTime = sdf.format(retrieved); + assertThat(retrievedTime).isEqualTo(expectedDate); - Date result = businessCal.calculateBusinessTimeAsDate("2d30m"); + delay = "10s"; + retrieved = businessCal.calculateBusinessTimeAsDate(delay); + expectedDate = "2024-11-28 14:00:10"; + retrievedTime = sdf.format(retrieved); + assertThat(retrievedTime).isEqualTo(expectedDate); - assertThat(formatDate("yyyy-MM-dd HH:mm", result)).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 testCalculateISOHours() { - 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("PT3H"); - - 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 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 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 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 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 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"); + 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; - assertThat(formatDate("yyyy-MM-dd HH:mm:ss.SSS", result)).isEqualTo(expectedDate); + 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 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); - } - + 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 testBusinessCalendarWithoutProvidedConfiguration() { - assertDoesNotThrow(() -> new BusinessCalendarImpl()); - } - - @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); + 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); - assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate); } @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); + 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 testCalculateMinutesBeforeStartHour() { - Properties config = new Properties(); - config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); - config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); - config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); - String currentDate = "2024-11-28 10:48:33.000"; - String duration = "10m"; - String expectedDate = "2024-11-28 14:10:00"; - - SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(currentDate).getTime()); - BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock); - - Date result = businessCal.calculateBusinessTimeAsDate(duration); - - assertThat(formatDate("yyyy-MM-dd HH:mm:ss", 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 testCalculateSecondsBeforeStartHour() { - Properties config = new Properties(); - config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); - config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); - config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); - String currentDate = "2024-11-28 10:48:33.000"; - String duration = "10s"; - String expectedDate = "2024-11-28 14:00:10"; - - SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(currentDate).getTime()); - BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock); - - Date result = businessCal.calculateBusinessTimeAsDate(duration); - - assertThat(formatDate("yyyy-MM-dd HH:mm:ss", result)).isEqualTo(expectedDate); - } - - @Test - public void testCalculateMinutesBeforeEndHour() { - Properties config = new Properties(); - config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); - config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); - config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); - String currentDate = "2024-11-28 17:58:33.000"; - String duration = "10m"; - String expectedDate = "2024-11-29 14:08:33"; - - SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(currentDate).getTime()); - BusinessCalendarImpl businessCal = new BusinessCalendarImpl(config, clock); - - Date result = businessCal.calculateBusinessTimeAsDate(duration); - - assertThat(formatDate("yyyy-MM-dd HH:mm:ss", 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()); + } + + 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); + } + + 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); + } + + 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 testCalculateSecondsBeforeEndHour() { Properties config = new Properties(); - config.setProperty(BusinessCalendarImpl.HOURS_PER_DAY, "4"); - config.setProperty(BusinessCalendarImpl.START_HOUR, "14"); - config.setProperty(BusinessCalendarImpl.END_HOUR, "18"); - String currentDate = "2024-11-28 17:59:33.000"; - String duration = "50s"; - String expectedDate = "2024-11-29 14:00:23"; - - SessionPseudoClock clock = new StaticPseudoClock(parseToDateWithTimeAndMillis(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"); + 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); - Date testTime; - try { - testTime = sdf.parse(dateString); + Date expectedStart = startCalendar.getTime(); + Date expectedEnd = endCalendar.getTime(); - return testTime; - } catch (ParseException e) { - return null; - } - } + Instant retrievedInstant = retrieved.toInstant(); + Instant expectedStartTime = expectedStart.toInstant(); + Instant expectedEndTime = expectedEnd.toInstant(); - private String formatDate(String pattern, Date date) { - SimpleDateFormat sdf = new SimpleDateFormat(pattern); - - String testTime = sdf.format(date); - - 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; - } - - public long advanceTime(long amount, TimeUnit unit) { - throw new UnsupportedOperationException("It is static clock and does not allow advance time operation"); + private Calendar getCalendarAtExpectedWeekDay(int weekDay) { + Calendar toReturn = Calendar.getInstance(); + while (toReturn.get(Calendar.DAY_OF_WEEK) != weekDay) { + toReturn.add(Calendar.DAY_OF_YEAR, 1); } - + 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/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