Repository: JDEasyFlow/jd-easyflow Branch: master Commit: c777019f49ff Files: 1205 Total size: 16.1 MB Directory structure: gitextract_yjh79igv/ ├── .gitignore ├── LICENSE.txt ├── NOTICE.txt ├── README-zh.md ├── README.md ├── easyflow-flow/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── jd/ │ │ │ └── easyflow/ │ │ │ └── flow/ │ │ │ ├── common/ │ │ │ │ └── FlowExecutor.java │ │ │ ├── el/ │ │ │ │ ├── ElEvaluator.java │ │ │ │ ├── ElFactory.java │ │ │ │ ├── ElRootMap.java │ │ │ │ └── SpelEvaluator.java │ │ │ ├── engine/ │ │ │ │ ├── FlowContext.java │ │ │ │ ├── FlowEngine.java │ │ │ │ ├── FlowParam.java │ │ │ │ ├── FlowResult.java │ │ │ │ ├── FlowRunner.java │ │ │ │ ├── builder/ │ │ │ │ │ └── FlowParamBuilder.java │ │ │ │ ├── event/ │ │ │ │ │ ├── BaseFlowEventListener.java │ │ │ │ │ ├── ExpFlowEventListener.java │ │ │ │ │ ├── FlowEvent.java │ │ │ │ │ ├── FlowEventListener.java │ │ │ │ │ ├── FlowEventTrigger.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── EventFlowListener.java │ │ │ │ │ └── InterruptFlowListener.java │ │ │ │ ├── filter/ │ │ │ │ │ ├── FlowEngineFilter.java │ │ │ │ │ ├── FlowEngineFilterManager.java │ │ │ │ │ └── impl/ │ │ │ │ │ └── BaseFlowEngineFilter.java │ │ │ │ └── impl/ │ │ │ │ ├── BaseFlowRunner.java │ │ │ │ ├── CoreFlowEngine.java │ │ │ │ ├── ExpFlowRunner.java │ │ │ │ ├── FlowContextImpl.java │ │ │ │ ├── FlowEngineImpl.java │ │ │ │ ├── MultipleThreadFlowRunner.java │ │ │ │ ├── ReusableThreadFlowRunner.java │ │ │ │ └── SingleThreadFlowRunner.java │ │ │ ├── exception/ │ │ │ │ └── FlowException.java │ │ │ ├── filter/ │ │ │ │ ├── BaseFilter.java │ │ │ │ ├── ExpFilter.java │ │ │ │ ├── Filter.java │ │ │ │ ├── FilterChain.java │ │ │ │ └── FilterChainImpl.java │ │ │ ├── model/ │ │ │ │ ├── Flow.java │ │ │ │ ├── FlowLifeCycle.java │ │ │ │ ├── FlowNode.java │ │ │ │ ├── FlowPostHandler.java │ │ │ │ ├── FlowPreHandler.java │ │ │ │ ├── InitContext.java │ │ │ │ ├── NodeAction.java │ │ │ │ ├── NodeContext.java │ │ │ │ ├── NodeContextAccessor.java │ │ │ │ ├── NodeExecutor.java │ │ │ │ ├── NodePostHandler.java │ │ │ │ ├── NodePreHandler.java │ │ │ │ ├── action/ │ │ │ │ │ ├── ActionResultEl.java │ │ │ │ │ ├── CompensateNodeAction.java │ │ │ │ │ ├── EventNodeAction.java │ │ │ │ │ ├── ExecutorNodeAction.java │ │ │ │ │ ├── ExpNodeAction.java │ │ │ │ │ ├── FlowNodeAction.java │ │ │ │ │ ├── InterruptNodeAction.java │ │ │ │ │ ├── LoopNodeAction.java │ │ │ │ │ ├── ParamExecutorNodeAction.java │ │ │ │ │ └── compensate/ │ │ │ │ │ ├── CompensateAction.java │ │ │ │ │ ├── CompensateFlowFilter.java │ │ │ │ │ ├── CompensateFlowParseEventListener.java │ │ │ │ │ ├── CompensateHelper.java │ │ │ │ │ ├── CompensateNodeFilter.java │ │ │ │ │ ├── CompensateNodePreHandlerFilter.java │ │ │ │ │ └── FlowCompensateAction.java │ │ │ │ ├── builder/ │ │ │ │ │ └── FlowBuilder.java │ │ │ │ ├── definition/ │ │ │ │ │ └── DefConstants.java │ │ │ │ ├── filter/ │ │ │ │ │ ├── FlowFilter.java │ │ │ │ │ ├── FlowFilterManager.java │ │ │ │ │ ├── FlowPostHandlerFilter.java │ │ │ │ │ ├── FlowPreHandlerFilter.java │ │ │ │ │ ├── NodeActionFilter.java │ │ │ │ │ ├── NodeFilter.java │ │ │ │ │ ├── NodePostHandlerFilter.java │ │ │ │ │ ├── NodePreHandlerFilter.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── BaseFlowFilter.java │ │ │ │ │ ├── BaseFlowPostHandlerFilter.java │ │ │ │ │ ├── BaseFlowPreHandlerFilter.java │ │ │ │ │ ├── BaseNodeActionFilter.java │ │ │ │ │ ├── BaseNodeFilter.java │ │ │ │ │ ├── BaseNodePostHandlerFilter.java │ │ │ │ │ └── BaseNodePreHandlerFilter.java │ │ │ │ ├── flow/ │ │ │ │ │ ├── post/ │ │ │ │ │ │ └── ExpFlowPostHandler.java │ │ │ │ │ └── pre/ │ │ │ │ │ └── ExpFlowPreHandler.java │ │ │ │ ├── node/ │ │ │ │ │ └── NodeImpl.java │ │ │ │ ├── parser/ │ │ │ │ │ ├── FlowParser.java │ │ │ │ │ ├── FlowParserImpl.java │ │ │ │ │ ├── event/ │ │ │ │ │ │ ├── ExpFlowParseEventListener.java │ │ │ │ │ │ ├── FlowParseEvent.java │ │ │ │ │ │ ├── FlowParseEventListener.java │ │ │ │ │ │ └── FlowParseEventTypes.java │ │ │ │ │ └── param/ │ │ │ │ │ ├── ActionParseParam.java │ │ │ │ │ ├── FlowParseParam.java │ │ │ │ │ ├── PostParseParam.java │ │ │ │ │ └── PreParseParam.java │ │ │ │ ├── post/ │ │ │ │ │ ├── AbstractNodePostHandler.java │ │ │ │ │ ├── ConditionalNodePostHandler.java │ │ │ │ │ ├── EventPostHandler.java │ │ │ │ │ ├── ExecutorNodePostHandler.java │ │ │ │ │ ├── ExpNodePostHandler.java │ │ │ │ │ └── FixedNodePostHandler.java │ │ │ │ └── pre/ │ │ │ │ ├── ExpNodePreHandler.java │ │ │ │ ├── InclusiveCheckPreHandler.java │ │ │ │ ├── MultiCheckPreHandler.java │ │ │ │ ├── NodePreHandlerHelper.java │ │ │ │ └── NodePrePropertyGetter.java │ │ │ └── util/ │ │ │ ├── ExceptionUtil.java │ │ │ ├── FlowConstants.java │ │ │ ├── FlowEventTypes.java │ │ │ ├── FlowIOUtil.java │ │ │ ├── FlowNodeLinkUtil.java │ │ │ ├── FlowStringUtil.java │ │ │ ├── FlowUtil.java │ │ │ ├── JsonFacade.java │ │ │ ├── JsonFacadeJacksonImpl.java │ │ │ ├── JsonPrettyHelper.java │ │ │ ├── JsonUtil.java │ │ │ ├── LockUtil.java │ │ │ ├── Pair.java │ │ │ └── Triple.java │ │ └── resources/ │ │ └── pretty/ │ │ └── pretty-flow.json │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── jd/ │ │ └── easyflow/ │ │ └── flow/ │ │ ├── FlowTestSuite.java │ │ ├── cases/ │ │ │ ├── action/ │ │ │ │ ├── ActionTest.java │ │ │ │ ├── CompensateTest.java │ │ │ │ ├── LoopNodeActionTest.java │ │ │ │ ├── MockActionClass.java │ │ │ │ ├── MockClassMethodAction.java │ │ │ │ ├── MockInputOutputEventListener.java │ │ │ │ └── MultipleActionTest.java │ │ │ ├── common/ │ │ │ │ └── TestNodeExecutor.java │ │ │ ├── event/ │ │ │ │ ├── EventFlowTest.java │ │ │ │ └── TestEventNodeAction.java │ │ │ ├── filter/ │ │ │ │ └── FilterTest.java │ │ │ ├── flowengine/ │ │ │ │ ├── FlowEngineImplTest.java │ │ │ │ ├── PocFlowEngineImpl.java │ │ │ │ └── PocNodeImpl.java │ │ │ ├── inclusive/ │ │ │ │ └── InclusiveTest.java │ │ │ ├── interrupt/ │ │ │ │ └── InterruptTest.java │ │ │ ├── listener/ │ │ │ │ └── InterruptFlowListenerTest.java │ │ │ ├── logflag/ │ │ │ │ └── LogFlagTest.java │ │ │ ├── mockbiz/ │ │ │ │ ├── CheckBiz.java │ │ │ │ ├── LimitBiz.java │ │ │ │ ├── LoanBiz.java │ │ │ │ ├── LoanContractSignBiz.java │ │ │ │ ├── MockFlowListener.java │ │ │ │ └── MockLoanTest.java │ │ │ ├── parallel/ │ │ │ │ └── ParallelTest.java │ │ │ ├── parser/ │ │ │ │ ├── FlowParserTest.java │ │ │ │ ├── TestAddFilterParseListener.java │ │ │ │ ├── TestFlowParamAndResultPrintFilter.java │ │ │ │ ├── TestFlowParseListener.java │ │ │ │ └── TestFlowParserImpl.java │ │ │ ├── performance/ │ │ │ │ ├── EmptyStepAction.java │ │ │ │ ├── LoopStepAction.java │ │ │ │ ├── MemoryTest.java │ │ │ │ └── PerformanceTest.java │ │ │ ├── posthandler/ │ │ │ │ ├── ConditionalPostHandlerTest.java │ │ │ │ ├── FlowIndexTest.java │ │ │ │ ├── NodePostHandlerTest.java │ │ │ │ ├── PostHandlerFilterTest.java │ │ │ │ ├── PostParamNode1Action.java │ │ │ │ ├── PostParamNode2Action.java │ │ │ │ ├── PostParamNode3Action.java │ │ │ │ └── TestPostHandlerFilter.java │ │ │ ├── prehandler/ │ │ │ │ └── PreHandlerTest.java │ │ │ ├── pretty/ │ │ │ │ └── FlowDefPrettyHelperTest.java │ │ │ ├── runner/ │ │ │ │ ├── MultiThreadTest.java │ │ │ │ ├── ReusableThreadTest.java │ │ │ │ ├── TestInterruptNodeAction.java │ │ │ │ ├── TestMultiRunner.java │ │ │ │ └── TestReusableThreadRunner.java │ │ │ ├── share/ │ │ │ │ └── nodeaction/ │ │ │ │ └── TestStepAction.java │ │ │ ├── spring/ │ │ │ │ └── SpringFlowTest.java │ │ │ └── subflow/ │ │ │ ├── SubFlowTest.java │ │ │ └── TestInvokeSubFlowNodeAction.java │ │ ├── quickstart/ │ │ │ ├── QuickStart001Node01Action.java │ │ │ ├── QuickStart002Node01Action.java │ │ │ ├── QuickStart003Node01Action.java │ │ │ └── QuickStartTest.java │ │ └── unit/ │ │ └── util/ │ │ ├── JsonTest.java │ │ └── TreeMapTest.java │ └── resources/ │ ├── flow/ │ │ ├── cases/ │ │ │ ├── action/ │ │ │ │ ├── action_interrupt_001.json │ │ │ │ ├── compensate_001.json │ │ │ │ ├── compensate_002.json │ │ │ │ ├── compensate_flow_001.json │ │ │ │ ├── loop_test_001.json │ │ │ │ └── multiple_action_001.json │ │ │ ├── event/ │ │ │ │ ├── flow_event_001.json │ │ │ │ ├── flow_event_002.json │ │ │ │ ├── flow_event_003.json │ │ │ │ └── flow_event_nolistener_001.json │ │ │ ├── filter/ │ │ │ │ ├── inner_flow_engine_filter_001.json │ │ │ │ ├── inner_flow_filter_001.json │ │ │ │ └── inner_node_action_filter_001.json │ │ │ ├── inclusive/ │ │ │ │ ├── flow_inclusive201.json │ │ │ │ ├── flow_inclusive202.json │ │ │ │ ├── flow_inclusive203.json │ │ │ │ └── flow_inclusive303.json │ │ │ ├── interrupt/ │ │ │ │ ├── interrupt_001.json │ │ │ │ └── interrupt_002.json │ │ │ ├── listener/ │ │ │ │ ├── interrupt_listener_001.json │ │ │ │ ├── interrupt_listener_002.json │ │ │ │ └── interrupt_listener_003.json │ │ │ ├── logflag/ │ │ │ │ └── logflag_001.json │ │ │ ├── mockbiz/ │ │ │ │ └── mock_loan_001.json │ │ │ ├── parallel/ │ │ │ │ └── flow_parallel001.json │ │ │ ├── parser/ │ │ │ │ ├── flow_multiple001.json │ │ │ │ ├── parser_test_001.json │ │ │ │ ├── parser_test_002.json │ │ │ │ ├── parser_test_003.json │ │ │ │ ├── parser_test_subflow_001.json │ │ │ │ └── parser_test_subflow_002.json │ │ │ ├── performance/ │ │ │ │ ├── flow_memory001.json │ │ │ │ └── flow_performance001.json │ │ │ ├── posthandler/ │ │ │ │ ├── flow_createexp_post_to_001.json │ │ │ │ ├── flow_exp_post_to_001.json │ │ │ │ ├── flow_index001.json │ │ │ │ ├── flow_post_handler_filter_001.json │ │ │ │ ├── flow_post_param_001.json │ │ │ │ ├── flow_postdata_001.json │ │ │ │ └── flow_when_createexp_001.json │ │ │ ├── prehandler/ │ │ │ │ └── flow_prehandler_001.json │ │ │ ├── pretty/ │ │ │ │ ├── pretty_multiple_test.json │ │ │ │ └── pretty_test.json │ │ │ ├── runner/ │ │ │ │ ├── flow_interrupt001.json │ │ │ │ ├── flow_interrupt002.json │ │ │ │ ├── flow_interrupt003.json │ │ │ │ ├── flow_multi001.json │ │ │ │ ├── flow_multi002.json │ │ │ │ ├── flow_multi003.json │ │ │ │ ├── flow_multi004.json │ │ │ │ ├── flow_reusable001.json │ │ │ │ ├── flow_reusable002.json │ │ │ │ ├── flow_reusable003.json │ │ │ │ └── flow_reusable004.json │ │ │ ├── spring/ │ │ │ │ ├── applicationContext-flow-test.xml │ │ │ │ └── flow_spring_001.json │ │ │ └── subflow/ │ │ │ ├── flow_subflow_001.json │ │ │ ├── flow_subflow_002.json │ │ │ ├── flow_subflow_002_01.json │ │ │ ├── flow_subflow_003.json │ │ │ └── flow_subflow_003_01.json │ │ └── quickstart/ │ │ └── quickstart_001.json │ └── logback.xml ├── easyflow-flow-bpmn/ │ ├── BPMNDesigner.html │ ├── pom.xml │ ├── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ ├── .gitkeep │ │ │ └── com/ │ │ │ └── jd/ │ │ │ └── easyflow/ │ │ │ └── flow/ │ │ │ └── bpmn/ │ │ │ ├── BpmnFlowParser.java │ │ │ └── converter/ │ │ │ ├── BaseFlowNodeConverter.java │ │ │ ├── BpmnConverter.java │ │ │ ├── FlowNodeConverter.java │ │ │ ├── activity/ │ │ │ │ ├── AdhocSubProcessConverter.java │ │ │ │ ├── BusinessRuleTaskConverter.java │ │ │ │ ├── CallActivityConverter.java │ │ │ │ ├── EventSubProcessConverter.java │ │ │ │ ├── ManualTaskConverter.java │ │ │ │ ├── ReceiveTaskConverter.java │ │ │ │ ├── ScriptTaskConverter.java │ │ │ │ ├── SendTaskConverter.java │ │ │ │ ├── ServiceTaskConverter.java │ │ │ │ ├── SubProcessConverter.java │ │ │ │ ├── TaskConverter.java │ │ │ │ ├── TransactionConverter.java │ │ │ │ └── UserTaskConverter.java │ │ │ ├── event/ │ │ │ │ ├── EndEventConverter.java │ │ │ │ ├── IntermediateCatchEventConverter.java │ │ │ │ ├── StartEventConverter.java │ │ │ │ └── ThrowEventConverter.java │ │ │ ├── gateway/ │ │ │ │ ├── ComplexGatewayConverter.java │ │ │ │ ├── EventGatewayConverter.java │ │ │ │ ├── ExclusiveGatewayConverter.java │ │ │ │ ├── InclusiveGatewayConverter.java │ │ │ │ └── ParallelGatewayConverter.java │ │ │ └── util/ │ │ │ ├── BpmnXmlConstants.java │ │ │ └── ConvertUtil.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── jd/ │ │ │ └── easyflow/ │ │ │ └── flow/ │ │ │ └── bpmn/ │ │ │ ├── FlowBpmnTestSuite.java │ │ │ ├── cases/ │ │ │ │ ├── callactivity/ │ │ │ │ │ └── BpmnCallActivityTest.java │ │ │ │ ├── chain/ │ │ │ │ │ └── FlowChainTest.java │ │ │ │ ├── converter/ │ │ │ │ │ ├── BpmnConverterTest.java │ │ │ │ │ └── BpmnTestService.java │ │ │ │ ├── defaultflow/ │ │ │ │ │ └── DefaultFlowTest.java │ │ │ │ ├── extension/ │ │ │ │ │ └── BpmnExtensionTest.java │ │ │ │ ├── inclusive/ │ │ │ │ │ └── BpmnInclusiveTest.java │ │ │ │ ├── logflag/ │ │ │ │ │ └── LogFlagTest.java │ │ │ │ ├── parallel/ │ │ │ │ │ └── BpmnParallelTest.java │ │ │ │ ├── share/ │ │ │ │ │ └── nodeaction/ │ │ │ │ │ ├── TestEventNodeAction.java │ │ │ │ │ └── TestStepAction.java │ │ │ │ ├── subprocess/ │ │ │ │ │ └── BpmnSubProcessTest.java │ │ │ │ ├── terminate/ │ │ │ │ │ └── BpmnTerminateTest.java │ │ │ │ └── throwevent/ │ │ │ │ └── BpmnThrowEventTest.java │ │ │ ├── ext/ │ │ │ │ └── cases/ │ │ │ │ └── chain/ │ │ │ │ ├── ChainTest.java │ │ │ │ ├── ChainTestService.java │ │ │ │ ├── Plugin1.java │ │ │ │ ├── Plugin2.java │ │ │ │ └── Plugin3.java │ │ │ └── quickstart/ │ │ │ ├── QuickStart001Node01Action.java │ │ │ ├── QuickStart002Node01Action.java │ │ │ ├── QuickStart003Node01Action.java │ │ │ └── QuickStartTest.java │ │ └── resources/ │ │ ├── flow/ │ │ │ ├── cases/ │ │ │ │ ├── callactivity/ │ │ │ │ │ ├── flow_callactivity_test_001.bpmn │ │ │ │ │ └── flow_sub_of_called.bpmn │ │ │ │ ├── chain/ │ │ │ │ │ └── flow_chaintest1.bpmn │ │ │ │ ├── converter/ │ │ │ │ │ ├── compensate001.bpmn │ │ │ │ │ ├── flow001.json │ │ │ │ │ ├── flow_multiple_001.bpmn │ │ │ │ │ ├── process_1.bpmn │ │ │ │ │ └── process_1_simple.bpmn │ │ │ │ ├── defaultflow/ │ │ │ │ │ └── default_flow_001.bpmn │ │ │ │ ├── extension/ │ │ │ │ │ └── process_1.bpmn │ │ │ │ ├── inclusive/ │ │ │ │ │ ├── flow_inclusive001.bpmn │ │ │ │ │ └── flow_inclusive002.bpmn │ │ │ │ ├── logflag/ │ │ │ │ │ └── logflag_001.bpmn │ │ │ │ ├── parallel/ │ │ │ │ │ ├── flow_multi_parallel_subflow_001.bpmn │ │ │ │ │ └── flow_parallel001.bpmn │ │ │ │ ├── subprocess/ │ │ │ │ │ └── flow_subprocess_test_001.bpmn │ │ │ │ ├── terminate/ │ │ │ │ │ └── flow_terminate001.bpmn │ │ │ │ └── throwevent/ │ │ │ │ └── flow_throwevent001.bpmn │ │ │ ├── ext/ │ │ │ │ └── cases/ │ │ │ │ └── chain/ │ │ │ │ └── flow_chaintest1.bpmn │ │ │ └── quickstart/ │ │ │ └── quickstart_001.bpmn │ │ └── logback.xml │ └── static/ │ ├── plugins/ │ │ ├── bootstrap/ │ │ │ ├── css/ │ │ │ │ ├── bootstrap-grid.css │ │ │ │ ├── bootstrap-reboot.css │ │ │ │ └── bootstrap.css │ │ │ └── js/ │ │ │ ├── bootstrap.bundle.js │ │ │ └── bootstrap.js │ │ ├── bootstrap-multiselect/ │ │ │ ├── css/ │ │ │ │ └── bootstrap-multiselect.css │ │ │ ├── js/ │ │ │ │ └── bootstrap-multiselect.js │ │ │ └── less/ │ │ │ └── bootstrap-multiselect.less │ │ ├── bpmnjs/ │ │ │ ├── assets/ │ │ │ │ ├── bpmn-font/ │ │ │ │ │ └── css/ │ │ │ │ │ ├── bpmn-codes.css │ │ │ │ │ ├── bpmn-embedded.css │ │ │ │ │ └── bpmn.css │ │ │ │ ├── bpmn-js.css │ │ │ │ └── diagram-js.css │ │ │ ├── bpmn-modeler.development.js │ │ │ ├── bpmn-navigated-viewer.development.js │ │ │ ├── bpmn-viewer.development.js │ │ │ └── changeForEasyflow/ │ │ │ ├── changelog.txt │ │ │ └── rollup.config.js │ │ ├── flow/ │ │ │ ├── bpmn.js │ │ │ ├── flow.css │ │ │ └── flow.js │ │ ├── fontawesome/ │ │ │ └── css/ │ │ │ └── all.css │ │ ├── jquery/ │ │ │ └── jquery-3.6.0.js │ │ ├── jquery-validation/ │ │ │ └── jquery.validate.js │ │ └── std-common/ │ │ ├── common.css │ │ ├── common.js │ │ ├── messages_default.js │ │ └── messages_zh.js │ └── test/ │ └── JsonPropertyEditor.html ├── easyflow-flow-extension/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── jd/ │ │ └── easyflow/ │ │ └── flow/ │ │ └── ext/ │ │ ├── chain/ │ │ │ ├── BaseChainPlugin.java │ │ │ ├── ChainConstants.java │ │ │ ├── ChainInvoker.java │ │ │ └── TargetAction.java │ │ ├── check/ │ │ │ ├── CheckErrorItem.java │ │ │ ├── CheckParam.java │ │ │ ├── CheckResult.java │ │ │ ├── FlowChecker.java │ │ │ └── impl/ │ │ │ ├── AllFlowNodeLinkChecker.java │ │ │ ├── ExpNodeActionDotCheckFlowParseListener.java │ │ │ ├── FlowNodeLinkCheckConfig.java │ │ │ ├── FlowNodeLinkCheckFlowParseListener.java │ │ │ ├── FlowNodeLinkChecker.java │ │ │ ├── InfiniteLoopCheckFlowParseListener.java │ │ │ └── SubFlowCheckFlowParseListener.java │ │ ├── funcall/ │ │ │ ├── FunCallConstants.java │ │ │ ├── FunCallEventListener.java │ │ │ ├── FunCallFlowPostHandler.java │ │ │ ├── FunCallFlowPreHandler.java │ │ │ └── FunCallNodeAction.java │ │ ├── interrupt/ │ │ │ ├── ExtInterruptNodeAction.java │ │ │ ├── ExtNodeThreadHolder.java │ │ │ ├── ExtNodeThreadInterruptFlowFilter.java │ │ │ ├── ExtNodeThreadInterruptHelper.java │ │ │ └── ExtNodeThreadInterruptNodeFilter.java │ │ ├── model/ │ │ │ └── action/ │ │ │ ├── JavaScriptNodeAction.java │ │ │ └── ShellNodeAction.java │ │ ├── serialize/ │ │ │ ├── FlowParamAssembleData.java │ │ │ ├── FlowParamAssembleManager.java │ │ │ ├── FlowParamAssembler.java │ │ │ ├── FlowParamSerializeManager.java │ │ │ ├── FlowParamSerializer.java │ │ │ └── impl/ │ │ │ ├── BaseFlowParamSerializer.java │ │ │ ├── DefaultFlowParamAssembler.java │ │ │ ├── JavaFlowParamSerializer.java │ │ │ └── JsonFlowParamSerializer.java │ │ ├── session/ │ │ │ ├── FlowSession.java │ │ │ ├── FlowSessionConstants.java │ │ │ ├── FlowSessionImpl.java │ │ │ └── SessionMultiCheckPreHandler.java │ │ └── timeout/ │ │ ├── TimeoutFlowFilter.java │ │ ├── TimeoutNodeActionFilter.java │ │ └── TimeoutTemplate.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── jd/ │ │ └── easyflow/ │ │ └── flow/ │ │ └── ext/ │ │ ├── FlowExtTestSuite.java │ │ ├── cases/ │ │ │ ├── chain/ │ │ │ │ ├── ChainTest.java │ │ │ │ ├── ChainTestService.java │ │ │ │ ├── Filter1.java │ │ │ │ ├── Filter2.java │ │ │ │ ├── Filter3.java │ │ │ │ ├── Plugin1.java │ │ │ │ ├── Plugin2.java │ │ │ │ └── Plugin3.java │ │ │ ├── check/ │ │ │ │ ├── FlowNodeLinkCheckTest.java │ │ │ │ └── InfiniteLoopCheckTest.java │ │ │ ├── funcall/ │ │ │ │ ├── FunCallTest.java │ │ │ │ └── MockActionClass.java │ │ │ ├── interrupt/ │ │ │ │ └── ExtInterruptTest.java │ │ │ ├── model/ │ │ │ │ └── action/ │ │ │ │ ├── JavaScriptNodeActionTest.java │ │ │ │ └── ShellNodeActionTest.java │ │ │ ├── session/ │ │ │ │ ├── MockRequestAsyncCall.java │ │ │ │ └── SessionFlowTest.java │ │ │ └── timeout/ │ │ │ ├── TestTimeoutNodeAction.java │ │ │ └── TimeoutFilterTest.java │ │ └── serialize/ │ │ └── impl/ │ │ └── JsonFlowParamSerializerTest.java │ └── resources/ │ └── flow/ │ └── cases/ │ ├── chain/ │ │ └── flow_chaintest2.json │ ├── check/ │ │ ├── check_infinite_loop_001.json │ │ ├── check_infinite_loop_002.json │ │ ├── check_infinite_loop_003.json │ │ ├── check_isolated_001.json │ │ ├── check_isolated_002.json │ │ ├── check_nonEnd_001.json │ │ ├── check_nonStart_001.json │ │ ├── check_notexists_001.json │ │ └── check_preCheckNodesNotExists_001.json │ ├── funcall/ │ │ ├── funcall_test_001.json │ │ └── funcall_test_002.json │ ├── interrupt/ │ │ └── ext_interrupt_001.json │ ├── model/ │ │ └── action/ │ │ ├── javascript_test_001.json │ │ └── shell_test_001.json │ ├── session/ │ │ └── flow_session_001.json │ └── timeout/ │ ├── flow_timeout_001.json │ └── flow_timeout_002.json ├── easyflow-fsm/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── jd/ │ │ └── easyflow/ │ │ └── fsm/ │ │ ├── CoreFsmManager.java │ │ ├── Fsm.java │ │ ├── FsmContext.java │ │ ├── FsmManager.java │ │ ├── FsmParam.java │ │ ├── FsmResult.java │ │ ├── builder/ │ │ │ ├── FsmBuilder.java │ │ │ └── FsmContextBuilder.java │ │ ├── el/ │ │ │ ├── ElEvaluator.java │ │ │ ├── ElFactory.java │ │ │ ├── ElRootMap.java │ │ │ └── SpelEvaluator.java │ │ ├── event/ │ │ │ ├── ExpFsmEventListener.java │ │ │ ├── FsmEvent.java │ │ │ ├── FsmEventListener.java │ │ │ └── FsmEventTrigger.java │ │ ├── exception/ │ │ │ └── FsmException.java │ │ ├── filter/ │ │ │ ├── ExpFilter.java │ │ │ ├── Filter.java │ │ │ └── FilterChain.java │ │ ├── listener/ │ │ │ └── FsmCommonListener.java │ │ ├── model/ │ │ │ ├── Event.java │ │ │ ├── FsmLifeCycle.java │ │ │ ├── FsmPostHandler.java │ │ │ ├── FsmPreHandler.java │ │ │ ├── InitContext.java │ │ │ ├── PostHandleResult.java │ │ │ ├── State.java │ │ │ ├── Transition.java │ │ │ ├── TransitionAction.java │ │ │ ├── TransitionContext.java │ │ │ ├── TransitionExecutor.java │ │ │ ├── TransitionPostHandler.java │ │ │ ├── TransitionPreHandler.java │ │ │ ├── builder/ │ │ │ │ └── TransitionBuilder.java │ │ │ ├── check/ │ │ │ │ ├── CheckErrorItem.java │ │ │ │ ├── CheckParam.java │ │ │ │ ├── CheckResult.java │ │ │ │ ├── FsmChecker.java │ │ │ │ └── impl/ │ │ │ │ ├── AllFsmStateLinkChecker.java │ │ │ │ ├── FsmStateLinkCheckConfig.java │ │ │ │ ├── FsmStateLinkCheckFsmParseListener.java │ │ │ │ └── FsmStateLinkChecker.java │ │ │ ├── definition/ │ │ │ │ └── DefConstants.java │ │ │ └── impl/ │ │ │ ├── EventImpl.java │ │ │ ├── StateImpl.java │ │ │ ├── TransitionImpl.java │ │ │ ├── action/ │ │ │ │ ├── ActionResultEl.java │ │ │ │ └── ExpTransitionAction.java │ │ │ ├── fsm/ │ │ │ │ ├── post/ │ │ │ │ │ └── ExpFsmPostHandler.java │ │ │ │ └── pre/ │ │ │ │ └── ExpFsmPreHandler.java │ │ │ ├── post/ │ │ │ │ ├── AbstractTransitionPostHandler.java │ │ │ │ ├── ConditionalTransitionPostHandler.java │ │ │ │ ├── ExpTransitionPostHandler.java │ │ │ │ ├── FixedTransitionPostHandler.java │ │ │ │ └── TransitionContextResultPostHandler.java │ │ │ └── pre/ │ │ │ └── ExpTransitionPreHandler.java │ │ ├── parser/ │ │ │ ├── FsmParser.java │ │ │ └── event/ │ │ │ ├── ExpFsmParseEventListener.java │ │ │ ├── FsmParseEvent.java │ │ │ ├── FsmParseEventListener.java │ │ │ └── FsmParseEventTypes.java │ │ └── util/ │ │ ├── FsmConstants.java │ │ ├── FsmEventTypes.java │ │ ├── FsmIOUtil.java │ │ ├── FsmStateLinkUtil.java │ │ ├── FsmStringUtil.java │ │ ├── FsmUtil.java │ │ ├── JsonFacade.java │ │ ├── JsonFacadeJacksonImpl.java │ │ ├── JsonUtil.java │ │ ├── Pair.java │ │ ├── TransitionUtil.java │ │ └── Triple.java │ └── test/ │ ├── java/ │ │ └── com/ │ │ └── jd/ │ │ └── easyflow/ │ │ └── fsm/ │ │ ├── FsmTestSuite.java │ │ ├── cases/ │ │ │ ├── check/ │ │ │ │ └── FsmStateLinkCheckTest.java │ │ │ ├── event/ │ │ │ │ └── MultipleEventTest.java │ │ │ ├── filter/ │ │ │ │ ├── FsmFilterTest.java │ │ │ │ ├── PreHandlerPostConstructTestFilter.java │ │ │ │ ├── StateTransitionPreHandlerTestFilter.java │ │ │ │ ├── TransitionEmptyFilter.java │ │ │ │ └── TransitionPreHandlerTestFilter.java │ │ │ ├── fsmmanager/ │ │ │ │ ├── PocFsm.java │ │ │ │ ├── PocFsmManager.java │ │ │ │ └── PocTransition.java │ │ │ ├── mockbiz/ │ │ │ │ ├── CompanyAuthService.java │ │ │ │ ├── FsmMockBizTest.java │ │ │ │ └── WhitelistService.java │ │ │ ├── parser/ │ │ │ │ ├── FsmParserTest.java │ │ │ │ └── TestTransitionAction.java │ │ │ ├── post/ │ │ │ │ ├── PostTest.java │ │ │ │ └── TestPostManuallyTransitionPreHandlerFilter.java │ │ │ └── postevent/ │ │ │ └── PostEventTest.java │ │ └── quickstart/ │ │ ├── AStateE1EventTstAction.java │ │ └── FsmQuickStartTest.java │ └── resources/ │ ├── fsm/ │ │ ├── cases/ │ │ │ ├── check/ │ │ │ │ ├── check_isolated_001.json │ │ │ │ ├── check_nonEndNoNext_001.json │ │ │ │ ├── check_nonStartNoPrevious_001.json │ │ │ │ └── check_notExists_001.json │ │ │ ├── filter/ │ │ │ │ ├── tst_empty_filter_001.json │ │ │ │ ├── tst_prehandler_filter_001.json │ │ │ │ ├── tst_prehandler_filter_002.json │ │ │ │ └── tst_prehandler_filter_003.json │ │ │ ├── mockbiz/ │ │ │ │ └── fsm_mockbiz.json │ │ │ ├── multipleevent/ │ │ │ │ └── fsm_multipleevent.json │ │ │ ├── parser/ │ │ │ │ ├── fsm_parser_test.json │ │ │ │ └── fsm_parser_test_create_exp.json │ │ │ ├── post/ │ │ │ │ └── fsm_post_manually.json │ │ │ └── postevent/ │ │ │ └── fsm_postevent.json │ │ └── quickstart/ │ │ └── quickstart_001.json │ └── logback.xml ├── easyflow-process/ │ ├── easyflow-process-admin/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── jd/ │ │ │ └── easyflow/ │ │ │ ├── admin/ │ │ │ │ ├── form/ │ │ │ │ │ └── adapter/ │ │ │ │ │ └── page/ │ │ │ │ │ ├── FormTemplateAdminController.java │ │ │ │ │ └── converter/ │ │ │ │ │ └── PagerConverter.java │ │ │ │ ├── process/ │ │ │ │ │ └── adapter/ │ │ │ │ │ └── page/ │ │ │ │ │ ├── Bpmn2JsonController.java │ │ │ │ │ ├── ProcessDefinitionController.java │ │ │ │ │ ├── ProcessInstanceController.java │ │ │ │ │ ├── ProcessTaskController.java │ │ │ │ │ ├── converter/ │ │ │ │ │ │ ├── PagerConverter.java │ │ │ │ │ │ ├── ProcessDefinitionConverter.java │ │ │ │ │ │ ├── ProcessInstanceConverter.java │ │ │ │ │ │ └── ProcessTaskConverter.java │ │ │ │ │ ├── dto/ │ │ │ │ │ │ ├── CommonTaskExecuteDTO.java │ │ │ │ │ │ ├── CommonTaskProcessInstanceCreateDTO.java │ │ │ │ │ │ ├── ProcessDefDTO.java │ │ │ │ │ │ ├── ProcessInstanceInfoForPagerDTO.java │ │ │ │ │ │ └── ProcessTaskInfoForPagerDTO.java │ │ │ │ │ ├── extension/ │ │ │ │ │ │ └── UserGroupAdminExtension.java │ │ │ │ │ └── util/ │ │ │ │ │ ├── AdminProcessConstants.java │ │ │ │ │ └── ClientErrorCode.java │ │ │ │ └── processunit/ │ │ │ │ └── adapter/ │ │ │ │ └── page/ │ │ │ │ ├── ProcessUnitController.java │ │ │ │ └── converter/ │ │ │ │ └── PagerConverter.java │ │ │ └── common/ │ │ │ └── adapter/ │ │ │ └── page/ │ │ │ ├── BasePageController.java │ │ │ ├── WebResponse.java │ │ │ └── WebResponseCode.java │ │ └── resources/ │ │ ├── easyflow/ │ │ │ └── messages/ │ │ │ ├── easyflow-admin-framework.properties │ │ │ ├── easyflow-admin-framework_zh.properties │ │ │ ├── easyflow-form-admin.properties │ │ │ ├── easyflow-form-admin_zh.properties │ │ │ ├── easyflow-process-admin.properties │ │ │ ├── easyflow-process-admin_zh.properties │ │ │ ├── easyflow-processunit-admin.properties │ │ │ └── easyflow-processunit-admin_zh.properties │ │ ├── static/ │ │ │ ├── css/ │ │ │ │ ├── common.css │ │ │ │ └── frame.css │ │ │ ├── js/ │ │ │ │ ├── biz.js │ │ │ │ ├── common.js │ │ │ │ ├── messages_default.js │ │ │ │ └── messages_zh.js │ │ │ └── plugins/ │ │ │ ├── bootstrap-multiselect/ │ │ │ │ ├── css/ │ │ │ │ │ └── bootstrap-multiselect.css │ │ │ │ ├── js/ │ │ │ │ │ └── bootstrap-multiselect.js │ │ │ │ └── less/ │ │ │ │ └── bootstrap-multiselect.less │ │ │ ├── bootstrap-table/ │ │ │ │ ├── bootstrap-table-locale-all.js │ │ │ │ ├── bootstrap-table-locale-all.min.js │ │ │ │ ├── bootstrap-table.css │ │ │ │ ├── bootstrap-table.js │ │ │ │ ├── bootstrap-table.min.css │ │ │ │ └── bootstrap-table.min.js │ │ │ ├── bootstrap4/ │ │ │ │ ├── css/ │ │ │ │ │ ├── bootstrap-grid.css │ │ │ │ │ ├── bootstrap-grid.css.map │ │ │ │ │ ├── bootstrap-grid.min.css │ │ │ │ │ ├── bootstrap-grid.min.css.map │ │ │ │ │ ├── bootstrap-reboot.css │ │ │ │ │ ├── bootstrap-reboot.css.map │ │ │ │ │ ├── bootstrap-reboot.min.css │ │ │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ │ │ ├── bootstrap.css │ │ │ │ │ ├── bootstrap.css.map │ │ │ │ │ ├── bootstrap.min.css │ │ │ │ │ └── bootstrap.min.css.map │ │ │ │ └── js/ │ │ │ │ ├── bootstrap.bundle.js │ │ │ │ ├── bootstrap.bundle.js.map │ │ │ │ ├── bootstrap.bundle.min.js │ │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ │ ├── bootstrap.js │ │ │ │ ├── bootstrap.js.map │ │ │ │ ├── bootstrap.min.js │ │ │ │ └── bootstrap.min.js.map │ │ │ ├── bpmnjs/ │ │ │ │ ├── assets/ │ │ │ │ │ ├── bpmn-font/ │ │ │ │ │ │ └── css/ │ │ │ │ │ │ ├── bpmn-codes.css │ │ │ │ │ │ ├── bpmn-embedded.css │ │ │ │ │ │ └── bpmn.css │ │ │ │ │ ├── bpmn-js.css │ │ │ │ │ └── diagram-js.css │ │ │ │ ├── bpmn-modeler.development.js │ │ │ │ ├── bpmn-modeler.production.min.js │ │ │ │ ├── bpmn-navigated-viewer.development.js │ │ │ │ ├── bpmn-navigated-viewer.production.min.js │ │ │ │ ├── bpmn-viewer.development.js │ │ │ │ ├── bpmn-viewer.production.min.js │ │ │ │ └── changeForEasyflow/ │ │ │ │ ├── changelog.txt │ │ │ │ └── rollup.config.js │ │ │ ├── dataview/ │ │ │ │ ├── dataview-app.js │ │ │ │ ├── dataview-biz.js │ │ │ │ ├── dataview-config.js │ │ │ │ ├── dataview-demo.html │ │ │ │ ├── dataview.css │ │ │ │ └── dataview.js │ │ │ ├── flow/ │ │ │ │ ├── bpmn.js │ │ │ │ ├── flow.css │ │ │ │ └── flow.js │ │ │ ├── fontawesome/ │ │ │ │ └── css/ │ │ │ │ ├── all.css │ │ │ │ └── all.min.css │ │ │ ├── jquery/ │ │ │ │ ├── jquery-3.6.0.js │ │ │ │ ├── jquery-3.6.0.min.js │ │ │ │ └── jquery-3.6.0.min.map │ │ │ └── jquery-validation/ │ │ │ ├── custom.validate.js │ │ │ ├── jquery.validate.js │ │ │ ├── jquery.validate.min.js │ │ │ └── messages_zh.js │ │ └── templates/ │ │ ├── easyflow/ │ │ │ ├── formtemplate/ │ │ │ │ ├── formTemplateAdd.html │ │ │ │ ├── formTemplateDetail.html │ │ │ │ ├── formTemplateEdit.html │ │ │ │ └── formTemplateList.html │ │ │ ├── process/ │ │ │ │ ├── processdefinition/ │ │ │ │ │ ├── processDefinitionAdd.html │ │ │ │ │ ├── processDefinitionDetail.html │ │ │ │ │ ├── processDefinitionEdit.html │ │ │ │ │ ├── processDefinitionList.html │ │ │ │ │ └── processSelectFrame.html │ │ │ │ ├── processinstance/ │ │ │ │ │ ├── commonTaskProcessInstanceAdd.html │ │ │ │ │ ├── processInstanceCreate.html │ │ │ │ │ ├── processInstanceDetail.html │ │ │ │ │ └── processInstanceList.html │ │ │ │ └── processtask/ │ │ │ │ ├── commonProcessTaskDetail.html │ │ │ │ ├── commonProcessTaskExecute.html │ │ │ │ ├── commonProcessTaskPassReject.html │ │ │ │ └── processTaskList.html │ │ │ └── processunit/ │ │ │ ├── processUnitExecutionDetail.html │ │ │ ├── processUnitExecutionList.html │ │ │ ├── processUnitInstanceDetail.html │ │ │ └── processUnitInstanceList.html │ │ └── layout.html │ ├── easyflow-process-api/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── jd/ │ │ └── easyflow/ │ │ ├── codegenerator/ │ │ │ └── adapter/ │ │ │ └── export/ │ │ │ ├── CodeGenerateExport.java │ │ │ └── dto/ │ │ │ ├── BatchGenerateParam.java │ │ │ ├── BatchGenerateResult.java │ │ │ ├── GenerateParam.java │ │ │ └── GenerateResult.java │ │ ├── common/ │ │ │ ├── adapter/ │ │ │ │ ├── export/ │ │ │ │ │ ├── dto/ │ │ │ │ │ │ ├── ExportRequest.java │ │ │ │ │ │ ├── ExportResponse.java │ │ │ │ │ │ ├── ExportResponseCode.java │ │ │ │ │ │ └── pager/ │ │ │ │ │ │ ├── FieldEntry.java │ │ │ │ │ │ ├── PagerCondition.java │ │ │ │ │ │ ├── PagerConditionHelper.java │ │ │ │ │ │ ├── PagerResult.java │ │ │ │ │ │ └── SortEntry.java │ │ │ │ │ └── util/ │ │ │ │ │ └── ExportResponseUtil.java │ │ │ │ └── message/ │ │ │ │ └── BaseMessage.java │ │ │ └── client/ │ │ │ └── dto/ │ │ │ ├── ClientRequest.java │ │ │ ├── ClientResponse.java │ │ │ └── ClientResponseCode.java │ │ ├── form/ │ │ │ └── adapter/ │ │ │ └── export/ │ │ │ ├── FormTemplateExport.java │ │ │ └── dto/ │ │ │ └── FormTemplateDTO.java │ │ ├── process/ │ │ │ ├── adapter/ │ │ │ │ ├── export/ │ │ │ │ │ ├── ProcessDefinitionExport.java │ │ │ │ │ ├── ProcessInstanceExport.java │ │ │ │ │ ├── ProcessScheduleExport.java │ │ │ │ │ ├── ProcessTaskExport.java │ │ │ │ │ ├── ProcessToolExport.java │ │ │ │ │ ├── ProcessTransactionExport.java │ │ │ │ │ ├── constant/ │ │ │ │ │ │ ├── ProcessInstanceConstants.java │ │ │ │ │ │ ├── ProcessTaskConstants.java │ │ │ │ │ │ ├── ProcessTransactionConstants.java │ │ │ │ │ │ └── ShardingConstants.java │ │ │ │ │ ├── dto/ │ │ │ │ │ │ ├── definition/ │ │ │ │ │ │ │ ├── NodeDTO.java │ │ │ │ │ │ │ ├── ProcessDTO.java │ │ │ │ │ │ │ ├── ProcessDefinitionDTO.java │ │ │ │ │ │ │ ├── QueryNodeReq.java │ │ │ │ │ │ │ └── QueryProcessDefinitionReq.java │ │ │ │ │ │ ├── instance/ │ │ │ │ │ │ │ ├── CanCancelProcessInstanceReq.java │ │ │ │ │ │ │ ├── CanCancelProcessInstanceRes.java │ │ │ │ │ │ │ ├── CancelProcessInstanceReq.java │ │ │ │ │ │ │ ├── CancelProcessInstanceRes.java │ │ │ │ │ │ │ ├── CreateProcessInstanceReq.java │ │ │ │ │ │ │ ├── CreateProcessInstanceRes.java │ │ │ │ │ │ │ ├── LockProcessInstanceReq.java │ │ │ │ │ │ │ ├── PersistDTO.java │ │ │ │ │ │ │ ├── ProcessInstanceDTO.java │ │ │ │ │ │ │ ├── ProcessNodeExecutionDTO.java │ │ │ │ │ │ │ ├── ProcessNodeInstanceDTO.java │ │ │ │ │ │ │ ├── QueryOpenNodeInstanceReq.java │ │ │ │ │ │ │ ├── QueryProcessInstanceReq.java │ │ │ │ │ │ │ ├── QueryProcessNodeReqDTO.java │ │ │ │ │ │ │ ├── RollbackNodeReq.java │ │ │ │ │ │ │ ├── RollbackNodeRes.java │ │ │ │ │ │ │ ├── StdProcessContextDTO.java │ │ │ │ │ │ │ ├── StdProcessDTO.java │ │ │ │ │ │ │ └── UnlockProcessInstanceReq.java │ │ │ │ │ │ ├── schedule/ │ │ │ │ │ │ │ ├── ScheduleProcessReq.java │ │ │ │ │ │ │ └── ScheduleProcessRes.java │ │ │ │ │ │ ├── task/ │ │ │ │ │ │ │ ├── CanWithdrawTaskReq.java │ │ │ │ │ │ │ ├── CanWithdrawTaskRes.java │ │ │ │ │ │ │ ├── ExecuteTaskReq.java │ │ │ │ │ │ │ ├── ExecuteTaskRes.java │ │ │ │ │ │ │ ├── ProcessTaskAssignDTO.java │ │ │ │ │ │ │ ├── ProcessTaskDTO.java │ │ │ │ │ │ │ ├── ProcessTaskEventDTO.java │ │ │ │ │ │ │ ├── QueryTaskReq.java │ │ │ │ │ │ │ ├── TaskOperateCmd.java │ │ │ │ │ │ │ ├── TaskOperateCmdResult.java │ │ │ │ │ │ │ ├── TaskOperateCommand.java │ │ │ │ │ │ │ ├── TaskOperateCommandResult.java │ │ │ │ │ │ │ ├── TaskOperationsReq.java │ │ │ │ │ │ │ ├── TaskOperationsRes.java │ │ │ │ │ │ │ ├── WithdrawTaskReq.java │ │ │ │ │ │ │ ├── WithdrawTaskRes.java │ │ │ │ │ │ │ ├── cmd/ │ │ │ │ │ │ │ │ ├── TaskCreateCmd.java │ │ │ │ │ │ │ │ └── TaskExecuteCmd.java │ │ │ │ │ │ │ └── command/ │ │ │ │ │ │ │ ├── MultipleTaskCreateCommand.java │ │ │ │ │ │ │ ├── MultipleTaskCreateCommandResult.java │ │ │ │ │ │ │ ├── TaskCreateCommand.java │ │ │ │ │ │ │ ├── TaskCreateCommandResult.java │ │ │ │ │ │ │ ├── TaskExecuteCommand.java │ │ │ │ │ │ │ └── TaskExecuteCommandResult.java │ │ │ │ │ │ └── transaction/ │ │ │ │ │ │ ├── BatchObjectIdReq.java │ │ │ │ │ │ ├── BatchObjectIdRes.java │ │ │ │ │ │ ├── TxnCommand.java │ │ │ │ │ │ ├── TxnCommandResult.java │ │ │ │ │ │ ├── TxnReq.java │ │ │ │ │ │ ├── TxnRes.java │ │ │ │ │ │ └── command/ │ │ │ │ │ │ ├── BatchUpdateTxnCommand.java │ │ │ │ │ │ ├── BatchUpdateTxnCommandResult.java │ │ │ │ │ │ ├── InterruptTxnCommand.java │ │ │ │ │ │ └── InterruptTxnCommandResult.java │ │ │ │ │ └── enums/ │ │ │ │ │ └── ProcessExportResponseCode.java │ │ │ │ └── message/ │ │ │ │ ├── ProcessInstanceStatusMessage.java │ │ │ │ ├── ProcessNodeInstanceStatusMessage.java │ │ │ │ └── ProcessTaskStatusMessage.java │ │ │ └── spi/ │ │ │ └── client/ │ │ │ ├── ProcessScheduleClientService.java │ │ │ ├── ProcessTaskClientService.java │ │ │ ├── dto/ │ │ │ │ ├── ProcessExecuteReq.java │ │ │ │ ├── ProcessExecuteRes.java │ │ │ │ ├── ProcessTaskCallReq.java │ │ │ │ └── ProcessTaskCallRes.java │ │ │ └── enums/ │ │ │ └── ProcessClientResponseCode.java │ │ └── processunit/ │ │ ├── adapter/ │ │ │ └── export/ │ │ │ ├── ProcessUnitExport.java │ │ │ └── dto/ │ │ │ ├── ExecPolicyDTO.java │ │ │ ├── ProcessUnitCreateReq.java │ │ │ ├── ProcessUnitCreateRes.java │ │ │ ├── ProcessUnitDTO.java │ │ │ ├── ProcessUnitExecuteReq.java │ │ │ ├── ProcessUnitExecuteRes.java │ │ │ ├── ProcessUnitExecutionDTO.java │ │ │ ├── ProcessUnitExecutionQueryReq.java │ │ │ ├── ProcessUnitInstanceDTO.java │ │ │ ├── ProcessUnitInstanceQueryReq.java │ │ │ ├── ProcessUnitQueryReq.java │ │ │ ├── ProcessUnitUpdateReq.java │ │ │ ├── ProcessUnitUpdateRes.java │ │ │ ├── ShardingInfoDTO.java │ │ │ ├── ShardingInfoQueryReq.java │ │ │ ├── ShutdownReq.java │ │ │ ├── ShutdownRes.java │ │ │ ├── SyncAfterCallReq.java │ │ │ ├── SyncAfterCallRes.java │ │ │ ├── SyncBeforeCallReq.java │ │ │ └── SyncBeforeCallRes.java │ │ └── spi/ │ │ └── client/ │ │ ├── ProcessUnitClientService.java │ │ └── dto/ │ │ ├── AsyncCallRealReq.java │ │ └── AsyncCallRealRes.java │ ├── easyflow-process-client/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── jd/ │ │ │ └── easyflow/ │ │ │ ├── codegenerator/ │ │ │ │ └── client/ │ │ │ │ ├── CodeGenerateHelper.java │ │ │ │ └── CodeGenerateParam.java │ │ │ ├── process/ │ │ │ │ └── client/ │ │ │ │ ├── common/ │ │ │ │ │ └── PropertiesUtil.java │ │ │ │ ├── flow/ │ │ │ │ │ ├── StdFlowEngineImpl.java │ │ │ │ │ ├── StdFlowProcessConstants.java │ │ │ │ │ ├── StdProcessFlowEngineFilter.java │ │ │ │ │ ├── StdProcessFlowFilter.java │ │ │ │ │ ├── StdProcessFlowListener.java │ │ │ │ │ ├── StdProcessFlowNodeFilter.java │ │ │ │ │ ├── asyncnode/ │ │ │ │ │ │ ├── CreateAsyncProcessUnitNodeAction.java │ │ │ │ │ │ └── ExecuteAsyncProcessUnitNodeEl.java │ │ │ │ │ ├── compensate/ │ │ │ │ │ │ ├── StdProcessCompensateFlowFilter.java │ │ │ │ │ │ ├── StdProcessCompensateHelper.java │ │ │ │ │ │ └── StdProcessCompensateNodeFilter.java │ │ │ │ │ ├── eventinfo/ │ │ │ │ │ │ └── StdFlowProcessEventInfoListener.java │ │ │ │ │ ├── flowretry/ │ │ │ │ │ │ ├── FlowRetryEl.java │ │ │ │ │ │ └── FlowRetryFilter.java │ │ │ │ │ ├── noderetry/ │ │ │ │ │ │ └── NodeActionRetryFilter.java │ │ │ │ │ └── util/ │ │ │ │ │ ├── StdFlowEl.java │ │ │ │ │ └── StdProcessFlowUtil.java │ │ │ │ ├── fsm/ │ │ │ │ │ ├── StdFsmManager.java │ │ │ │ │ ├── StdFsmProcessConstants.java │ │ │ │ │ ├── StdProcessFsmFilter.java │ │ │ │ │ ├── StdProcessFsmListener.java │ │ │ │ │ ├── StdProcessFsmManagerFilter.java │ │ │ │ │ ├── StdProcessFsmTransitionFilter.java │ │ │ │ │ └── eventinfo/ │ │ │ │ │ └── StdFsmProcessEventInfoListener.java │ │ │ │ ├── runtime/ │ │ │ │ │ ├── CacheObject.java │ │ │ │ │ ├── ObjectIdManager.java │ │ │ │ │ ├── ProcessCache.java │ │ │ │ │ ├── ProcessConverter.java │ │ │ │ │ ├── ProcessInclusiveCheckHelper.java │ │ │ │ │ ├── ProcessRuntimeErrorCode.java │ │ │ │ │ ├── ProcessRuntimeManager.java │ │ │ │ │ ├── ProcessRuntimeService.java │ │ │ │ │ ├── StdNode.java │ │ │ │ │ ├── StdNodeContext.java │ │ │ │ │ ├── StdProcess.java │ │ │ │ │ ├── StdProcessConstants.java │ │ │ │ │ ├── StdProcessContext.java │ │ │ │ │ ├── core/ │ │ │ │ │ │ ├── Node.java │ │ │ │ │ │ ├── NodeContext.java │ │ │ │ │ │ ├── Process.java │ │ │ │ │ │ ├── ProcessContext.java │ │ │ │ │ │ ├── ProcessEngine.java │ │ │ │ │ │ ├── ProcessEngineImpl.java │ │ │ │ │ │ ├── ProcessException.java │ │ │ │ │ │ ├── ProcessParam.java │ │ │ │ │ │ └── ProcessResult.java │ │ │ │ │ └── eventinfo/ │ │ │ │ │ ├── NodeInstanceEventInfo.java │ │ │ │ │ ├── ProcessEventInfoCache.java │ │ │ │ │ ├── ProcessEventInfoListener.java │ │ │ │ │ └── ProcessInstanceEventInfo.java │ │ │ │ ├── schedule/ │ │ │ │ │ └── ProcessScheduleClientServiceImpl.java │ │ │ │ ├── task/ │ │ │ │ │ ├── TaskConstants.java │ │ │ │ │ ├── TaskErrorCode.java │ │ │ │ │ ├── biz/ │ │ │ │ │ │ ├── TaskBizService.java │ │ │ │ │ │ ├── dto/ │ │ │ │ │ │ │ ├── TaskBizParam.java │ │ │ │ │ │ │ └── TaskBizResult.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ ├── CallTaskBizService.java │ │ │ │ │ │ ├── FlowTaskBizService.java │ │ │ │ │ │ ├── RuleAndCallTaskBizService.java │ │ │ │ │ │ └── RuleTaskBizService.java │ │ │ │ │ ├── flow/ │ │ │ │ │ │ ├── BaseTaskNodeAction.java │ │ │ │ │ │ ├── TaskCreateAndExecuteNodeAction.java │ │ │ │ │ │ ├── TaskCreateNodeAction.java │ │ │ │ │ │ ├── TaskExecuteNodeAction.java │ │ │ │ │ │ └── TaskFlowListener.java │ │ │ │ │ ├── service/ │ │ │ │ │ │ ├── TaskClientManager.java │ │ │ │ │ │ ├── TaskClientOperation.java │ │ │ │ │ │ ├── TaskClientService.java │ │ │ │ │ │ ├── dto/ │ │ │ │ │ │ │ ├── TaskCreateParam.java │ │ │ │ │ │ │ └── TaskExecuteParam.java │ │ │ │ │ │ └── operation/ │ │ │ │ │ │ ├── BaseTaskOperation.java │ │ │ │ │ │ ├── ExecuteTaskOperation.java │ │ │ │ │ │ └── SaveTaskOperation.java │ │ │ │ │ └── util/ │ │ │ │ │ └── TaskEl.java │ │ │ │ └── util/ │ │ │ │ ├── ElUtil.java │ │ │ │ ├── ExportRequestBuilder.java │ │ │ │ ├── Pair.java │ │ │ │ ├── ProcessConstants.java │ │ │ │ ├── ProcessUtil.java │ │ │ │ └── StdProcessUtil.java │ │ │ └── processunit/ │ │ │ └── client/ │ │ │ ├── ProcessUnitClient.java │ │ │ ├── ProcessUnitHelper.java │ │ │ ├── bean/ │ │ │ │ ├── ExecContext.java │ │ │ │ ├── ExecParam.java │ │ │ │ ├── ExecPolicy.java │ │ │ │ ├── ExecResult.java │ │ │ │ ├── ProcessUnit.java │ │ │ │ ├── ProcessUnitCreateAndExecuteReq.java │ │ │ │ ├── ProcessUnitCreateAndExecuteRes.java │ │ │ │ ├── ProcessUnitExecution.java │ │ │ │ └── ProcessUnitInstance.java │ │ │ ├── converter/ │ │ │ │ └── ProcessUnitConverter.java │ │ │ ├── gateway/ │ │ │ │ └── ProcessUnitServerGateway.java │ │ │ ├── message/ │ │ │ │ └── ProcessUnitClientServiceMessageListener.java │ │ │ ├── service/ │ │ │ │ ├── ProcessUnitExecutor.java │ │ │ │ └── impl/ │ │ │ │ ├── AsyncClientProcessUnitExecutor.java │ │ │ │ ├── BaseProcessUnitExecutor.java │ │ │ │ ├── ProcessUnitClientAsyncExecuteSpel.java │ │ │ │ ├── ProcessUnitClientServiceImpl.java │ │ │ │ └── SyncClientProcessUnitExecutor.java │ │ │ └── util/ │ │ │ └── ProcessUnitConstants.java │ │ └── resources/ │ │ └── easyflow/ │ │ ├── easyflow-spring-process-client-processunit.xml │ │ ├── easyflow-spring-process-client.xml │ │ └── easyflow-spring-processunit-client.xml │ ├── easyflow-process-common/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── jd/ │ │ │ └── easyflow/ │ │ │ ├── common/ │ │ │ │ ├── dto/ │ │ │ │ │ ├── BaseRequest.java │ │ │ │ │ ├── BaseResponse.java │ │ │ │ │ ├── DataRequest.java │ │ │ │ │ ├── DataResponse.java │ │ │ │ │ └── pager/ │ │ │ │ │ ├── FieldEntry.java │ │ │ │ │ ├── PagerCondition.java │ │ │ │ │ ├── PagerConditionHelper.java │ │ │ │ │ ├── PagerResult.java │ │ │ │ │ └── SortEntry.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── EasyFlowException.java │ │ │ │ │ ├── UserException.java │ │ │ │ │ └── util/ │ │ │ │ │ └── ExceptionUtil.java │ │ │ │ └── util/ │ │ │ │ ├── AssertUtils.java │ │ │ │ ├── CommonErrorCode.java │ │ │ │ ├── IAssertUtils.java │ │ │ │ ├── MessageUtil.java │ │ │ │ └── UUIDUtil.java │ │ │ ├── el/ │ │ │ │ ├── ElEvaluator.java │ │ │ │ ├── ElFactory.java │ │ │ │ └── SpelEvaluator.java │ │ │ ├── message/ │ │ │ │ ├── BaseMessageListener.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageListener.java │ │ │ │ ├── MessageSendService.java │ │ │ │ ├── impl/ │ │ │ │ │ └── NoopMessageSendServiceImpl.java │ │ │ │ ├── spring/ │ │ │ │ │ ├── EventMessage.java │ │ │ │ │ ├── EventMessageListener.java │ │ │ │ │ └── SpringMessageSendServiceImpl.java │ │ │ │ └── util/ │ │ │ │ └── MsgBizIdWrapper.java │ │ │ ├── net/ │ │ │ │ └── NetUtils.java │ │ │ ├── objects/ │ │ │ │ └── factory/ │ │ │ │ ├── DefaultObjectFactory.java │ │ │ │ ├── ObjectFactory.java │ │ │ │ ├── ObjectFactorys.java │ │ │ │ ├── ObjectProvider.java │ │ │ │ └── spring/ │ │ │ │ └── SpringObjectProvider.java │ │ │ ├── properties/ │ │ │ │ ├── PropertiesAccessor.java │ │ │ │ ├── PropertyUtil.java │ │ │ │ ├── classpath/ │ │ │ │ │ └── ClassPathPropertiesAccessor.java │ │ │ │ └── spring/ │ │ │ │ ├── SpringEnvPropertiesAccessor.java │ │ │ │ └── SpringPropertySourcesPropertiesAccessor.java │ │ │ ├── spel/ │ │ │ │ └── SpelHelper.java │ │ │ └── utils/ │ │ │ └── json/ │ │ │ ├── JSON.java │ │ │ ├── JsonFacade.java │ │ │ ├── JsonFacadeJacksonImpl.java │ │ │ └── JsonSerializeConfig.java │ │ └── resources/ │ │ └── easyflow/ │ │ └── easyflow-spring-message-spring.xml │ ├── easyflow-process-sample/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── jd/ │ │ │ └── easyflow/ │ │ │ └── integration/ │ │ │ ├── all/ │ │ │ │ └── admin/ │ │ │ │ ├── IndexController.java │ │ │ │ ├── filter/ │ │ │ │ │ ├── JakartaSampleAuthFilter.java │ │ │ │ │ └── SampleAuthFilter.java │ │ │ │ ├── process/ │ │ │ │ │ └── SampleUserGroupAdminExtensionImpl.java │ │ │ │ └── sample/ │ │ │ │ └── SampleUserHolder.java │ │ │ └── open/ │ │ │ └── IntegrationOpenApplication.java │ │ ├── resources/ │ │ │ ├── application-open-all.yml │ │ │ ├── config/ │ │ │ │ └── sampleUserGroup.json │ │ │ ├── flow/ │ │ │ │ ├── client/ │ │ │ │ │ └── flow_empty.json │ │ │ │ └── server/ │ │ │ │ ├── flow_task_001.bpmn │ │ │ │ └── flow_task_001.json │ │ │ ├── fsm/ │ │ │ │ └── client/ │ │ │ │ └── fsm_empty.json │ │ │ ├── logback.xml │ │ │ └── spring/ │ │ │ └── open/ │ │ │ ├── easyflow-spring-open-all.xml │ │ │ ├── spring-datasource-sharding.xml │ │ │ └── spring-datasource.xml │ │ └── script/ │ │ └── sql/ │ │ ├── create_all_table.sql │ │ ├── drop_all_table.sql │ │ ├── sample_form_template.sql │ │ ├── sample_form_template_zh_CN.sql │ │ └── sample_sharding.sql │ ├── easyflow-process-server/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── jd/ │ │ │ │ └── easyflow/ │ │ │ │ ├── action/ │ │ │ │ │ ├── Action.java │ │ │ │ │ ├── ActionAspect.java │ │ │ │ │ ├── ActionConstants.java │ │ │ │ │ ├── ActionInfo.java │ │ │ │ │ └── SimpleExportAopFunction.java │ │ │ │ ├── alert/ │ │ │ │ │ ├── AlertClient.java │ │ │ │ │ ├── AlertParam.java │ │ │ │ │ ├── AlertUtil.java │ │ │ │ │ └── LogAlertClient.java │ │ │ │ ├── cache/ │ │ │ │ │ ├── CacheService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── LocalCacheServiceImpl.java │ │ │ │ │ ├── NoopCacheServiceImpl.java │ │ │ │ │ └── UnsupportedCacheServiceImpl.java │ │ │ │ ├── codegenerator/ │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ └── export/ │ │ │ │ │ │ └── CodeGenerateExportImpl.java │ │ │ │ │ ├── domain/ │ │ │ │ │ │ ├── constant/ │ │ │ │ │ │ │ └── CodeGeneratorConstants.java │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ ├── entity/ │ │ │ │ │ │ │ │ └── SequenceEntity.java │ │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ │ └── CodeGenerateReq.java │ │ │ │ │ │ ├── repository/ │ │ │ │ │ │ │ └── SequenceRepository.java │ │ │ │ │ │ └── service/ │ │ │ │ │ │ ├── CodeGenerateDomainService.java │ │ │ │ │ │ └── CodeGenerator.java │ │ │ │ │ └── infrastructure/ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ └── SequenceConverter.java │ │ │ │ │ ├── persistence/ │ │ │ │ │ │ ├── mapper/ │ │ │ │ │ │ │ └── SequenceMapper.java │ │ │ │ │ │ └── po/ │ │ │ │ │ │ └── Sequence.java │ │ │ │ │ └── repository/ │ │ │ │ │ └── SequenceRepositoryImpl.java │ │ │ │ ├── common/ │ │ │ │ │ └── app/ │ │ │ │ │ ├── AppResponseTransformer.java │ │ │ │ │ └── dto/ │ │ │ │ │ ├── AppRequest.java │ │ │ │ │ └── AppResponse.java │ │ │ │ ├── form/ │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ └── export/ │ │ │ │ │ │ ├── FormTemplateExportImpl.java │ │ │ │ │ │ └── converter/ │ │ │ │ │ │ ├── FormTemplateConverter.java │ │ │ │ │ │ └── PagerConverter.java │ │ │ │ │ ├── domain/ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ └── entity/ │ │ │ │ │ │ │ └── FormTemplateEntity.java │ │ │ │ │ │ ├── repository/ │ │ │ │ │ │ │ └── FormTemplateRepository.java │ │ │ │ │ │ └── service/ │ │ │ │ │ │ └── FormTemplateDomainService.java │ │ │ │ │ └── infrastructure/ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ └── FormTemplateConverter.java │ │ │ │ │ ├── persistence/ │ │ │ │ │ │ ├── mapper/ │ │ │ │ │ │ │ └── FormTemplateMapper.java │ │ │ │ │ │ └── po/ │ │ │ │ │ │ └── FormTemplate.java │ │ │ │ │ └── repository/ │ │ │ │ │ └── FormTemplateRepositoryImpl.java │ │ │ │ ├── lock/ │ │ │ │ │ ├── LockService.java │ │ │ │ │ ├── LockUtil.java │ │ │ │ │ ├── Locker.java │ │ │ │ │ ├── db/ │ │ │ │ │ │ └── DbLockService.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── BaseLockService.java │ │ │ │ │ ├── LockerImpl.java │ │ │ │ │ └── NoopLocker.java │ │ │ │ ├── process/ │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ └── export/ │ │ │ │ │ │ ├── ProcessDefinitionExportImpl.java │ │ │ │ │ │ ├── ProcessInstanceExportImpl.java │ │ │ │ │ │ ├── ProcessScheduleExportImpl.java │ │ │ │ │ │ ├── ProcessTaskExportImpl.java │ │ │ │ │ │ ├── ProcessToolExportImpl.java │ │ │ │ │ │ ├── ProcessTransactionExportImpl.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ ├── PagerConverter.java │ │ │ │ │ │ │ ├── ProcessDefinitionConverter.java │ │ │ │ │ │ │ ├── ProcessInstanceConverter.java │ │ │ │ │ │ │ ├── ProcessScheduleConverter.java │ │ │ │ │ │ │ ├── ProcessTaskConverter.java │ │ │ │ │ │ │ └── ProcessToolConverter.java │ │ │ │ │ │ └── sharding/ │ │ │ │ │ │ ├── ProcessInstanceExportShardingImpl.java │ │ │ │ │ │ ├── ProcessTaskExportShardingImpl.java │ │ │ │ │ │ ├── ProcessToolExportShardingImpl.java │ │ │ │ │ │ └── ProcessTransactionExportShardingImpl.java │ │ │ │ │ ├── domain/ │ │ │ │ │ │ ├── constant/ │ │ │ │ │ │ │ ├── ProcessConstants.java │ │ │ │ │ │ │ ├── ProcessDefinitionConstants.java │ │ │ │ │ │ │ ├── ProcessTaskConstants.java │ │ │ │ │ │ │ └── StdProcessConstants.java │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ ├── ProcessInstanceDomainConverter.java │ │ │ │ │ │ │ └── ProcessTaskDomainConverter.java │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ ├── entity/ │ │ │ │ │ │ │ │ ├── ProcessDefinitionEntity.java │ │ │ │ │ │ │ │ ├── ProcessInstanceEntity.java │ │ │ │ │ │ │ │ ├── ProcessNodeExecutionEntity.java │ │ │ │ │ │ │ │ ├── ProcessNodeInstanceEntity.java │ │ │ │ │ │ │ │ ├── ProcessTaskAssignEntity.java │ │ │ │ │ │ │ │ ├── ProcessTaskEntity.java │ │ │ │ │ │ │ │ └── ProcessTaskEventEntity.java │ │ │ │ │ │ │ ├── enums/ │ │ │ │ │ │ │ │ └── ProcessResponseCode.java │ │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ │ ├── CreateProcessInstanceReqVO.java │ │ │ │ │ │ │ ├── CreateProcessInstanceResVO.java │ │ │ │ │ │ │ ├── CreateProcessTaskVO.java │ │ │ │ │ │ │ ├── DoExecuteProcessTaskReqVO.java │ │ │ │ │ │ │ ├── ExecuteProcessTaskReqVO.java │ │ │ │ │ │ │ ├── ProcessDefinitionForListVO.java │ │ │ │ │ │ │ ├── QueryProcessNodeReq.java │ │ │ │ │ │ │ ├── QueryTaskAssignReqVO.java │ │ │ │ │ │ │ ├── QueryTaskEventReqVO.java │ │ │ │ │ │ │ ├── QueryTaskReqVO.java │ │ │ │ │ │ │ ├── RollbackNodeReqVO.java │ │ │ │ │ │ │ ├── ScheduleProcessReqVO.java │ │ │ │ │ │ │ ├── ScheduleProcessResVO.java │ │ │ │ │ │ │ └── TaskInfoForPagerVO.java │ │ │ │ │ │ ├── repository/ │ │ │ │ │ │ │ ├── ProcessRepository.java │ │ │ │ │ │ │ └── ProcessTaskRepository.java │ │ │ │ │ │ └── service/ │ │ │ │ │ │ ├── ProcessDefinitionDomainService.java │ │ │ │ │ │ ├── ProcessInstanceDomainService.java │ │ │ │ │ │ ├── ProcessScheduleDomainService.java │ │ │ │ │ │ ├── ProcessScheduleInvoker.java │ │ │ │ │ │ ├── ProcessTaskDomainService.java │ │ │ │ │ │ ├── ProcessToolDomainService.java │ │ │ │ │ │ ├── TaskOperateDomainService.java │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ └── ProcessScheduleSpiInvoker.java │ │ │ │ │ └── infrastructure/ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ ├── ProcessConverter.java │ │ │ │ │ │ └── ProcessTaskConverter.java │ │ │ │ │ ├── persistence/ │ │ │ │ │ │ ├── mapper/ │ │ │ │ │ │ │ ├── ProcessDefinitionMapper.java │ │ │ │ │ │ │ ├── ProcessInstanceMapper.java │ │ │ │ │ │ │ ├── ProcessNodeExecutionMapper.java │ │ │ │ │ │ │ ├── ProcessNodeInstanceMapper.java │ │ │ │ │ │ │ ├── ProcessTaskAssignMapper.java │ │ │ │ │ │ │ ├── ProcessTaskEventMapper.java │ │ │ │ │ │ │ └── ProcessTaskMapper.java │ │ │ │ │ │ └── po/ │ │ │ │ │ │ ├── ProcessDefinition.java │ │ │ │ │ │ ├── ProcessInstance.java │ │ │ │ │ │ ├── ProcessNodeExecution.java │ │ │ │ │ │ ├── ProcessNodeInstance.java │ │ │ │ │ │ ├── ProcessTask.java │ │ │ │ │ │ ├── ProcessTaskAssign.java │ │ │ │ │ │ └── ProcessTaskEvent.java │ │ │ │ │ ├── repository/ │ │ │ │ │ │ ├── ProcessRepositoryImpl.java │ │ │ │ │ │ └── ProcessTaskRepositoryImpl.java │ │ │ │ │ └── sharding/ │ │ │ │ │ └── ProcessShardingDataQuerier.java │ │ │ │ ├── processunit/ │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ ├── export/ │ │ │ │ │ │ │ ├── ProcessUnitExportImpl.java │ │ │ │ │ │ │ └── converter/ │ │ │ │ │ │ │ ├── PagerConverter.java │ │ │ │ │ │ │ └── ProcessUnitConverter.java │ │ │ │ │ │ ├── message/ │ │ │ │ │ │ │ ├── ProcessUnitCreateMessageListener.java │ │ │ │ │ │ │ ├── ProcessUnitExecuteMessageListener.java │ │ │ │ │ │ │ └── ProcessUnitUpdateMessageListener.java │ │ │ │ │ │ └── task/ │ │ │ │ │ │ ├── ProcessUnitAutoRunFlagUpdateTask.java │ │ │ │ │ │ ├── ProcessUnitAutoRunTask.java │ │ │ │ │ │ ├── ProcessUnitShardingCompareTask.java │ │ │ │ │ │ └── ProcessUnitShardingMigrateTask.java │ │ │ │ │ ├── domain/ │ │ │ │ │ │ ├── constant/ │ │ │ │ │ │ │ ├── ContextDataKeys.java │ │ │ │ │ │ │ └── ProcessUnitConstants.java │ │ │ │ │ │ ├── gateway/ │ │ │ │ │ │ │ └── ProcessUnitClientGateway.java │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ │ │ └── ProcessUnitExecuteConverter.java │ │ │ │ │ │ │ ├── entity/ │ │ │ │ │ │ │ │ ├── ProcessUnitEntity.java │ │ │ │ │ │ │ │ ├── ProcessUnitExecutionEntity.java │ │ │ │ │ │ │ │ └── ProcessUnitInstanceEntity.java │ │ │ │ │ │ │ ├── enums/ │ │ │ │ │ │ │ │ └── ProcessUnitErrorCodeEnum.java │ │ │ │ │ │ │ └── vo/ │ │ │ │ │ │ │ ├── BatchExecuteReq.java │ │ │ │ │ │ │ ├── BatchExecuteRes.java │ │ │ │ │ │ │ ├── ExecContext.java │ │ │ │ │ │ │ ├── ExecParam.java │ │ │ │ │ │ │ ├── ExecPolicy.java │ │ │ │ │ │ │ ├── ExecResult.java │ │ │ │ │ │ │ ├── ExecuteReq.java │ │ │ │ │ │ │ ├── ExecuteRes.java │ │ │ │ │ │ │ ├── ProcessUnitExecuteMessage.java │ │ │ │ │ │ │ ├── ProcessUnitInstanceKey.java │ │ │ │ │ │ │ ├── QueryAsyncInstanceVO.java │ │ │ │ │ │ │ ├── ShardingCompareContext.java │ │ │ │ │ │ │ ├── ShardingMigrateContext.java │ │ │ │ │ │ │ ├── SyncAfterCallReq.java │ │ │ │ │ │ │ ├── SyncAfterCallRes.java │ │ │ │ │ │ │ ├── SyncBeforeCallReq.java │ │ │ │ │ │ │ └── SyncBeforeCallRes.java │ │ │ │ │ │ ├── repository/ │ │ │ │ │ │ │ └── ProcessUnitRepository.java │ │ │ │ │ │ ├── service/ │ │ │ │ │ │ │ ├── ProcessUnitExecutionPersistHandler.java │ │ │ │ │ │ │ ├── ProcessUnitExecutor.java │ │ │ │ │ │ │ ├── ProcessUnitService.java │ │ │ │ │ │ │ ├── ProcessUnitShardingToolService.java │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ ├── AsyncServerProcessUnitExecutor.java │ │ │ │ │ │ │ ├── BaseProcessUnitExecutor.java │ │ │ │ │ │ │ ├── SyncServerProcessUnitExecutor.java │ │ │ │ │ │ │ └── execution/ │ │ │ │ │ │ │ ├── DummyPersistHandler.java │ │ │ │ │ │ │ ├── ExecutionPersister.java │ │ │ │ │ │ │ ├── SyncPersistHandler.java │ │ │ │ │ │ │ └── ThreadPoolAsyncPersistHandler.java │ │ │ │ │ │ └── support/ │ │ │ │ │ │ ├── LockManager.java │ │ │ │ │ │ └── PuTransactionTemplate.java │ │ │ │ │ └── infrastructure/ │ │ │ │ │ ├── converter/ │ │ │ │ │ │ └── ProcessUnitConverter.java │ │ │ │ │ ├── gateway/ │ │ │ │ │ │ ├── ProcessUnitClientGatewayImpl.java │ │ │ │ │ │ └── ProcessUnitClientServiceMessageImpl.java │ │ │ │ │ ├── persistence/ │ │ │ │ │ │ ├── mapper/ │ │ │ │ │ │ │ ├── ProcessUnitExecutionMapper.java │ │ │ │ │ │ │ └── ProcessUnitInstanceMapper.java │ │ │ │ │ │ └── po/ │ │ │ │ │ │ ├── ProcessUnitExecution.java │ │ │ │ │ │ └── ProcessUnitInstance.java │ │ │ │ │ └── repository/ │ │ │ │ │ ├── ProcessUnitConfigCache.java │ │ │ │ │ ├── ProcessUnitConfigFileCache.java │ │ │ │ │ └── ProcessUnitRepositoryImpl.java │ │ │ │ └── sharding/ │ │ │ │ ├── CurrentShardInfo.java │ │ │ │ ├── ShardingHolder.java │ │ │ │ ├── config/ │ │ │ │ │ ├── DataSourceInfo.java │ │ │ │ │ ├── ShardGroupInfo.java │ │ │ │ │ ├── ShardInfo.java │ │ │ │ │ ├── ShardingConfig.java │ │ │ │ │ ├── ShardingConfigManager.java │ │ │ │ │ └── impl/ │ │ │ │ │ ├── ShardingConfigManagerFileImpl.java │ │ │ │ │ └── ShardingConfigManagerImpl.java │ │ │ │ ├── datasource/ │ │ │ │ │ ├── DataSourceFactory.java │ │ │ │ │ ├── ShardingDataSource.java │ │ │ │ │ └── impl/ │ │ │ │ │ └── HikariDataSourceFactory.java │ │ │ │ ├── mybatis/ │ │ │ │ │ ├── ShardingNotSupportedMapper.java │ │ │ │ │ └── ShardingSupportedMapper.java │ │ │ │ ├── service/ │ │ │ │ │ ├── ExportRequestShardComputer.java │ │ │ │ │ ├── ShardingComputeResult.java │ │ │ │ │ ├── ShardingData.java │ │ │ │ │ ├── ShardingDataQueryer.java │ │ │ │ │ ├── ShardingService.java │ │ │ │ │ └── impl/ │ │ │ │ │ └── ShardingServiceImpl.java │ │ │ │ └── transaction/ │ │ │ │ └── ShardingSpringManagedTransactionFactory.java │ │ │ └── resources/ │ │ │ └── easyflow/ │ │ │ ├── easyflow-spring-codegenerator-server.xml │ │ │ ├── easyflow-spring-form-server.xml │ │ │ ├── easyflow-spring-lock-db.xml │ │ │ ├── easyflow-spring-process-server-sharding.xml │ │ │ ├── easyflow-spring-process-server.xml │ │ │ ├── easyflow-spring-processunit-server-sharding.xml │ │ │ ├── easyflow-spring-processunit-server.xml │ │ │ ├── mapper/ │ │ │ │ ├── FormTemplateMapper.xml │ │ │ │ ├── ProcessDefinitionMapper.xml │ │ │ │ ├── ProcessInstanceMapper.xml │ │ │ │ ├── ProcessNodeExecutionMapper.xml │ │ │ │ ├── ProcessNodeInstanceMapper.xml │ │ │ │ ├── ProcessTaskAssignMapper.xml │ │ │ │ ├── ProcessTaskEventMapper.xml │ │ │ │ ├── ProcessTaskMapper.xml │ │ │ │ ├── ProcessUnitExecutionMapper.xml │ │ │ │ ├── ProcessUnitInstanceMapper.xml │ │ │ │ └── SequenceMapper.xml │ │ │ ├── messages/ │ │ │ │ ├── easyflow-process-server.properties │ │ │ │ └── easyflow-process-server_zh.properties │ │ │ └── shardingmapper/ │ │ │ ├── ProcessInstanceMapper.xml │ │ │ ├── ProcessNodeExecutionMapper.xml │ │ │ ├── ProcessNodeInstanceMapper.xml │ │ │ ├── ProcessTaskAssignMapper.xml │ │ │ ├── ProcessTaskEventMapper.xml │ │ │ ├── ProcessTaskMapper.xml │ │ │ ├── ProcessUnitExecutionMapper.xml │ │ │ └── ProcessUnitInstanceMapper.xml │ │ └── script/ │ │ ├── codegenerator-latest.sql │ │ ├── form-latest.sql │ │ ├── lock-latest.sql │ │ ├── process-latest.sql │ │ └── process-latest_zh.sql │ └── pom.xml └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/** ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ ### VS Code ### .vscode/ .project .mvn/ local/ .DS_Store .metadata/ .joycode ================================================ FILE: LICENSE.txt ================================================ Copyright 2021-2022 Jingdong Technology Holding Co. LTD. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =============================================================================== JD-EASYFLOW SUBCOMPONENTS: JDEasyFlow includes a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the following licenses. >>> Bpmn-js: Copyright (c) 2014-present Camunda Services GmbH Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The source code responsible for displaying the bpmn.io project watermark that links back to https://bpmn.io as part of rendered diagrams MUST NOT be removed or changed. When this software is being used in a website or application, the watermark must stay fully visible and not visually overlapped by other elements. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. >>> Bootstrap: The MIT License (MIT) Copyright (c) 2011-2018 Twitter, Inc. Copyright (c) 2011-2018 The Bootstrap Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. >>> Bootstrap-multiselect: Apache License, Version 2.0: Copyright (c) 2012 - 2021 David Stutz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. BSD 3-Clause License: Copyright (c) 2012 - 2021 David Stutz All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of David Stutz nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >>> Fontawesome: Font Awesome Free License ------------------------- Font Awesome Free is free, open source, and GPL friendly. You can use it for commercial projects, open source projects, or really almost whatever you want. Full Font Awesome Free license: https://fontawesome.com/license/free. # Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) In the Font Awesome Free download, the CC BY 4.0 license applies to all icons packaged as SVG and JS file types. # Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) In the Font Awesome Free download, the SIL OFL license applies to all icons packaged as web and desktop font files. # Code: MIT License (https://opensource.org/licenses/MIT) In the Font Awesome Free download, the MIT license applies to all non-font and non-icon files. # Attribution Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font Awesome Free files already contain embedded comments with sufficient attribution, so you shouldn't need to do anything additional when using these files normally. We've kept attribution comments terse, so we ask that you do not actively work to remove them from files, especially code. They're a great way for folks to learn about Font Awesome. # Brand Icons All brand icons are trademarks of their respective owners. The use of these trademarks does not indicate endorsement of the trademark holder by Font Awesome, nor vice versa. **Please do not use brand logos for any purpose except to represent the company, product, or service to which they refer.** >>> JQuery: Copyright OpenJS Foundation and other contributors, https://openjsf.org/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. >>> Jquery validation: The MIT License (MIT) Copyright Jörn Zaefferer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. >>> bootstrap table: (The MIT License) Copyright (c) 2012-2019 Zhixin Wen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =============================================================================== ================================================ FILE: NOTICE.txt ================================================ 1.JDEasyflow use bpmn-js(https://bpmn.io/) component to render bpmn diagram. You should obey their license rule(https://bpmn.io/license/) when using related function. Especially, you should notice and obey the bellow rule in their license: "The source code responsible for displaying the bpmn.io project watermark that links back to https://bpmn.io as part of rendered diagrams MUST NOT be removed or changed. When this software is being used in a website or application, the watermark must stay fully visible and not visually overlapped by other elements." 2.JDEasyFlow license can be seen in LICENSE.txt. In addition, JDEasyFlow depends on a number of components with separate copyright notices and license terms. Your use of these components is subject to the terms and conditions of the licenses of the following links: >>> Apache maven: https://github.com/apache/maven/blob/master/LICENSE >>> Spring framework: https://github.com/spring-projects/spring-framework/blob/main/src/docs/dist/license.txt >>> Commons-logging: https://github.com/apache/commons-logging/blob/master/LICENSE.txt >>> Logback: https://logback.qos.ch/license.html >>> Slf4j: https://www.slf4j.org/license.html >>> Commons-lang3: https://github.com/apache/commons-lang/blob/master/LICENSE.txt >>> Commons-io: https://github.com/apache/commons-io/blob/master/LICENSE.txt >>> Commons-collections: https://github.com/apache/commons-collections/blob/master/LICENSE.txt >>> Jackson: https://github.com/FasterXML/jackson-databind/blob/2.11.4/LICENSE https://github.com/FasterXML/jackson-annotations/blob/2.11.4/LICENSE https://github.com/FasterXML/jackson-core/blob/2.11.4/LICENSE >>> Junit: https://junit.org/junit4/license.html >>> Hamcrest-core: http://hamcrest.org/JavaHamcrest/ >>> Lombok: https://github.com/projectlombok/lombok/blob/master/LICENSE >>> Activiti: https://github.com/Activiti/Activiti/blob/develop/LICENSE.txt >>> Joda: https://www.joda.org/joda-time/licenses.html >>> HikariCP: https://github.com/brettwooldridge/HikariCP/blob/dev/LICENSE >>> log4j: https://github.com/apache/logging-log4j2/blob/2.x/LICENSE.txt >>> attoparser: https://www.attoparser.org/license.html >>> tomcat: https://github.com/apache/tomcat/blob/main/LICENSE >>> mybatis: https://github.com/mybatis/mybatis-3/blob/master/LICENSE >>> thymeleaf: https://github.com/thymeleaf/thymeleaf/blob/3.1-master/LICENSE.txt >>> Spring Boot: https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt >>> unbescapse: https://www.unbescape.org/license.html >>> aspectj: https://github.com/eclipse-aspectj/aspectj/blob/master/LICENSE >>> snakeyml: https://github.com/snakeyaml/snakeyaml/blob/master/LICENSE.txt ================================================ FILE: README-zh.md ================================================ # JDEasyFlow ### 介绍 JDEasyFlow是一款通用流程编排组件, 适用于服务编排、工作流、任务审批等场景。它的特点是简单、灵活、易扩展。开发人员30分钟可入门上手,半天可掌握其原理。 ### 软件架构 JDEasyFlow底层为流程引擎/状态机模块(使用时选一便可,建议优先使用流程引擎),此模块提供了基于JSON格式的JDEasyFlow规范进行流程编排的能力。 BPMN模块提供了基于BPMN规范进行流程定义和可视化的能力,流程可视化基于[bpmn-js](https://bpmn.io/),其本质为提供了将BPMN格式流程定义转换为JDEasyFlow格式的能力。 ### 使用说明 在源码的test目录下有quickstart测试用例,可直接运行或调试以了解使用方式和运行原理。 #### 流程引擎 1. 代码中引入easyflow-flow jar包,以maven为例: ``` com.jd.easyflow easyflow-flow {latestVersion} ``` 2. 编写流程定义文件 以node001->node002->node003的执行顺序为例: ``` {"id": "quickstart_001", "name": "Quick Start 001", "nodes": [ {"id": "node001","name": "Node001","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart001Node01Action()"},"start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart002Node01Action()"},"post": {"to": "node003"}}, {"id": "node003","name": "Node003","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart003Node01Action()"}} ] } ``` 其中QuickStart001Node01Action等为java节点动作类 3. 编写应用启动时加载流程引擎的代码 ``` FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/quickstart/quickstart_001.json"); flowEngine.init(); ``` Spring环境可直接定义FlowEngineImpl bean(不要显式配置或调用init方法). 4. 编写具体流程调用执行的代码 ``` FlowParam param = new FlowParam("quickstart_001"); FlowResult result = flowEngine.execute(param); ``` 日志打印结果如下: ``` [main ] INFO FlowEngineImpl - Start parsing definition files:easyflow-flow/target/test-classes/flow/quickstart/quickstart_001.json [main ] INFO FlowEngineImpl - SART EXECUTE FLOW, flowId:quickstart_001 nodeIds:null [main ] INFO BaseFlowRunner - EXECUTE NODE:node001 [main ] INFO QuickStart001Node01Action - Execute Node 001 [main ] INFO BaseFlowRunner - NEXT NODES:node002 [main ] INFO BaseFlowRunner - EXECUTE NODE:node002 [main ] INFO QuickStart002Node01Action - Execute Node 002 [main ] INFO BaseFlowRunner - NEXT NODES:node003 [main ] INFO BaseFlowRunner - EXECUTE NODE:node003 [main ] INFO QuickStart003Node01Action - Execute Node 003 [main ] INFO BaseFlowRunner - NEXT NODES: [main ] INFO QuickStartTest - Execute finish, current node is:node003 ``` 以上只是简单使用示例,EasyFlow可支持很多的配置项和使用场景,更多使用见wiki文档. #### 流程引擎-BPMN 打开easyflow-flow-bpmn/BPMNDesigner.html流程设计器. 点击导入按钮,导入easyflow-flow-bpmn/src/test/resources/flow/quickstart/quickstart_001.bpmn文件,可在设计器中看到和以上JSON定义等价的BPMN流程定义. 使用时只需要将FlowEngineImpl的flowParser设置为BpmnFlowParser. #### 工作流 工作流模块提供了基于流程引擎的持久化和任务审批能力, 需要关系型数据库支持. 源码中的示例工程启动步骤如下: 1. 检出本工程库代码至本地. 1. 安装关系型数据库, 如MYSQL(使用时需遵守其许可). 1. 建库表, 数据库名为easyflow, 表结构可参考源码中的create_all_table.sql和sample_form_template.sql. 1. sample模块的application-open-all.yml文件中配置数据库连接信息, pom中引入数据库驱动jar包. 1. 编译构建代码工程, 启动IntegrationOpenApplication类. 启动成功后访问http://localhost:9888便可看到JDEasyFlow管理界面. ### 更多 JDEasyFlow具有非常灵活的扩展性,你可以基于目前已开源组件扩展做更多的功能. ### 联系我们 email: liyuliang5@jd.com ================================================ FILE: README.md ================================================ # JDEasyFlow ### Introduce JDEasyFlow is a general flow orchestration component, suitable for service orchestration, workflow, auditing, etc. The characteristics are easy use, flexible, easy extended. Developer can understand using it in 30 minutes, understand its principle half of the day. ### Architecture JDEasyFlow bottom layer is flow engine/state machine.(select one when use it, flow engine is recommanded), this module supply flow orchestration ability base on JSON format flow definition. BPMN module supply define flow based on BPMN and visualization ability. visualization is based on [bpmn-js](https://bpmn.io/). The essence of this module is convert BPMN format definition to JDEasyFlow JSON format definition. ### Usage There are test cases in the test package of source code. You can run or debug directly to understand its usage and implement principle. #### Flow Engine 1. Import easyflow-flow jar. take maven as example: ``` com.jd.easyflow easyflow-flow {latestVersion} ``` 2. Write flow definition. For example, the sequence is node001->node002->node003: ``` {"id": "quickstart_001", "name": "Quick Start 001", "nodes": [ {"id": "node001","name": "Node001","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart001Node01Action()"},"start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart002Node01Action()"},"post": {"to": "node003"}}, {"id": "node003","name": "Node003","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart003Node01Action()"}} ] } ``` QuickStart001Node01Action and so on is java node action class. 3. Write the code of loading flow engine when application start. ``` FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/quickstart/quickstart_001.json"); flowEngine.init(); ``` You can define FlowEngineImpl bean in Spring(DO NOT configure or invoke init() method explicitly). 4. Write the code of invoking flow engine. ``` FlowParam param = new FlowParam("quickstart_001"); FlowResult result = flowEngine.execute(param); ``` The executing log are as follows: ``` [main ] INFO FlowEngineImpl - Start parsing definition files:easyflow-flow/target/test-classes/flow/quickstart/quickstart_001.json [main ] INFO FlowEngineImpl - SART EXECUTE FLOW, flowId:quickstart_001 nodeIds:null [main ] INFO BaseFlowRunner - EXECUTE NODE:node001 [main ] INFO QuickStart001Node01Action - Execute Node 001 [main ] INFO BaseFlowRunner - NEXT NODES:node002 [main ] INFO BaseFlowRunner - EXECUTE NODE:node002 [main ] INFO QuickStart002Node01Action - Execute Node 002 [main ] INFO BaseFlowRunner - NEXT NODES:node003 [main ] INFO BaseFlowRunner - EXECUTE NODE:node003 [main ] INFO QuickStart003Node01Action - Execute Node 003 [main ] INFO BaseFlowRunner - NEXT NODES: [main ] INFO QuickStartTest - Execute finish, current node is:node003 ``` Above is simple usecase, JDEasyFlow support many configurations and use cases. More can be seen in wiki doc. #### FlowEngine-BPMN Open flow designer with path easyflow-flow-bpmn/BPMNDesigner.html. Click import button, import easyflow-flow-bpmn/src/test/resources/flow/quickstart/quickstart_001.bpmn file. You can see bpmn flow definition of equal JSON format. You only need set flowPaser of FlowEngineImpl to BpmnFlowParser when use. #### Process Process module provide persistence and task audit ability based on flow engine, needing relation database support. Sample application in the source code can be started as follows: 1. Checkout source code. 1. Install relation database system, for example MYSQL(You should obey their license). 1. Create database and table. Database name is easyflow, Table schema can be referred by create_all_table.sql and sample_form_template.sql. 1. Config database connection info in application-open-all.yml of sample module, Import database driver jar in pom.xml of sample module. 1. Build the project. Start IntegrationOpenApplication. Access http://localhost:9888. ### More JDEasyFlow has very flexible extension ability. You can implement more features based on current component. For example flow data persistence, auditting, exception retry. ### Contact US mailTo: liyuliang5@jd.com ================================================ FILE: easyflow-flow/pom.xml ================================================ 4.0.0 easyflow-parent com.jd.easyflow 1.7.3 easyflow-flow easyflow-flow jar org.springframework spring-context org.springframework spring-expression ch.qos.logback logback-classic test org.slf4j slf4j-api com.fasterxml.jackson.core jackson-databind junit junit test ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/common/FlowExecutor.java ================================================ package com.jd.easyflow.flow.common; import com.jd.easyflow.flow.engine.FlowContext; /** * * @author liyuliang5 * * @param type */ public interface FlowExecutor { /** * Common execute method * * @param context FlowContext * @return result */ T execute(FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/el/ElEvaluator.java ================================================ package com.jd.easyflow.flow.el; import java.util.Map; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public interface ElEvaluator { /** * Evaluate with default context. * @param * @param exp * @param root * @param cache * @return */ T evalWithDefaultContext(String exp, Object root, boolean cache); /** * Evaluate el value. * @param * @param exp * @param nodeContext * @param flowContext * @param data * @return */ T eval(String exp, NodeContext nodeContext, FlowContext flowContext, Map data); String evalTemplateWithDefaultContext(String template, Object root, boolean cache); String evalTemplate(String template, NodeContext nodeContext, FlowContext flowContext, Map data); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/el/ElFactory.java ================================================ package com.jd.easyflow.flow.el; /** * * @author liyuliang5 * */ public class ElFactory { private static ElEvaluator defaultEvaluator = new SpelEvaluator(); public static ElEvaluator get() { return defaultEvaluator; } public static void setDefaultEvaluator(ElEvaluator evaluator) { defaultEvaluator = evaluator; } public void setDefault(ElEvaluator evaluator) { ElFactory.defaultEvaluator = evaluator; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/el/ElRootMap.java ================================================ package com.jd.easyflow.flow.el; import java.util.Collection; import java.util.Map; import java.util.Set; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 */ public class ElRootMap implements Map { private static final String KEY_NODE_CONTEXT = "nodeContext"; private static final String KEY_ACTION_RESULT = "actionResult"; private static final String KEY_NODE_BIZ_CONTEXT = "nodeBizContext"; private static final String KEY_CONTEXT = "context"; private static final String KEY_BIZ_CONTEXT = "bizContext"; private static final String KEY_PARAM = "param"; private static final String KEY_BIZ_PARAM = "bizParam"; private static final String KEY_PARAM_DATA = "paramData"; private static final String KEY_RESULT = "result"; private static final String KEY_BIZ_RESULT = "bizResult"; private static final String KEY_RESULT_DATA = "resultData"; NodeContext nodeContext; FlowContext context; Map data; @Override public boolean isEmpty() { return false; } @Override public boolean containsKey(Object key) { if (key == null) { return data == null ? false : data.containsKey(key); } if (key.getClass() == String.class) { switch ((String) key) { case KEY_NODE_CONTEXT: case KEY_ACTION_RESULT: case KEY_NODE_BIZ_CONTEXT: case KEY_CONTEXT: case KEY_BIZ_CONTEXT: case KEY_PARAM: case KEY_BIZ_PARAM: case KEY_PARAM_DATA: case KEY_RESULT: case KEY_BIZ_RESULT: case KEY_RESULT_DATA: return true; default: return data == null ? false : data.containsKey(key); } } return false; } @Override public Object get(Object key) { if (key == null) { return data == null ? null : data.get(key); } if (key.getClass() == String.class) { Object value = null; switch ((String) key) { case KEY_NODE_CONTEXT: value = nodeContext; break; case KEY_ACTION_RESULT: value = nodeContext == null ? null : nodeContext.getActionResult(); break; case KEY_NODE_BIZ_CONTEXT: value = nodeContext == null ? null : nodeContext.getNodeContext(); break; case KEY_CONTEXT: value = context; break; case KEY_BIZ_CONTEXT: value = context == null ? null : context.getContext(); break; case KEY_PARAM: value = context == null ? null : context.getParam(); break; case KEY_BIZ_PARAM: value = context == null ? null : (context.getParam() == null ? null : context.getParam().getParam()); break; case KEY_PARAM_DATA: value = context == null ? null : (context.getParam() == null ? null : context.getParam().getDataMap()); break; case KEY_RESULT: value = context == null ? null : context.getResult(); break; case KEY_BIZ_RESULT: value = context == null ? null : (context.getResult() == null ? null : context.getResult().getResult()); break; case KEY_RESULT_DATA: value = context == null ? null : (context.getResult() == null ? null : context.getResult().getDataMap()); default: // NOOP } if (value == null && data != null) { value = data.get(key); } return value; } return null; } @Override public int size() { throw new UnsupportedOperationException(); } @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); } @Override public Object put(String key, Object value) { throw new UnsupportedOperationException(); } @Override public Object remove(Object key) { throw new UnsupportedOperationException(); } @Override public void putAll(Map m) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Set keySet() { throw new UnsupportedOperationException(); } @Override public Collection values() { throw new UnsupportedOperationException(); } @Override public Set> entrySet() { throw new UnsupportedOperationException(); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/el/SpelEvaluator.java ================================================ package com.jd.easyflow.flow.el; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.MapAccessor; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class SpelEvaluator implements ElEvaluator, ApplicationContextAware { private static final Logger logger = LoggerFactory.getLogger(SpelEvaluator.class); // Normal implementation. public static final int ROOT_TYPE_HASH_MAP = 0; // Performance is higher than HashMap public static final int ROOT_TYPE_ROOT_MAP = 1; private int rootType = ROOT_TYPE_ROOT_MAP; private boolean cache = true; private StandardEvaluationContext context = new StandardEvaluationContext(); private Map cacheMap = new ConcurrentHashMap(); private ExpressionParser parser = new SpelExpressionParser(); private ApplicationContext applicationContext; private static Map templateCacheMap = new ConcurrentHashMap(); { context.addPropertyAccessor(new MapAccessor()); } @Override public T evalWithDefaultContext(String exp, Object root, boolean cache) { try { Expression expression; if (cache) { expression = cacheMap.get(exp); if (expression == null) { expression = parser.parseExpression(exp); cacheMap.put(exp, expression); } } else { expression = parser.parseExpression(exp); } Object value = expression.getValue(context, root); return (T) value; } catch (Exception e) { if (logger.isErrorEnabled()) { logger.error("Eval spel exception, exp:" + exp + "," + e.getMessage()); } throw e; } } @Override public T eval(String exp, NodeContext nodeContext, FlowContext flowContext, Map data) { if (flowContext.isLogOn() && logger.isInfoEnabled()) { logger.info("EVAL SPEL:" + exp); } Object root = null; switch (rootType) { case ROOT_TYPE_HASH_MAP: root = buildHashMapRoot(nodeContext, flowContext, data); break; case ROOT_TYPE_ROOT_MAP: root = buildRootMapRoot(nodeContext, flowContext, data); break; } Object result = null; try { result = evalWithDefaultContext(exp, root, cache); } catch (Exception e) { if (flowContext.isLogOn() && logger.isErrorEnabled()) { logger.error("EVAL SPEL EXCEPTION, EXP:" + exp + "," + e.getMessage()); } throw e; } if (flowContext.isLogOn() && logger.isInfoEnabled()) { try { logger.info("SPEL RESULT:" + JsonUtil.toJsonString(result)); } catch (Throwable t) { logger.info("spel result to json string exception:" + t.getMessage()); } } return (T) result; } private Object buildHashMapRoot(NodeContext nodeContext, FlowContext flowContext, Map data) { Map root = new HashMap<>(); if (nodeContext != null) { root.put("nodeContext", nodeContext); root.put("actionResult", nodeContext.getActionResult()); if (nodeContext.getNodeContext() != null) { root.put("nodeBizContext", nodeContext.getNodeContext()); } } if (flowContext != null) { root.put("context", flowContext); if (flowContext.getContext() != null) { root.put("bizContext", flowContext.getContext()); } root.put("param", flowContext.getParam()); if (flowContext.getParam() != null && flowContext.getParam().getParam() != null) { root.put("bizParam", flowContext.getParam().getParam()); } if (flowContext.getParam() != null && flowContext.getParam().getDataMap() != null) { root.put("paramData", flowContext.getParam().getDataMap()); } root.put("result", flowContext.getResult()); if (flowContext.getResult() != null && flowContext.getResult().getResult() != null) { root.put("bizResult", flowContext.getResult().getResult()); root.put("resultData", flowContext.getResult().getDataMap()); } } if (data != null) { root.putAll(data); } return root; } private Object buildRootMapRoot(NodeContext nodeContext, FlowContext flowContext, Map data) { ElRootMap root = new ElRootMap(); root.nodeContext = nodeContext; root.context = flowContext; if (data != null) { root.data = data; } return root; } @Override public String evalTemplate(String template, NodeContext nodeContext, FlowContext flowContext, Map data) { if (flowContext.isLogOn() && logger.isInfoEnabled()) { logger.info("EVAL TEMPLATE:" + template); } Object root = null; switch (rootType) { case ROOT_TYPE_HASH_MAP: root = buildHashMapRoot(nodeContext, flowContext, data); break; case ROOT_TYPE_ROOT_MAP: root = buildRootMapRoot(nodeContext, flowContext, data); break; } String result = null; try { result = evalTemplateWithDefaultContext(template, root, cache); } catch (Exception e) { if (flowContext.isLogOn() && logger.isErrorEnabled()) { logger.error("EVAL TEMPLATE EXCEPTION, TEMPLATE:" + template + "," + e.getMessage()); } throw e; } if (flowContext.isLogOn() && logger.isInfoEnabled()) { logger.info("TEMPLATE RESULT:" + result); } return result; } @Override public String evalTemplateWithDefaultContext(String template, Object root, boolean cache) { try { Expression expression; if (cache) { expression = templateCacheMap.get(template); if (expression == null) { expression = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION); templateCacheMap.put(template, expression); } } else { expression = parser.parseExpression(template, ParserContext.TEMPLATE_EXPRESSION); } Object result = null; result = expression.getValue(context, root); if (result == null) { return null; } return result.toString(); } catch (Exception e) { logger.error("SPEL template eval exception, template:" + template, e); throw e; } } public int getRootType() { return rootType; } public void setRootType(int rootType) { this.rootType = rootType; } public boolean isCache() { return cache; } public void setCache(boolean cache) { this.cache = cache; } public ApplicationContext getApplicationContext() { return applicationContext; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; context.setBeanResolver(new BeanFactoryResolver(applicationContext)); } public StandardEvaluationContext getContext() { return context; } public void setContext(StandardEvaluationContext context) { this.context = context; } public ExpressionParser getParser() { return parser; } public void setParser(ExpressionParser parser) { this.parser = parser; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/FlowContext.java ================================================ package com.jd.easyflow.flow.engine; import java.io.Serializable; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.NodeContext; /** * Flow context. * @author liyuliang5 * */ public interface FlowContext extends Serializable { /** * Get start node context list. * @return */ List getStartNodes(); /** * Set start node context list. * @param startNodes */ void setStartNodes(List startNodes); /** * Get end node context list. * From every start node, if execute to no next node(Including execute nodes), we add it to end node list. * @return */ List getEndNodes(); /** * Put context data. * @param key * @param value */ void put(String key, Object value); /** * Get context data. * @param * @param key * @return */ T get(String key); /** * Delete key. * @param key */ void remove(String key); /** * Get data. * @return */ Map getData(); /** * Get flow param. * @return */ FlowParam getParam(); /** * Set flow param. * @param param */ void setParam(FlowParam param); /** * Get flow result. * @return */ FlowResult getResult(); /** * Set flow result. * @param result */ void setResult(FlowResult result); /** * Get flow id. * @return */ String getFlowId(); /** * Set flow id. * @param flowId */ void setFlowId(String flowId); /** * Get flow. * @return */ Flow getFlow(); /** * Set flow. * @param flow */ void setFlow(Flow flow); /** * Get flow engine. * @return */ FlowEngine getFlowEngine(); /** * Set interrupted. only can from false to true. * @param interrupted */ void setInterrupted(); /** * Get interrupted. * @return */ boolean isInterrupted(); /** * Get pre result. * @return */ Boolean getPreResult(); /** * Get log flag. * @return */ Boolean getLogFlag(); /** * Set log flag. * @param logFlag */ void setLogFlag(Boolean logFlag); /** * * @return */ boolean isLogOn(); /** * Get business context. * @param * @return */ T getContext(); /** * Set business context. * @param context */ void setContext(Object context); /** * Get ElEvaluator. * @return */ ElEvaluator getElEvaluator(); /** * Whether record history. * @return */ boolean isRecordHistory(); /** * Get FlowContext of parent flow. * @return */ FlowContext getParentContext(); /** * Get nodeContext of parent flow. * @return */ NodeContext getParentNodeContext(); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/FlowEngine.java ================================================ package com.jd.easyflow.flow.engine; import java.util.Map; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.parser.FlowParser; /** * * @author liyuliang5 * */ public interface FlowEngine { /** * Execute flow. * @param param * @return */ FlowResult execute(FlowParam param); /** * Get flow definition. * @param id * @return */ Flow getFlow(String id); /** * Get flow parser. * @return */ FlowParser getFlowParser(); /** * Get engine properties. * @return */ Map getProperties(); /** * Get engine property. * @param * @param key * @return */ T getProperty(String key); /** * * @return */ ElEvaluator getElEvaluator(); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/FlowParam.java ================================================ package com.jd.easyflow.flow.engine; import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import com.fasterxml.jackson.annotation.JsonIgnore; import com.jd.easyflow.flow.engine.impl.FlowContextImpl; /** * * @author liyuliang5 * */ public class FlowParam implements Serializable { public FlowParam() { // NOOP } public FlowParam(String flowId) { this.flowId = flowId; } public FlowParam(String flowId, Object param) { this.flowId = flowId; this.param = param; } public FlowParam(String flowId, String[] nodeIds, Object param) { this.flowId = flowId; this.nodeIds = nodeIds; this.param = param; } public FlowParam(String flowId, String[] nodeIds, Object param, Map dataMap) { this.flowId = flowId; this.nodeIds = nodeIds; this.param = param; if (dataMap != null) { for (Entry entry : dataMap.entrySet()) { if (entry.getValue() == null) { this.dataMap.remove(entry.getKey()); } else { this.dataMap.put(entry.getKey(), entry.getValue()); } } } this.dataMap = dataMap; } public FlowParam(String flowId, String nodeId, Object param) { this.flowId = flowId; this.nodeIds = new String[] { nodeId }; this.param = param; } private String flowId; private String[] nodeIds; /** * business param. */ private Object param; /** * flow context. */ @JsonIgnore private FlowContext context; /** * common param data. */ private Map dataMap = new ConcurrentHashMap(); /** * log flag */ private Boolean logFlag; public String getFlowId() { return flowId; } public void setFlowId(String flowId) { this.flowId = flowId; } public String[] getNodeIds() { return nodeIds; } public void setNodeIds(String[] nodeIds) { this.nodeIds = nodeIds; } public void setNodeId(String nodeId) { if (nodeId == null) { return; } this.nodeIds = new String[] { nodeId }; } public T getParam() { return (T) param; } /** * param should be Object[] or List * * @param * @param index * @return */ public T getParam(int index) { if (param == null) { return null; } if (param instanceof Object[]) { return (T) ((Object[]) param)[index]; } else if (param instanceof List) { return (T) ((List) param).get(index); } throw new IllegalStateException("Param:" + param + " is not index type"); } /** * param should be Map * * @param * @param key * @return */ public T getParam(String key) { if (param == null) { return null; } return (T) ((Map) param).get(key); } public void putParam(String key, Object value) { if (param == null) { param = new ConcurrentHashMap<>(); } if (value == null) { ((Map) param).remove(key); } else { ((Map) param).put(key, value); } } public void setParam(Object param) { this.param = param; } public FlowContext getContext() { return context; } /** * High level method. * @param context. SHOULD use new FlowContext Object and can only invoke some methods. */ public void setContext(FlowContext context) { this.context = context; } public Map getDataMap() { return dataMap; } public void setDataMapFrom(FlowParam fromParam) { this.dataMap = fromParam.dataMap; } public void put(String key, Object value) { if (value == null) { dataMap.remove(key); } else { dataMap.put(key, value); } } public T get(String key) { return (T) dataMap.get(key); } public void putContextData(String key, Object value) { if (context == null) { context = new FlowContextImpl(); } if (value == null) { context.remove(key); } else { context.put(key, value); } } public void setBizContext(Object bizContext) { if (context == null) { context = new FlowContextImpl(); } context.setContext(bizContext); } public Boolean getLogFlag() { return logFlag; } public void setLogFlag(Boolean logFlag) { this.logFlag = logFlag; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/FlowResult.java ================================================ package com.jd.easyflow.flow.engine; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.fasterxml.jackson.annotation.JsonIgnore; /** * * @author liyuliang5 * */ public class FlowResult implements Serializable { @JsonIgnore private FlowContext context; private Object result; /** * common result data. */ private Map dataMap = new ConcurrentHashMap(); public FlowContext getContext() { return context; } public void setContext(FlowContext context) { this.context = context; } public synchronized T getResult() { return (T) result; } public synchronized void setResult(Object result) { this.result = result; } /** * * Put result. putResult or addResult should use only one. * * @param key * @param value */ public synchronized void putResult(String key, Object value) { if (result == null) { result = new ConcurrentHashMap<>(); } if (value == null) { ((Map) result).remove(key); } else { ((Map) result).put(key, value); } } /** * * Add result. putResult or addResult should use only one. * * @param value */ public synchronized void addResult(Object value) { if (result == null) { result = new ArrayList(); } ((List) result).add(value); } public synchronized T getResult(String key) { if (result == null) { return null; } return (T) ((Map) result).get(key); } public synchronized T getResult(int index) { if (result == null) { return null; } return (T) ((List) result).get(index); } public Map getDataMap() { return dataMap; } public void setDataMapFrom(FlowResult result) { this.dataMap = result.dataMap; } public void put(String key, Object value) { if (value == null) { dataMap.remove(key); } else { dataMap.put(key, value); } } public T get(String key) { return (T) dataMap.get(key); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/FlowRunner.java ================================================ package com.jd.easyflow.flow.engine; import com.jd.easyflow.flow.model.FlowLifeCycle; /** * Flow runner. * @author liyuliang5 * @date 2021/07/26 */ public interface FlowRunner extends FlowLifeCycle { /** * Run flow. * @param context */ void run(FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/builder/FlowParamBuilder.java ================================================ package com.jd.easyflow.flow.engine.builder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; /** * * @author liyuliang5 * */ public class FlowParamBuilder { private FlowParam param; public static FlowParamBuilder create(String flowId, String nodeId) { FlowParam param = new FlowParam(); param.setFlowId(flowId); param.setNodeIds(new String[] {nodeId}); FlowParamBuilder builder = new FlowParamBuilder(); builder.param = param; return builder; } public static FlowParamBuilder create(String flowId, String[] nodeIds) { FlowParam param = new FlowParam(); param.setFlowId(flowId); param.setNodeIds(nodeIds); FlowParamBuilder builder = new FlowParamBuilder(); builder.param = param; return builder; } public FlowParamBuilder putParam(String key, Object value) { if (param.getParam() == null) { param.setParam( new HashMap<>()); } ((Map) param.getParam()).put(key, value); return this; } public FlowParamBuilder addParam(Object value) { if (param.getParam() == null) { param.setParam(new ArrayList()); } ((List) param.getParam()).add(value); return this; } public FlowParamBuilder addParams(Object... values) { if (param.getParam() == null) { param.setParam(new ArrayList()); } ((List) param.getParam()).addAll(Arrays.asList(values)); return this; } public FlowParamBuilder paramObject(Object o) { param.setParam(o); return this; } public FlowParamBuilder putData(String key, Object value) { param.put(key, value); return this; } public FlowParamBuilder putData(Map dataMap) { if (dataMap != null) { for (Entry entry : dataMap.entrySet()) { param.put(entry.getKey(), entry.getValue()); } } return this; } public FlowParamBuilder setContext(FlowContext context) { param.setContext(context); return this; } public FlowParam build() { return param; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/event/BaseFlowEventListener.java ================================================ package com.jd.easyflow.flow.engine.event; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public abstract class BaseFlowEventListener implements FlowEventListener { protected Pair[] acceptedEvents; @Override public Pair[] getAcceptedEvents() { return acceptedEvents; } public void setAcceptedEvents(Pair[] acceptedEvents) { this.acceptedEvents = acceptedEvents; } @Override public void postConstruct(Map definition, Map context) { if (definition == null) { return; } List> acceptedEvents = (List>)definition.get("acceptedEvents"); if (acceptedEvents != null) { this.acceptedEvents = new Pair[acceptedEvents.size()]; for (int i = 0; i < acceptedEvents.size(); i++) { Map acceptedEvent = acceptedEvents.get(i); String event = (String) acceptedEvent.get("event"); Integer order = (Integer) acceptedEvent.get("order"); this.acceptedEvents[i] = Pair.of(event, order); } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/event/ExpFlowEventListener.java ================================================ package com.jd.easyflow.flow.engine.event; import java.util.HashMap; import java.util.Map; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.el.ElFactory; /** * * @author liyuliang5 * */ public class ExpFlowEventListener extends BaseFlowEventListener { private ElEvaluator elEvaluator; private String exp; public ExpFlowEventListener() { } public ExpFlowEventListener(String exp) { this.exp = exp; } @Override public void on(FlowEvent flowEvent) { Map data = new HashMap<>(); data.put("event", flowEvent); ElEvaluator evaluator = elEvaluator; if (evaluator == null && flowEvent.getContext() != null) { elEvaluator = flowEvent.getContext().getElEvaluator(); } if (evaluator == null) { evaluator = ElFactory.get(); } evaluator.eval(exp, null, flowEvent.getContext(), data); } public ElEvaluator getElEvaluator() { return elEvaluator; } public void setElEvaluator(ElEvaluator elEvaluator) { this.elEvaluator = elEvaluator; } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/event/FlowEvent.java ================================================ package com.jd.easyflow.flow.engine.event; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.engine.FlowContext; /** * * @author liyuliang5 * */ public class FlowEvent { private String type; private Object data; private FlowContext context; public FlowEvent() { } public FlowEvent(String type, Object data, FlowContext context) { this.type = type; this.data = data; this.context = context; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public T getMapData(String key) { if (data == null) { return null; } return (T) ((Map) data).get(key); } public T getListData(int index) { if (data == null) { return null; } return (T) ((List) data).get(index); } public FlowContext getContext() { return context; } public void setContext(FlowContext context) { this.context = context; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/event/FlowEventListener.java ================================================ package com.jd.easyflow.flow.engine.event; import com.jd.easyflow.flow.model.FlowLifeCycle; import com.jd.easyflow.flow.util.Pair; /** * * @author liyuliang5 * */ public interface FlowEventListener extends FlowLifeCycle { /** * * @return List of Event and order priority pair. * The highest order values has highest priority. * If order value same, the front configured has higher priority. */ default Pair[] getAcceptedEvents() { return null; } /** * Listener Id. * @return */ default String getId() { return this.getClass().getName(); } /** * Callback of event. * @param flowEvent */ public void on(FlowEvent flowEvent); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/event/FlowEventTrigger.java ================================================ package com.jd.easyflow.flow.engine.event; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.util.Pair; /** * * @author liyuliang5 * */ public class FlowEventTrigger { public static final Logger logger = LoggerFactory.getLogger(FlowEventTrigger.class); public static final String NULL_KEY = null; private List listenerList; private Map>> listenerMap; public void init(InitContext initContext, Flow flow) { if (listenerList != null) { for (FlowEventListener listener : listenerList) { listener.init(initContext, flow); } } } public void destroy () { if (listenerList != null) { for (FlowEventListener listener : listenerList) { listener.destroy(); } } } public void addListener(FlowEventListener listener) { if (listenerList == null) { listenerList = new ArrayList(); listenerMap = new HashMap<>(); } listenerList.add(listener); Pair[] pairs = listener.getAcceptedEvents(); if (pairs == null) { pairs = new Pair[] { Pair.of(NULL_KEY, 0) }; } for (Pair pair : pairs) { TreeMap> map = listenerMap.get(pair.getLeft()); if (map == null) { map = new TreeMap>(Comparator.reverseOrder()); listenerMap.put(pair.getLeft(), map); } List listeners = map.get(pair.getRight()); if (listeners == null) { listeners = new ArrayList(); map.put(pair.getRight(), listeners); } listeners.add(listener); } } public void triggerEvent(String eventType, FlowContext context) { triggerEvent(eventType, null, context, false); } public void triggerEvent(String eventType, Object eventData, FlowContext context, boolean catchThrowable) { if (listenerList == null || listenerList.size() == 0) { return; } FlowEvent event = null; TreeMap> map = listenerMap.get(eventType); if (map != null) { event = createEvent(eventType, eventData, context); } // priority>0 if (map != null) { for (Entry> entry : map.entrySet()) { if (entry.getKey() <= 0) { break; } for (FlowEventListener listener : entry.getValue()) { onEvent(event, listener, catchThrowable); } } } // key exists and priority =0 if (map != null) { List keyZeroListeners = map.get(0); if (keyZeroListeners != null) { for (FlowEventListener listener : keyZeroListeners) { onEvent(event, listener, catchThrowable); } } } // key is null TreeMap> nullMap = listenerMap.get(NULL_KEY); if (nullMap != null) { List nullZeroListeners = nullMap.get(0); if (nullZeroListeners != null) { if (event == null) { event = createEvent(eventType, eventData, context); } for (FlowEventListener listener : nullZeroListeners) { onEvent(event, listener, catchThrowable); } } } // priority<0 if (map != null) { for (Entry> entry : map.entrySet()) { if (entry.getKey() >= 0) { continue; } for (int i = 0; i < entry.getValue().size(); i++) { onEvent(event, entry.getValue().get(entry.getValue().size() - 1 - i), catchThrowable); } } } } private FlowEvent createEvent(String eventType, Object eventData, FlowContext context) { FlowEvent event = new FlowEvent(); event.setType(eventType); event.setData(eventData); event.setContext(context); return event; } private void onEvent(FlowEvent event, FlowEventListener listener, boolean catchThrowable) { if (!catchThrowable) { listener.on(event); } else { try { listener.on(event); } catch (Throwable t) { //NOSONAR logger.error("Exception on event of " + listener.getClass().getName() + ", message:" + t.getMessage(), t); } } } public List getListenerList() { return listenerList; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/event/impl/EventFlowListener.java ================================================ package com.jd.easyflow.flow.engine.event.impl; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.event.BaseFlowEventListener; import com.jd.easyflow.flow.engine.event.FlowEvent; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.FlowEventTypes; import com.jd.easyflow.flow.util.FlowStringUtil; import com.jd.easyflow.flow.util.Pair; /** * Listener of event node. * @author liyuliang5 * */ public class EventFlowListener extends BaseFlowEventListener { private static final Logger logger = LoggerFactory.getLogger(EventFlowListener.class); private int initEndEventOrder = FlowConstants.EVENT_ORDER_START; @Override public Pair[] getAcceptedEvents() { if (this.acceptedEvents != null) { return acceptedEvents; } return new Pair[] { Pair.of(FlowEventTypes.INIT_END, initEndEventOrder) }; } @Override public void on(FlowEvent event) { switch (event.getType()) { case FlowEventTypes.INIT_END: { String eventId = event.getContext().getParam().get(FlowConstants.PARAM_DATA_EVENT); if (FlowStringUtil.isNotEmpty(eventId)) { List startNodes = event.getContext().getStartNodes(); if (event.getContext().isLogOn() && logger.isInfoEnabled()) { logger.info("EVENT ID:" + eventId); } if (startNodes != null && ! startNodes.isEmpty()) { startNodes.forEach(node -> node.put(FlowConstants.NODE_CONTEXT_DATA_EVENT, eventId)); } } break; } default: break; } } public int getInitEndEventOrder() { return initEndEventOrder; } public void setInitEndEventOrder(int initEndEventOrder) { this.initEndEventOrder = initEndEventOrder; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/event/impl/InterruptFlowListener.java ================================================ package com.jd.easyflow.flow.engine.event.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.event.BaseFlowEventListener; import com.jd.easyflow.flow.engine.event.FlowEvent; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.FlowEventTypes; import com.jd.easyflow.flow.util.FlowUtil; import com.jd.easyflow.flow.util.Pair; /** * Listen node end event, process interrupt scene. * @author liyuliang5 * */ public class InterruptFlowListener extends BaseFlowEventListener { private static final Logger logger = LoggerFactory.getLogger(InterruptFlowListener.class); private static final Pair[] DEFAULT_ACCEPTED_EVENTS = new Pair[]{Pair.of(FlowEventTypes.NODE_END, FlowConstants.EVENT_ORDER_START)}; public InterruptFlowListener() { this.acceptedEvents = DEFAULT_ACCEPTED_EVENTS; } @Override public void on(FlowEvent event) { switch (event.getType()) { case FlowEventTypes.NODE_END: { NodeContext nodeContext = (NodeContext) event.getData(); FlowContext context = event.getContext(); Boolean interrupt = FlowUtil.nodeProperty(FlowConstants.PROP_INTERRUPT, nodeContext, context); if (interrupt == null) { String interruptExp = FlowUtil.nodeProperty(FlowConstants.PROP_INTERRUPT_EXP, nodeContext, context); if (interruptExp != null) { interrupt = context.getElEvaluator().eval(interruptExp, nodeContext, context, null); } if (interrupt == null) { String flowInterruptExp = context.getFlow().getProperty(FlowConstants.PROP_INTERRUPT_EXP); if (flowInterruptExp != null) { interrupt = context.getElEvaluator().eval(flowInterruptExp, nodeContext, context, null); } } } if (logger.isDebugEnabled()) { logger.debug("Flow interrupt result:" + interrupt); } if (Boolean.TRUE.equals(interrupt)) { context.setInterrupted(); } break; } default: break; } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/filter/FlowEngineFilter.java ================================================ package com.jd.easyflow.flow.engine.filter; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public interface FlowEngineFilter extends Filter, FlowResult> { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/filter/FlowEngineFilterManager.java ================================================ package com.jd.easyflow.flow.engine.filter; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.filter.FilterChainImpl; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public class FlowEngineFilterManager { protected List, FlowResult>> filters; protected List, FlowResult>> innerFilters; protected List, FlowResult>> outerFilters; public void init(InitContext initContext, FlowEngine flowEngine) { if (filters != null) { filters.forEach(filter -> { filter.init(initContext, flowEngine); }); refreshFilters(); } } public boolean noOuterFilter() { return outerFilters == null || outerFilters.size() == 0; } public FlowResult doOuterFilter(Pair p, Function, FlowResult> outerInvoker) { FilterChain, FlowResult> chain = new FilterChainImpl, FlowResult>(outerFilters, outerInvoker); return chain.doFilter(p); } public boolean noInnerFilter() { return innerFilters == null || innerFilters.size() == 0; } public FlowResult doInnerFilter(Pair p, Function, FlowResult> innerInvoker) { FilterChain, FlowResult> chain = new FilterChainImpl, FlowResult>(innerFilters, innerInvoker); return chain.doFilter(p); } public List, FlowResult>> getFilters() { return filters; } public void setFilters(List, FlowResult>> filters) { this.filters = filters; refreshFilters(); } private void refreshFilters() { if (filters == null) { this.innerFilters = this.outerFilters = null; } else { this.innerFilters = new ArrayList, FlowResult>>(); this.outerFilters = new ArrayList, FlowResult>>(); for (Filter filter : filters) { int filterOrder = filter.getOrder(); int pos = 0; if (filterOrder < 0) { for (; pos < innerFilters.size(); pos++) { if (((Filter) innerFilters.get(pos)).getOrder() < filterOrder) { break; } } innerFilters.add(pos, filter); } else { for (; pos < outerFilters.size(); pos++) { if (((Filter) outerFilters.get(pos)).getOrder() < filterOrder) { break; } } outerFilters.add(pos, filter); } } } } public List, FlowResult>> getInnerFilters() { return innerFilters; } public List, FlowResult>> getOuterFilters() { return outerFilters; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/filter/impl/BaseFlowEngineFilter.java ================================================ package com.jd.easyflow.flow.engine.filter.impl; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.filter.FlowEngineFilter; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public abstract class BaseFlowEngineFilter extends BaseFilter, FlowResult> implements FlowEngineFilter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/BaseFlowRunner.java ================================================ package com.jd.easyflow.flow.engine.impl; import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowRunner; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeContextAccessor; import com.jd.easyflow.flow.util.FlowEventTypes; import com.jd.easyflow.flow.util.Triple; /** * * @author liyuliang5 * */ public abstract class BaseFlowRunner implements FlowRunner { private static final Logger logger = LoggerFactory.getLogger(BaseFlowRunner.class); private Function, NodeContext> outerNodeInvoker = p -> invokeNode(p.getLeft(), p.getMiddle(), p.getRight()); private Function, NodeContext> innerNodeInvoker = p -> p.getLeft().execute(p.getMiddle(), p.getRight()); private Function outerFlowPreHandlerInvoker = p -> invokePreHandler(p.getFlow(), p); private Function innerFlowPreHandlerInvoker = p -> p.getFlow().getPreHandler().preHandle(p); private Function outerFlowPostHandlerInvoker = p -> {invokePostHandler(p.getFlow(), p); return null;}; private Function innerFlowPostHandlerInvoker = p -> {p.getFlow().getPostHandler().postHandle(p); return null;}; @Override public void run(FlowContext context) { Flow flow = context.getFlow(); flow.triggerEvent(FlowEventTypes.RUN_START, context); if (! executePreHandler(flow, context)) { flow.triggerEvent(FlowEventTypes.RUN_END, context); return; } runNodes((FlowContextImpl) context); executePostHandler(flow, context); flow.triggerEvent(FlowEventTypes.RUN_END, context); } /** * Run flow. * * @param context */ public abstract void runNodes(FlowContextImpl context); protected NodeContext[] runOneNodeAndAddNextNodes(NodeContext currentNode, FlowContextImpl context) { NodeContext[] nextNodes = runOneNode(currentNode, context); if (nextNodes != null) { context.addNodes(nextNodes); } return nextNodes; } /** * Run one node. * * @param currentNode * @param context * @param flow * @return next nodes */ protected NodeContext[] runOneNode(NodeContext currentNode, FlowContextImpl context) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("EXECUTE NODE:" + currentNode.getNodeId()); } FlowNode node = context.getFlow().getNode(currentNode.getNodeId()); if (node == null) { throw new FlowException("Node " + currentNode.getNodeId() + " not exists"); } NodeContext[] nextNodes = null; try { currentNode = runNode(node, currentNode, context); // get next nodes nextNodes = currentNode.getNextNodes(); } catch (Throwable t) { // NOSONAR NodeContextAccessor.setThrowable(currentNode, t); throw t; } finally { if (nextNodes == null) { ((FlowContextImpl) context).addEndNode(currentNode); } } // print nodes info if (context.isLogOn() && logger.isInfoEnabled()) { if (nextNodes == null || nextNodes.length == 0) { logger.info("NEXT NODES:"); } else if (nextNodes.length == 1) { logger.info("NEXT NODES:" + nextNodes[0].getNodeId()); } else { StringBuilder builder = new StringBuilder(); for (NodeContext n : nextNodes) { builder.append(n.getNodeId()).append(","); } logger.info("NEXT NODES:" + (builder.length() == 0 ? "" : builder.substring(0, builder.length() - 1))); } } // Clear previous node to avoid OOM if (!context.isRecordHistory()) { NodeContextAccessor.setPreviousNode(currentNode, null); NodeContextAccessor.setNextNodes(currentNode, null); } return nextNodes; } protected NodeContext runNode(FlowNode node, NodeContext currentNode, FlowContextImpl context) { Flow flow = context.getFlow(); if (flow.getFilterManager().noOuterNodeFilter()) { return invokeNode(node, currentNode, context); } return flow.getFilterManager().doOuterNodeFilter(Triple.of(node, currentNode, context), outerNodeInvoker); } private NodeContext invokeNode(FlowNode node, NodeContext currentNode, FlowContext context) { Throwable throwable = null; Flow flow = context.getFlow(); try { flow.triggerEvent(FlowEventTypes.NODE_START, currentNode, context, false); // Execute node if (flow.getFilterManager().noInnerNodeFilter()) { currentNode = node.execute(currentNode, context); } else { currentNode = flow.getFilterManager().doInnerNodeFilter(Triple.of(node, currentNode, context), innerNodeInvoker); } flow.triggerEvent(FlowEventTypes.NODE_END, currentNode, context, false); return currentNode; } catch (Throwable t) {// NOSONAR throwable = t; if (context.isLogOn() && logger.isErrorEnabled()) { logger.error("Flow node execute exception, Node:" + currentNode.getNodeId() + "," + t.getMessage()); } throw t; } finally { NodeContextAccessor.setThrowable(currentNode, throwable); flow.triggerEvent(FlowEventTypes.NODE_COMPLETE, currentNode, context, true); } } private boolean executePreHandler(Flow flow, FlowContext context) { if (flow.getFilterManager().noOuterFlowPreHandlerFilter()) { return invokePreHandler(flow, context); } else { Boolean preResult = flow.getFilterManager().doOuterFlowPreHandlerFilter(context, outerFlowPreHandlerInvoker); ((FlowContextImpl) context).setPreResult(preResult); return preResult == null ? true : preResult; } } private boolean invokePreHandler(Flow flow, FlowContext context) { if (flow.getPreHandler() != null) { flow.triggerEvent(FlowEventTypes.FLOW_PRE_START, context); boolean preResult; if (flow.getFilterManager().noInnerFlowPreHandlerFilter()) { preResult = flow.getPreHandler().preHandle(context); } else { Boolean result = flow.getFilterManager().doInnerFlowPreHandlerFilter(context, innerFlowPreHandlerInvoker); preResult = result == null ? true : result; } ((FlowContextImpl) context).setPreResult(preResult); flow.triggerEvent(FlowEventTypes.FLOW_PRE_END, context); } return context.getPreResult() == null ? true : context.getPreResult(); } private void executePostHandler(Flow flow, FlowContext context) { if (flow.getFilterManager().noOuterFlowPostHandlerFilter()) { invokePostHandler(flow, context); } else { flow.getFilterManager().doOuterFlowPostHandlerFilter(context, outerFlowPostHandlerInvoker); } } private void invokePostHandler(Flow flow, FlowContext context) { if (flow.getPostHandler() != null) { flow.triggerEvent(FlowEventTypes.FLOW_POST_START, context); if (flow.getFilterManager().noInnerFlowPostHandlerFilter()) { flow.getPostHandler().postHandle(context); } else { flow.getFilterManager().doInnerFlowPostHandlerFilter(context, innerFlowPostHandlerInvoker); } flow.triggerEvent(FlowEventTypes.FLOW_POST_END, context); } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/CoreFlowEngine.java ================================================ package com.jd.easyflow.flow.engine.impl; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.el.ElFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.FlowRunner; import com.jd.easyflow.flow.engine.event.FlowEventListener; import com.jd.easyflow.flow.engine.event.FlowEventTrigger; import com.jd.easyflow.flow.engine.filter.FlowEngineFilterManager; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.parser.FlowParser; import com.jd.easyflow.flow.model.parser.FlowParserImpl; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.FlowEventTypes; import com.jd.easyflow.flow.util.FlowIOUtil; import com.jd.easyflow.flow.util.JsonUtil; import com.jd.easyflow.flow.util.Pair; /** * * @author liyuliang5 */ public abstract class CoreFlowEngine implements FlowEngine { public static final Logger logger = LoggerFactory.getLogger(CoreFlowEngine.class); protected Map flowMap = new ConcurrentHashMap<>(); protected Map flowDefinitionMap = new ConcurrentHashMap(); protected FlowEventTrigger eventTrigger = new FlowEventTrigger(); protected FlowEngineFilterManager filterManager = new FlowEngineFilterManager(); protected List listeners; protected List, FlowResult>> filters; protected String flowPath; protected volatile boolean inited; protected FlowRunner defaultFlowRunner = new SingleThreadFlowRunner(); /** * Default is json definition parser. */ protected FlowParser flowParser = new FlowParserImpl(); protected Map properties = new ConcurrentHashMap<>(); protected ElEvaluator elEvaluator; private Function, FlowResult> outerFlowEngineInvoker = p -> invokeFlowEngine(p.getLeft()); private Function, FlowResult> innerFlowEngineInvoker = p -> executeFlow(p.getLeft()); private Function outerFlowInvoker = p -> invokeFlow(p); private Function innerFlowInvoker = p -> { init(p); run(p); return p.getResult(); }; public void init() { if (inited) { return; } if (elEvaluator == null) { elEvaluator = ElFactory.get(); } if (flowParser instanceof FlowParserImpl) { ((FlowParserImpl) flowParser).setElEvaluator(elEvaluator); } loadFlow(); if (listeners != null) { listeners.forEach(listener -> { eventTrigger.addListener(listener); }); } eventTrigger.init(null, null); if (filters != null) { filterManager.setFilters(filters); } filterManager.init(null, null); if (defaultFlowRunner != null) { defaultFlowRunner.init(null, null); } inited = true; } protected abstract void loadFlow(); protected void loadFlowInputStream(InputStream inputStream) throws IOException { String flowDefinition = FlowIOUtil.toString(inputStream); List flowList = flowParser.parse(flowDefinition); flowDefinitionMap.put(flowList.get(0).getId(), flowDefinition); flowList.forEach(flow -> { if (flowMap.containsKey(flow.getId())) { throw new FlowException("Flow " + flow.getId() + " exists"); } flowMap.put(flow.getId(), flow); }); } /** * Start flow engine, exeucte flow. */ @Override public FlowResult execute(FlowParam param) { if (! inited) { throw new FlowException("Flow engine is not inited. flowId:" + param.getFlowId()); } boolean logOn = (param.getContext() != null && param.getContext().getLogFlag() != null) ? param.getContext().getLogFlag() : (param.getLogFlag() == null || param.getLogFlag()); if (logOn && logger.isInfoEnabled()) { logger.info("START EXECUTE FLOW, flowId:" + param.getFlowId() + " nodeIds:" + Arrays.toString(param.getNodeIds())); } if (logOn && logger.isDebugEnabled()) { try { logger.debug("Flow param:" + JsonUtil.toJsonString(param)); } catch (Throwable t) { logger.debug("Flow param to json string exception:" + t.getMessage()); } } if (filterManager.noOuterFilter()) { return invokeFlowEngine(param); } else { return filterManager.doOuterFilter(Pair.of(param, this), outerFlowEngineInvoker); } } protected FlowResult invokeFlowEngine(FlowParam param) { FlowResult result = null; // No flow engine listener scenario if (eventTrigger.getListenerList() == null || eventTrigger.getListenerList().size() == 0) { if (filterManager.noInnerFilter()) { result = executeFlow(param); } else { result = filterManager.doInnerFilter(Pair.of(param, this), innerFlowEngineInvoker); } return result; } // Has flow engine listener scenario Map data = new HashMap<>(); data.put(FlowConstants.FLOW_ENGINE_EVENT_DATA_KEY_PARAM, param); data.put(FlowConstants.FLOW_ENGINE_EVENT_DATA_KEY_FLOW_ENGINE, this); try { eventTrigger.triggerEvent(FlowEventTypes.FLOW_ENGINE_START, data, null, false); if (filterManager.noInnerFilter()) { result = executeFlow(param); } else { result = filterManager.doInnerFilter(Pair.of(param, this), innerFlowEngineInvoker); } data.put(FlowConstants.FLOW_ENGINE_EVENT_DATA_KEY_RESULT, result); eventTrigger.triggerEvent(FlowEventTypes.FLOW_ENGINE_END, data, null, false); return result; } catch (Throwable t) { data.put(FlowConstants.FLOW_ENGINE_EVENT_DATA_KEY_EXCEPTION, t); throw t; } finally { eventTrigger.triggerEvent(FlowEventTypes.FLOW_ENGINE_COMPLETE, data, null, true); } } protected FlowResult executeFlow(FlowParam param) { // init flow context FlowContext context = initContext(param); // find flow definition Flow flow = findFlow(context); if (flow == null) { throw new FlowException("Flow is null, context flow id:" + context.getFlowId() + ", param flow id:" + param.getFlowId()); } // set log flag if (context.getLogFlag() == null) { if (param.getLogFlag() != null) { context.setLogFlag(param.getLogFlag()); } else { context.setLogFlag(flow.getLogFlag()); } } if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("EXECUTE FLOW, flowId:" + flow.getId()); } if (flow.getFilterManager().noOuterFilter()) { return invokeFlow(context); } else { return flow.getFilterManager().doOuterFilter(context, outerFlowInvoker); } } protected FlowResult invokeFlow(FlowContext context) { Flow flow = context.getFlow(); Throwable throwable = null; try { flow.triggerEvent(FlowEventTypes.FLOW_START, context); if (flow.getFilterManager().noInnerFilter()) { init(context); run(context); } else { FlowResult flowResult = flow.getFilterManager().doInnerFilter(context, innerFlowInvoker); context.setResult(flowResult); } flow.triggerEvent(FlowEventTypes.FLOW_END, context); return context.getResult(); } catch (Throwable t) { // NOSONAR throwable = t; if (context.isLogOn() && logger.isErrorEnabled()) { logger.error("Flow execute exception, Flow:" + flow.getId() + "," + t.getMessage()); } throw t; } finally { flow.triggerEvent(FlowEventTypes.FLOW_COMPLETE, throwable, context, true); } } protected FlowContext initContext(FlowParam param) { FlowContext context = param.getContext() != null ? param.getContext() : new FlowContextImpl(); if (context.getParam() == null) { context.setParam(param); } if (context.getResult() == null) { FlowResult result = new FlowResult(); context.setResult(result); result.setContext(context); } if (context.getFlowId() == null) { context.setFlowId(param.getFlowId()); } ((FlowContextImpl) context).setFlowEngine(this); if (context.getElEvaluator() == null) { ((FlowContextImpl) context).setElEvaluator(getElEvaluator()); } return context; } protected void run(FlowContext context) { FlowRunner flowRunner = context.getFlow().getRunner(); if (flowRunner == null) { flowRunner = defaultFlowRunner; } flowRunner.run(context); } /** * * Find flow definition. * * @return */ protected Flow findFlow(FlowContext context) { Flow flow = context.getFlow(); if (flow == null) { flow = getFlow(context.getFlowId()); if (flow == null) { throw new FlowException("Flow " + context.getFlowId() + " not exists"); } context.setFlow(flow); } // Exists scenario changing flow id context.setFlowId(flow.getId()); return flow; } @Override public Flow getFlow(String flowId) { return flowMap.get(flowId); } /** * * Init flow context. * * @param param */ protected void init(FlowContext context) { context.getFlow().triggerEvent(FlowEventTypes.INIT_START, context); if (context.getStartNodes() == null) { String[] nodeIds = context.getParam().getNodeIds(); // If nodeIds is null, using startNodeIds: if is empty array, run empty flow // instance. if (nodeIds == null) { nodeIds = context.getFlow().getStartNodeIds(); } if (nodeIds == null) { throw new FlowException("no start node"); } NodeContext[] nodes = new NodeContext[nodeIds.length]; for (int i = 0; i < nodeIds.length; i++) { nodes[i] = new NodeContext(nodeIds[i]); } ((FlowContextImpl) context).addNodes(nodes); context.setStartNodes(Arrays.asList(nodes)); } else { ((FlowContextImpl) context).addNodes(context.getStartNodes().toArray(new NodeContext[context.getStartNodes().size()])); } context.getFlow().triggerEvent(FlowEventTypes.INIT_END, context); } public void addFlow(Flow flow) { flowMap.put(flow.getId(), flow); } public void destroy() { if (defaultFlowRunner != null) { defaultFlowRunner.destroy(); } if (flowMap != null && flowMap.size() > 0) { for (Entry entry : flowMap.entrySet()) { entry.getValue().destroy(); } } this.eventTrigger.destroy(); if (this.filters != null) { filters.forEach( filter -> { filter.destroy(); }); } } public String getFlowPath() { return flowPath; } public void setFlowPath(String flowPath) { this.flowPath = flowPath; } public Map getFlowMap() { return flowMap; } public void setFlowMap(Map flowMap) { this.flowMap = flowMap; } public Map getFlowDefinitionMap() { return flowDefinitionMap; } public void setFlowDefinitionMap(Map flowDefinitionMap) { this.flowDefinitionMap = flowDefinitionMap; } public List, FlowResult>> getFilters() { return filters; } public void setFilters(List, FlowResult>> filters) { this.filters = filters; } public FlowEventTrigger getEventTrigger() { return eventTrigger; } public void setEventTrigger(FlowEventTrigger eventTrigger) { this.eventTrigger = eventTrigger; } public List getListeners() { return listeners; } public void setListeners(List listeners) { this.listeners = listeners; } public FlowRunner getDefaultFlowRunner() { return defaultFlowRunner; } public void setDefaultFlowRunner(FlowRunner defaultFlowRunner) { this.defaultFlowRunner = defaultFlowRunner; } @Override public FlowParser getFlowParser() { return flowParser; } public void setFlowParser(FlowParser flowParser) { this.flowParser = flowParser; } public boolean isInited() { return inited; } public void setInited(boolean inited) { this.inited = inited; } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } @Override public T getProperty(String key) { return (T) properties.get(key); } public void setProperty(String key, Object value) { properties.put(key, value); } public ElEvaluator getElEvaluator() { return elEvaluator; } public void setElEvaluator(ElEvaluator elEvaluator) { this.elEvaluator = elEvaluator; } public FlowEngineFilterManager getFilterManager() { return filterManager; } public void setFilterManager(FlowEngineFilterManager filterManager) { this.filterManager = filterManager; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/ExpFlowRunner.java ================================================ package com.jd.easyflow.flow.engine.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowRunner; /** * * @author liyuliang5 * */ public class ExpFlowRunner implements FlowRunner{ private String exp; public ExpFlowRunner() { } public ExpFlowRunner(String exp) { this.exp = exp; } @Override public void run(FlowContext context) { context.getElEvaluator().eval(exp, null, context, null); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/FlowContextImpl.java ================================================ package com.jd.easyflow.flow.engine.impl; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.fasterxml.jackson.annotation.JsonIgnore; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowConstants; /** * Flow Context. * * @author liyuliang5 * */ public class FlowContextImpl implements FlowContext { /** * Flow id. */ private String flowId; /** * Flow definition. */ private Flow flow; /** * Flow param. */ private FlowParam param; /** * Flow result. */ private FlowResult result; /** * Start nodes. */ private List startNodes; /** * End nodes. */ private List endNodes; /** * Node context list waiting execute. */ private Deque nodeStack = new ArrayDeque(); /** * Common data map. */ private Map data = new ConcurrentHashMap(); /** * business context */ private Object context; /** * Flow engine. */ @JsonIgnore private FlowEngine flowEngine; /** * interrupted */ private volatile boolean interrupted = false; private Boolean preResult; private Boolean logFlag; private ElEvaluator elEvaluator; private Boolean recordHistory; private FlowContext parentContext; private NodeContext parentNodeContext; @Override public void put(String key, Object value) { if (value == null) { data.remove(key); } else { data.put(key, value); } } @Override public T get(String key) { return (T) data.get(key); } @Override public void remove(String key) { data.remove(key); } public synchronized void addNodes(NodeContext[] nodes) { for (int i = 0; i < nodes.length; i++) { nodeStack.push(nodes[nodes.length - 1 - i]); } } /** * Get next node. */ public synchronized NodeContext getNextNode() { if (nodeStack.isEmpty()) { return null; } return nodeStack.pop(); } @Override public FlowParam getParam() { return param; } @Override public void setParam(FlowParam param) { this.param = param; } @Override public FlowResult getResult() { return result; } @Override public void setResult(FlowResult result) { this.result = result; } @Override public String getFlowId() { return flowId; } @Override public void setFlowId(String flowId) { this.flowId = flowId; } @Override public Flow getFlow() { return flow; } @Override public void setFlow(Flow flow) { this.flow = flow; } @Override public Map getData() { return data; } public void setDataFrom(FlowContext fromContext) { this.data = fromContext.getData(); } @Override public FlowEngine getFlowEngine() { return flowEngine; } public void setFlowEngine(FlowEngine flowEngine) { this.flowEngine = flowEngine; } @Override public List getStartNodes() { return startNodes; } @Override public void setStartNodes(List startNodes) { this.startNodes = startNodes; } @Override public List getEndNodes() { return endNodes; } public void setEndNodes(List endNodes) { this.endNodes = endNodes; } public synchronized void addEndNode(NodeContext node) { if (endNodes == null) { endNodes = new ArrayList(); } endNodes.add(node); } @Override public boolean isInterrupted() { return interrupted; } @Override public void setInterrupted() { this.interrupted = true; } @Override public Boolean getPreResult() { return preResult; } public void setPreResult(Boolean preResult) { this.preResult = preResult; } @Override public Boolean getLogFlag() { return logFlag; } @Override public void setLogFlag(Boolean logFlag) { this.logFlag = logFlag; } @Override public boolean isLogOn() { return logFlag == null || this.logFlag.booleanValue(); } @Override public T getContext() { return (T) context; } @Override public void setContext(Object context) { this.context = context; } @Override public ElEvaluator getElEvaluator() { return elEvaluator; } public void setElEvaluator(ElEvaluator elEvaluator) { this.elEvaluator = elEvaluator; } @Override public boolean isRecordHistory() { if (recordHistory == null) { recordHistory = ! Boolean.FALSE.equals(flow.getProperty(FlowConstants.FLOW_PROPERTY_RECORD_HISTORY)); } return recordHistory; } public Boolean getRecordHistory() { return recordHistory; } public void setRecordHistory(Boolean recordHistory) { this.recordHistory = recordHistory; } @Override public FlowContext getParentContext() { return parentContext; } public void setParentContext(FlowContext parentContext) { this.parentContext = parentContext; } @Override public NodeContext getParentNodeContext() { return parentNodeContext; } public void setParentNodeContext(NodeContext parentNodeContext) { this.parentNodeContext = parentNodeContext; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/FlowEngineImpl.java ================================================ package com.jd.easyflow.flow.engine.impl; import java.io.IOException; import java.io.InputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.SmartLifecycle; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.el.ElFactory; import com.jd.easyflow.flow.el.SpelEvaluator; /** * FlowEngineImpl. Adding spring integration based on CoreFlowEngine. * @author liyuliang5 * */ public class FlowEngineImpl extends CoreFlowEngine implements SmartLifecycle, ApplicationContextAware { public static final Logger logger = LoggerFactory.getLogger(FlowEngineImpl.class); private ApplicationContext applicationContext; private int phase = Integer.MIN_VALUE; private boolean autoStartup = true; private volatile boolean isRunning = false; @Override public void init() { if (inited) { return; } if (applicationContext != null) { ElEvaluator elEvaluator = ElFactory.get(); if (elEvaluator instanceof SpelEvaluator) { SpelEvaluator spelEvaluator = (SpelEvaluator) elEvaluator; if (spelEvaluator.getApplicationContext() == null) { spelEvaluator.setApplicationContext(applicationContext); } } } super.init(); inited = true; } protected void loadFlow() { if (flowPath == null) { return; } PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources; String[] flowPaths = flowPath.split(","); for (String path : flowPaths) { try { resources = resolver.getResources(path.trim()); for (Resource resource : resources) { if (logger.isInfoEnabled()) { logger.info("Start parsing definition files:" + resource.getURI()); } try (InputStream is = resource.getInputStream()) { loadFlowInputStream(is); } } } catch (IOException e) { throw new RuntimeException("Flow definition file parse exception", e); } } } public ApplicationContext getApplicationContext() { return applicationContext; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Override public void start() { init(); isRunning = true; } @Override public void stop() { isRunning = false; destroy(); } @Override public boolean isAutoStartup() { return autoStartup; } @Override public void stop(Runnable callback) { stop(); callback.run(); } @Override public boolean isRunning() { return isRunning; } @Override public int getPhase() { return phase; } public void setPhase(int phase) { this.phase = phase; } public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/MultipleThreadFlowRunner.java ================================================ package com.jd.easyflow.flow.engine.impl; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.ExceptionUtil; import com.jd.easyflow.flow.util.FlowConstants; /** * Multiple thread runner. * * @author liyuliang5 * @date 2021/07/26 */ public class MultipleThreadFlowRunner extends BaseFlowRunner { private static final Logger logger = LoggerFactory.getLogger(MultipleThreadFlowRunner.class); protected static long startId = System.currentTimeMillis(); protected Executor executor; protected long timeout = 0; protected boolean throwExceptionOnTimeout = false; public MultipleThreadFlowRunner() { } public MultipleThreadFlowRunner(Executor executor, long timeout) { this.executor = executor; this.timeout = timeout; } public MultipleThreadFlowRunner(Executor executor, long timeout, boolean throwExceptionOnTimeout) { this.executor = executor; this.timeout = timeout; this.throwExceptionOnTimeout = throwExceptionOnTimeout; } @Override public void runNodes(FlowContextImpl context) { String runId = startId++ + ""; if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Start running flow node, runId:" + runId); } CountDownLatch lock = new CountDownLatch(1); AtomicInteger counter = new AtomicInteger(); scheduleNodes(context, counter, lock, runId); try { if (timeout == 0) { lock.await(); } else { boolean result = lock.await(timeout, TimeUnit.MILLISECONDS); context.put(FlowConstants.FLOW_CTX_MULTI_AWAIT_RESULT, result); if (result == false) { context.setInterrupted(); if (throwExceptionOnTimeout) { throw new FlowException("flow execution timeout, runId:" + runId + ", flowId:" + context.getFlowId()); } } } } catch (InterruptedException e) { throw new FlowException(e); } List exceptions = context.get(FlowConstants.FLOW_CTX_MULTI_EXCEPTIONS); // Default behavior is throwing first exception. if (exceptions != null && exceptions.size() > 0) { if (context.isLogOn()) { logger.error("Flow execute exception"); } Throwable t = exceptions.get(0).get(FlowConstants.NODE_CTX_MULTI_EXCEPTION); throw ExceptionUtil.throwException(t); } } protected void scheduleNodes(FlowContextImpl context, AtomicInteger counter, CountDownLatch lock, String runId) { addTaskIfExists(context, counter, lock, runId); } /** * Add task. * * @param context * @param executor * @param counter * @param lock */ private void addTaskIfExists(FlowContextImpl context, AtomicInteger counter, CountDownLatch lock, String runId) { NodeContext currentNode; while ((currentNode = context.getNextNode()) != null) { final NodeContext finalCurrentNode = currentNode; counter.addAndGet(1); executor.execute(() -> { try { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Start execute flow node:" + finalCurrentNode.getNodeId() + ", runId:" + runId); } NodeContext[] nextNodes = runOneNode(finalCurrentNode, context); if (nextNodes != null) { context.addNodes(nextNodes); } if (context.isInterrupted()) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Flow state is interrupted"); } lock.countDown(); return; } addTaskIfExists(context, counter, lock, runId); int count = counter.addAndGet(-1); if (count == 0) { lock.countDown(); } } catch (Throwable t) { // NOSONAR addException(context, finalCurrentNode, t); if (context.isInterrupted()) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Flow state is interrupted"); } lock.countDown(); return; } int count = counter.addAndGet(-1); if (count == 0) { lock.countDown(); } } }); } } /** * Add exception. * * @param context * @param nodeContext * @param t */ protected void addException(FlowContext context, NodeContext nodeContext, Throwable t) { synchronized (context) { List exceptionNodes = context.get(FlowConstants.FLOW_CTX_MULTI_EXCEPTIONS); if (exceptionNodes == null) { exceptionNodes = new ArrayList(); context.put(FlowConstants.FLOW_CTX_MULTI_EXCEPTIONS, exceptionNodes); } nodeContext.put(FlowConstants.NODE_CTX_MULTI_EXCEPTION, t); exceptionNodes.add(nodeContext); } } protected void printStackTrace() { StackTraceElement[] stack = Thread.currentThread().getStackTrace(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < stack.length; i++) { builder.append(stack[i].toString() + "\n"); } logger.error("Flow execute exception,\n " + builder); } public Executor getExecutor() { return executor; } public void setExecutor(Executor executor) { this.executor = executor; } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } public boolean isThrowExceptionOnTimeout() { return throwExceptionOnTimeout; } public void setThrowExceptionOnTimeout(boolean throwExceptionOnTimeout) { this.throwExceptionOnTimeout = throwExceptionOnTimeout; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/ReusableThreadFlowRunner.java ================================================ package com.jd.easyflow.flow.engine.impl; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.model.NodeContext; /** * Reuse thread first. * * @author liyuliang5 * */ public class ReusableThreadFlowRunner extends MultipleThreadFlowRunner { private static final Logger logger = LoggerFactory.getLogger(ReusableThreadFlowRunner.class); public ReusableThreadFlowRunner() { } public ReusableThreadFlowRunner(Executor executor, long timeout) { this.executor = executor; this.timeout = timeout; } public ReusableThreadFlowRunner(Executor executor, long timeout, boolean throwExceptionOnTimeout) { this.executor = executor; this.timeout = timeout; this.throwExceptionOnTimeout = throwExceptionOnTimeout; } @Override protected void scheduleNodes(FlowContextImpl context, AtomicInteger counter, CountDownLatch lock, String runId) { List startNodes = context.getStartNodes(); if (startNodes.size() == 0) { lock.countDown(); return; } counter.addAndGet(startNodes.size()); if (timeout == 0) { runNodes(startNodes.toArray(new NodeContext[startNodes.size()]), context, counter, lock, runId); } else { executor.execute(() -> { runNodes(startNodes.toArray(new NodeContext[startNodes.size()]), context, counter, lock, runId); }); } } private void runNodes(NodeContext[] nodes, FlowContextImpl context, AtomicInteger counter, CountDownLatch lock, String runId) { while (true) { if (nodes == null || nodes.length == 0) { return; } if (nodes.length > 1) { for (int i = 1; i < nodes.length; i++) { final NodeContext finalNode = nodes[i]; executor.execute(() -> { runNodes(new NodeContext[] { finalNode }, context, counter, lock, runId); }); } } NodeContext[] nextNodes = doRunOneNode(nodes[0], context, counter, lock, runId); nodes = nextNodes; } } private NodeContext[] doRunOneNode(NodeContext node, FlowContextImpl context, AtomicInteger counter, CountDownLatch lock, String runId) { NodeContext[] nextNodes = null; try { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Start execute flow node:" + node.getNodeId() + ", runId:" + runId); } nextNodes = super.runOneNode(node, context); } catch (Throwable t) { // NOSONAR addException(context, node, t); } if (context.isInterrupted()) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Flow state is interrupted"); } lock.countDown(); return null; } if (nextNodes != null && nextNodes.length > 0) { counter.addAndGet(nextNodes.length); } int count = counter.addAndGet(-1); if (count == 0) { lock.countDown(); } return nextNodes; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/engine/impl/SingleThreadFlowRunner.java ================================================ package com.jd.easyflow.flow.engine.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.model.NodeContext; /** * Single thread executor. * * @author liyuliang5 * @date 2021/07/25 */ public class SingleThreadFlowRunner extends BaseFlowRunner { public static final Logger logger = LoggerFactory.getLogger(SingleThreadFlowRunner.class); /** * * DFS. * * @param context */ @Override public void runNodes(FlowContextImpl context) { NodeContext currentNode; // Loop execute. while ((currentNode = context.getNextNode()) != null) { if (context.isInterrupted()) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Flow interrupted!"); } break; } NodeContext[] nextNodes = runOneNode(currentNode, context); if (nextNodes != null) { context.addNodes(nextNodes); } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/exception/FlowException.java ================================================ package com.jd.easyflow.flow.exception; /** * * @author liyuliang5 * */ public class FlowException extends RuntimeException { public FlowException() { } public FlowException(String message) { super(message); } public FlowException(Throwable cause) { super(cause); } public FlowException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/filter/BaseFilter.java ================================================ package com.jd.easyflow.flow.filter; import java.util.Map; /** * */ public abstract class BaseFilter implements Filter { protected int order; @Override public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } @Override public void postConstruct(Map definition, Map context) { if (definition == null) { return; } Integer order = (Integer) definition.get("order"); if (order != null) { this.order = order; } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/filter/ExpFilter.java ================================================ package com.jd.easyflow.flow.filter; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.el.ElFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.util.JsonUtil; import com.jd.easyflow.flow.util.Pair; import com.jd.easyflow.flow.util.Triple; /** * * @author liyuliang5 * * @param * @param */ public class ExpFilter extends BaseFilter { private static final Logger logger = LoggerFactory.getLogger(ExpFilter.class); private ElEvaluator elEvaluator; private String exp; public ExpFilter() { } public ExpFilter(String exp) { this.exp = exp; } @Override public R doFilter(T request, FilterChain chain) { Map data = new HashMap<>(); data.put("request", request); data.put("chain", chain); if (logger.isDebugEnabled()) { logger.debug("EVAL SPEL:" + exp); } ElEvaluator evaluator = this.elEvaluator; if (evaluator == null) { if (request instanceof FlowContext) { evaluator = ((FlowContext) request).getElEvaluator(); } else if (request instanceof Pair) { if (((Pair) request).getRight() instanceof FlowContext) { evaluator = ((FlowContext) ((Pair) request).getRight()).getElEvaluator(); } else if (((Pair) request).getRight() instanceof FlowEngine) { evaluator = ((FlowEngine) ((Pair) request).getRight()).getElEvaluator(); } } else if (request instanceof Triple && ((Triple) request).getRight() instanceof FlowContext) { evaluator = ((FlowContext) ((Triple) request).getRight()).getElEvaluator(); } } if (evaluator == null) { evaluator = ElFactory.get(); } R result = evaluator.evalWithDefaultContext(exp, data, true); if (logger.isDebugEnabled()) { try { logger.debug("SPEL RESULT:" + JsonUtil.toJsonString(result)); } catch (Throwable t) { logger.debug("SPEL RESULT to json string exception:" + t.getMessage()); } } return result; } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } public ElEvaluator getElEvaluator() { return elEvaluator; } public void setElEvaluator(ElEvaluator elEvaluator) { this.elEvaluator = elEvaluator; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/filter/Filter.java ================================================ package com.jd.easyflow.flow.filter; import com.jd.easyflow.flow.model.FlowLifeCycle; /** * * @author liyuliang5 * * @param * @param */ public interface Filter extends FlowLifeCycle { /** * Do filter. * @param request * @param chain * @return */ R doFilter(T request, FilterChain chain); /** * * The highest order value has the highest priority. * If order value same, the front configured has higher priority. * DO NOT override this method unless you have requirement and know its behavior. * @return */ default int getOrder() {return 0;}; } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/filter/FilterChain.java ================================================ package com.jd.easyflow.flow.filter; /** * @author liyuliang5 * @param * @param */ public interface FilterChain { public R doFilter(T param); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/filter/FilterChainImpl.java ================================================ package com.jd.easyflow.flow.filter; import java.util.List; import java.util.function.Function; /** * * @author liyuliang5 * * @param * @param */ public class FilterChainImpl implements FilterChain { private List> filters; private Function invoker; private int pos = -1; public FilterChainImpl(List> filters, Function invoker) { this.filters = filters; this.invoker = invoker; } public R doFilter(T param) { pos++; if (pos < filters.size()) { return filters.get(pos).doFilter(param, this); } return invoker.apply(param); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/Flow.java ================================================ package com.jd.easyflow.flow.model; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import com.fasterxml.jackson.annotation.JsonIgnore; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowRunner; import com.jd.easyflow.flow.engine.event.FlowEventTrigger; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.filter.FlowFilterManager; import com.jd.easyflow.flow.model.parser.FlowParser; /** * * @author liyuliang5 * */ public class Flow implements FlowLifeCycle { public static final String DOLLAR = "$"; private String id; private String name; private String[] startNodeIds; private List nodeList; private Map properties = new ConcurrentHashMap<>(); private Map nodeMap; /** * relation of node id and index. */ private Map nodeIndexMap; private FlowPreHandler preHandler; private FlowPostHandler postHandler; private FlowEventTrigger eventTrigger = new FlowEventTrigger(); private FlowFilterManager filterManager = new FlowFilterManager(); private FlowRunner runner; @JsonIgnore private FlowParser flowParser; private Boolean logFlag; @Override public void init(InitContext initContext, Object parent) { if (preHandler != null) { preHandler.init(initContext, this); } if (nodeList != null) { for (FlowNode flowNode : nodeList) { flowNode.init(initContext, this); } } if (postHandler != null) { postHandler.init(initContext, this); } eventTrigger.init(initContext, this); filterManager.init(initContext, this); if (runner != null) { runner.init(initContext, this); } } @Override public void destroy() { if (preHandler != null) { preHandler.destroy(); } if (nodeList != null) { for (FlowNode flowNode : nodeList) { flowNode.destroy(); } } if (postHandler != null) { postHandler.destroy(); } eventTrigger.destroy(); filterManager.destroy(); if (runner != null) { runner.destroy(); } } public String getId() { return id; } public void setId(String id) { this.id = id; } public List getNodeList() { return nodeList; } public void initNodeList(List nodeList) { for (FlowNode node : nodeList) { addNode(node); } } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties.clear(); putProperties(properties); } public void putProperties(Map properties) { if (properties == null) { return; } for (Entry entry : properties.entrySet()) { if (entry.getValue() == null) { this.properties.remove(entry.getKey()); } else { this.properties.put(entry.getKey(), entry.getValue()); } } } public Map getNodeMap() { return nodeMap; } public void setProperty(String key, Object value) { if (value == null) { properties.remove(key); } else { properties.put(key, value); } } public T getProperty(String key) { return (T) properties.get(key); } public void addNode(FlowNode node) { if (node.getId().startsWith(DOLLAR)) { throw new IllegalArgumentException("Node ID CANNOT start with $"); } if (this.nodeList == null) { this.nodeList = new ArrayList<>(); } if (this.nodeMap == null) { this.nodeMap = new HashMap<>(); } if (this.nodeMap.containsKey(node.getId())) { throw new FlowException("Node:" + node.getId() + " duplicate"); } if (this.nodeIndexMap == null) { this.nodeIndexMap = new HashMap<>(); } this.nodeList.add(node); this.nodeMap.put(node.getId(), node); this.nodeIndexMap.put(node.getId(), this.nodeList.size() - 1); } public void setNodeList(List nodeList) { if (nodeList == null) { this.nodeList = null; this.nodeMap = null; this.nodeIndexMap = null; } else { this.nodeList = nodeList; this.nodeMap = new HashMap<>(); this.nodeIndexMap = new HashMap<>(); for (FlowNode node : nodeList) { this.nodeMap.put(node.getId(), node); this.nodeIndexMap.put(node.getId(), this.nodeList.size() - 1); } } } public FlowNode getNode(String nodeId) { return this.nodeMap.get(nodeId); } public int getNodeIndex(String nodeId) { return this.nodeIndexMap.get(nodeId); } public String getName() { return name; } public void setName(String name) { this.name = name; } public FlowEventTrigger getEventTrigger() { return eventTrigger; } public void setEventTrigger(FlowEventTrigger eventTrigger) { this.eventTrigger = eventTrigger; } public void triggerEvent(String eventType, FlowContext context) { this.eventTrigger.triggerEvent(eventType, context); } public void triggerEvent(String eventType, Object eventData, FlowContext context, boolean catchThrowable) { this.eventTrigger.triggerEvent(eventType, eventData, context, catchThrowable); } public String[] getStartNodeIds() { return startNodeIds; } public void setStartNodeIds(String[] startNodeIds) { this.startNodeIds = startNodeIds; } public FlowRunner getRunner() { return runner; } public void setRunner(FlowRunner runner) { this.runner = runner; } public String stringify() { if (flowParser == null) { return null; } return flowParser.stringify(this); } public FlowParser getFlowParser() { return flowParser; } public void setFlowParser(FlowParser flowParser) { this.flowParser = flowParser; } public FlowPreHandler getPreHandler() { return preHandler; } public void setPreHandler(FlowPreHandler preHandler) { this.preHandler = preHandler; } public FlowPostHandler getPostHandler() { return postHandler; } public void setPostHandler(FlowPostHandler postHandler) { this.postHandler = postHandler; } public Boolean getLogFlag() { return logFlag; } public void setLogFlag(Boolean logFlag) { this.logFlag = logFlag; } public FlowFilterManager getFilterManager() { return filterManager; } public void setFilterManager(FlowFilterManager filterManager) { this.filterManager = filterManager; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/FlowLifeCycle.java ================================================ package com.jd.easyflow.flow.model; import java.util.Map; /** * * @author liyuliang5 */ public interface FlowLifeCycle { /** * Invoked after element constructed on flow parsing, the element SHOULD BE new instance, SHOULD NOT BE singleton or reusable. * @param definition * @param context */ default void postConstruct(Map definition, Map context) {} /** * Invoked after Flow is constructed, the element SHOULD BE new instance, SHOULD NOT BE singleton or reusable. * @param initContext * @param parent */ default void init(InitContext initContext, Object parent) {} /** * Invoked on FlowEngine destroy, the element SHOULD BE new instance, SHOULD NOT BE singleton or reusable. */ default void destroy() {} } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/FlowNode.java ================================================ package com.jd.easyflow.flow.model; import java.util.Map; import com.jd.easyflow.flow.engine.FlowContext; /** * * @author liyuliang5 * */ public interface FlowNode extends FlowLifeCycle { /** * Execute node. * @param nodeContext * @param context * @return */ NodeContext execute(NodeContext nodeContext, FlowContext context); /** * Get node ID * @return */ default String getId() { return null; } /** * Get node name. * @return */ default String getName() { return null; } /** * Get all node properties. * @return */ default Map getProperties() { return null; } /** * Get property. * @param * @param key * @return */ default T getProperty(String key) { Map properties = getProperties(); if (properties == null) { return null; } return (T) properties.get(key); } /** * Set property. * @param key * @param value */ default void setProperty(String key, Object value) { if (value == null) { getProperties().remove(key); } else { getProperties().put(key, value); } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/FlowPostHandler.java ================================================ package com.jd.easyflow.flow.model; import com.jd.easyflow.flow.engine.FlowContext; /** * * @author liyuliang5 * */ public interface FlowPostHandler extends FlowLifeCycle { void postHandle(FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/FlowPreHandler.java ================================================ package com.jd.easyflow.flow.model; import com.jd.easyflow.flow.engine.FlowContext; /** * * @author liyuliang5 * */ public interface FlowPreHandler extends FlowLifeCycle { boolean preHandle(FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/InitContext.java ================================================ package com.jd.easyflow.flow.model; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.model.parser.FlowParser; /** * Flow node init context. * @author liyuliang5 * */ public class InitContext { private boolean parseEl; private FlowParser flowParser; private List flowList; private Map flowDefinitionMap; private Flow flow; public boolean isParseEl() { return parseEl; } public void setParseEl(boolean parseEl) { this.parseEl = parseEl; } public FlowParser getFlowParser() { return flowParser; } public void setFlowParser(FlowParser flowParser) { this.flowParser = flowParser; } public List getFlowList() { return flowList; } public void setFlowList(List flowList) { this.flowList = flowList; } public Map getFlowDefinitionMap() { return flowDefinitionMap; } public void setFlowDefinitionMap(Map flowDefinitionMap) { this.flowDefinitionMap = flowDefinitionMap; } public Flow getFlow() { return flow; } public void setFlow(Flow flow) { this.flow = flow; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/NodeAction.java ================================================ package com.jd.easyflow.flow.model; import com.jd.easyflow.flow.engine.FlowContext; /** * * @author liyuliang5 * */ public interface NodeAction extends FlowLifeCycle { /** * Execute node action. * @param * @param nodeContext * @param context * @return */ T execute(NodeContext nodeContext, FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/NodeContext.java ================================================ package com.jd.easyflow.flow.model; import java.io.Serializable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * * Generally setter is not needed for user. Using @see NodeContextAccessor if necessary. * @author liyuliang5 * */ public class NodeContext implements Serializable { public NodeContext(String nodeId) { this.nodeId = nodeId; } private String nodeId; private NodeContext previousNode; private NodeContext[] nextNodes; private Boolean preResult; private Object actionResult; private Throwable throwable; private volatile Map dataMap; /** * business context */ private Object nodeContext; public Object getActionResult() { return actionResult; } protected void setActionResult(Object actionResult) { this.actionResult = actionResult; } public Map getDataMap() { return dataMap; } protected void setDataMap(Map dataMap) { this.dataMap = dataMap; } public void put(String key, Object value) { if (dataMap == null) { synchronized (this) { if (dataMap == null) { dataMap = new ConcurrentHashMap(); } } } if (value == null) { dataMap.remove(key); } else { dataMap.put(key, value); } } public T get(String key) { if (dataMap == null) { return null; } return (T) dataMap.get(key); } public void remove(String key) { dataMap.remove(key); } protected void setNextNodeIds(String[] nextNodeIds) { if (nextNodeIds == null) { this.nextNodes = null; return; } nextNodes = new NodeContext[nextNodeIds.length]; for (int i = 0; i < nextNodeIds.length; i++) { NodeContext nextNode = new NodeContext(nextNodeIds[i]); nextNode.setPreviousNode(this); nextNodes[i] = nextNode; } } protected void setNextNodes(NodeContext[] nextNodes) { this.nextNodes = nextNodes; if (nextNodes != null) { for (int i = 0; i < nextNodes.length; i++) { nextNodes[i].setPreviousNode(this); } } } public NodeContext[] getNextNodes() { return nextNodes; } public String getNodeId() { return nodeId; } protected void setNodeId(String nodeId) { this.nodeId = nodeId; } public NodeContext getPreviousNode() { return previousNode; } protected void setPreviousNode(NodeContext previousNode) { this.previousNode = previousNode; } public Throwable getThrowable() { return throwable; } protected void setThrowable(Throwable throwable) { this.throwable = throwable; } public Boolean getPreResult() { return preResult; } protected void setPreResult(Boolean preResult) { this.preResult = preResult; } public T getNodeContext() { return (T) nodeContext; } public void setNodeContext(Object nodeContext) { this.nodeContext = nodeContext; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/NodeContextAccessor.java ================================================ package com.jd.easyflow.flow.model; import java.util.Map; /** * @author liyuliang5. * Using separate setter to avoid mistake setting. * */ public class NodeContextAccessor { public static void setNodeId(NodeContext nodeContext, String nodeId) { nodeContext.setNodeId(nodeId); } public static void setPreResult(NodeContext nodeContext, Boolean preResult) { nodeContext.setPreResult(preResult); } public static void setActionResult(NodeContext nodeContext, Object actionResult) { nodeContext.setActionResult(actionResult); } public static void setNextNodeIds(NodeContext nodeContext, String[] nextNodeIds) { nodeContext.setNextNodeIds(nextNodeIds); } public static void setNextNodes(NodeContext nodeContext, NodeContext[] nextNodes) { nodeContext.setNextNodes(nextNodes); } public static void setPreviousNode(NodeContext nodeContext, NodeContext previousNode) { nodeContext.setPreviousNode(previousNode); } public static void setThrowable(NodeContext nodeContext, Throwable t) { nodeContext.setThrowable(t); } public static void setDataMap(NodeContext nodeContext, Map dataMap) { nodeContext.setDataMap(dataMap); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/NodeExecutor.java ================================================ package com.jd.easyflow.flow.model; import com.jd.easyflow.flow.engine.FlowContext; /** * Executor of node and subcomponent level. * @author liyuliang5 * * @param */ public interface NodeExecutor { /** * Execute. * @param nodeContext * @param context * @return */ public T execute(NodeContext nodeContext, FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/NodePostHandler.java ================================================ package com.jd.easyflow.flow.model; import com.jd.easyflow.flow.engine.FlowContext; /** * Node post handler. * @author liyuliang5 * @date 2021/07/09 */ public interface NodePostHandler extends FlowLifeCycle { /** * Post handle. * @param nodeContext * @param context * @return */ public NodeContext[] postHandle(NodeContext nodeContext, FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/NodePreHandler.java ================================================ package com.jd.easyflow.flow.model; import com.jd.easyflow.flow.engine.FlowContext; /** * * @author liyuliang5 * */ public interface NodePreHandler extends FlowLifeCycle { /** * Pre handle. * @param nodeContext * @param context * @return */ boolean preHandle(NodeContext nodeContext, FlowContext context); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/ActionResultEl.java ================================================ package com.jd.easyflow.flow.model.action; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; /** * Used for condition create expression. * @author liyuliang5 */ public class ActionResultEl implements NodeExecutor { public static final String OP_EQ = "=="; public static final String OP_NEQ = "!="; private String operator; private Object value; public ActionResultEl() { } public ActionResultEl(String operator, Object value) { this.operator = operator; this.value = value; } public static ActionResultEl create(String operator, Object value) { ActionResultEl el = new ActionResultEl(); el.operator = operator; el.value = value; return el; } @Override public Boolean execute(NodeContext nodeContext, FlowContext context) { Object actionResult = nodeContext.getActionResult(); switch (operator) { case OP_EQ: return actionResult == value || (actionResult != null && actionResult.equals(value)); case OP_NEQ: return actionResult != value && (actionResult == null || ! actionResult.equals(value)); default: { throw new UnsupportedOperationException("Unsupported operator " + operator); } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/CompensateNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import java.util.ArrayList; import java.util.List; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.action.compensate.CompensateHelper; import com.jd.easyflow.flow.model.action.compensate.CompensateNodeFilter; import com.jd.easyflow.flow.model.action.compensate.CompensateNodePreHandlerFilter; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.Pair; import com.jd.easyflow.flow.util.Triple; /** * @author liyuliang5 */ public class CompensateNodeAction implements NodeAction { @Override public T execute(NodeContext nodeContext, FlowContext context) { CompensateHelper.compensate(context); return null; } @Override public void init(InitContext initContext, Object flowNode) { boolean recordHistory = ! Boolean.FALSE.equals(initContext.getFlow().getProperty(FlowConstants.FLOW_PROPERTY_RECORD_HISTORY)); if (! recordHistory) { throw new FlowException("CompensateNodeAction must record history"); } initNodeFilter(initContext, flowNode); initNodePreHandlerFilter(initContext, flowNode); } private void initNodeFilter(InitContext initContext, Object flowNode) { List, NodeContext>> filters = initContext.getFlow().getFilterManager().getNodeFilters(); boolean contains = false; if (filters == null) { filters = new ArrayList<>(); initContext.getFlow().getFilterManager().setNodeFilters(filters); } for (Filter filter : filters) { if (filter instanceof CompensateNodeFilter) { contains = true; break; } } if (!contains) { filters.add(0, new CompensateNodeFilter(Integer.MAX_VALUE)); initContext.getFlow().getFilterManager().setNodeFilters(filters); } } private void initNodePreHandlerFilter(InitContext initContext, Object flowNode) { List, Boolean>> nodePreHandlerFilters = initContext.getFlow().getFilterManager().getNodePreHandlerFilters(); boolean contains = false; if (nodePreHandlerFilters == null) { nodePreHandlerFilters = new ArrayList<>(); initContext.getFlow().getFilterManager().setNodePreHandlerFilters(nodePreHandlerFilters); } for (Filter filter : nodePreHandlerFilters) { if (filter instanceof CompensateNodePreHandlerFilter) { contains = true; break; } } if (!contains) { nodePreHandlerFilters.add(0, new CompensateNodePreHandlerFilter(Integer.MAX_VALUE)); initContext.getFlow().getFilterManager().setNodePreHandlerFilters(nodePreHandlerFilters); } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/EventNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.event.FlowEventListener; import com.jd.easyflow.flow.engine.event.impl.EventFlowListener; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.model.parser.param.ActionParseParam; import com.jd.easyflow.flow.util.FlowConstants; /** * Event node action. * * @author liyuliang5 * */ public class EventNodeAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(EventNodeAction.class); private boolean autoAddEventFlowListener = true; public EventNodeAction() { } public EventNodeAction(boolean autoAddEventFlowListener) { this.autoAddEventFlowListener = autoAddEventFlowListener; } @Override public void init(InitContext initContext, Object flowNode) { initEventActionMap(initContext, (FlowNode) flowNode); if (autoAddEventFlowListener) { List listeners = initContext.getFlow().getEventTrigger().getListenerList(); boolean exists = false; if (listeners != null) { for (FlowEventListener listener : listeners) { if (listener instanceof EventFlowListener) { exists = true; break; } } } if (! exists) { logger.info("Auto add EventFlowListener"); initContext.getFlow().getEventTrigger().addListener(new EventFlowListener()); } } } @Override public Object execute(NodeContext nodeContext, FlowContext context) { String event = nodeContext.get(FlowConstants.NODE_CONTEXT_DATA_EVENT); if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Event is:" + event); } if (event == null) { event = FlowConstants.NONE_EVENT; } Map eventActionMap = context.getFlow().getNode(nodeContext.getNodeId()) .getProperty(FlowConstants.PROP_RUNTIME_EVENT_NODE_ACTION_MAP); // For exp scenario if (eventActionMap == null) { InitContext initContext = new InitContext(); initContext.setFlowParser(context.getFlowEngine().getFlowParser()); initContext.setParseEl(true); initContext.setFlow(context.getFlow()); initEventActionMap(initContext, context.getFlow().getNode(nodeContext.getNodeId())); eventActionMap = context.getFlow().getNode(nodeContext.getNodeId()) .getProperty(FlowConstants.PROP_RUNTIME_EVENT_NODE_ACTION_MAP); } NodeAction nodeAction = eventActionMap.get(event); if (nodeAction == null) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Event node action is null"); } return null; } return nodeAction.execute(nodeContext, context); } private void initEventActionMap(InitContext initContext, FlowNode flowNode) { Map eventActionMap = flowNode.getProperty(FlowConstants.PROP_RUNTIME_EVENT_NODE_ACTION_MAP); if (eventActionMap != null) { return; } eventActionMap = new ConcurrentHashMap<>(); Map map = flowNode.getProperty(FlowConstants.NODE_PROP_EVENTS); if (map != null) { for (Entry entry : map.entrySet()) { String event = entry.getKey(); Object eventConf = (Object) entry.getValue(); Map eventActionConfMap = null; if (eventConf instanceof String) { eventActionConfMap = new HashMap<>(); eventActionConfMap.put(DefConstants.COMMON_PROP_EXP, (String) eventConf); } else { Map eventConfMap = (Map) eventConf; eventActionConfMap = (Map) eventConfMap.get("action"); } if (eventActionConfMap != null) { NodeAction nodeAction = initContext.getFlowParser() .parseNodeAction(new ActionParseParam(eventActionConfMap, initContext.getFlowList(), initContext.isParseEl(), initContext.getFlow(), flowNode)); if (nodeAction != null) { nodeAction.init(initContext, flowNode); eventActionMap.put(event, nodeAction); } } } } flowNode.setProperty(FlowConstants.PROP_RUNTIME_EVENT_NODE_ACTION_MAP, eventActionMap); } public boolean isAutoAddEventFlowListener() { return autoAddEventFlowListener; } public void setAutoAddEventFlowListener(boolean autoAddEventFlowListener) { this.autoAddEventFlowListener = autoAddEventFlowListener; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/ExecutorNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; /** * * @author liyuliang5 * */ public class ExecutorNodeAction implements NodeAction { private NodeExecutor executor; public ExecutorNodeAction() { } public ExecutorNodeAction(NodeExecutor executor) { this.executor = executor; } @Override public T execute(NodeContext nodeContext, FlowContext context) { Object result = executor.execute(nodeContext, context); return (T) result; } public NodeExecutor getExecutor() { return executor; } public void setExecutor(NodeExecutor executor) { this.executor = executor; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/ExpNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class ExpNodeAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(ExpNodeAction.class); private String exp; public ExpNodeAction() { } public ExpNodeAction(String exp) { this.exp = exp; } @Override public T execute(NodeContext nodeContext, FlowContext context) { Object result = context.getElEvaluator().eval(exp, nodeContext, context, null); return (T) result; } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/FlowNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowContextImpl; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.model.parser.param.FlowParseParam; import com.jd.easyflow.flow.util.FlowConstants; /** * * Sub flow NodeAction. * * IMPORTANT NOTICE! This class should not be singleton! * * @author liyuliang5 * @version 1.0 * @since 1.0 */ public class FlowNodeAction implements NodeAction { protected String flowId; protected String[] startNodeIds; protected Boolean inherit; protected Flow flow; public FlowNodeAction() { // NOOP } public FlowNodeAction(String flowId, String[] startNodeIds, boolean inherit) { this.flowId = flowId; this.startNodeIds = startNodeIds; this.inherit = inherit; } @Override public T execute(NodeContext nodeContext, FlowContext context) { FlowEngine engine = context.getFlowEngine(); FlowParam param = buildFlowParam(nodeContext, context); FlowResult subResult = engine.execute(param); processFlowResult(subResult, nodeContext, context); return (T) subResult; } protected FlowParam buildFlowParam(NodeContext nodeContext, FlowContext context) { // init param. FlowParam param = new FlowParam(); param.setFlowId(flow == null ? flowId : flow.getId()); param.setNodeIds(startNodeIds); if (inherit) { param.setParam(context.getParam().getParam()); param.setDataMapFrom(context.getParam()); param.setLogFlag(context.getParam().getLogFlag()); } // init context. FlowContextImpl subContext = new FlowContextImpl(); if (inherit) { subContext.setDataFrom(context); subContext.setContext(context.getContext()); subContext.setLogFlag(context.getLogFlag()); subContext.setElEvaluator(context.getElEvaluator()); } else { subContext.put(FlowConstants.CTX_PARENT_CONTEXT, context); subContext.put(FlowConstants.CTX_PARENT_NODE_CONTEXT, nodeContext); } subContext.setParentContext(context); subContext.setParentNodeContext(nodeContext); // init result. FlowResult result = new FlowResult(); if (inherit) { result.setResult(context.getResult().getResult()); result.setDataMapFrom(context.getResult()); } subContext.setFlow(flow); param.setContext(subContext); subContext.setResult(result); return param; } protected void processFlowResult(FlowResult subResult, NodeContext nodeContext, FlowContext context) { if (inherit) { if (subResult != null && subResult.getContext() != null && subResult.getContext().isInterrupted()) { context.setInterrupted(); } } } public String getFlowId() { return flowId; } public void setFlowId(String flowId) { this.flowId = flowId; } public String[] getStartNodeIds() { return startNodeIds; } public void setStartNodeIds(String[] startNodeIds) { this.startNodeIds = startNodeIds; } public boolean isInherit() { return inherit; } public void setInherit(boolean inherit) { this.inherit = inherit; } @Override public void init(InitContext initContext, Object parent) { FlowNode node = (FlowNode) parent; if (flowId == null) { flowId = node.getProperty(DefConstants.COMMON_PROP_FLOW_ID); if (flowId == null) { Map flowDef = (Map) node.getProperty(DefConstants.COMMON_PROP_FLOW); if (flowDef != null) { FlowParseParam param = new FlowParseParam(); param.setObjectDefinition(flowDef); param.setParseEl(initContext.isParseEl()); List flowList = initContext.getFlowParser().parse(param); initContext.getFlowList().addAll(flowList); Flow subFlow = flowList.get(0); flowId = subFlow.getId(); flow = subFlow; } } } if (startNodeIds == null) { Object startNodeId = node.getProperty(DefConstants.NODE_ACTION_PROP_START_NODE_ID); if (startNodeId != null) { if (startNodeId instanceof String) { startNodeIds = new String[] { (String) startNodeId }; } else { startNodeIds = ((List) startNodeId).toArray(new String[] {}); } } } if (inherit == null) { inherit = node.getProperty(DefConstants.NODE_ACTION_PROP_INHERIT); } if (inherit == null) { inherit = true; } } public void setFlow(Flow flow) { this.flow = flow; } public Flow getFlow() { return flow; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/InterruptNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class InterruptNodeAction implements NodeAction { @Override public T execute(NodeContext nodeContext, FlowContext context) { context.setInterrupted(); return null; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/LoopNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; import com.jd.easyflow.flow.model.parser.param.ActionParseParam; /** * * IMPORTANT NOTICE! This class should not be singleton! * @author liyuliang5 */ public class LoopNodeAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(LoopNodeAction.class); // Condition private Boolean testBefore; private String loopConditionExp; private NodeExecutor loopConditionExecutor; // MaxCount private Long loopMaxCount; private String loopMaxCountExp; private NodeExecutor loopMaxCountExecutor; // Action protected NodeAction loopAction; private String loopPreExp; private NodeExecutor loopPreExecutor; private String loopPostExp; private NodeExecutor loopPostExecutor; @Override public T execute(NodeContext nodeContext, FlowContext context) { // compute loop maximum Long maximum = null; if (loopMaxCountExecutor != null) { maximum = loopMaxCountExecutor.execute(nodeContext, context); } else if (loopMaxCountExp != null) { maximum = ((Number) context.getElEvaluator().eval(loopMaxCountExp, nodeContext, context, null)).longValue(); } else if (loopMaxCount != null) { maximum = loopMaxCount; } if (context.isLogOn() && logger.isDebugEnabled()) { logger.debug("Loop maximum:" + loopMaxCount); } if (maximum == null && loopConditionExp == null && loopConditionExecutor == null) { throw new FlowException("Loop maximum and condition can not both be null"); } if (loopPreExecutor != null) { loopPreExecutor.execute(nodeContext, context); } else if (loopPreExp != null) { context.getElEvaluator().eval(loopPreExp, nodeContext, context, null); } for (long i = 0; maximum == null || i < maximum; i++) { nodeContext.put("currentLoopIndex", i); // test before if (testBefore) { if (loopConditionExecutor != null) { boolean testResult = loopConditionExecutor.execute(nodeContext, context); if (!testResult) { break; } } else if (loopConditionExp != null) { boolean testResult = context.getElEvaluator().eval(loopConditionExp, nodeContext, context, null); if (!testResult) { break; } } } // execute loopAction.execute(nodeContext, context); // test after if (!testBefore) { if (loopConditionExecutor != null) { boolean testResult = loopConditionExecutor.execute(nodeContext, context); if (!testResult) { break; } } else if (loopConditionExp != null) { boolean testResult = context.getElEvaluator().eval(loopConditionExp, nodeContext, context, null); if (!testResult) { break; } } } } if (loopPostExecutor != null) { loopPostExecutor.execute(nodeContext, context); } else if (loopPostExp != null) { context.getElEvaluator().eval(loopPostExp, nodeContext, context, null); } // result put to NodeContext return null; } @Override public void init(InitContext initContext, Object parent) { FlowNode node = (FlowNode) parent; testBefore = node.getProperty("loopTestBefore"); if (testBefore == null) { throw new FlowException("Test before can not be null"); } loopConditionExp = node.getProperty("loopConditionExp"); Map loopConditionExecutorConf = (Map) node .getProperty("loopConditionExecutor"); if (loopConditionExecutorConf != null) { String createExp = (String) loopConditionExecutorConf.get("createExp"); if (initContext.isParseEl() && createExp != null) { loopConditionExecutor = initContext.getFlowParser().getElEvaluator().evalWithDefaultContext(createExp, initContext, false); } } loopPreExp = node.getProperty("loopPreExp"); Map loopPreExecutorConf = (Map) node .getProperty("loopPreExecutor"); if (loopPreExecutorConf != null) { String createExp = (String) loopPreExecutorConf.get("createExp"); if (initContext.isParseEl() && createExp != null) { loopPreExecutor = initContext.getFlowParser().getElEvaluator().evalWithDefaultContext(createExp, initContext, false); } } loopPostExp = node.getProperty("loopPostExp"); Map loopPostExecutorConf = (Map) node .getProperty("loopPostExecutor"); if (loopPostExecutorConf != null) { String createExp = (String) loopPostExecutorConf.get("createExp"); if (initContext.isParseEl() && createExp != null) { loopPostExecutor = initContext.getFlowParser().getElEvaluator().evalWithDefaultContext(createExp, initContext, false); } } Object loopMaxCountConf = node.getProperty("loopMaxCount"); if (loopMaxCountConf != null) { loopMaxCount = ((Number)loopMaxCountConf).longValue(); } loopMaxCountExp = node.getProperty("loopMaxCountExp"); Map loopMaxCountExecutorConf = (Map) node.getProperty("loopMaxCountExecutor"); if (loopMaxCountExecutorConf != null) { String createExp = (String) loopMaxCountExecutorConf.get("createExp"); if (initContext.isParseEl() && createExp != null) { loopMaxCountExecutor = initContext.getFlowParser().getElEvaluator().evalWithDefaultContext(createExp, initContext, false); } } Object loopActionConf = node.getProperty("loopAction"); if (loopActionConf != null) { ActionParseParam param = new ActionParseParam(loopActionConf, initContext.getFlowList(), initContext.isParseEl(), initContext.getFlow(), node); loopAction = initContext.getFlowParser().parseNodeAction(param); } if (loopAction != null) { loopAction.init(initContext, node); } if (initContext.isParseEl()) { if (loopAction == null) { throw new FlowException("Loop node action can not be null"); } if (loopMaxCount == null && loopMaxCountExp == null && loopMaxCountExecutor == null && loopConditionExp == null && loopConditionExecutor == null) { throw new FlowException("Loop maximum and loop condition can not both be null"); } } } public NodeAction getLoopAction() { return loopAction; } public void setLoopAction(NodeAction loopAction) { this.loopAction = loopAction; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/ParamExecutorNodeAction.java ================================================ package com.jd.easyflow.flow.model.action; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; import com.jd.easyflow.flow.util.FlowConstants; /** * * @author liyuliang5 * */ public class ParamExecutorNodeAction implements NodeAction { @Override public T execute(NodeContext nodeContext, FlowContext context) { NodeExecutor executor = context.getParam().get(FlowConstants.PARAM_ACTION_EXECUTOR); Object result = executor.execute(nodeContext, context); return (T) result; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/compensate/CompensateAction.java ================================================ package com.jd.easyflow.flow.model.action.compensate; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; /** * @author liyuliang5 */ public interface CompensateAction { public T compensate(NodeContext nodeContext, FlowContext flowContext); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/compensate/CompensateFlowFilter.java ================================================ package com.jd.easyflow.flow.model.action.compensate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.filter.impl.BaseFlowFilter; import com.jd.easyflow.flow.util.FlowConstants; /** * @author liyuliang5 */ public class CompensateFlowFilter extends BaseFlowFilter { private static final String CTX_KEY_ORIGINAL_START_NODES = "_original_start_nodes"; public CompensateFlowFilter() { } public CompensateFlowFilter(int order) { this.order = order; } @Override public FlowResult doFilter(FlowContext request, FilterChain chain) { if (CompensateHelper.isCompensating(request)) { List originalStartNodes = request.get(CTX_KEY_ORIGINAL_START_NODES); if (originalStartNodes == null) { originalStartNodes = request.getStartNodes(); request.put(CTX_KEY_ORIGINAL_START_NODES, originalStartNodes); } Set unCompensatedNodes = new HashSet(); getUnCompensatedEndNodes(unCompensatedNodes, originalStartNodes); List startNodes = new ArrayList(); for (NodeContext endNode : unCompensatedNodes) { NodeContext startNode = CompensateHelper.createCompensateNode(endNode); startNodes.add(startNode); } request.setStartNodes(startNodes); } FlowResult result = chain.doFilter(request); return result; } private void getUnCompensatedEndNodes(Set nodes, List flowStartNodes) { if (flowStartNodes == null || flowStartNodes.size() == 0) { return; } for (NodeContext nodeContext : flowStartNodes) { if (Boolean.TRUE.equals(nodeContext.get(FlowConstants.NODECTX_COMPENSATE_NODE_FLAG))) { continue; } if (nodeContext.getNextNodes() == null || nodeContext.getNextNodes().length == 0) { nodes.add(nodeContext); continue; } boolean hasNext = false; for (NodeContext next : nodeContext.getNextNodes()) { if (! Boolean.TRUE.equals(nodeContext.get(FlowConstants.NODECTX_COMPENSATE_NODE_FLAG))) { hasNext = true; break; } } if (! hasNext) { nodes.add(nodeContext); } else { getUnCompensatedEndNodes(nodes, Arrays.asList(nodeContext.getNextNodes())); } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/compensate/CompensateFlowParseEventListener.java ================================================ package com.jd.easyflow.flow.model.action.compensate; import java.util.ArrayList; import java.util.List; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.parser.event.FlowParseEvent; import com.jd.easyflow.flow.model.parser.event.FlowParseEventListener; import com.jd.easyflow.flow.model.parser.event.FlowParseEventTypes; import com.jd.easyflow.flow.util.Triple; /** * @author liyuliang5 */ public class CompensateFlowParseEventListener implements FlowParseEventListener { @Override public void on(FlowParseEvent event) { switch (event.getType()) { case FlowParseEventTypes.INIT_FLOW_END: { // CompensateNodeFilter Flow flow = event.getFlow(); List, NodeContext>> nodeFilters = flow.getFilterManager().getNodeFilters(); boolean contains = false; if (nodeFilters == null) { nodeFilters = new ArrayList<>(); flow.getFilterManager().setNodeFilters(nodeFilters); } for (Filter filter : nodeFilters) { if (filter instanceof CompensateNodeFilter) { contains = true; break; } } if (!contains) { nodeFilters.add(0, new CompensateNodeFilter(Integer.MAX_VALUE)); flow.getFilterManager().setNodeFilters(nodeFilters); } // CompensateFlowFilter List> filters = flow.getFilterManager().getFilters(); contains = false; if (filters == null) { filters = new ArrayList<>(); flow.getFilterManager().setFilters(filters); } for (Filter filter : filters) { if (filter instanceof CompensateFlowFilter) { contains = true; break; } } if (!contains) { filters.add(0, new CompensateFlowFilter(Integer.MAX_VALUE)); flow.getFilterManager().setFilters(filters); } } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/compensate/CompensateHelper.java ================================================ package com.jd.easyflow.flow.model.action.compensate; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowConstants; /** * Unstable!!! Only for memory model!!! * @author liyuliang5 */ public class CompensateHelper { private static final String FLOW_CONTEXT_COMPENSATE_FLAG = "_compensateFlag"; static final String FLOW_CONTEXT_COMPENSATE_END_NODES_FLAG = "_compensateEndNodesFlag"; static final String NODE_CONTEXT_COMPENSATING_FLAG = "_compensatingFlag"; public static void compensate(FlowContext context) { context.put(FLOW_CONTEXT_COMPENSATE_FLAG, true); } public static boolean isCompensating(FlowContext context) { return Boolean.TRUE.equals(context.get(FLOW_CONTEXT_COMPENSATE_FLAG)); } public static NodeContext createCompensateNode(NodeContext nodeContext) { NodeContext nodeCtx = new NodeContext(nodeContext.getNodeId()); nodeCtx.put(FlowConstants.NODECTX_COMPENSATE_NODE_FLAG, true); nodeCtx.put(FlowConstants.NODECTX_COMPENSATE_FOR, nodeContext); nodeContext.put(FlowConstants.NODECTX_COMPENSATED_BY, nodeCtx); return nodeCtx; } public static FlowResult compensateFlow(FlowContext context) { FlowParam compensateParam = new FlowParam(); CompensateHelper.compensate(context); compensateParam.setContext(context); FlowResult compensateResult = context.getFlowEngine().execute(compensateParam); return compensateResult; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/compensate/CompensateNodeFilter.java ================================================ package com.jd.easyflow.flow.model.action.compensate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeContextAccessor; import com.jd.easyflow.flow.model.filter.impl.BaseNodeFilter; import com.jd.easyflow.flow.model.node.NodeImpl; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.LockUtil; import com.jd.easyflow.flow.util.Triple; /** * @author liyuliang5 */ public class CompensateNodeFilter extends BaseNodeFilter { private static final Logger logger = LoggerFactory.getLogger(CompensateNodeFilter.class); private static final String CTX_COMENSATED_END_NODES_LOCK_KEY = "_compensated_end_nodes_lock"; private static final String NODE_CTX_COMENSATING_LOCK_KEY = "_compensating_lock"; public CompensateNodeFilter() { } public CompensateNodeFilter(int order) { this.order = order; } @Override public NodeContext doFilter(Triple request, FilterChain, NodeContext> chain) { FlowContext context = request.getRight(); NodeContext nodeContext = request.getMiddle(); if (! Boolean.TRUE.equals(nodeContext.get(FlowConstants.NODECTX_COMPENSATE_NODE_FLAG))) { // Normal scenario. NodeContext newNodeContext = chain.doFilter(request); if (CompensateHelper.isCompensating(context)) { // post to previous node. List compensatedNextNodes = new ArrayList(); // current node previous nodes. compensatedNextNodes.add(CompensateHelper.createCompensateNode(newNodeContext)); if (!Boolean.TRUE.equals(context.get(CompensateHelper.FLOW_CONTEXT_COMPENSATE_END_NODES_FLAG))) { Object lockObj = LockUtil.getFlowContextLock(CTX_COMENSATED_END_NODES_LOCK_KEY, context); synchronized (lockObj) { // add end nodes. if (!Boolean.TRUE.equals(context.get(CompensateHelper.FLOW_CONTEXT_COMPENSATE_END_NODES_FLAG))) { Set endNodes = new HashSet(); getEndNodes(context.getStartNodes().toArray(new NodeContext[context.getStartNodes().size()]), endNodes); for (NodeContext endNode : endNodes) { compensatedNextNodes.add(CompensateHelper.createCompensateNode(endNode)); } context.put(CompensateHelper.FLOW_CONTEXT_COMPENSATE_END_NODES_FLAG, true); } NodeContextAccessor.setNextNodes(newNodeContext, compensatedNextNodes.toArray(new NodeContext[compensatedNextNodes.size()])); } } } newNodeContext.put(CompensateNodeFilter.class.getName(), true); return newNodeContext; } else { // Compensate scenario. NodeImpl nodeImpl = (NodeImpl) context.getFlow().getNode(nodeContext.getNodeId()); NodeContext originNodeCtx = nodeContext.get(FlowConstants.NODECTX_COMPENSATE_FOR); Object lockObj = LockUtil.getNodeContextLock(NODE_CTX_COMENSATING_LOCK_KEY, originNodeCtx); synchronized (lockObj) { // Judge compensate if (Boolean.TRUE.equals(originNodeCtx.get(CompensateHelper.NODE_CONTEXT_COMPENSATING_FLAG))) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Node:" + originNodeCtx.getNodeId() + "has been compensated, return"); } return nodeContext; } List originNextNodes = originNodeCtx.get(FlowConstants.NODECTX_NEXT_NODES); if (originNextNodes == null && originNodeCtx.getNextNodes() != null) { originNextNodes = Arrays.asList(originNodeCtx.getNextNodes()); } if (originNextNodes != null && originNextNodes.size() > 0) { String notCompensated = null; for (NodeContext next : originNextNodes) { if (Boolean.TRUE.equals(next.get(FlowConstants.NODECTX_COMPENSATE_NODE_FLAG))) { continue; } if (!Boolean.TRUE.equals(next.get(FlowConstants.NODECTX_COMPENSATED_FLAG))) { notCompensated = next.getNodeId(); break; } } if (notCompensated != null) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Next nodes of " + originNodeCtx.getNodeId() + " not all compensated:" + notCompensated); } return nodeContext; } } originNodeCtx.put(CompensateHelper.NODE_CONTEXT_COMPENSATING_FLAG, true); } // Execute compensate action if (Boolean.FALSE.equals(originNodeCtx.getPreResult())) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Node:" + originNodeCtx.getNodeId() + " pre result is false, skip compensate action."); } } else { NodeAction nodeAction = nodeImpl.getAction(); Object compensateResult = null; NodeAction compensateNodeAction = (NodeAction) nodeImpl .getProperty(FlowConstants.PROP_RUNTIME_COMPENSATE_ACTION); if (compensateNodeAction != null) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Compensate using NodeAction:" + compensateNodeAction.getClass()); } compensateResult = compensateNodeAction.execute(nodeContext, context); } else if (nodeAction instanceof CompensateAction) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Compensate using CompensateAction"); } compensateResult = ((CompensateAction) nodeAction).compensate(nodeContext, context); } NodeContextAccessor.setActionResult(nodeContext, compensateResult); } originNodeCtx.put(FlowConstants.NODECTX_COMPENSATED_FLAG, true); // post to previous node. List previousList = originNodeCtx.get(FlowConstants.NODECTX_PREVIOUS_NODES); if (previousList != null) { // MultiCheck and InclusiveCheck if (previousList.size() == 0) { NodeContextAccessor.setNextNodes(nodeContext, null); } else { NodeContext[] previouses = new NodeContext[previousList.size()]; for (int i = 0; i < previousList.size(); i++) { previouses[i] = CompensateHelper.createCompensateNode(previousList.get(i)); } NodeContextAccessor.setNextNodes(nodeContext, previouses); } } else if (originNodeCtx.getPreviousNode() != null) { NodeContextAccessor.setNextNodes(nodeContext, new NodeContext[] { CompensateHelper.createCompensateNode(originNodeCtx.getPreviousNode()) }); } else { NodeContextAccessor.setNextNodes(nodeContext, null); } return nodeContext; } } private void getEndNodes(NodeContext[] nodes, Set result) { if (nodes == null || nodes.length == 0) { return; } for (NodeContext node : nodes) { if (!Boolean.TRUE.equals(node.get(CompensateNodeFilter.class.getName()))) { continue; } if (node.getNextNodes() == null || node.getNextNodes().length == 0) { result.add(node); } else { getEndNodes(node.getNextNodes(), result); } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/compensate/CompensateNodePreHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.action.compensate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.filter.impl.BaseNodePreHandlerFilter; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public class CompensateNodePreHandlerFilter extends BaseNodePreHandlerFilter { private static final Logger logger = LoggerFactory.getLogger(CompensateNodePreHandlerFilter.class); public CompensateNodePreHandlerFilter() { } public CompensateNodePreHandlerFilter(int order) { this.order = order; } @Override public Boolean doFilter(Pair request, FilterChain, Boolean> chain) { Boolean result = chain.doFilter(request); if (CompensateHelper.isCompensating(request.getRight())) { if (request.getRight().isLogOn() && logger.isInfoEnabled()) { logger.info("Compensating, NodePreHandler return false, node:" + request.getLeft().getNodeId()); } return false; } else { return result; } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/action/compensate/FlowCompensateAction.java ================================================ package com.jd.easyflow.flow.model.action.compensate; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * @author liyuliang5 */ public class FlowCompensateAction implements NodeAction { @Override public FlowResult execute(NodeContext nodeContext, FlowContext context) { FlowResult result = (FlowResult) nodeContext.getActionResult(); FlowParam param = new FlowParam(); param.setContext(result.getContext()); CompensateHelper.compensate(result.getContext()); FlowResult compensateResult = context.getFlowEngine().execute(param); return compensateResult; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/builder/FlowBuilder.java ================================================ package com.jd.easyflow.flow.model.builder; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.FlowPostHandler; import com.jd.easyflow.flow.model.FlowPreHandler; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.node.NodeImpl; import com.jd.easyflow.flow.model.parser.FlowParser; /** * * @author liyuliang5 * */ public class FlowBuilder { private Flow flow; public static FlowBuilder create(String id, String name) { Flow flow = new Flow(); flow.setId(id); flow.setName(name); FlowBuilder builder = new FlowBuilder(); builder.flow = flow; return builder; } public FlowBuilder setProperty(String key, Object value) { flow.setProperty(key, value); return this; } public FlowBuilder addNode(String nodeId, NodeAction action) { return addNode(nodeId, action, null); } public FlowBuilder addNode(String nodeId, NodeAction action, NodePostHandler postHandler) { NodeImpl node = new NodeImpl(); node.setId(nodeId); node.setAction(action); node.setPostHandler(postHandler); flow.addNode(node); return this; } public FlowBuilder addNode(FlowNode node) { flow.addNode(node); return this; } public FlowBuilder setStartNodeId(String startNodeId) { flow.setStartNodeIds(new String[] {startNodeId}); return this; } public FlowBuilder setStartNodeIds(String[] startNodeIds) { flow.setStartNodeIds(startNodeIds); return this; } public FlowBuilder setFlowPreHandler(FlowPreHandler preHandler) { flow.setPreHandler(preHandler); return this; } public FlowBuilder setFlowPostHandler(FlowPostHandler postHandler) { flow.setPostHandler(postHandler); return this; } public Flow build() { return flow; } public Flow buildAndInit() { InitContext initContext = new InitContext(); initContext.setFlowParser(null); initContext.setParseEl(true); initContext.setFlowList(null); initContext.setFlowDefinitionMap(null); initContext.setFlow(flow); flow.init(initContext, null); return flow; } public Flow buildAndInit(FlowParser flowParser) { InitContext initContext = new InitContext(); initContext.setFlowParser(flowParser); initContext.setParseEl(true); initContext.setFlowList(null); initContext.setFlowDefinitionMap(null); initContext.setFlow(flow); flow.init(initContext, null); return flow; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/definition/DefConstants.java ================================================ package com.jd.easyflow.flow.model.definition; /** * * @author liyuliang5 * */ public class DefConstants { public static final String COMMON_PROP_ID = "id"; public static final String COMMON_PROP_NAME = "name"; public static final String COMMON_PROP_EXP = "exp"; public static final String COMMON_PROP_FLOW = "flow"; public static final String COMMON_PROP_FLOW_ID = "flowId"; public static final String COMMON_PROP_CREATE_EXP = "createExp"; public static final String COMMON_PROP_PROPERTIES = "properties"; public static final String COMMON_PROP_CREATE = "create"; public static final String COMMON_PROP_TYPE = "type"; public static final String FLOW_PROP_PARSE_LISTENERS = "parseListeners"; public static final String FLOW_PROP_PRE = "pre"; public static final String FLOW_PROP_POST = "post"; public static final String FLOW_PROP_NODES = "nodes"; public static final String FLOW_PROP_LISTENERS = "listeners"; public static final String FLOW_PROP_FILTERS = "filters"; public static final String FLOW_PROP_NODE_FILTERS = "nodeFilters"; public static final String FLOW_PROP_NODE_PRE_HANDLER_FILTERS = "nodePreHandlerFilters"; public static final String FLOW_PROP_NODE_ACTION_FILTERS = "nodeActionFilters"; public static final String FLOW_PROP_NODE_POST_HANDLER_FILTERS = "nodePostHandlerFilters"; public static final String FLOW_PROP_FLOW_PRE_HANDLER_FILTERS = "flowPreHandlerFilters"; public static final String FLOW_PROP_FLOW_POST_HANDLER_FILTERS = "flowPostHandlerFilters"; public static final String FLOW_PROP_RUNNER = "runner"; public static final String FLOW_PROP_LOG_FLAG = "logFlag"; // Node property public static final String NODE_PROP_START = "start"; public static final String NODE_PROPERTIES_PROP_END = "end"; public static final String NODE_PROP_ACTION = "action"; public static final String NODE_PROP_PRE = "pre"; public static final String NODE_PROP_POST = "post"; public static final String NODE_PROPERTIES_PROP_PRE_NODES = "preNodes"; public static final String NODE_PROPERTIES_PROP_COMPENSATE_ACTION = "compensateAction"; /** * Node pre property */ public static final String NODE_PRE_TYPE_MULTICHECK = "multiCheck"; public static final String NODE_PRE_TYPE_INCLUSIVECHECK = "inclusiveCheck"; public static final String NODE_PRE_PROP_PRE_NODES = "preNodes"; /** * Node action property */ public static final String NODE_ACTION_TYPE_EVENT = "event"; public static final String NODE_ACTION_TYPE_FLOW = "flow"; public static final String NODE_ACTION_TYPE_INTERRUPT = "interrupt"; public static final String NODE_ACTION_TYPE_COMPENSATE = "compensate"; public static final String NODE_ACTION_TYPE_LOOP = "loop"; public static final String NODE_ACTION_TYPE_PARAM_EXECUTOR = "paramExecutor"; public static final String NODE_ACTION_PROP_START_NODE_ID = "startNodeId"; public static final String NODE_ACTION_PROP_INHERIT = "inherit"; /** * Node post property */ public static final String NODE_POST_TYPE_CONDITION = "condition"; public static final String NODE_POST_TYPE_EVENT = "event"; public static final String NODE_POST_PROP_CONDITIONS = "conditions"; public static final String NODE_POST_PROP_WHEN = "when"; public static final String NODE_POST_PROP_TO = "to"; public static final String NODE_POST_TYPE_FIXED = "fixed"; public static final String NODE_POST_PROP_CONDITION_TYPE = "conditionType"; public static final String NODE_POST_PROP_DEFAULT_TO = "defaultTo"; } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/FlowFilter.java ================================================ package com.jd.easyflow.flow.model.filter; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.Filter; /** * @author liyuliang5 */ public interface FlowFilter extends Filter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/FlowFilterManager.java ================================================ package com.jd.easyflow.flow.model.filter; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.filter.FilterChainImpl; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.Pair; import com.jd.easyflow.flow.util.Triple; /** * @author liyuliang5 */ public class FlowFilterManager { private List> filters; private List, NodeContext>> nodeFilters; /** * nodeAction is not standard model. define here for performance. */ private List, Object>> nodeActionFilters; /** * nodePreHandler is not standard model. define here for performance. */ private List, Boolean>> nodePreHandlerFilters; /** * nodePostHandler is not standard model. define here for performance. */ private List, NodeContext[]>> nodePostHandlerFilters; private List> flowPreHandlerFilters; private List> flowPostHandlerFilters; public void init(InitContext initContext, Flow flow) { if (filters != null) { for (Filter filter: filters) { filter.init(initContext, flow); } refreshFilters(); } if (nodeFilters != null) { for (Filter filter: nodeFilters) { filter.init(initContext, flow); } refreshNodeFilters(); } if (nodeActionFilters != null) { for (Filter filter: nodeActionFilters) { filter.init(initContext, flow); } refreshNodeActionFilters(); } if (nodePreHandlerFilters != null) { for (Filter filter: nodePreHandlerFilters) { filter.init(initContext, flow); } refreshNodePreHandlerFilters(); } if (nodePostHandlerFilters != null) { for (Filter filter: nodePostHandlerFilters) { filter.init(initContext, flow); } refreshNodePostHandlerFilters(); } if (flowPreHandlerFilters != null) { for (Filter filter: flowPreHandlerFilters) { filter.init(initContext, flow); } refreshFlowPreHandlerFilters(); } if (flowPostHandlerFilters != null) { for (Filter filter: flowPostHandlerFilters) { filter.init(initContext, flow); } refreshFlowPostHandlerFilters(); } } public void destroy() { if (filters != null) { for (Filter filter: filters) { filter.destroy(); } } if (nodeFilters != null) { for (Filter filter: nodeFilters) { filter.destroy(); } } if (nodeActionFilters != null) { for (Filter filter: nodeActionFilters) { filter.destroy(); } } if (nodePreHandlerFilters != null) { for (Filter filter: nodePreHandlerFilters) { filter.destroy(); } } if (nodePostHandlerFilters != null) { for (Filter filter: nodePostHandlerFilters) { filter.destroy(); } } if (flowPreHandlerFilters != null) { for (Filter filter: flowPreHandlerFilters) { filter.destroy(); } } if (flowPostHandlerFilters != null) { for (Filter filter: flowPostHandlerFilters) { filter.destroy(); } } } /* Filters */ private List> innerFilters; private List> outerFilters; public List> getFilters() { return filters; } public List> getInnerFilters() { return innerFilters; } public List> getOuterFilters() { return outerFilters; } public void setFilters(List> filters) { this.filters = filters; refreshFilters(); } private void refreshFilters() { if (filters == null) { this.innerFilters = this.outerFilters = null; } else { this.innerFilters = new ArrayList>(); this.outerFilters = new ArrayList>(); for (Filter filter : filters) { addFilter(filter, this.innerFilters, this.outerFilters); } } } public void addFilter(Filter filter) { if (this.filters == null) { this.filters = new ArrayList>(); this.innerFilters = new ArrayList>(); this.outerFilters = new ArrayList>(); } this.filters.add(filter); addFilter(filter, this.innerFilters, this.outerFilters); } public boolean noOuterFilter() { return outerFilters == null || outerFilters.size() == 0; } public FlowResult doOuterFilter(FlowContext context, Function outerInvoker) { FilterChain chain = new FilterChainImpl(outerFilters, outerInvoker); return chain.doFilter(context); } public boolean noInnerFilter() { return innerFilters == null || innerFilters.size() == 0; } public FlowResult doInnerFilter(FlowContext context, Function innerInvoker) { FilterChain chain = new FilterChainImpl(innerFilters, innerInvoker); return chain.doFilter(context); } /* Node Filters */ private List, NodeContext>> innerNodeFilters; private List, NodeContext>> outerNodeFilters; public List, NodeContext>> getNodeFilters() { return nodeFilters; } public List, NodeContext>> getInnerNodeFilters() { return innerNodeFilters; } public List, NodeContext>> getOuterNodeFilters() { return outerNodeFilters; } public void setNodeFilters(List, NodeContext>> nodeFilters) { this.nodeFilters = nodeFilters; refreshNodeFilters(); } private void refreshNodeFilters() { if (nodeFilters.size() == 0) { this.innerNodeFilters = this.outerNodeFilters = null; } else { this.innerNodeFilters = new ArrayList, NodeContext>>(); this.outerNodeFilters = new ArrayList, NodeContext>>(); for (Filter filter : nodeFilters) { addFilter(filter, this.innerNodeFilters, this.outerNodeFilters); } } } public void addNodeFilter(Filter, NodeContext> filter) { if (this.nodeFilters == null) { this.nodeFilters = new ArrayList, NodeContext>>(); this.innerNodeFilters = new ArrayList,NodeContext>>(); this.outerNodeFilters = new ArrayList,NodeContext>>(); } this.nodeFilters.add(filter); addFilter(filter, this.innerNodeFilters, this.outerNodeFilters); } public boolean noOuterNodeFilter() { return outerNodeFilters == null || outerNodeFilters.size() == 0; } public NodeContext doOuterNodeFilter(Triple p, Function, NodeContext> outerNodeInvoker) { FilterChain, NodeContext> chain = new FilterChainImpl<>(outerNodeFilters, outerNodeInvoker); return chain.doFilter(p); } public boolean noInnerNodeFilter() { return innerNodeFilters == null || innerNodeFilters.size() == 0; } public NodeContext doInnerNodeFilter(Triple p, Function, NodeContext> innerNodeInvoker) { FilterChain, NodeContext> chain = new FilterChainImpl<>(innerNodeFilters, innerNodeInvoker); return chain.doFilter(p); } /* NodeActionFilters */ private List, Object>> innerNodeActionFilters; private List, Object>> outerNodeActionFilters; public List, Object>> getNodeActionFilters() { return nodeActionFilters; } public List, Object>> getInnerNodeActionFilters() { return innerNodeActionFilters; } public List, Object>> getOuterNodeActionFilters() { return outerNodeActionFilters; } public void setNodeActionFilters(List, Object>> nodeActionFilters) { this.nodeActionFilters = nodeActionFilters; refreshNodeActionFilters(); } private void refreshNodeActionFilters() { if (nodeActionFilters == null) { this.innerNodeActionFilters = this.outerNodeActionFilters = null; } else { this.innerNodeActionFilters = new ArrayList,Object>>(); this.outerNodeActionFilters = new ArrayList,Object>>(); for (Filter filter : nodeActionFilters) { addFilter(filter, this.innerNodeActionFilters, this.outerNodeActionFilters); } } } public void addNodeActionFilter(Filter, Object> filter) { if (this.nodeActionFilters == null) { this.nodeActionFilters = new ArrayList, Object>>(); this.innerNodeActionFilters = new ArrayList, Object>>(); this.outerNodeActionFilters = new ArrayList, Object>>(); } this.nodeActionFilters.add(filter); addFilter(filter, this.innerNodeActionFilters, this.outerNodeActionFilters); } public boolean noOuterNodeActionFilter() { return outerNodeActionFilters == null || outerNodeActionFilters.size() == 0; } public Object doOuterNodeActionFilter(Pair p, Function, Object> outerNodeActionInvoker) { FilterChain, Object> chain = new FilterChainImpl, Object>(outerNodeActionFilters, outerNodeActionInvoker); return chain.doFilter(p); } public boolean noInnerNodeActionFilter() { return innerNodeActionFilters == null || innerNodeActionFilters.size() == 0; } public Object doInnerNodeActionFilter(Pair p, Function, Object> innerNodeActionInvoker) { FilterChain, Object> chain = new FilterChainImpl, Object>(innerNodeActionFilters, innerNodeActionInvoker); return chain.doFilter(p); } /* NodePreHandlerFilters*/ private List, Boolean>> innerNodePreHandlerFilters; private List, Boolean>> outerNodePreHandlerFilters; public List, Boolean>> getNodePreHandlerFilters() { return nodePreHandlerFilters; } public List, Boolean>> getInnerNodePreHandlerFilters() { return innerNodePreHandlerFilters; } public List, Boolean>> getOuterNodePreHandlerFilters() { return outerNodePreHandlerFilters; } public void setNodePreHandlerFilters(List, Boolean>> nodePreHandlerFilters) { this.nodePreHandlerFilters = nodePreHandlerFilters; refreshNodePreHandlerFilters(); } private void refreshNodePreHandlerFilters() { if (nodePreHandlerFilters == null) { this.innerNodePreHandlerFilters = this.outerNodePreHandlerFilters = null; } else { this.innerNodePreHandlerFilters = new ArrayList, Boolean>>(); this.outerNodePreHandlerFilters = new ArrayList, Boolean>>(); for (Filter filter : nodePreHandlerFilters) { addFilter(filter, this.innerNodePreHandlerFilters, this.outerNodePreHandlerFilters); } } } public void addNodePreHandlerFilter(Filter, Boolean> filter) { if (this.nodePreHandlerFilters == null) { this.nodePreHandlerFilters = new ArrayList, Boolean>>(); this.innerNodePreHandlerFilters = new ArrayList,Boolean>>(); this.outerNodePreHandlerFilters = new ArrayList,Boolean>>(); } this.nodePreHandlerFilters.add(filter); addFilter(filter, this.innerNodePreHandlerFilters, this.outerNodePreHandlerFilters); } public boolean noOuterNodePreHandlerFilter() { return outerNodePreHandlerFilters == null || outerNodePreHandlerFilters.size() == 0; } public Boolean doOuterNodePreHandlerFilter(Pair p, Function, Boolean> outerNodePreHandlerInvoker) { FilterChain, Boolean> chain = new FilterChainImpl, Boolean>(outerNodePreHandlerFilters, outerNodePreHandlerInvoker); return chain.doFilter(p); } public boolean noInnerNodePreHandlerFilter() { return innerNodePreHandlerFilters == null || innerNodePreHandlerFilters.size() == 0; } public Boolean doInnerNodePreHandlerFilter(Pair p, Function, Boolean> innerNodePreHandlerInvoker) { FilterChain, Boolean> chain = new FilterChainImpl, Boolean>(innerNodePreHandlerFilters, innerNodePreHandlerInvoker); return chain.doFilter(p); } /* NodePostHandlerFilter */ private List, NodeContext[]>> innerNodePostHandlerFilters; private List, NodeContext[]>> outerNodePostHandlerFilters; public List, NodeContext[]>> getNodePostHandlerFilters() { return nodePostHandlerFilters; } public List, NodeContext[]>> getInnerNodePostHandlerFilters() { return innerNodePostHandlerFilters; } public List, NodeContext[]>> getOuterNodePostHandlerFilters() { return outerNodePostHandlerFilters; } public void setNodePostHandlerFilters( List, NodeContext[]>> nodePostHandlerFilters) { this.nodePostHandlerFilters = nodePostHandlerFilters; refreshNodePostHandlerFilters(); } private void refreshNodePostHandlerFilters() { if (nodePostHandlerFilters == null) { this.innerNodePostHandlerFilters = this.outerNodePostHandlerFilters = null; } else { this.innerNodePostHandlerFilters = new ArrayList,NodeContext[]>>(); this.outerNodePostHandlerFilters = new ArrayList,NodeContext[]>>(); for (Filter filter : nodePostHandlerFilters) { addFilter(filter, this.innerNodePostHandlerFilters, this.outerNodePostHandlerFilters); } } } public void addNodePostHandlerFilter(Filter, NodeContext[]> filter) { if (this.nodePostHandlerFilters == null) { this.nodePostHandlerFilters = new ArrayList, NodeContext[]>>(); this.innerNodePostHandlerFilters = new ArrayList, NodeContext[]>>(); this.outerNodePostHandlerFilters = new ArrayList, NodeContext[]>>(); } this.nodePostHandlerFilters.add(filter); addFilter(filter, this.innerNodePostHandlerFilters, this.outerNodePostHandlerFilters); } public boolean noOuterNodePostHandlerFilter() { return outerNodePostHandlerFilters == null || outerNodePostHandlerFilters.size() == 0; } public NodeContext[] doOuterNodePostHandlerFilter(Pair p, Function, NodeContext[]> outerNodePostHandlerInvoker) { FilterChain, NodeContext[]> chain = new FilterChainImpl, NodeContext[]>(outerNodePostHandlerFilters, outerNodePostHandlerInvoker); return chain.doFilter(p); } public boolean noInnerNodePostHandlerFilter() { return innerNodePostHandlerFilters == null || innerNodePostHandlerFilters.size() == 0; } public NodeContext[] doInnerNodePostHandlerFilter(Pair p, Function, NodeContext[]> innerNodePostHandlerInvoker) { FilterChain, NodeContext[]> chain = new FilterChainImpl, NodeContext[]>(innerNodePostHandlerFilters, innerNodePostHandlerInvoker); return chain.doFilter(p); } /* FlowPreHandlerFilter */ private List> innerFlowPreHandlerFilters; private List> outerFlowPreHandlerFilters; public List> getFlowPreHandlerFilters() { return flowPreHandlerFilters; } public List> getInnerFlowPreHandlerFilters() { return innerFlowPreHandlerFilters; } public List> getOuterFlowPreHandlerFilters() { return outerFlowPreHandlerFilters; } public void setFlowPreHandlerFilters(List> flowPreHandlerFilters) { this.flowPreHandlerFilters = flowPreHandlerFilters; refreshFlowPreHandlerFilters(); } private void refreshFlowPreHandlerFilters() { if (flowPreHandlerFilters == null) { this.innerFlowPreHandlerFilters = this.outerFlowPreHandlerFilters = null; } else { this.innerFlowPreHandlerFilters = new ArrayList>(); this.outerFlowPreHandlerFilters = new ArrayList>(); for (Filter filter : flowPreHandlerFilters) { addFilter(filter, this.innerFlowPreHandlerFilters, this.outerFlowPreHandlerFilters); } } } public void addFlowPreHandlerFilter(Filter filter) { if (this.flowPreHandlerFilters == null) { this.flowPreHandlerFilters = new ArrayList>(); this.innerFlowPreHandlerFilters = new ArrayList>(); this.outerFlowPreHandlerFilters = new ArrayList>(); } this.flowPreHandlerFilters.add(filter); addFilter(filter, this.innerFlowPreHandlerFilters, this.outerFlowPreHandlerFilters); } public boolean noOuterFlowPreHandlerFilter() { return outerFlowPreHandlerFilters == null || outerFlowPreHandlerFilters.size() == 0; } public Boolean doOuterFlowPreHandlerFilter(FlowContext context, Function outerFlowPreHandlerInvoker) { FilterChain chain = new FilterChainImpl<>(outerFlowPreHandlerFilters, outerFlowPreHandlerInvoker); return chain.doFilter(context); } public boolean noInnerFlowPreHandlerFilter() { return innerFlowPreHandlerFilters == null || innerFlowPreHandlerFilters.size() == 0; } public Boolean doInnerFlowPreHandlerFilter(FlowContext context, Function innerFlowPreHandlerInvoker) { FilterChain chain = new FilterChainImpl<>(innerFlowPreHandlerFilters, innerFlowPreHandlerInvoker); return chain.doFilter(context); } /* FlowPostHandlerFilter */ private List> innerFlowPostHandlerFilters; private List> outerFlowPostHandlerFilters; public List> getFlowPostHandlerFilters() { return flowPostHandlerFilters; } public List> getInnerFlowPostHandlerFilters() { return innerFlowPostHandlerFilters; } public List> getOuterFlowPostHandlerFilters() { return outerFlowPostHandlerFilters; } public void setFlowPostHandlerFilters(List> flowPostHandlerFilters) { this.flowPostHandlerFilters = flowPostHandlerFilters; refreshFlowPostHandlerFilters(); } private void refreshFlowPostHandlerFilters() { if (flowPostHandlerFilters == null) { this.innerFlowPostHandlerFilters = this.outerFlowPostHandlerFilters = null; } else { this.innerFlowPostHandlerFilters = new ArrayList>(); this.outerFlowPostHandlerFilters = new ArrayList>(); for (Filter filter : flowPostHandlerFilters) { addFilter(filter, this.innerFlowPostHandlerFilters, this.outerFlowPostHandlerFilters); } } } public void addFlowPostHandlerFilter(Filter filter) { if (this.flowPostHandlerFilters == null) { this.flowPostHandlerFilters = new ArrayList>(); this.innerFlowPostHandlerFilters = new ArrayList>(); this.outerFlowPostHandlerFilters = new ArrayList>(); } this.flowPostHandlerFilters.add(filter); addFilter(filter, this.innerFlowPostHandlerFilters, this.outerFlowPostHandlerFilters); } public boolean noOuterFlowPostHandlerFilter() { return outerFlowPostHandlerFilters == null || outerFlowPostHandlerFilters.size() == 0; } public Void doOuterFlowPostHandlerFilter(FlowContext context, Function outerFlowPostHandlerInvoker) { FilterChain chain = new FilterChainImpl<>(outerFlowPostHandlerFilters, outerFlowPostHandlerInvoker); return chain.doFilter(context); } public boolean noInnerFlowPostHandlerFilter() { return innerFlowPostHandlerFilters == null || innerFlowPostHandlerFilters.size() == 0; } public Void doInnerFlowPostHandlerFilter(FlowContext context, Function innerFlowPostHandlerInvoker) { FilterChain chain = new FilterChainImpl<>(innerFlowPostHandlerFilters, innerFlowPostHandlerInvoker); return chain.doFilter(context); } private void addFilter(Filter filter, List innerFilters, List outerFilters) { int filterOrder = filter.getOrder(); int pos = 0; if (filterOrder < 0) { for (; pos < innerFilters.size(); pos++) { if (((Filter) innerFilters.get(pos)).getOrder() < filterOrder) { break; } } innerFilters.add(pos, filter); } else { for (; pos < outerFilters.size(); pos++) { if (((Filter)outerFilters.get(pos)).getOrder() < filterOrder) { break; } } outerFilters.add(pos, filter); } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/FlowPostHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.Filter; /** * @author liyuliang5 */ public interface FlowPostHandlerFilter extends Filter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/FlowPreHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.Filter; /** * @author liyuliang5 */ public interface FlowPreHandlerFilter extends Filter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/NodeActionFilter.java ================================================ package com.jd.easyflow.flow.model.filter; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public interface NodeActionFilter extends Filter, Object> { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/NodeFilter.java ================================================ package com.jd.easyflow.flow.model.filter; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.Triple; /** * @author liyuliang5 */ public interface NodeFilter extends Filter, NodeContext> { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/NodePostHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public interface NodePostHandlerFilter extends Filter, NodeContext[]> { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/NodePreHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public interface NodePreHandlerFilter extends Filter, Boolean> { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/impl/BaseFlowFilter.java ================================================ package com.jd.easyflow.flow.model.filter.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.model.filter.FlowFilter; /** * @author liyuliang5 */ public abstract class BaseFlowFilter extends BaseFilter implements FlowFilter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/impl/BaseFlowPostHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.model.FlowPostHandler; /** * @author liyuliang5 */ public abstract class BaseFlowPostHandlerFilter extends BaseFilter implements FlowPostHandler { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/impl/BaseFlowPreHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.model.filter.FlowPreHandlerFilter; /** * @author liyuliang5 */ public abstract class BaseFlowPreHandlerFilter extends BaseFilter implements FlowPreHandlerFilter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/impl/BaseNodeActionFilter.java ================================================ package com.jd.easyflow.flow.model.filter.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.filter.NodeActionFilter; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public abstract class BaseNodeActionFilter extends BaseFilter, Object> implements NodeActionFilter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/impl/BaseNodeFilter.java ================================================ package com.jd.easyflow.flow.model.filter.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.filter.NodeFilter; import com.jd.easyflow.flow.util.Triple; /** * @author liyuliang5 */ public abstract class BaseNodeFilter extends BaseFilter, NodeContext> implements NodeFilter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/impl/BaseNodePostHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.filter.NodePostHandlerFilter; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 */ public abstract class BaseNodePostHandlerFilter extends BaseFilter, NodeContext[]> implements NodePostHandlerFilter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/filter/impl/BaseNodePreHandlerFilter.java ================================================ package com.jd.easyflow.flow.model.filter.impl; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.filter.NodePreHandlerFilter; import com.jd.easyflow.flow.util.Pair; /** * @author liyuliang5 * */ public abstract class BaseNodePreHandlerFilter extends BaseFilter, Boolean> implements NodePreHandlerFilter { } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/flow/post/ExpFlowPostHandler.java ================================================ package com.jd.easyflow.flow.model.flow.post; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowPostHandler; /** * * @author liyuliang5 * */ public class ExpFlowPostHandler implements FlowPostHandler { private static final Logger logger = LoggerFactory.getLogger(ExpFlowPostHandler.class); private String exp; public ExpFlowPostHandler() { } public ExpFlowPostHandler(String exp) { this.exp = exp; } @Override public void postHandle(FlowContext context) { Object result = context.getElEvaluator().eval(exp, null, context, null); if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Exp:" + exp + " result:" + result); } } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/flow/pre/ExpFlowPreHandler.java ================================================ package com.jd.easyflow.flow.model.flow.pre; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowPreHandler; /** * * @author liyuliang5 * */ public class ExpFlowPreHandler implements FlowPreHandler { private static final Logger logger = LoggerFactory.getLogger(ExpFlowPreHandler.class); private String exp; public ExpFlowPreHandler() { } public ExpFlowPreHandler(String exp) { this.exp = exp; } @Override public boolean preHandle(FlowContext context) { boolean result = context.getElEvaluator().eval(exp, null, context, null); if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Exp:" + exp + " result:" + result); } return result; } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/node/NodeImpl.java ================================================ package com.jd.easyflow.flow.model.node; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeContextAccessor; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.NodePreHandler; import com.jd.easyflow.flow.util.FlowEventTypes; import com.jd.easyflow.flow.util.Pair; /** * * @author liyuliang5 * */ public class NodeImpl implements FlowNode { protected String id; protected String name; protected Map properties = new ConcurrentHashMap<>(); protected NodePreHandler preHandler; protected NodeAction action; protected NodePostHandler postHandler; private Function, Boolean> outerNodePreHandlerInvoker = p -> invokePreHandler(p.getLeft(), p.getRight()); private Function, Boolean> innerNodePreHandlerInvoker = p -> preHandler.preHandle(p.getLeft(), p.getRight()); private Function, Object> outerNodeActionInvoker = p -> invokeAction(p.getLeft(), p.getRight()); private Function, Object> innerNodeActionInvoker = p -> action.execute(p.getLeft(), p.getRight()); private Function, NodeContext[]> outerNodePostHandlerInvoker = p -> invokePostHandler(p.getLeft(), p.getRight()); private Function, NodeContext[]> innerNodePostHandlerInvoker = p -> postHandler.postHandle(p.getLeft(), p.getRight()); @Override public void init(InitContext initContext, Object parent) { if (preHandler != null) { preHandler.init(initContext, this); } if (action != null) { action.init(initContext, this); } if (postHandler != null) { postHandler.init(initContext, this); } } @Override public void destroy() { if (preHandler != null) { preHandler.destroy(); } if (action != null) { action.destroy(); } if (postHandler != null) { postHandler.destroy(); } } @Override public NodeContext execute(NodeContext nodeContext, FlowContext context) { if (!executePreHandler(nodeContext, context)) { return nodeContext; } executeAction(nodeContext, context); executePostHandler(nodeContext, context); return nodeContext; } protected boolean executePreHandler(NodeContext nodeContext, FlowContext context) { if (context.getFlow().getFilterManager().noOuterNodePreHandlerFilter()) { return invokePreHandler(nodeContext, context); } else { Boolean preResult = context.getFlow().getFilterManager().doOuterNodePreHandlerFilter(Pair.of(nodeContext, context), outerNodePreHandlerInvoker); NodeContextAccessor.setPreResult(nodeContext, preResult); return preResult == null ? true : preResult; } } protected boolean invokePreHandler(NodeContext nodeContext, FlowContext context) { if (preHandler != null) { context.getFlow().triggerEvent(FlowEventTypes.NODE_PRE_START, nodeContext, context, false); boolean preResult; if (context.getFlow().getFilterManager().noInnerNodePreHandlerFilter()) { preResult = preHandler.preHandle(nodeContext, context); } else { Boolean result = context.getFlow().getFilterManager().doInnerNodePreHandlerFilter(Pair.of(nodeContext, context), innerNodePreHandlerInvoker); preResult = result == null ? true : result; } NodeContextAccessor.setPreResult(nodeContext, preResult); context.getFlow().triggerEvent(FlowEventTypes.NODE_PRE_END, nodeContext, context, false); } return nodeContext.getPreResult() == null ? true : nodeContext.getPreResult(); } /** * Execute node action. * * @param nodeContext * @param context */ protected void executeAction(NodeContext nodeContext, FlowContext context) { if (context.getFlow().getFilterManager().noOuterNodeActionFilter()) { invokeAction(nodeContext, context); } else { Object result = context.getFlow().getFilterManager().doOuterNodeActionFilter(Pair.of(nodeContext, context), outerNodeActionInvoker); NodeContextAccessor.setActionResult(nodeContext,result); } } protected Object invokeAction(NodeContext nodeContext, FlowContext context) { if (action != null) { context.getFlow().triggerEvent(FlowEventTypes.NODE_ACTION_START, nodeContext, context, false); Object result = null; if (context.getFlow().getFilterManager().noInnerNodeActionFilter()) { result = action.execute(nodeContext, context); } else { result = context.getFlow().getFilterManager().doInnerNodeActionFilter(Pair.of(nodeContext, context), innerNodeActionInvoker); } NodeContextAccessor.setActionResult(nodeContext,result); context.getFlow().triggerEvent(FlowEventTypes.NODE_ACTION_END, nodeContext, context, false); } return nodeContext.getActionResult(); } protected void executePostHandler(NodeContext nodeContext, FlowContext context) { if (context.getFlow().getFilterManager().noOuterNodePostHandlerFilter()) { invokePostHandler(nodeContext, context); } else { NodeContext[] result = context.getFlow().getFilterManager().doOuterNodePostHandlerFilter(Pair.of(nodeContext, context), outerNodePostHandlerInvoker); NodeContextAccessor.setNextNodes(nodeContext, result); } } protected NodeContext[] invokePostHandler(NodeContext nodeContext, FlowContext context) { if (postHandler != null) { context.getFlow().triggerEvent(FlowEventTypes.NODE_POST_START, nodeContext, context, false); NodeContext[] nextNodes = null; if (context.getFlow().getFilterManager().noInnerNodePostHandlerFilter()) { nextNodes = postHandler.postHandle(nodeContext, context); } else { nextNodes = context.getFlow().getFilterManager().doInnerNodePostHandlerFilter(Pair.of(nodeContext, context), innerNodePostHandlerInvoker); } if (nextNodes != null) { NodeContextAccessor.setNextNodes(nodeContext,nextNodes); } context.getFlow().triggerEvent(FlowEventTypes.NODE_POST_END, nodeContext, context, false); } return nodeContext.getNextNodes(); } public NodeAction getAction() { return action; } public void setAction(NodeAction action) { this.action = action; } public NodePreHandler getPreHandler() { return preHandler; } public void setPreHandler(NodePreHandler preHandler) { this.preHandler = preHandler; } public NodePostHandler getPostHandler() { return postHandler; } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } public void setPostHandler(NodePostHandler postHandler) { this.postHandler = postHandler; } @Override public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties.clear(); this.putProperties(properties); } public void putProperties(Map properties) { if (properties == null) { return; } for (Entry entry : properties.entrySet()) { if (entry.getValue() == null) { this.properties.remove(entry.getKey()); } else { this.properties.put(entry.getKey(), entry.getValue()); } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/FlowParser.java ================================================ package com.jd.easyflow.flow.model.parser; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.NodePreHandler; import com.jd.easyflow.flow.model.parser.param.ActionParseParam; import com.jd.easyflow.flow.model.parser.param.FlowParseParam; import com.jd.easyflow.flow.model.parser.param.PostParseParam; import com.jd.easyflow.flow.model.parser.param.PreParseParam; /** * Flow definition parser. * * @author liyuliang5 * @version 1.0 * @since 1.0 */ public interface FlowParser { /** * Parse definition string to java model. * * @param data * @return */ List parse(String data); /** * Parse definition map to java model. * @param map * @return */ List parse(Map map); /** * Parse definition string to java model. * @param data * @param parseEl * @return */ List parse(String data, boolean parseEl); /** * Parse definition map to java model. * @param map * @param parseEl * @return */ List parse(Map map, boolean parseEl); /** * General parse method. * @param param * @return */ List parse(FlowParseParam param); /** * Parse preHandler. * @param param * @return */ NodePreHandler parseNodePreHandler(PreParseParam param); /** * Parse nodeAction. * @param param * @return */ NodeAction parseNodeAction(ActionParseParam param); /** * Parse postHandler. * @param param. * @return */ NodePostHandler parseNodePostHandler(PostParseParam param); /** * * Convert java model to string. * * @param flow * @return */ String stringify(Flow flow); /** * * @return */ public ElEvaluator getElEvaluator(); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/FlowParserImpl.java ================================================ package com.jd.easyflow.flow.model.parser; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.el.ElEvaluator; import com.jd.easyflow.flow.el.ElFactory; import com.jd.easyflow.flow.engine.FlowRunner; import com.jd.easyflow.flow.engine.event.ExpFlowEventListener; import com.jd.easyflow.flow.engine.event.FlowEventListener; import com.jd.easyflow.flow.engine.impl.ExpFlowRunner; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.filter.ExpFilter; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.FlowPostHandler; import com.jd.easyflow.flow.model.FlowPreHandler; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.NodePreHandler; import com.jd.easyflow.flow.model.action.CompensateNodeAction; import com.jd.easyflow.flow.model.action.EventNodeAction; import com.jd.easyflow.flow.model.action.ExpNodeAction; import com.jd.easyflow.flow.model.action.FlowNodeAction; import com.jd.easyflow.flow.model.action.InterruptNodeAction; import com.jd.easyflow.flow.model.action.LoopNodeAction; import com.jd.easyflow.flow.model.action.ParamExecutorNodeAction; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.model.flow.post.ExpFlowPostHandler; import com.jd.easyflow.flow.model.flow.pre.ExpFlowPreHandler; import com.jd.easyflow.flow.model.node.NodeImpl; import com.jd.easyflow.flow.model.parser.event.ExpFlowParseEventListener; import com.jd.easyflow.flow.model.parser.event.FlowParseEvent; import com.jd.easyflow.flow.model.parser.event.FlowParseEventListener; import com.jd.easyflow.flow.model.parser.event.FlowParseEventTypes; import com.jd.easyflow.flow.model.parser.param.ActionParseParam; import com.jd.easyflow.flow.model.parser.param.FlowParseParam; import com.jd.easyflow.flow.model.parser.param.PostParseParam; import com.jd.easyflow.flow.model.parser.param.PreParseParam; import com.jd.easyflow.flow.model.post.ConditionalNodePostHandler; import com.jd.easyflow.flow.model.post.EventPostHandler; import com.jd.easyflow.flow.model.post.ExpNodePostHandler; import com.jd.easyflow.flow.model.post.FixedNodePostHandler; import com.jd.easyflow.flow.model.pre.ExpNodePreHandler; import com.jd.easyflow.flow.model.pre.InclusiveCheckPreHandler; import com.jd.easyflow.flow.model.pre.MultiCheckPreHandler; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class FlowParserImpl implements FlowParser { private static final Logger logger = LoggerFactory.getLogger(FlowParser.class); private static final String FLOW_STRING_KEY = "_flow_string"; private static final String PARENT_FLOW_ID_KEY = "_parent_flow_id"; private static final String MAIN_FLOW_ID_KEY = "_main_flow_id"; private ElEvaluator elEvaluator; private List preListeners; private List postListeners; @Override public List parse(String data) { return parse(new FlowParseParam(data)); } @Override public List parse(Map map) { return parse(new FlowParseParam(map)); } @Override public List parse(String data, boolean parseEl) { return parse(new FlowParseParam(data, parseEl)); } @Override public List parse(Map map, boolean parseEl) { return parse(new FlowParseParam(map, parseEl)); } @Override public List parse(FlowParseParam param) { String stringDef = param.getStringDefinition(); Object defObj = param.getObjectDefinition(); if (stringDef == null && defObj == null) { throw new FlowException("definition is empty"); } Object stringDefObj = stringDef == null ? null : JsonUtil.parseObject(stringDef, Object.class);; if (defObj == null) { defObj = stringDefObj; } if (stringDef == null) { stringDef = JsonUtil.toJsonString(defObj); } List allFlowList = null; if (defObj instanceof Map) { allFlowList = parseMapDef((Map) defObj, param.isParseEl()); } else { allFlowList = new ArrayList(); for (int i = 0; i < ((List) defObj).size(); i++) { Map defMap = ((List>) defObj).get(i); List flowList = parseMapDef(defMap, param.isParseEl()); allFlowList.addAll(flowList); if (i > 0) { flowList.get(0).setProperty(FLOW_STRING_KEY, JsonUtil.toJsonString(defMap)); } } } for (int i = 1; i < allFlowList.size(); i++) { allFlowList.get(i).setProperty(MAIN_FLOW_ID_KEY, allFlowList.get(0).getId()); } allFlowList.get(0).setProperty(FLOW_STRING_KEY, stringDef); return allFlowList; } private List parseMapDef(Map mapDef, boolean parseEl) { List flowList = new ArrayList(); parse(mapDef, flowList, parseEl); return flowList; } private Flow parse(Map map, List flowList, boolean parseEl) { Flow flow = new Flow(); flow.setFlowParser(this); flowList.add(flow); int subFlowStartIndex = flowList.size(); List parseListeners = parseParseListeners(map, flow, parseEl); if (preListeners != null && preListeners.size() > 0) { if (parseListeners == null) { parseListeners = new ArrayList(); } parseListeners.addAll(0, preListeners); } if (postListeners != null && postListeners.size() > 0) { if (parseListeners == null) { parseListeners = new ArrayList(); } parseListeners.addAll(postListeners); } triggerParseEvent(parseListeners, FlowParseEventTypes.PARSE_FLOW_START, map, flow, null, parseEl); flow.setId((String) map.get(DefConstants.COMMON_PROP_ID)); flow.setName((String) map.get(DefConstants.COMMON_PROP_NAME)); flow.setLogFlag((Boolean) map.get(DefConstants.FLOW_PROP_LOG_FLAG)); // Parse property Map properties = (Map) map.get(DefConstants.COMMON_PROP_PROPERTIES); flow.putProperties(properties); // Parse flow pre handler parseFlowPreHandler(map.get(DefConstants.FLOW_PROP_PRE), flow, parseEl); // Parse node List> nodeListConf = (List>) map.get(DefConstants.FLOW_PROP_NODES); List startNodeIdList = new ArrayList(); if (nodeListConf != null) { for (Map nodeConf : nodeListConf) { // start node judge boolean start = nodeConf.get(DefConstants.NODE_PROP_START) == null ? false : (Boolean) nodeConf.get(DefConstants.NODE_PROP_START); if (start) { startNodeIdList.add((String) nodeConf.get(DefConstants.COMMON_PROP_ID)); } // create node String type = (String) nodeConf.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || nodeConf.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) nodeConf.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(nodeConf, null, flow); FlowNode node = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (node != null) { node.postConstruct(nodeConf, null); flow.addNode(node); } } } else { NodeImpl node = new NodeImpl(); node.setId((String) nodeConf.get(DefConstants.COMMON_PROP_ID)); node.setName((String) nodeConf.get(DefConstants.COMMON_PROP_NAME)); node.putProperties((Map) nodeConf.get(DefConstants.COMMON_PROP_PROPERTIES)); node.setPreHandler(parseNodePreHandler( new PreParseParam(nodeConf.get(DefConstants.NODE_PROP_PRE), flowList, parseEl, flow, node))); node.setAction(parseNodeAction(new ActionParseParam(nodeConf.get(DefConstants.NODE_PROP_ACTION), flowList, parseEl, flow, node))); node.setPostHandler(parseNodePostHandler( new PostParseParam(nodeConf.get(DefConstants.NODE_PROP_POST), flowList, parseEl, flow, node))); // compensate action parse if (node.getProperty(DefConstants.NODE_PROPERTIES_PROP_COMPENSATE_ACTION) != null) { NodeAction compensateAction = parseNodeAction(new ActionParseParam(nodeConf.get(DefConstants.NODE_PROPERTIES_PROP_COMPENSATE_ACTION), flowList, parseEl, flow, node)); node.setProperty(FlowConstants.PROP_RUNTIME_COMPENSATE_ACTION, compensateAction); } node.postConstruct(nodeConf, null); flow.addNode(node); } } } // set start node. if (startNodeIdList.size() > 0) { flow.setStartNodeIds(startNodeIdList.toArray(new String[] {})); } // Parse flow post handler parseFlowPostHandler(map.get(DefConstants.FLOW_PROP_POST), flow, parseEl); // Listener parseListeners(map, flow, parseEl); // Filter parseFilters(map, flow, parseEl); // Node filter parseNodeFilter(map, flow, parseEl); // Node pre handler filter parseNodePreHandlerFilter(map, flow, parseEl); // Node action filter parseNodeActionFilter(map, flow, parseEl); // Node post handler filter parseNodePostHandlerFilter(map, flow, parseEl); // Flow pre handler filter parseFlowPreHandlerFilter(map, flow, parseEl); // Flow post handler filter parseFlowPostHandlerFilter(map, flow, parseEl); // Flow runner parseRunner(map, flow, parseEl); flow.postConstruct(map, null); triggerParseEvent(parseListeners, FlowParseEventTypes.PARSE_FLOW_END, map, flow, null, parseEl); triggerParseEvent(parseListeners, FlowParseEventTypes.INIT_FLOW_START, map, flow, null, parseEl); InitContext initContext = new InitContext(); initContext.setFlowParser(this); initContext.setParseEl(parseEl); initContext.setFlowList(flowList); initContext.setFlowDefinitionMap(map); initContext.setFlow(flow); flow.init(initContext, null); triggerParseEvent(parseListeners, FlowParseEventTypes.INIT_FLOW_END, map, flow, null, parseEl); for (int j = subFlowStartIndex; j < flowList.size(); j++) { if (flowList.get(j).getProperty(PARENT_FLOW_ID_KEY) == null) { flowList.get(j).setProperty(PARENT_FLOW_ID_KEY, flowList.get(subFlowStartIndex - 1).getId()); } } return flow; } protected void parseFlowPreHandler(Object preDef, Flow flow, boolean parseEl) { if (preDef == null) { return; } if (preDef instanceof String) { ExpFlowPreHandler handler = new ExpFlowPreHandler(); handler.setExp((String) preDef); flow.setPreHandler(handler); return; } Map pre = (Map) preDef; String type = (String) pre.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || pre.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (!parseEl) { return; } String exp = (String) pre.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(pre, null, flow); FlowPreHandler preHandler = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (preHandler != null) { preHandler.postConstruct(pre, null); flow.setPreHandler(preHandler); } return; } throw new IllegalArgumentException("Param illegal " + preDef); } protected void parseFlowPostHandler(Object postDef, Flow flow, boolean parseEl) { if (postDef == null) { return; } if (postDef instanceof String) { ExpFlowPostHandler handler = new ExpFlowPostHandler(); handler.setExp((String) postDef); flow.setPostHandler(handler); return; } Map post = (Map) postDef; String type = (String) post.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || post.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (!parseEl) { return; } String exp = (String) post.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(post, null, flow); FlowPostHandler postHandler = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (postHandler != null) { postHandler.postConstruct(post, null); flow.setPostHandler(postHandler); } return; } throw new IllegalArgumentException("Param illegal " + postDef); } /** * Parse listener. * * @param map * @param flow * @param parseEl */ private void parseListeners(Map map, Flow flow, boolean parseEl) { List> listeners = (List>) map.get(DefConstants.FLOW_PROP_LISTENERS); if (listeners != null) { for (Object listenerObj : listeners) { if (listenerObj instanceof String) { ExpFlowEventListener expListener = new ExpFlowEventListener((String) listenerObj); flow.getEventTrigger().addListener(expListener); } else { Map listener = (Map) listenerObj; String type = (String) listener.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || listener.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) listener.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(listener, null, flow); FlowEventListener eventListener = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (eventListener != null) { eventListener.postConstruct(listener, null); flow.getEventTrigger().addListener(eventListener); } } } } } } } /** * Parse filter. * * @param map * @param flow * @param parseEl */ private void parseFilters(Map map, Flow flow, boolean parseEl) { List> filters = (List>) map.get(DefConstants.FLOW_PROP_FILTERS); if (filters != null) { for (Object filterObj : filters) { if (filterObj instanceof String) { ExpFilter expFilter = new ExpFilter<>((String) filterObj); flow.getFilterManager().addFilter(expFilter); } else { Map filter = (Map) filterObj; String type = (String) filter.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || filter.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) filter.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(filter, null, flow); Filter flowFilter = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (flowFilter != null) { flowFilter.postConstruct(filter, null); flow.getFilterManager().addFilter(flowFilter); } } } } } } } /** * Parse node filter. * * @param map * @param flow * @param parseEl */ private void parseNodeFilter(Map map, Flow flow, boolean parseEl) { List> nodeFilters = (List>) map .get(DefConstants.FLOW_PROP_NODE_FILTERS); if (nodeFilters != null) { for (Object filterObj : nodeFilters) { if (filterObj instanceof String) { ExpFilter expFilter = new ExpFilter<>((String) filterObj); flow.getFilterManager().addFilter(expFilter); } else { Map filter = (Map) filterObj; String type = (String) filter.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || filter.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) filter.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(filter, null, flow); Filter nodeFilter = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (nodeFilter != null) { nodeFilter.postConstruct(filter, null); flow.getFilterManager().addNodeFilter(nodeFilter); } } } } } } } /** * Parse node pre handler filter. * * @param map * @param flow * @param parseEl */ private void parseNodePreHandlerFilter(Map map, Flow flow, boolean parseEl) { List> nodePreHandlerFilters = (List>) map .get(DefConstants.FLOW_PROP_NODE_PRE_HANDLER_FILTERS); if (nodePreHandlerFilters != null) { for (Object filterObj : nodePreHandlerFilters) { if (filterObj instanceof String) { ExpFilter expFilter = new ExpFilter<>((String) filterObj); flow.getFilterManager().addFilter(expFilter); } else { Map filter = (Map) filterObj; String type = (String) filter.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || filter.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) filter.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(filter, null, flow); Filter nodePreHandlerFilter = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (nodePreHandlerFilter != null) { nodePreHandlerFilter.postConstruct(filter, null); flow.getFilterManager().addNodePreHandlerFilter(nodePreHandlerFilter); } } } } } } } /** * Parse flow pre handler filter. * * @param map * @param flow * @param parseEl */ private void parseFlowPreHandlerFilter(Map map, Flow flow, boolean parseEl) { List> flowPreHandlerFilters = (List>) map .get(DefConstants.FLOW_PROP_FLOW_PRE_HANDLER_FILTERS); if (flowPreHandlerFilters != null) { for (Object filterObj : flowPreHandlerFilters) { if (filterObj instanceof String) { ExpFilter expFilter = new ExpFilter<>((String) filterObj); flow.getFilterManager().addFilter(expFilter); } else { Map filter = (Map) filterObj; String type = (String) filter.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || filter.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) filter.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(filter, null, flow); Filter flowPreHandlerFilter = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (flowPreHandlerFilter != null) { flowPreHandlerFilter.postConstruct(filter, null); flow.getFilterManager().addFlowPreHandlerFilter(flowPreHandlerFilter); } } } } } } } /** * Parse flow pre handler filter. * * @param map * @param flow * @param parseEl */ private void parseFlowPostHandlerFilter(Map map, Flow flow, boolean parseEl) { List> flowPostHandlerFilters = (List>) map .get(DefConstants.FLOW_PROP_FLOW_POST_HANDLER_FILTERS); if (flowPostHandlerFilters != null) { for (Object filterObj : flowPostHandlerFilters) { if (filterObj instanceof String) { ExpFilter expFilter = new ExpFilter<>((String) filterObj); flow.getFilterManager().addFilter(expFilter); } else { Map filter = (Map) filterObj; String type = (String) filter.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || filter.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) filter.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(filter, null, flow); Filter flowPostHandlerFilter = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (flowPostHandlerFilter != null) { flowPostHandlerFilter.postConstruct(filter, null); flow.getFilterManager().addFlowPostHandlerFilter(flowPostHandlerFilter); } } } } } } } /** * Parse node action filter. * * @param map * @param flow * @param parseEl */ private void parseNodeActionFilter(Map map, Flow flow, boolean parseEl) { List> nodeActionFilters = (List>) map .get(DefConstants.FLOW_PROP_NODE_ACTION_FILTERS); if (nodeActionFilters != null) { for (Object filterObj : nodeActionFilters) { if (filterObj instanceof String) { ExpFilter expFilter = new ExpFilter<>((String) filterObj); flow.getFilterManager().addFilter(expFilter); } else { Map filter = (Map) filterObj; String type = (String) filter.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || filter.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) filter.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(filter, null, flow); Filter nodeActionFilter = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (nodeActionFilter != null) { nodeActionFilter.postConstruct(filter, null); flow.getFilterManager().addNodeActionFilter(nodeActionFilter); } } } } } } } /** * Parse node post handler filter. * * @param map * @param flow * @param parseEl */ private void parseNodePostHandlerFilter(Map map, Flow flow, boolean parseEl) { List> nodePostHandlerFilters = (List>) map .get(DefConstants.FLOW_PROP_NODE_POST_HANDLER_FILTERS); if (nodePostHandlerFilters != null) { for (Object filterObj : nodePostHandlerFilters) { if (filterObj instanceof String) { ExpFilter expFilter = new ExpFilter<>((String) filterObj); flow.getFilterManager().addFilter(expFilter); } else { Map filter = (Map) filterObj; String type = (String) filter.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || filter.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) filter.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(filter, null, flow); Filter nodePostHandlerFilter = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (nodePostHandlerFilter != null) { nodePostHandlerFilter.postConstruct(filter, null); flow.getFilterManager().addNodePostHandlerFilter(nodePostHandlerFilter); } } } } } } } /** * Parse flow runner. * * @param map * @param flow * @param parseEl */ private void parseRunner(Map map, Flow flow, boolean parseEl) { Object runnerObj = (Map) map.get(DefConstants.FLOW_PROP_RUNNER); if (runnerObj != null) { if (runnerObj instanceof String) { ExpFlowRunner expFlowRunner = new ExpFlowRunner((String) runnerObj); flow.setRunner(expFlowRunner); } else { Map runner = (Map) runnerObj; String type = (String) runner.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || runner.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) runner.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(runner, null, flow); FlowRunner flowRunner = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (flowRunner != null) { flowRunner.postConstruct(runner, null); flow.setRunner(flowRunner); } } } } } } @Override public NodePreHandler parseNodePreHandler(PreParseParam param) { Object preDef = param.getPreDef(); if (preDef == null) { return null; } if (preDef instanceof String) { ExpNodePreHandler handler = new ExpNodePreHandler(); handler.setExp((String) preDef); return handler; } Map pre = (Map) preDef; String type = (String) pre.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || pre.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (!param.isParseEl()) { return null; } String exp = (String) pre.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(pre, param.getNode(), param.getFlow()); NodePreHandler preHandler = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (preHandler != null) { preHandler.postConstruct(pre, null); } return preHandler; } else if (DefConstants.COMMON_PROP_EXP.equals(type) || pre.containsKey(DefConstants.COMMON_PROP_EXP)) { String exp = (String) pre.get(DefConstants.COMMON_PROP_EXP); ExpNodePreHandler preHandler = new ExpNodePreHandler(); preHandler.setExp(exp); preHandler.postConstruct(pre, null); return preHandler; } else if (DefConstants.NODE_PRE_TYPE_INCLUSIVECHECK.equals(type)) { List preNodes = (List) pre.get(DefConstants.NODE_PRE_PROP_PRE_NODES); InclusiveCheckPreHandler preHandler = new InclusiveCheckPreHandler(); preHandler.setPreNodes(preNodes); preHandler.postConstruct(pre, null); return preHandler; } else if (DefConstants.NODE_PRE_TYPE_MULTICHECK.equals(type) || pre.containsKey(DefConstants.NODE_PRE_PROP_PRE_NODES)) { List preNodes = (List) pre.get(DefConstants.NODE_PRE_PROP_PRE_NODES); MultiCheckPreHandler preHandler = new MultiCheckPreHandler(); preHandler.setPreNodes(preNodes); preHandler.postConstruct(pre, null); return preHandler; } throw new IllegalArgumentException("Param illegal " + pre); } @Override public NodeAction parseNodeAction(ActionParseParam param) { Object actionDef = param.getActionDef(); if (actionDef == null) { return null; } if (actionDef instanceof String) { ExpNodeAction nodeAction = new ExpNodeAction(); nodeAction.setExp((String) actionDef); return nodeAction; } Map action = (Map) actionDef; String type = (String) action.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || action.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (!param.isParseEl()) { return null; } String exp = (String) action.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(action, param.getNode(), param.getFlow()); NodeAction nodeAction = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (nodeAction != null) { nodeAction.postConstruct(action, null); } return nodeAction; } else if (DefConstants.COMMON_PROP_EXP.equals(type) || action.containsKey(DefConstants.COMMON_PROP_EXP)) { ExpNodeAction nodeAction = new ExpNodeAction(); String exp = (String) action.get(DefConstants.COMMON_PROP_EXP); nodeAction.setExp(exp); nodeAction.postConstruct(action, null); return nodeAction; } else if (DefConstants.NODE_ACTION_TYPE_FLOW.equals(type) || (type == null && action.containsKey(DefConstants.COMMON_PROP_FLOW)) || (type == null && action.containsKey(DefConstants.COMMON_PROP_FLOW_ID))) { FlowNodeAction nodeAction = new FlowNodeAction(); if (action.containsKey(DefConstants.COMMON_PROP_FLOW)) { Flow flow = parse((Map) action.get(DefConstants.COMMON_PROP_FLOW), param.getFlowList(), param.isParseEl()); flow.setProperty(FLOW_STRING_KEY, JsonUtil.toJsonString(action.get(DefConstants.COMMON_PROP_FLOW))); nodeAction.setFlowId(flow.getId()); nodeAction.setFlow(flow); } else if (action.containsKey(DefConstants.COMMON_PROP_FLOW_ID)) { nodeAction.setFlowId((String) action.get(DefConstants.COMMON_PROP_FLOW_ID)); } Object startNodeId = action.get(DefConstants.NODE_ACTION_PROP_START_NODE_ID); if (startNodeId != null) { if (startNodeId instanceof String) { nodeAction.setStartNodeIds(new String[] { (String) startNodeId }); } else { nodeAction.setStartNodeIds(((List) startNodeId).toArray(new String[] {})); } } Boolean inherit = (Boolean) action.get(DefConstants.NODE_ACTION_PROP_INHERIT); if (inherit != null) { nodeAction.setInherit(inherit); } nodeAction.postConstruct(action, null); return nodeAction; } else if (DefConstants.NODE_ACTION_TYPE_EVENT.equals(type)) { EventNodeAction nodeAction = new EventNodeAction(); nodeAction.postConstruct(action, null); return nodeAction; } else if (DefConstants.NODE_ACTION_TYPE_LOOP.equals(type)) { LoopNodeAction nodeAction = new LoopNodeAction(); nodeAction.postConstruct(action, null); return nodeAction; } else if (DefConstants.NODE_ACTION_TYPE_INTERRUPT.equals(type)) { InterruptNodeAction nodeAction = new InterruptNodeAction(); nodeAction.postConstruct(action, null); return nodeAction; }else if (DefConstants.NODE_ACTION_TYPE_COMPENSATE.equals(type)) { CompensateNodeAction nodeAction = new CompensateNodeAction(); nodeAction.postConstruct(action, null); return nodeAction; } else if (DefConstants.NODE_ACTION_TYPE_PARAM_EXECUTOR.equals(type)) { ParamExecutorNodeAction nodeAction = new ParamExecutorNodeAction(); nodeAction.postConstruct(action, null); return nodeAction; } throw new IllegalArgumentException("Param illegal " + action); } @Override public NodePostHandler parseNodePostHandler(PostParseParam param) { Object postDef = param.getPostDef(); if (postDef == null) { return null; } if (postDef instanceof String) { ExpNodePostHandler postHandler = new ExpNodePostHandler(); String exp = (String) postDef; postHandler.setExp(exp); return postHandler; } Map post = (Map) postDef; String type = (String) post.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || post.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (!param.isParseEl()) { return null; } String exp = (String) post.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(post, param.getNode(), param.getFlow()); NodePostHandler postHandler = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (postHandler != null) { postHandler.postConstruct(post, null); } return postHandler; } else if (DefConstants.COMMON_PROP_EXP.equals(type) || post.containsKey(DefConstants.COMMON_PROP_EXP)) { ExpNodePostHandler postHandler = new ExpNodePostHandler(); String exp = (String) post.get(DefConstants.COMMON_PROP_EXP); postHandler.setExp(exp); postHandler.postConstruct(post, null); return postHandler; } else if (DefConstants.NODE_POST_TYPE_CONDITION.equals(type) || (type == null && post.containsKey(DefConstants.NODE_POST_PROP_CONDITIONS)) || (type == null && post.containsKey(DefConstants.NODE_POST_PROP_WHEN))) { List> conditionList = null; if (post.containsKey(DefConstants.NODE_POST_PROP_CONDITIONS)) { conditionList = (List>) post.get(DefConstants.NODE_POST_PROP_CONDITIONS); } else { conditionList = Arrays.asList(post); } String conditionType = (String) post.get(DefConstants.NODE_POST_PROP_CONDITION_TYPE); Object defaultBranch = post.get(DefConstants.NODE_POST_PROP_DEFAULT_TO); ConditionalNodePostHandler postHandler = new ConditionalNodePostHandler(conditionType, conditionList, defaultBranch); postHandler.postConstruct(post, null); return postHandler; } else if (DefConstants.NODE_POST_TYPE_FIXED.equals(type) || (type == null && post.containsKey(DefConstants.NODE_POST_PROP_TO))) { Object nextStartId = post.get(DefConstants.NODE_POST_PROP_TO); FixedNodePostHandler postHandler = new FixedNodePostHandler(nextStartId); postHandler.postConstruct(post, null); return postHandler; } else if (DefConstants.NODE_POST_TYPE_EVENT.equals(type)) { EventPostHandler postHandler = new EventPostHandler(); postHandler.postConstruct(post, null); return postHandler; } throw new IllegalArgumentException("Param illegal " + post); } protected List parseParseListeners(Map map, Flow flow, boolean parseEl) { List> parseListenerConfList = (List>) map .get(DefConstants.FLOW_PROP_PARSE_LISTENERS); if (parseListenerConfList == null) { return null; } List parseListeners = new ArrayList<>(); for (Object listenerObj : parseListenerConfList) { if (listenerObj instanceof String) { ExpFlowParseEventListener parseListener = new ExpFlowParseEventListener((String) listenerObj); parseListeners.add(parseListener); } else { Map listener = (Map) listenerObj; String type = (String) listener.get(DefConstants.COMMON_PROP_TYPE); if (DefConstants.COMMON_PROP_CREATE.equals(type) || listener.containsKey(DefConstants.COMMON_PROP_CREATE_EXP)) { if (parseEl) { String exp = (String) listener.get(DefConstants.COMMON_PROP_CREATE_EXP); Map elContext = createElContext(listener, null, flow); FlowParseEventListener parseListener = getElEvaluator().evalWithDefaultContext(exp, elContext, false); if (parseListener != null) { parseListener.postConstruct(listener, null); parseListeners.add(parseListener); } } } } } return parseListeners; } private void triggerParseEvent(List listeners, String eventType, Map flowDef, Flow flow, Object data, boolean parseEl) { if (listeners == null || listeners.size() == 0) { return; } FlowParseEvent event = new FlowParseEvent(); event.setType(eventType); event.setFlow(flow); event.setFlowDef(flowDef); event.setData(data); event.setFlowParser(this); event.setParseEl(parseEl); for (FlowParseEventListener listener : listeners) { listener.on(event); } } /** * * Convert java model to string. * * @param flow * @return */ @Override public String stringify(Flow flow) { if (flow.getProperty(FLOW_STRING_KEY) != null) { return flow.getProperty(FLOW_STRING_KEY); } logger.warn("No original string definition, unsupported now. flowId: " + flow.getId()); return null; } private Map createElContext(Map currentDef, FlowNode node, Flow flow) { Map context = new HashMap<>(3); context.put("definition", currentDef); if (node != null) { context.put("node", node); } if (flow != null) { context.put("flow", flow); } context.put("flowParser", this); return context; } public ElEvaluator getElEvaluator() { if (elEvaluator == null) { elEvaluator = ElFactory.get(); } return elEvaluator; } public void setElEvaluator(ElEvaluator elEvaluator) { this.elEvaluator = elEvaluator; } public List getPreListeners() { return preListeners; } public void setPreListeners(List preListeners) { this.preListeners = preListeners; } public List getPostListeners() { return postListeners; } public void setPostListeners(List postListeners) { this.postListeners = postListeners; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/event/ExpFlowParseEventListener.java ================================================ package com.jd.easyflow.flow.model.parser.event; import java.util.HashMap; import java.util.Map; /** * * @author liyuliang5 */ public class ExpFlowParseEventListener implements FlowParseEventListener { private String exp; public ExpFlowParseEventListener() { } public ExpFlowParseEventListener(String exp) { this.exp = exp; } @Override public void on(FlowParseEvent event) { Map data = new HashMap<>(); data.put("event", event); event.getFlowParser().getElEvaluator().evalWithDefaultContext(exp, data, false); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/event/FlowParseEvent.java ================================================ package com.jd.easyflow.flow.model.parser.event; import java.util.Map; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.parser.FlowParser; /** * * @author liyuliang5 * */ public class FlowParseEvent { private String type; private Map flowDef; private Flow flow; private Object data; private FlowParser flowParser; private boolean parseEl; public Flow getFlow() { return flow; } public void setFlow(Flow flow) { this.flow = flow; } public String getType() { return type; } public void setType(String type) { this.type = type; } public Map getFlowDef() { return flowDef; } public void setFlowDef(Map flowDef) { this.flowDef = flowDef; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public FlowParser getFlowParser() { return flowParser; } public void setFlowParser(FlowParser flowParser) { this.flowParser = flowParser; } public boolean isParseEl() { return parseEl; } public void setParseEl(boolean parseEl) { this.parseEl = parseEl; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/event/FlowParseEventListener.java ================================================ package com.jd.easyflow.flow.model.parser.event; import java.util.Map; /** * * @author liyuliang5 * */ public interface FlowParseEventListener { public void on(FlowParseEvent event); default void postConstruct(Map definition, Map context) {} } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/event/FlowParseEventTypes.java ================================================ package com.jd.easyflow.flow.model.parser.event; /** * * @author liyuliang5 * */ public class FlowParseEventTypes { public static final String PARSE_FLOW_START = "PARSE_FLOW_START"; public static final String PARSE_FLOW_END = "PARSE_FLOW_END"; public static final String INIT_FLOW_START = "INIT_FLOW_START"; public static final String INIT_FLOW_END = "INIT_FLOW_END"; } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/param/ActionParseParam.java ================================================ package com.jd.easyflow.flow.model.parser.param; import java.util.List; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.parser.FlowParser; /** * * @author liyuliang5 * */ public class ActionParseParam { private Object actionDef; private List flowList; private boolean parseEl; private Flow flow; private FlowNode node; public ActionParseParam() { // NOOP } public ActionParseParam(Object actionDef, List flowList, boolean parseEl, Flow flow, FlowNode node) { this.actionDef = actionDef; this.flowList = flowList; this.parseEl = parseEl; this.flow = flow; this.node = node; } public Object getActionDef() { return actionDef; } public void setActionDef(Object actionDef) { this.actionDef = actionDef; } public List getFlowList() { return flowList; } public void setFlowList(List flowList) { this.flowList = flowList; } public boolean isParseEl() { return parseEl; } public void setParseEl(boolean parseEl) { this.parseEl = parseEl; } public FlowNode getNode() { return node; } public void setNode(FlowNode node) { this.node = node; } public Flow getFlow() { return flow; } public void setFlow(Flow flow) { this.flow = flow; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/param/FlowParseParam.java ================================================ package com.jd.easyflow.flow.model.parser.param; import java.util.Map; /** * * @author liyuliang5 * */ public class FlowParseParam { private String stringDefinition; private Object objectDefinition; private boolean parseEl = true; public FlowParseParam() { // NOOP } public FlowParseParam(String stringDefinition) { this.stringDefinition = stringDefinition; } public FlowParseParam(Object objectDefinition) { this.objectDefinition = objectDefinition; } public FlowParseParam(String stringDefinition, boolean parseEl) { this.stringDefinition = stringDefinition; this.parseEl = parseEl; } public FlowParseParam(Object objectDefinition, boolean parseEl) { this.objectDefinition = objectDefinition; this.parseEl = parseEl; } public String getStringDefinition() { return stringDefinition; } public void setStringDefinition(String stringDefinition) { this.stringDefinition = stringDefinition; } public Object getObjectDefinition() { return objectDefinition; } public void setObjectDefinition(Object objectDefinition) { this.objectDefinition = objectDefinition; } public boolean isParseEl() { return parseEl; } public void setParseEl(boolean parseEl) { this.parseEl = parseEl; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/param/PostParseParam.java ================================================ package com.jd.easyflow.flow.model.parser.param; import java.util.List; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; /** * * @author liyuliang5 * */ public class PostParseParam { private Object postDef; private List flowList; private boolean parseEl; private Flow flow; private FlowNode node; public PostParseParam() { // NOOP } public PostParseParam(Object postDef,List flowList, boolean parseEl, Flow flow, FlowNode node) { this.postDef = postDef; this.flowList = flowList; this.parseEl = parseEl; this.flow = flow; this.node = node; } public Object getPostDef() { return postDef; } public void setPostDef(Object postDef) { this.postDef = postDef; } public boolean isParseEl() { return parseEl; } public void setParseEl(boolean parseEl) { this.parseEl = parseEl; } public FlowNode getNode() { return node; } public void setNode(FlowNode node) { this.node = node; } public List getFlowList() { return flowList; } public void setFlowList(List flowList) { this.flowList = flowList; } public Flow getFlow() { return flow; } public void setFlow(Flow flow) { this.flow = flow; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/parser/param/PreParseParam.java ================================================ package com.jd.easyflow.flow.model.parser.param; import java.util.List; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; /** * * @author liyuliang5 * */ public class PreParseParam { private Object preDef; private List flowList; private boolean parseEl; private Flow flow; private FlowNode node; public PreParseParam() { // NOOP } public PreParseParam(Object preDef, List flowList, boolean parseEl, Flow flow, FlowNode node) { this.preDef = preDef; this.flowList = flowList; this.parseEl = parseEl; this.flow = flow; this.node = node; } public Object getPreDef() { return preDef; } public void setPreDef(Object preDef) { this.preDef = preDef; } public boolean isParseEl() { return parseEl; } public void setParseEl(boolean parseEl) { this.parseEl = parseEl; } public FlowNode getNode() { return node; } public void setNode(FlowNode node) { this.node = node; } public List getFlowList() { return flowList; } public void setFlowList(List flowList) { this.flowList = flowList; } public Flow getFlow() { return flow; } public void setFlow(Flow flow) { this.flow = flow; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/post/AbstractNodePostHandler.java ================================================ package com.jd.easyflow.flow.model.post; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.definition.DefConstants; /** * * @author liyuliang5 * */ public abstract class AbstractNodePostHandler implements NodePostHandler { private static final String IDX_VAR_PREFIX = "$"; protected NodeContext[] parseToNodes(Object to, NodeContext nodeContext, FlowContext context) { if (to == null) { return null; } if (to instanceof String) { String toStr = (String) to; if (!toStr.startsWith(IDX_VAR_PREFIX)) { return new NodeContext[] { nodeId2Node(toStr) }; } else { return new NodeContext[] { nodeId2Node(parseIndexVar(toStr, nodeContext, context)) }; } } else if (to instanceof Integer) { int toIdx = (Integer) to; return new NodeContext[] { nodeId2Node(context.getFlow().getNodeList().get(toIdx).getId()) }; } else if (to instanceof List) { List toList = (List) to; List toResult = new ArrayList<>(toList.size()); for (Object toObj : toList) { NodeContext[] nodes = parseToNodes(toObj, nodeContext, context); addArray2List(nodes, toResult); } NodeContext[] result = new NodeContext[toResult.size()]; return toResult.toArray(result); } else if (to instanceof Map) { Map toMap = (Map) to; NodeContext[] toNodes = null; String toExp = (String) toMap.get("exp"); if (toExp != null) { // parse exp Object result = context.getElEvaluator().eval(toExp, nodeContext, context, null); if (result == null) { return null; } else if (result instanceof NodeContext[]) { toNodes = (NodeContext[]) result; } else { toNodes = parseToNodes(result, nodeContext, context); } } else { // parse node Object node = toMap.get("node"); toNodes = parseToNodes(node, nodeContext, context); } // fill context data Map dataConf = (Map) toMap.get("data"); if (dataConf != null && toNodes != null) { for (Entry entry : dataConf.entrySet()) { Object value = parseDataValue(entry.getValue(), nodeContext, context); for (NodeContext node : toNodes) { node.put(entry.getKey(), value); } } } return toNodes; } else if (to instanceof NodeExecutor) { Object result = ((NodeExecutor) to).execute(nodeContext, context); if (result == null) { return null; } else if (result instanceof NodeContext[]) { return (NodeContext[]) result; } else { return parseToNodes(result, nodeContext, context); } } else { throw new UnsupportedOperationException("Unsupported type:" + to.getClass()); } } protected Object parseToDefinition(Object to, FlowNode node, InitContext initContext) { if (to instanceof List) { List list = (List) to; for (int i = 0; i < list.size(); i++) { Object element = list.get(i); if (element instanceof List || element instanceof Map) { list.set(i, parseToDefinition(element, node, initContext)); } } } else if (to instanceof Map) { Map map = (Map) to; if (map.get("type") != null) { throw new IllegalArgumentException("type is reserved"); } Map data = (Map) map.get("data"); String createExp = (String) map.get(DefConstants.COMMON_PROP_CREATE_EXP); if (createExp != null && initContext.isParseEl()) { if (data != null) { throw new IllegalArgumentException("data is reserved"); } Map context = new HashMap<>(3); context.put("definition", map); context.put("node", node); context.put("flow", initContext.getFlow()); context.put("flowParser", initContext.getFlowParser()); NodeExecutor executor = initContext.getFlowParser().getElEvaluator().evalWithDefaultContext(createExp, context, false); return executor; } if (data != null) { for (Entry entry : data.entrySet()) { if (entry.getValue() instanceof Map) { entry.setValue(parseDataValueDefinition(entry.getValue(), node, initContext)); } } } } return to; } private Object parseDataValueDefinition(Object dataValue, FlowNode node, InitContext initContext) { if (dataValue instanceof Map) { Map map = (Map) dataValue; if (map.get("type") != null) { throw new IllegalArgumentException("type is reserved"); } String createExp = (String) map.get(DefConstants.COMMON_PROP_CREATE_EXP); if (createExp != null && initContext.isParseEl()) { Map context = new HashMap<>(3); context.put("definition", map); context.put("node", node); context.put("flow", initContext.getFlow()); context.put("flowParser", initContext.getFlowParser()); NodeExecutor executor = initContext.getFlowParser().getElEvaluator().evalWithDefaultContext(createExp, context, false); return executor; } } return dataValue; } private Object parseDataValue(Object value, NodeContext nodeContext, FlowContext context) { if (value instanceof String) { return context.getElEvaluator().eval((String) value, nodeContext, context, null); } else if (value instanceof Map) { Map valueMap = (Map) value; if (valueMap.containsKey(DefConstants.COMMON_PROP_EXP)) { String exp = (String) valueMap.get(DefConstants.COMMON_PROP_EXP); return context.getElEvaluator().eval(exp, nodeContext, context, null); } else if (valueMap.containsKey("fixedValue")) { return valueMap.get("fixedValue"); } else { throw new IllegalArgumentException("illegal data map, " + value); } } else if (value instanceof NodeExecutor) { return ((NodeExecutor) value).execute(nodeContext, context); } else { throw new IllegalArgumentException("illegal data " + value); } } private String parseIndexVar(String var, NodeContext nodeContext, FlowContext flowContext) { int index = -1; Flow flow = flowContext.getFlow(); switch (var) { case "$first": { index = 0; break; } case "$last": { index = flow.getNodeList().size() - 1; break; } case "$previous": { index = flow.getNodeIndex(nodeContext.getNodeId()) - 1; break; } case "$next": { index = flow.getNodeIndex(nodeContext.getNodeId()) + 1; break; } default: { throw new UnsupportedOperationException("Unsupported vars:" + var); } } return flow.getNodeList().get(index).getId(); } protected NodeContext[] list2Array(List list) { if (list == null) { return null; } NodeContext[] nodes = new NodeContext[list.size()]; return list.toArray(nodes); } protected NodeContext[] nodeIds2Nodes(List nodeIds) { if (nodeIds == null) { return null; } NodeContext[] nodes = new NodeContext[nodeIds.size()]; for (int i = 0; i < nodeIds.size(); i++) { nodes[i] = new NodeContext(nodeIds.get(i)); } return nodes; } protected NodeContext nodeId2Node(String nodeId) { if (nodeId == null) { return null; } return new NodeContext(nodeId); } protected void addArray2List(NodeContext[] nodes, List list) { if (nodes == null) { return; } for (NodeContext node : nodes) { list.add(node); } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/post/ConditionalNodePostHandler.java ================================================ package com.jd.easyflow.flow.model.post; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.util.FlowUtil; /** * * @author liyuliang5 * */ public class ConditionalNodePostHandler extends AbstractNodePostHandler { private static final Logger logger = LoggerFactory.getLogger(ConditionalNodePostHandler.class); public static final String EXCLUSIVE_TYPE = "exclusive"; public static final String INCLUSIVE_TYPE = "inclusive"; private String type; private List> branchList; private List branchInfoList; private Object defaultBranch; public ConditionalNodePostHandler() { } public ConditionalNodePostHandler(Map branch) { this.branchList = Arrays.asList(branch); } public ConditionalNodePostHandler(List> branchList) { this.branchList = branchList; } public ConditionalNodePostHandler(String type, List> branchList, Object defaultBranch) { this.type = type; this.branchList = branchList; this.defaultBranch = defaultBranch; } @Override public NodeContext[] postHandle(NodeContext nodeContext, FlowContext context) { if (branchInfoList == null) { init(nodeContext, context); } // Exclusive if (type == null || type.equals(EXCLUSIVE_TYPE)) { for (Branch branch : branchInfoList) { boolean result = branch.when == null ? true : branch.when.execute(nodeContext, context); if (result) { return parseToNodes(branch.to, nodeContext, context); } } if (defaultBranch != null) { return parseToNodes(defaultBranch, nodeContext, context); } // Inclusive } else { List nextList = new ArrayList<>(); for (Branch branch : branchInfoList) { boolean result = branch.when == null ? true : branch.when.execute(nodeContext, context); if (result) { addArray2List(parseToNodes(branch.to, nodeContext, context), nextList); } } if (nextList.isEmpty() && defaultBranch != null) { addArray2List(parseToNodes(defaultBranch, nodeContext, context), nextList); } if (!nextList.isEmpty()) { return list2Array(nextList); } } return null; } private void init(NodeContext nodeContext, FlowContext context) { InitContext initContext = new InitContext(); initContext.setFlowParser(context.getFlowEngine().getFlowParser()); initContext.setParseEl(true); FlowNode node = FlowUtil.node(nodeContext, context); init(initContext, node); } @Override public void init(InitContext initContext, Object parent) { List branchInfoList = new ArrayList(); for (Map branch : branchList) { Branch branchInfo = new Branch(); Object whenObj = branch.get("when"); if (whenObj == null) { branchInfo.when = null; } else if (whenObj instanceof String) { branchInfo.when = new ExpWhen((String) whenObj); } else if (whenObj instanceof Map) { Map map = (Map) whenObj; String type = (String) map.get(DefConstants.COMMON_PROP_TYPE); String createExp = (String) map.get(DefConstants.COMMON_PROP_CREATE_EXP); if (DefConstants.COMMON_PROP_CREATE.equals(type) || createExp != null) { if (initContext.isParseEl()) { Map context = new HashMap<>(3); context.put("definition", map); context.put("node", (FlowNode) parent); context.put("flow", initContext.getFlow()); context.put("flowParser", initContext.getFlowParser()); NodeExecutor executor = initContext.getFlowParser().getElEvaluator().evalWithDefaultContext(createExp, context, false); branchInfo.when = new ExecutorWhen(executor); } } else { throw new IllegalArgumentException("illegal param " + branch); } } else if (whenObj instanceof NodeExecutor) { branchInfo.when = new ExecutorWhen((NodeExecutor) whenObj); } else { throw new IllegalArgumentException("illegal param " + branch); } branchInfo.to = parseToDefinition(branch.get("to"), (FlowNode) parent, initContext); branchInfoList.add(branchInfo); } this.branchInfoList = branchInfoList; if (this.defaultBranch != null) { this.defaultBranch = parseToDefinition(defaultBranch, (FlowNode) parent, initContext); } } public String getType() { return type; } public void setType(String type) { this.type = type; } public Object getDefaultBranch() { return defaultBranch; } public void setDefaultBranch(Object defaultBranch) { this.defaultBranch = defaultBranch; } public List> getBranchList() { return branchList; } public void setBranchList(List> branchList) { this.branchList = branchList; } private static class Branch { private When when; private Object to; } private static interface When { public boolean execute(NodeContext nodeContext, FlowContext context); } private static class ExpWhen implements When { String exp; ExpWhen(String exp) { this.exp = exp; } @Override public boolean execute(NodeContext nodeContext, FlowContext context) { return context.getElEvaluator().eval(exp, nodeContext, context, null); } } private static class ExecutorWhen implements When { NodeExecutor executor; ExecutorWhen(NodeExecutor executor) { this.executor = executor; } @Override public boolean execute(NodeContext nodeContext, FlowContext context) { return executor.execute(nodeContext, context); } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/post/EventPostHandler.java ================================================ package com.jd.easyflow.flow.model.post; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.event.FlowEventListener; import com.jd.easyflow.flow.engine.event.impl.EventFlowListener; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.parser.param.PostParseParam; import com.jd.easyflow.flow.util.FlowConstants; /** * * @author liyuliang5 * */ public class EventPostHandler implements NodePostHandler { private static final Logger logger = LoggerFactory.getLogger(EventPostHandler.class); private boolean autoAddEventFlowListener = true; public EventPostHandler() { } public EventPostHandler(boolean autoAddEventFlowListener) { this.autoAddEventFlowListener = autoAddEventFlowListener; } @Override public void init(InitContext initContext, Object flowNode) { initEventPostHandlerMap(initContext, (FlowNode) flowNode); if (autoAddEventFlowListener) { List listeners = initContext.getFlow().getEventTrigger().getListenerList(); boolean exists = false; if (listeners != null) { for (FlowEventListener listener : listeners) { if (listener instanceof EventFlowListener) { exists = true; break; } } } if (! exists) { logger.info("Auto add EventFlowListener"); initContext.getFlow().getEventTrigger().addListener(new EventFlowListener()); } } } @Override public NodeContext[] postHandle(NodeContext nodeContext, FlowContext context) { String event = nodeContext.get(FlowConstants.NODE_CONTEXT_DATA_EVENT); if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Event:" + event); } if (event == null) { event = FlowConstants.NONE_EVENT; } Map eventPostHandlerMap = context.getFlow().getNode(nodeContext.getNodeId()) .getProperty(FlowConstants.PROP_RUNTIME_EVENT_POST_HANDLER_MAP); NodePostHandler postHandler = eventPostHandlerMap.get(event); if (postHandler == null) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Event post handler is null"); } return null; } return postHandler.postHandle(nodeContext, context); } private void initEventPostHandlerMap(InitContext initContext, FlowNode flowNode) { Map eventPostHandlerMap = flowNode .getProperty(FlowConstants.PROP_RUNTIME_EVENT_POST_HANDLER_MAP); if (eventPostHandlerMap != null) { return; } eventPostHandlerMap = new ConcurrentHashMap(); flowNode.setProperty(FlowConstants.PROP_RUNTIME_EVENT_POST_HANDLER_MAP, eventPostHandlerMap); Map map = flowNode.getProperty(FlowConstants.NODE_PROP_EVENTS); if (map == null) { return; } for (Entry entry : map.entrySet()) { String event = entry.getKey(); Object eventConf = (Object) entry.getValue(); Map eventPostHandlerConfMap = null; if (eventConf instanceof Map) { Map eventConfMap = (Map) eventConf; eventPostHandlerConfMap = (Map) eventConfMap.get("post"); } if (eventPostHandlerConfMap != null) { NodePostHandler nodePostHandler = initContext.getFlowParser() .parseNodePostHandler(new PostParseParam(eventPostHandlerConfMap, initContext.getFlowList(), initContext.isParseEl(), initContext.getFlow(), flowNode)); if (nodePostHandler != null) { nodePostHandler.init(initContext, flowNode); eventPostHandlerMap.put(event, nodePostHandler); } } } } public boolean isAutoAddEventFlowListener() { return autoAddEventFlowListener; } public void setAutoAddEventFlowListener(boolean autoAddEventFlowListener) { this.autoAddEventFlowListener = autoAddEventFlowListener; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/post/ExecutorNodePostHandler.java ================================================ package com.jd.easyflow.flow.model.post; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; /** * * @author liyuliang5 * */ public class ExecutorNodePostHandler extends AbstractNodePostHandler { private NodeExecutor executor; public ExecutorNodePostHandler(NodeExecutor executor) { this.executor = executor; } @Override public NodeContext[] postHandle(NodeContext nodeContext, FlowContext context) { Object nodeIds = executor.execute(nodeContext, context); return super.parseToNodes(nodeIds, nodeContext, context); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/post/ExpNodePostHandler.java ================================================ package com.jd.easyflow.flow.model.post; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class ExpNodePostHandler extends AbstractNodePostHandler { private static final Logger logger = LoggerFactory.getLogger(ExpNodePostHandler.class); private String exp; public ExpNodePostHandler() { } public ExpNodePostHandler(String exp) { this.exp = exp; } @Override public NodeContext[] postHandle(NodeContext nodeContext, FlowContext context) { Object result = context.getElEvaluator().eval(exp, nodeContext, context, null); return parseToNodes(result, nodeContext, context); } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/post/FixedNodePostHandler.java ================================================ package com.jd.easyflow.flow.model.post; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class FixedNodePostHandler extends AbstractNodePostHandler { private Object to; public FixedNodePostHandler() { } public FixedNodePostHandler(Object to) { this.to = to; } @Override public NodeContext[] postHandle(NodeContext nodeContext, FlowContext context) { return parseToNodes(to, nodeContext, context); } @Override public void init(InitContext initContext, Object parent) { if (to != null) { to = parseToDefinition(to, (FlowNode) parent, initContext); } } public Object getTo() { return to; } public void setTo(Object to) { this.to = to; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/pre/ExpNodePreHandler.java ================================================ package com.jd.easyflow.flow.model.pre; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodePreHandler; import com.jd.easyflow.flow.model.post.ExpNodePostHandler; /** * * IMPORTANT NOTICE! This class should not be singleton! * @author liyuliang5 * */ public class ExpNodePreHandler implements NodePreHandler { private static final Logger logger = LoggerFactory.getLogger(ExpNodePostHandler.class); private String exp; public ExpNodePreHandler() { } public ExpNodePreHandler(String exp) { this.exp = exp; } @Override public boolean preHandle(NodeContext nodeContext, FlowContext context) { boolean result = context.getElEvaluator().eval(exp, nodeContext, context, null); if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Exp:" + exp + " result:" + result); } return result; } public String getExp() { return exp; } public void setExp(String exp) { this.exp = exp; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/pre/InclusiveCheckPreHandler.java ================================================ package com.jd.easyflow.flow.model.pre; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeContextAccessor; import com.jd.easyflow.flow.model.NodePreHandler; import com.jd.easyflow.flow.model.filter.impl.BaseNodeFilter; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.FlowNodeLinkUtil; import com.jd.easyflow.flow.util.LockUtil; import com.jd.easyflow.flow.util.Triple; /** * Inclusive check pre handler. * * IMPORTANT NOTICE! This class should not be singleton! * * @author liyuliang5 * */ public class InclusiveCheckPreHandler implements NodePreHandler, NodePrePropertyGetter { private static final Logger logger = LoggerFactory.getLogger(InclusiveCheckPreHandler.class); private static final String CTX_INCLUSIVE_LOCK = "_inclusive_lock"; private List preNodes; public InclusiveCheckPreHandler() { } public InclusiveCheckPreHandler(List preNodes) { this.preNodes = preNodes; } @Override public boolean preHandle(NodeContext nodeContext, final FlowContext context) { Boolean checkResult = nodeContext.get(FlowConstants.NODECTX_PRE_RESULT); if (checkResult != null) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Pre result checked:" + checkResult); } return checkResult; } NodeContext previousNode = nodeContext.getPreviousNode(); if (previousNode == null) { return false; } String previousNodeId = previousNode.getNodeId(); boolean result = false; Map map = null; WaitingNodeInfo waitingNodeInfo = null; Object lockObj = LockUtil.getFlowContextLock(CTX_INCLUSIVE_LOCK, context); synchronized (lockObj) { map = context.get(InclusiveCheckHelper.CTX_WAITING_NODE_MAP); if (map == null) { map = new HashMap(); context.put(InclusiveCheckHelper.CTX_WAITING_NODE_MAP, map); } waitingNodeInfo = map.get(nodeContext.getNodeId()); if (waitingNodeInfo == null) { waitingNodeInfo = new WaitingNodeInfo(); waitingNodeInfo.waitNodeId = nodeContext.getNodeId(); waitingNodeInfo.unknownPreNodes = new HashSet(); waitingNodeInfo.finishedPreNodes = new HashSet(); waitingNodeInfo.unreachablePreNodes = new HashSet(); waitingNodeInfo.previousNodes = new ArrayList(); FlowNode currentNode = context.getFlow().getNode(nodeContext.getNodeId()); List preNodeList = this.getPreNodes(nodeContext, context); List configPreNodes = preNodeList != null ? preNodeList : currentNode.getProperty(FlowConstants.PROP_PRENODES); if (! configPreNodes.contains(previousNodeId)) { if (context.isLogOn() && logger.isDebugEnabled()) { logger.info("Node:" + previousNodeId + " not in check list" ); } return false; } for (String preNode : configPreNodes) { if (FlowNodeLinkUtil.isReachable(preNode, nodeContext.getNodeId(), context.getFlow())) { waitingNodeInfo.unknownPreNodes.add(preNode); } else { waitingNodeInfo.unreachablePreNodes.add(preNode); } } map.put(nodeContext.getNodeId(), waitingNodeInfo); } waitingNodeInfo.previousNodes.add(previousNode); waitingNodeInfo.finishedPreNodes.add(previousNodeId); waitingNodeInfo.unknownPreNodes.remove(previousNodeId); if (waitingNodeInfo.unknownPreNodes.size() == 0) { result = true; } else { InclusiveCheckHelper.judgeOneWaitingNode(waitingNodeInfo, context); if (waitingNodeInfo.unknownPreNodes.size() == 0) { result = true; } else { if (context.isLogOn() && logger.isDebugEnabled()) { logger.debug("Finish nodes:" + waitingNodeInfo.finishedPreNodes + " Unreachable nodes:" + waitingNodeInfo.unreachablePreNodes + " Unknown nodes:" + waitingNodeInfo.unknownPreNodes); } return false; } } } if (result) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Node:" + nodeContext.getNodeId() + " is activated"); } map.remove(nodeContext.getNodeId()); nodeContext.put(FlowConstants.NODECTX_PREVIOUS_NODES, waitingNodeInfo.previousNodes); NodePreHandlerHelper.setNextNodesOfPreviousNode(waitingNodeInfo.previousNodes, nodeContext); } return result; } public List getPreNodes() { return preNodes; } public void setPreNodes(List preNodes) { this.preNodes = preNodes; } @Override public String getCheckType() { return FlowConstants.NODE_PRE_CHECK_TYPE_INCLUSIVECHECK; } @Override public List getPreNodes(NodeContext nodeContext, FlowContext flowContext) { return this.preNodes; } @Override public void init(InitContext initContext, Object parent) { boolean recordHistory = ! Boolean.FALSE.equals(initContext.getFlow().getProperty(FlowConstants.FLOW_PROPERTY_RECORD_HISTORY)); if (! recordHistory) { throw new FlowException("InclusiveCheck must record history"); } Flow flow = initContext.getFlow(); List, NodeContext>> filters = flow.getFilterManager().getNodeFilters(); boolean contains = false; if (filters == null) { filters = new ArrayList, NodeContext>>(); initContext.getFlow().getFilterManager().setNodeFilters(filters); } for (Filter filter : filters) { if (filter instanceof InclusiveCheckWaitNodeProcessFilter) { contains = true; break; } } if (!contains) { // after compensate filter. filters.add(0, new InclusiveCheckWaitNodeProcessFilter(Integer.MAX_VALUE - 1)); initContext.getFlow().getFilterManager().setNodeFilters(filters); } } public static class InclusiveCheckWaitNodeProcessFilter extends BaseNodeFilter { private static final Logger logger = LoggerFactory.getLogger(InclusiveCheckWaitNodeProcessFilter.class); public InclusiveCheckWaitNodeProcessFilter(int order) { this.order = order; } @Override public NodeContext doFilter(Triple request, FilterChain, NodeContext> chain) { chain.doFilter(request); NodeContext nodeContext = request.getMiddle(); FlowContext flowContext = request.getRight(); Object lockObj = LockUtil.getFlowContextLock(CTX_INCLUSIVE_LOCK, flowContext); synchronized (lockObj) { nodeContext.put(InclusiveCheckWaitNodeProcessFilter.class.getName(), true); Map map = flowContext.get(InclusiveCheckHelper.CTX_WAITING_NODE_MAP); if (map != null && !map.isEmpty()) { List additionalNextNodes = null; for (WaitingNodeInfo info : map.values()) { if (info.waitNodeId.equals(nodeContext.getNodeId())) { continue; } InclusiveCheckHelper.judgeOneWaitingNode(info, flowContext); if (info.unknownPreNodes.isEmpty()) { if (flowContext.isLogOn() && logger.isInfoEnabled()) { logger.info("Node:" + info.waitNodeId + " is activated in filter"); } if (additionalNextNodes == null) { additionalNextNodes = new ArrayList(); if (nodeContext.getNextNodes() != null) { for (NodeContext nc : nodeContext.getNextNodes()) { additionalNextNodes.add(nc); } } } NodeContext additionNode = new NodeContext(info.waitNodeId); additionalNextNodes.add(additionNode); } } if (additionalNextNodes != null) { NodeContextAccessor.setNextNodes(nodeContext, additionalNextNodes.toArray(new NodeContext[additionalNextNodes.size()])); } } } return nodeContext; } } private static class InclusiveCheckHelper { static final String CTX_WAITING_NODE_MAP = "_WAITING_NODE_MAP"; static final String CTX_RUNNING_NODES = "_RUNNING_NODES"; private static final List EMPTY_LIST = new ArrayList(); static void judgeOneWaitingNode(WaitingNodeInfo info, FlowContext context) { Set runningNodes = refreshRunningNodes(context); Map map = context.get(CTX_WAITING_NODE_MAP); Iterator iterator = info.unknownPreNodes.iterator(); while (iterator.hasNext()) { String unknownPreNode = iterator.next(); boolean reachable = false; for (NodeContext nodeContext : runningNodes) { if (nodeContext.getNodeId().equals(unknownPreNode)) { reachable = true; break; } if (nodeContext.getNodeId().equals(info.waitNodeId)) { if (nodeContext.getPreviousNode() != null && nodeContext.getPreviousNode().getNodeId().equals(unknownPreNode)) { reachable = true; break; } else { continue; } } if (FlowNodeLinkUtil.isReachable(nodeContext.getNodeId(), unknownPreNode, context.getFlow())) { reachable = true; break; } } if (! reachable) { for (String key : map.keySet()) { if (key.equals(info.waitNodeId)) { continue; } if (key.equals(unknownPreNode)) { reachable = true; break; } if (FlowNodeLinkUtil.isReachable(key, unknownPreNode, context.getFlow())) { reachable = true; break; } } } if (! reachable) { info.unreachablePreNodes.add(unknownPreNode); iterator.remove(); } } } private static Set refreshRunningNodes(FlowContext context) { Set runningNodes = context.get(CTX_RUNNING_NODES); if (runningNodes == null) { runningNodes = new HashSet(); runningNodes.addAll(context.getStartNodes()); context.put(CTX_RUNNING_NODES, runningNodes); } List addList = null; List removeList = null; for (NodeContext node : runningNodes) { if (Boolean.TRUE.equals(node.get(InclusiveCheckWaitNodeProcessFilter.class.getName()))) { if (node.getNextNodes() != null) { for (NodeContext nctx : node.getNextNodes()) { if (addList == null) { addList = new ArrayList(); } addList.add(nctx); } } if (removeList == null) { removeList = new ArrayList(); } removeList.add(node); } } if (removeList != null) { runningNodes.removeAll(removeList); } if (addList != null) { runningNodes.addAll(addList); } if (addList != null && !addList.isEmpty()) { refreshRunningNodes(context); } return runningNodes; } } private static class WaitingNodeInfo { String waitNodeId; Set finishedPreNodes; Set unreachablePreNodes; Set unknownPreNodes; List previousNodes; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/pre/MultiCheckPreHandler.java ================================================ package com.jd.easyflow.flow.model.pre; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodePreHandler; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.LockUtil; /** * * IMPORTANT NOTICE! This class should not be singleton! * @author liyuliang5 * */ public class MultiCheckPreHandler implements NodePreHandler, NodePrePropertyGetter { private static final Logger logger = LoggerFactory.getLogger(MultiCheckPreHandler.class); private List preNodes; public MultiCheckPreHandler() { } public MultiCheckPreHandler(List preNodes) { this.preNodes = preNodes; } /** * Judge all pre nodes finished. * */ @Override public boolean preHandle(NodeContext nodeContext, final FlowContext context) { Boolean checkResult = nodeContext.get(FlowConstants.NODECTX_PRE_RESULT); if (checkResult != null) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Pre result checked:" + checkResult); } return checkResult; } boolean result = false; List previousNodes = null; Object lockObj = LockUtil.getFlowContextLock(FlowConstants.CTX_LOCK_PREFIX + nodeContext.getNodeId(), context); synchronized (lockObj) { List preNodes = context.get(FlowConstants.CTX_PRE_NODES_PREFIX + nodeContext.getNodeId()); previousNodes = context.get(FlowConstants.CTX_PREVIOUS_NODES_PREFIX + nodeContext.getNodeId()); if (preNodes == null) { preNodes = new ArrayList(); context.put(FlowConstants.CTX_PRE_NODES_PREFIX + nodeContext.getNodeId(), preNodes); previousNodes = new ArrayList(); context.put(FlowConstants.CTX_PREVIOUS_NODES_PREFIX + nodeContext.getNodeId(), previousNodes); } preNodes.add(nodeContext.getPreviousNode().getNodeId()); previousNodes.add(nodeContext.getPreviousNode()); FlowNode currentNode = context.getFlow().getNode(nodeContext.getNodeId()); List preNodeList = this.getPreNodes(nodeContext, context); List configPreNodes = preNodeList != null ? preNodeList : currentNode.getProperty(FlowConstants.PROP_PRENODES); if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("Pre nodes executed:" + preNodes); } if (preNodes.size() < configPreNodes.size()) { return false; } for (String s : configPreNodes) { if (!preNodes.contains(s)) { return false; } } result = true; } if (result) { if (context.isLogOn() && logger.isInfoEnabled()) { logger.info("All pre nodes finished"); } context.remove(FlowConstants.CTX_PRE_NODES_PREFIX + nodeContext.getNodeId()); context.remove(FlowConstants.CTX_PREVIOUS_NODES_PREFIX + nodeContext.getNodeId()); nodeContext.put(FlowConstants.NODECTX_PREVIOUS_NODES, previousNodes); NodePreHandlerHelper.setNextNodesOfPreviousNode(previousNodes, nodeContext); } return result; } public List getPreNodes() { return preNodes; } public void setPreNodes(List preNodes) { this.preNodes = preNodes; } @Override public String getCheckType() { return FlowConstants.NODE_PRE_CHECK_TYPE_MULTICHECK; } public List getPreNodes(NodeContext nodeContext, FlowContext flowContext) { return this.preNodes; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/pre/NodePreHandlerHelper.java ================================================ package com.jd.easyflow.flow.model.pre; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowConstants; /** * @author liyuliang5 */ public class NodePreHandlerHelper { public static void setNextNodesOfPreviousNode(List previousNodes, NodeContext nodeContext) { for (NodeContext previous : previousNodes) { if (previous.getNextNodes().length == 1) { if (! previous.getNextNodes()[0].equals(nodeContext)) { previous.getNextNodes()[0].put(FlowConstants.NODECTX_PREVIOUS_NODES, new ArrayList<>(0)); } previous.put(FlowConstants.NODECTX_NEXT_NODES, Arrays.asList(nodeContext)); } else { synchronized (previous) { List nextNodes = previous.get(FlowConstants.NODECTX_PREVIOUS_NODES); if (nextNodes == null) { nextNodes = new ArrayList(previous.getNextNodes().length); for (NodeContext ctx : previous.getNextNodes()) { nextNodes.add(ctx); } previous.put(FlowConstants.NODECTX_NEXT_NODES, nextNodes); } for (int i = 0; i < nextNodes.size(); i++) { NodeContext next = nextNodes.get(i); if (next.getNodeId().equals(nodeContext.getNodeId()) && ! next.equals(nodeContext)) { nextNodes.get(i).put(FlowConstants.NODECTX_PREVIOUS_NODES, new ArrayList<>(0)); nextNodes.set(i, nodeContext); } } } } } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/model/pre/NodePrePropertyGetter.java ================================================ package com.jd.easyflow.flow.model.pre; import java.util.List; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public interface NodePrePropertyGetter { /** * used for static compute. * @return */ String getCheckType(); /** * used for static compute. * @return should contains all nodes in getPreNodes(nodeContext, flowContext) */ List getPreNodes(); /** * used for runtime. * @param nodeContext * @param flowContext * @return All nodes should in getPreNodes(). */ List getPreNodes(NodeContext nodeContext, FlowContext flowContext); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/ExceptionUtil.java ================================================ package com.jd.easyflow.flow.util; /** * * @author liyuliang5 * */ public class ExceptionUtil { public static RuntimeException throwException(Throwable t) { if (t == null) { throw new NullPointerException("Exception is null"); } return throw0(t); } private static T throw0(Throwable t) throws T { throw (T) t; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/FlowConstants.java ================================================ package com.jd.easyflow.flow.util; /** * * @author liyuliang5 * */ public class FlowConstants { public static final String NS_SEP = ":"; public static final String NS_EASYFLOW = "easyflow"; public static final String EASYFLOW_NS_PREFIX = NS_EASYFLOW + NS_SEP; /** * Param keys. */ public static final String PARAM_ACTION_EXECUTOR = "actionExecutor"; /** * Record execute history, default is true */ public static final String FLOW_PROPERTY_RECORD_HISTORY = "flow.recordHistory"; /** * Node action filter. */ public static final String FLOW_NODE_ACTION_FILTERS = "flow.nodeActionFilters"; public static final int EVENT_ORDER_START = 10000; /** * Exceptions in multiple thread scenario. */ public static final String FLOW_CTX_MULTI_EXCEPTIONS = "_flow.multi.exceptions"; public static final String FLOW_CTX_MULTI_AWAIT_RESULT = "_flow.multi.await.result"; public static final String NODE_CTX_MULTI_EXCEPTION = "_flow.multi.exception"; public static final String CTX_LOCK_PREFIX = "_node_lock_"; public static final String CTX_PRE_NODES_PREFIX = "_pre_nodes_"; public static final String CTX_PREVIOUS_NODES_PREFIX = "_previousNodes_"; public static final String CTX_PARENT_CONTEXT = "_parentContext"; public static final String CTX_PARENT_NODE_CONTEXT = "_parentNodeContext"; public static final String NODECTX_PRE_RESULT = "_preResult"; public static final String NODECTX_PREVIOUS_NODES = "_previousNodes"; public static final String NODECTX_NEXT_NODES = "_nextNodes"; public static final String NODECTX_COMPENSATE_NODE_FLAG = "_compensateNodeFlag"; public static final String NODECTX_COMPENSATE_FOR = "_compensateFor"; public static final String NODECTX_COMPENSATED_BY = "_compensatedBy"; public static final String NODECTX_COMPENSATED_FLAG = "_compensatedFlag"; public static final String PROP_PRENODES = "preNodes"; public static final String PROP_INTERRUPT = "interrupt"; public static final String PROP_INTERRUPT_EXP = "interruptExp"; public static final String PROP_RUNTIME_EVENT_NODE_ACTION_MAP = "_$eventNodeActionMap"; public static final String PROP_RUNTIME_EVENT_POST_HANDLER_MAP = "_$eventPostHandlerMap"; public static final String PROP_RUNTIME_COMPENSATE_ACTION = "_$compensateAction"; /** * Event param. */ public static final String NODE_CONTEXT_DATA_EVENT = "_EVENT"; public static final String NODE_PROP_EVENTS = "events"; public static final String PARAM_DATA_EVENT = "_EVENT"; public static final String NONE_EVENT = "NONE"; /** * Node Pre checkType */ public static final String NODE_PRE_CHECK_TYPE_MULTICHECK = "multiCheck"; public static final String NODE_PRE_CHECK_TYPE_INCLUSIVECHECK = "inclusiveCheck"; /** * initContextKey */ public static final String INIT_CONTEXT_KEY = "initContext"; public static final String FLOW_ENGINE_EVENT_DATA_KEY_PARAM = "param"; public static final String FLOW_ENGINE_EVENT_DATA_KEY_FLOW_ENGINE = "flowEngine"; public static final String FLOW_ENGINE_EVENT_DATA_KEY_RESULT = "result"; public static final String FLOW_ENGINE_EVENT_DATA_KEY_EXCEPTION = "exception"; /** * Node property */ public static final String NODE_PROP_NEXT_NODES = "nextNodes"; } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/FlowEventTypes.java ================================================ package com.jd.easyflow.flow.util; /** * Flow event types. * @author liyuliang5 * */ public class FlowEventTypes { /** * Common events. */ public static final String FLOW_ENGINE_START = "FLOW_ENGINE_START"; public static final String FLOW_ENGINE_END = "FLOW_ENGINE_END"; public static final String FLOW_ENGINE_COMPLETE = "FLOW_ENGINE_COMPLETE"; public static final String FLOW_START = "FLOW_START"; public static final String FLOW_END = "FLOW_END"; public static final String FLOW_COMPLETE = "FLOW_COMPLETE"; public static final String INIT_START = "INIT_START"; public static final String INIT_END = "INIT_END"; public static final String RUN_START = "RUN_START"; public static final String RUN_END = "RUN_END"; public static final String NODE_START = "NODE_START"; public static final String NODE_END = "NODE_END"; public static final String NODE_COMPLETE = "NODE_COMPLETE"; public static final String FLOW_PRE_START = "FLOW_PRE_START"; public static final String FLOW_PRE_END = "FLOW_PRE_END"; public static final String FLOW_POST_START = "FLOW_POST_START"; public static final String FLOW_POST_END = "FLOW_POST_END"; /** * Extension events. */ public static final String NODE_PRE_START = "NODE_PRE_START"; public static final String NODE_PRE_END = "NODE_PRE_END"; public static final String NODE_ACTION_START = "NODE_ACTION_START"; public static final String NODE_ACTION_END = "NODE_ACTION_END"; public static final String NODE_POST_START = "NODE_POST_START"; public static final String NODE_POST_END = "NODE_POST_END"; } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/FlowIOUtil.java ================================================ package com.jd.easyflow.flow.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; /** * * @author liyuliang5 */ public class FlowIOUtil { public static String toString(InputStream inputStream) throws IOException { ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) != -1) { result.write(buffer, 0, length); } String str = result.toString(StandardCharsets.UTF_8.name()); return str; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/FlowNodeLinkUtil.java ================================================ package com.jd.easyflow.flow.util; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.NodePreHandler; import com.jd.easyflow.flow.model.node.NodeImpl; import com.jd.easyflow.flow.model.post.ConditionalNodePostHandler; import com.jd.easyflow.flow.model.post.EventPostHandler; import com.jd.easyflow.flow.model.post.FixedNodePostHandler; import com.jd.easyflow.flow.model.pre.NodePrePropertyGetter; /** * @author liyuliang5 */ public class FlowNodeLinkUtil { private static final Logger logger = LoggerFactory.getLogger(FlowNodeLinkUtil.class); private static final String IDX_VAR_PREFIX = "$"; private static final String NODE_PROP_NEXT_NODES_CACHE = "_$nextNodesCache"; private static final String NODE_PROP_PREVIOUS_NODES_CACHE = "_$previousNodesCache"; private static final String NODE_PROP_PRE_CHECK_TYPE_CACHE = "_$preCheckTypeCache"; private static final String NODE_PROP_PRE_CHECK_NODES_CACHE = "_$preCheckNodesCache"; private static final String NODE_PROP_PRE_CHECK_TYPE = "preCheckType"; static final String FLOW_PROP_REACHABLE_MAP = "_$reachableMap"; public static final String NODE_ID_ALL = "$*"; public static final String NODE_ID_UNKNOWN = "$?"; public static final String NODE_PRE_CHECK_TYPE_UNKNOWN = "?"; private static List EMPTY_LIST = new ArrayList(0); /** * * @param nodeId * @param flow * @return resultList. may null or contains null! return null if uncomputable. * contains null as uncomputable. */ public static List getNextNodes(String nodeId, Flow flow) { FlowNode node = flow.getNode(nodeId); return getNextNodes(node, flow); } /** * * @param node * @param flow * @return resultList. may return $? as unknown. */ public static List getNextNodes(FlowNode node, Flow flow) { List nextNodeIds = node.getProperty(NODE_PROP_NEXT_NODES_CACHE); if (nextNodeIds != null) { return nextNodeIds; } nextNodeIds = node.getProperty(FlowConstants.NODE_PROP_NEXT_NODES); if (nextNodeIds == null) { if (node instanceof NodeImpl) { NodeImpl nodeImpl = (NodeImpl) node; NodePostHandler nodePostHandler = nodeImpl.getPostHandler(); nextNodeIds = getNextNodeIds(nodePostHandler, nodeImpl, flow); } else { nextNodeIds = Arrays.asList(NODE_ID_UNKNOWN); } } if (nextNodeIds.size() > 1) { Set nextNodeIdsSet = new LinkedHashSet(); nextNodeIdsSet.addAll(nextNodeIds); nextNodeIds = new ArrayList(nextNodeIdsSet); } if (nextNodeIds.contains(NODE_ID_UNKNOWN)) { if (logger.isInfoEnabled()) { logger.info(flow.getId() + ":" + node.getId() + " next nodes contains unknown"); } } node.setProperty(NODE_PROP_NEXT_NODES_CACHE, nextNodeIds); return nextNodeIds; } private static List getNextNodeIds(NodePostHandler nodePostHandler, FlowNode node, Flow flow) { if (nodePostHandler == null) { return EMPTY_LIST; } else if (nodePostHandler instanceof FixedNodePostHandler) { FixedNodePostHandler fixed = (FixedNodePostHandler) nodePostHandler; return parseTo(fixed.getTo(), node.getId(), flow); } else if (nodePostHandler instanceof ConditionalNodePostHandler) { List nextNodeIds = new ArrayList(); ConditionalNodePostHandler conditional = (ConditionalNodePostHandler) nodePostHandler; List> branchList = conditional.getBranchList(); for (Map branch : branchList) { Object next = branch.get("to"); List toList = parseTo(next, node.getId(), flow); nextNodeIds.addAll(toList); } if (conditional.getDefaultBranch() != null) { List defaultTo = parseTo(conditional.getDefaultBranch(), node.getId(), flow); nextNodeIds.addAll(defaultTo); } return nextNodeIds; } else if (nodePostHandler instanceof EventPostHandler) { Map handlerMap = node .getProperty(FlowConstants.PROP_RUNTIME_EVENT_POST_HANDLER_MAP); if (handlerMap == null) { return EMPTY_LIST; } List nextNodeIds = new ArrayList(); for (NodePostHandler handler : handlerMap.values()) { List list = getNextNodeIds(handler, node, flow); nextNodeIds.addAll(list); } return nextNodeIds; } else { return Arrays.asList(NODE_ID_UNKNOWN); } } public static List getPreviousNodes(String nodeId, Flow flow) { FlowNode node = flow.getNode(nodeId); return getPreviousNodeIds(node, flow); } public static List getPreviousNodeIds(FlowNode node, Flow flow) { List previousNodeIds = node.getProperty(NODE_PROP_PREVIOUS_NODES_CACHE); if (previousNodeIds != null) { return previousNodeIds; } String nodeId = node.getId(); previousNodeIds = new ArrayList(); boolean containsUnknown = false; for (FlowNode flowNode : flow.getNodeList()) { List nextNodeIds = getNextNodes(flowNode, flow); for (String nextNode : nextNodeIds) { if (NODE_ID_UNKNOWN.equals(nextNode)) { containsUnknown = true; } else if (nodeId.equals(nextNode) || NODE_ID_ALL.equals(nextNode)) { previousNodeIds.add(flowNode.getId()); break; } } } if (containsUnknown) { previousNodeIds.add(NODE_ID_UNKNOWN); } node.setProperty(NODE_PROP_PREVIOUS_NODES_CACHE, previousNodeIds); return previousNodeIds; } public static String getPreCheckType(String nodeId, Flow flow) { FlowNode node = flow.getNode(nodeId); return getPreCheckType(node, flow); } public static String getPreCheckType(FlowNode node, Flow flow) { String preCheckType = node.getProperty(NODE_PROP_PRE_CHECK_TYPE_CACHE); if (preCheckType != null) { return preCheckType; } preCheckType = node.getProperty(NODE_PROP_PRE_CHECK_TYPE); if (preCheckType == null) { if (node instanceof NodeImpl) { NodeImpl nodeImpl = (NodeImpl) node; NodePreHandler nodePreHandler = nodeImpl.getPreHandler(); if (nodePreHandler == null) { preCheckType = null; } else if (nodePreHandler instanceof NodePrePropertyGetter) { preCheckType = ((NodePrePropertyGetter) nodePreHandler).getCheckType(); } else { preCheckType = NODE_PRE_CHECK_TYPE_UNKNOWN; } } else { preCheckType = NODE_PRE_CHECK_TYPE_UNKNOWN; } } node.setProperty(NODE_PROP_PRE_CHECK_TYPE_CACHE, preCheckType); return preCheckType; } public static List getPreCheckNodes(String nodeId, Flow flow) { FlowNode node = flow.getNode(nodeId); return getPreCheckNodes(node, flow); } public static List getPreCheckNodes(FlowNode node, Flow flow) { List preNodes = node.getProperty(NODE_PROP_PRE_CHECK_NODES_CACHE); if (preNodes != null) { return preNodes; } preNodes = node.getProperty(FlowConstants.PROP_PRENODES); if (preNodes == null) { preNodes = new ArrayList(); if (node instanceof NodeImpl) { NodeImpl nodeImpl = (NodeImpl) node; NodePreHandler nodePreHandler = nodeImpl.getPreHandler(); if (nodePreHandler == null) { // NOOP } else if (nodePreHandler instanceof NodePrePropertyGetter) { preNodes = ((NodePrePropertyGetter) nodePreHandler).getPreNodes(); } else { preNodes = Arrays.asList(NODE_ID_UNKNOWN); } } else { preNodes = Arrays.asList(NODE_ID_UNKNOWN); } } node.setProperty(NODE_PROP_PRE_CHECK_NODES_CACHE, preNodes); return preNodes; } private static List parseTo(Object to, String nodeId, Flow flow) { List result = new ArrayList(); // String type if (to instanceof String) { String toStr = (String) to; if (!toStr.startsWith(IDX_VAR_PREFIX)) { result.add(toStr); } else { toStr = parseIndexVar(toStr, nodeId, flow); result.add(toStr); } } else if (to instanceof Integer) { int toIdx = (Integer) to; String toStr = flow.getNodeList().get(toIdx).getId(); result.add(toStr); // List type } else if (to instanceof List) { List toList = (List) to; List toResult = new ArrayList<>(toList.size()); for (Object toObj : toList) { List nodes = parseTo(toObj, nodeId, flow); result.addAll(nodes); } } else if (to instanceof Map) { Map toMap = (Map) to; Object exp = toMap.get("exp"); if (exp != null) { result.add(NODE_ID_UNKNOWN); } else { Object node = toMap.get("node"); if (node != null) { List nodes = parseTo(node, nodeId, flow); result.addAll(nodes); } } } else { result.add(NODE_ID_UNKNOWN); } return result; } private static String parseIndexVar(String var, String nodeId, Flow flow) { int index = -1; switch (var) { case "$first": { index = 0; break; } case "$last": { index = flow.getNodeList().size() - 1; break; } case "$previous": { index = flow.getNodeIndex(nodeId) - 1; break; } case "$next": { index = flow.getNodeIndex(nodeId) + 1; break; } default: { return NODE_ID_UNKNOWN; } } return flow.getNodeList().get(index).getId(); } public static boolean isReachable(String sourceNodeId, String targetNodeId, Flow flow) { Map reachableMap = flow.getProperty(FLOW_PROP_REACHABLE_MAP); if (reachableMap == null) { reachableMap = new ConcurrentHashMap(); flow.setProperty(FLOW_PROP_REACHABLE_MAP, reachableMap); } String key = buildKey(sourceNodeId, targetNodeId, flow); Boolean result = reachableMap.get(key); if (result != null) { return result; } Set set = new HashSet<>(); result = computeReachable(set, sourceNodeId, targetNodeId, flow); reachableMap.put(key, result); return result; } private static boolean computeReachable(Set nodeSet, String sourceNodeId, String targetNodeId, Flow flow) { boolean notContains = nodeSet.add(sourceNodeId); if (!notContains) { return false; } FlowNode sourceNode = flow.getNode(sourceNodeId); List nextNodeIds = getNextNodes(sourceNode, flow); for (String nextNodeId : nextNodeIds) { if (NODE_ID_UNKNOWN.equals(nextNodeId)) { logger.warn("nextNodeId contains unknown to ignore, flow:" + flow.getId() + " sourceNodeId:" + sourceNodeId + " targetNodeId:" + targetNodeId); } if (nextNodeId.equals(targetNodeId) || NODE_ID_ALL.equals(nextNodeId)) { return true; } boolean result = computeReachable(nodeSet, nextNodeId, targetNodeId, flow); if (result) { return true; } } return false; } private static String buildKey(String sourceNodeId, String targetNodeId, Flow flow) { return sourceNodeId + "$" + targetNodeId; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/FlowStringUtil.java ================================================ package com.jd.easyflow.flow.util; /** * * @author liyuliang5 */ public class FlowStringUtil { public static boolean isNotEmpty(final CharSequence cs) { return ! isEmpty(cs); } public static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } public static String repeat(final char ch, final int repeat) { if (repeat <= 0) { return ""; } final char[] buf = new char[repeat]; for (int i = repeat - 1; i >= 0; i--) { buf[i] = ch; } return new String(buf); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/FlowUtil.java ================================================ package com.jd.easyflow.flow.util; import java.util.ArrayList; import java.util.List; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class FlowUtil { /** * get node * @param nodeContext * @param context * @return */ public static FlowNode node(NodeContext nodeContext, FlowContext context) { return context.getFlow().getNode(nodeContext.getNodeId()); } /** * get node property * @param * @param key * @param nodeContext * @param context * @return */ public static T nodeProperty(String key, NodeContext nodeContext, FlowContext context) { return (T) node(nodeContext, context).getProperty(key); } /** * * @param nodes * @return */ public static String[] nodeIds(FlowNode[] nodes) { if (nodes == null) { return null; } String[] nodeIds = new String[nodes.length]; for (int i = 0; i < nodes.length; i++) { nodeIds[i] = nodes[i].getId(); } return nodeIds; } /** * * @param nodes * @return */ public static List nodeIdsOfNodeList(List nodes) { if (nodes == null) { return null; } List nodeIds = new ArrayList<>(nodes.size()); for (int i = 0; i < nodes.size(); i++) { nodeIds.add(nodes.get(i).getId()); } return nodeIds; } /** * * @param nodes * @return */ public static String[] nodeIds(NodeContext[] nodes) { if (nodes == null) { return null; } String[] nodeIds = new String[nodes.length]; for (int i = 0; i < nodes.length; i++) { nodeIds[i] = nodes[i].getNodeId(); } return nodeIds; } /** * * @param nodes * @return */ public static List nodeIdsOfNodeContextList(List nodes) { if (nodes == null) { return null; } List nodeIds = new ArrayList<>(nodes.size()); for (int i = 0; i < nodes.size(); i++) { nodeIds.add(nodes.get(i).getNodeId()); } return nodeIds; } public static String nsKey(String key) { return FlowConstants.NS_SEP + key; } public static String nsKey(String ns, String key) { return ns + FlowConstants.NS_SEP + key; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/JsonFacade.java ================================================ package com.jd.easyflow.flow.util; /** * @author liyuliang5 */ public interface JsonFacade { public String toJsonString(Object o); public T parseObject(String s, Class clazz); } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/JsonFacadeJacksonImpl.java ================================================ package com.jd.easyflow.flow.util; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; /** * @author liyuliang5 */ public class JsonFacadeJacksonImpl implements JsonFacade { private ObjectMapper mapper = new ObjectMapper(); { mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.setSerializationInclusion(Include.NON_NULL); } @Override public String toJsonString(Object o) { try { return mapper.writeValueAsString(o); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } @Override public T parseObject(String s, Class clazz) { try { return mapper.readValue(s, clazz); } catch (Exception e) { throw new RuntimeException(e); } } public ObjectMapper getMapper() { return mapper; } public void setMapper(ObjectMapper mapper) { this.mapper = mapper; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/JsonPrettyHelper.java ================================================ package com.jd.easyflow.flow.util; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JSON pretty handler to formating json definition. * * @author liyuliang5 * */ public class JsonPrettyHelper { public static final Logger logger = LoggerFactory.getLogger(JsonPrettyHelper.class); private static final int INDENT = 2; private static final String KEY_TYPE = "keyType"; public static String pretty(String valueStr, String prettyConfStr) { Object value = JsonUtil.parseObject(valueStr, Object.class); Map prettyConf = JsonUtil.parseObject(prettyConfStr, Map.class); StringBuilder builder = new StringBuilder(); pretty(value, prettyConf, (Map) prettyConf.get("default"), builder, 0); return builder.toString(); } public static String pretty(Object value, Map prettyConf) { StringBuilder builder = new StringBuilder(); pretty(value, prettyConf, (Map) prettyConf.get("default"), builder, 0); return builder.toString(); } private static void pretty(Object value, Map prettyConf, Map defaultConf, StringBuilder builder, int indent) { if (value == null) { builder.append("null"); } else if (value instanceof String) { builder.append(quote((String) value)); } else if (value instanceof Number || value instanceof Boolean) { builder.append(value); } else if (value instanceof Map) { prettyMap((Map) value, prettyConf, defaultConf, builder, indent); } else if (value instanceof List) { prettyList((List) value, prettyConf, defaultConf, builder, indent); } else { throw new UnsupportedOperationException("Unsupported type:" + value.getClass()); } } /** * Pretty Map * * @param value * @param prettyConf * @param builder * @param indent */ public static void prettyMap(Map value, Map prettyConf, Map defaultConf, StringBuilder builder, int indent) { if (prettyConf == null) { prettyConf = new HashMap<>(); } // Head builder.append("{"); // Element indent = indent + INDENT; List processedKey = new ArrayList<>(); List> subList = (List>) prettyConf.get("subList"); if (subList == null) { subList = new ArrayList<>(); } if (!subList.stream().anyMatch(map -> "OTHER".equals(map.get(KEY_TYPE)))) { Map otherConf = new HashMap<>(); otherConf.put("keyType", "OTHER"); subList.add(otherConf); } boolean hasElements = false; for (Map subConf : subList) { Map subDefaultConf = (Map) subConf.get("default"); subDefaultConf = subDefaultConf == null ? defaultConf : subDefaultConf; List keys = null; String confKey = (String) subConf.get("key"); String keyType = (String) subConf.get("keyType"); if (FlowStringUtil.isNotEmpty(confKey)) { keys = Arrays.asList(confKey); } else if ("OTHER".equals(keyType)) { keys = subtract(new ArrayList(value.keySet()), processedKey); } else if (subConf.containsKey("subList")){ continue; } else { throw new IllegalArgumentException("Config error," + subConf); } if (keys.size() > 0) { for (String key : keys) { if (logger.isDebugEnabled()) { logger.debug("Start pretty element:" + key); } if (!value.containsKey(key)) { continue; } boolean subNewLine = Boolean.TRUE.equals(getConf(subConf, subDefaultConf, "newLine")); if (subNewLine) { builder.append(newLine(indent)); } processedKey.add(key); builder.append(quoteColon(key)); pretty(value.get(key), subConf, subDefaultConf, builder, indent); builder.append(","); hasElements = true; } } } if (hasElements) { builder.deleteCharAt(builder.length() - 1); } // Tail indent = indent - INDENT; boolean endNewLine = Boolean.TRUE.equals(getConf(prettyConf, defaultConf, "endNewLine")); if (endNewLine) { builder.append(newLine(indent)); } builder.append("}"); } private static List subtract(List list1, List list2) { List result = new ArrayList<>(); if (list1 == null) { return result; } for (String s : list1) { if (!list2.contains(s)) { result.add(s); } } return result; } /** * Pretty Array * * @param value * @param prettyConf * @param builder * @param indent */ public static void prettyList(List value, Map prettyConf, Map defaultConf, StringBuilder builder, int indent) { if (prettyConf == null) { prettyConf = new HashMap<>(); } List> subConfList = (List>) prettyConf.get("subList"); if (subConfList == null) { subConfList = new ArrayList<>(); Map arrayConf = new HashMap<>(); subConfList.add(arrayConf); } // Head builder.append("["); // Element indent = indent + INDENT; Map subConf = subConfList.get(0); Map subDefaultConf = (Map) subConf.get("default"); subDefaultConf = subDefaultConf == null ? defaultConf : subDefaultConf; if (value.size() > 0) { for (Object o : value) { boolean subNewLine = Boolean.TRUE.equals(getConf(subConf, subDefaultConf, "newLine")); if (subNewLine) { builder.append(newLine(indent)); } pretty(o, subConf, subDefaultConf, builder, indent); builder.append(","); } builder.deleteCharAt(builder.length() - 1); } // Tail indent = indent - INDENT; boolean endNewLine = Boolean.TRUE.equals(getConf(prettyConf, defaultConf, "endNewLine")); if (endNewLine) { builder.append(newLine(indent)); } builder.append("]"); } // ===Utils=== private static T getConf(Map confMap, Map defaultConf, String key) { Object value = confMap.get(key); if (value != null) { return (T) value; } if (defaultConf != null) { value = defaultConf.get(key); } return (T) value; } private static String newLine(int indent) { return "\n" + FlowStringUtil.repeat(' ', indent); } private static String blank(int num) { return FlowStringUtil.repeat(' ', num); } private static String quote(String str) { return JsonUtil.toJsonString(str); } private static String quoteColon(String s) { return quote(s) + ":"; } /** * intent chars. * * @param s * @param indent * @return */ private static String indent(String s, int indent) { String[] lines = s.split("\n"); StringBuilder builder = new StringBuilder(); for (String line : lines) { builder.append(FlowStringUtil.repeat(' ', indent) + line); } return builder.toString(); } /** * intent chars. * * @param s * @param indent * @return */ private static String indent(String[] lines, int indent) { StringBuilder builder = new StringBuilder(); for (String line : lines) { builder.append(FlowStringUtil.repeat(' ', indent) + line); } return builder.toString(); } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/JsonUtil.java ================================================ package com.jd.easyflow.flow.util; /** * JSON Util. * * @author liyuliang5 * */ public class JsonUtil { private static JsonFacade jsonFacade; public static String toJsonString(Object o) { return jsonFacade().toJsonString(o); } public static T parseObject(String s, Class clazz) { return jsonFacade().parseObject(s, clazz); } private static JsonFacade jsonFacade() { if (jsonFacade == null) { jsonFacade = new JsonFacadeJacksonImpl(); } return jsonFacade; } public static JsonFacade getJsonFacade() { return jsonFacade; } public static void setJsonFacade(JsonFacade jsonFacade) { JsonUtil.jsonFacade = jsonFacade; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/LockUtil.java ================================================ package com.jd.easyflow.flow.util; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; /** * @author liyuliang5 */ public class LockUtil { public static Object getFlowContextLock(String lockKey, FlowContext context) { Object lockObj = context.get(lockKey); if (lockObj != null) { return lockObj; } synchronized (context) { lockObj = context.get(lockKey); if (lockObj == null) { lockObj = new Object(); context.put(lockKey, lockObj); } return lockObj; } } public static Object getNodeContextLock(String lockKey, NodeContext nodeContext) { Object lockObj = nodeContext.get(lockKey); if (lockObj != null) { return lockObj; } synchronized (nodeContext) { lockObj = nodeContext.get(lockKey); if (lockObj == null) { lockObj = new Object(); nodeContext.put(lockKey, lockObj); } return lockObj; } } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/Pair.java ================================================ package com.jd.easyflow.flow.util; /** * * @author liyuliang5 */ public class Pair { private L left; private R right; private Pair(L left, R right) { this.left = left; this.right = right; } public static Pair of(L left, R right) { return new Pair(left, right); } public L getLeft() { return left; } public void setLeft(L left) { this.left = left; } public R getRight() { return right; } public void setRight(R right) { this.right = right; } @Override public String toString() { return "Pair [left=" + left + ", right=" + right + "]"; } } ================================================ FILE: easyflow-flow/src/main/java/com/jd/easyflow/flow/util/Triple.java ================================================ package com.jd.easyflow.flow.util; /** * * @author liyuliang5 */ public class Triple { private L left; private M middle; private R right; private Triple(L left, M middle, R right) { this.left = left; this.middle = middle; this.right = right; } public static Triple of(L left, M middle, R right) { return new Triple(left, middle, right); } public L getLeft() { return left; } public void setLeft(L left) { this.left = left; } public M getMiddle() { return middle; } public void setMiddle(M middle) { this.middle = middle; } public R getRight() { return right; } public void setRight(R right) { this.right = right; } @Override public String toString() { return "Triple [left=" + left + ", middle=" + middle + ", right=" + right + "]"; } } ================================================ FILE: easyflow-flow/src/main/resources/pretty/pretty-flow.json ================================================ { "endNewLine": true, "subList": [ { "newLine": true, "subList":[ { "key": "id" }, { "key": "name", "newLine": true }, { "key": "pre", "newLine": true }, { "key": "nodes", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true, "subList": [ { "key": "id" }, { "key": "name" }, { "key": "start" }, { "key": "pre" }, { "key": "action", "subList": [ { "key": "flow", "subList": [ { "key": "id" }, { "key": "name", "newLine": true }, { "key": "pre", "newLine": true }, { "key": "nodes", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true, "subList": [ { "key": "id" }, { "key": "name" }, { "key": "start" }, { "key": "pre" }, { "key": "action" }, { "key": "post" }, { "key": "properties" } ] } ] }, { "key": "post", "newLine": true } ] } ] }, { "key": "post", "subList": [ { "key": "when" }, { "key": "to" }, { "key": "conditions", "subList": [ { "key": "default", "subList": [ { "key": "when" }, { "key": "to" } ] } ] } ] }, { "key": "properties", "default": { "newLine": true, "endNewLine": true } } ] } ] }, { "key": "post", "newLine": true }, { "key": "listeners", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "filters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodeFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodePreHandlerFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodeActionFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodePostHandlerFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "properties", "newLine": true, "endNewLine": true, "subList": [ { "keyType": "OTHER", "newLine": true } ], "default": { "newLine": true, "endNewLine": true } }, { "key": "parseListeners", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "keyType": "OTHER", "default": { "newLine": true, "endNewLine": true } } ] }, { "key": "id" }, { "key": "name", "newLine": true }, { "key": "pre", "newLine": true }, { "key": "nodes", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true, "subList": [ { "key": "id" }, { "key": "name" }, { "key": "start" }, { "key": "pre" }, { "key": "action", "subList": [ { "key": "flow", "subList": [ { "key": "id" }, { "key": "name", "newLine": true }, { "key": "pre", "newLine": true }, { "key": "nodes", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true, "subList": [ { "key": "id" }, { "key": "name" }, { "key": "start" }, { "key": "pre" }, { "key": "action" }, { "key": "post" }, { "key": "properties" } ] } ] }, { "key": "post", "newLine": true } ] } ] }, { "key": "post", "subList": [ { "key": "when" }, { "key": "to" }, { "key": "conditions", "subList": [ { "subList": [ { "key": "when" }, { "key": "to" } ] } ] }, { "key": "defaultTo" } ] }, { "key": "properties", "default": { "newLine": true, "endNewLine": true } } ] } ] }, { "key": "post", "newLine": true }, { "key": "listeners", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "filters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodeFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodePreHandlerFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodeActionFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "nodePostHandlerFilters", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "key": "properties", "newLine": true, "endNewLine": true, "subList": [ { "keyType": "OTHER", "newLine": true } ], "default": { "newLine": true, "endNewLine": true } }, { "key": "parseListeners", "newLine": true, "endNewLine": true, "subList": [ { "newLine": true } ] }, { "keyType": "OTHER", "default": { "newLine": true, "endNewLine": true } } ] } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/FlowTestSuite.java ================================================ package com.jd.easyflow.flow; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import com.jd.easyflow.flow.cases.action.ActionTest; import com.jd.easyflow.flow.cases.action.LoopNodeActionTest; import com.jd.easyflow.flow.cases.action.MultipleActionTest; import com.jd.easyflow.flow.cases.event.EventFlowTest; import com.jd.easyflow.flow.cases.filter.FilterTest; import com.jd.easyflow.flow.cases.flowengine.FlowEngineImplTest; import com.jd.easyflow.flow.cases.inclusive.InclusiveTest; import com.jd.easyflow.flow.cases.interrupt.InterruptTest; import com.jd.easyflow.flow.cases.listener.InterruptFlowListenerTest; import com.jd.easyflow.flow.cases.logflag.LogFlagTest; import com.jd.easyflow.flow.cases.mockbiz.MockLoanTest; import com.jd.easyflow.flow.cases.parallel.ParallelTest; import com.jd.easyflow.flow.cases.parser.FlowParserTest; import com.jd.easyflow.flow.cases.performance.PerformanceTest; import com.jd.easyflow.flow.cases.posthandler.ConditionalPostHandlerTest; import com.jd.easyflow.flow.cases.posthandler.FlowIndexTest; import com.jd.easyflow.flow.cases.posthandler.NodePostHandlerTest; import com.jd.easyflow.flow.cases.posthandler.PostHandlerFilterTest; import com.jd.easyflow.flow.cases.prehandler.PreHandlerTest; import com.jd.easyflow.flow.cases.pretty.FlowDefPrettyHelperTest; import com.jd.easyflow.flow.cases.runner.MultiThreadTest; import com.jd.easyflow.flow.cases.runner.ReusableThreadTest; import com.jd.easyflow.flow.cases.spring.SpringFlowTest; import com.jd.easyflow.flow.cases.subflow.SubFlowTest; import com.jd.easyflow.flow.quickstart.QuickStartTest; /** * * @author liyuliang5 * */ @RunWith(Suite.class) @SuiteClasses ({ QuickStartTest.class, MockLoanTest.class, FlowEngineImplTest.class, MultiThreadTest.class, ReusableThreadTest.class, EventFlowTest.class, FlowIndexTest.class, FlowParserTest.class, NodePostHandlerTest.class, PostHandlerFilterTest.class, SubFlowTest.class, ParallelTest.class, PreHandlerTest.class, FlowDefPrettyHelperTest.class, LoopNodeActionTest.class, ActionTest.class, LoopNodeActionTest.class, InclusiveTest.class, InterruptFlowListenerTest.class, FilterTest.class, InterruptTest.class, LogFlagTest.class, PerformanceTest.class, ConditionalPostHandlerTest.class, PreHandlerTest.class, FlowDefPrettyHelperTest.class, SpringFlowTest.class, MultipleActionTest.class }) public class FlowTestSuite { } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/action/ActionTest.java ================================================ package com.jd.easyflow.flow.cases.action; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class ActionTest { private static final Logger logger = LoggerFactory.getLogger(ActionTest.class); /** * Test interrupt. */ @Test public void testActionInterrupt001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/action/action_interrupt_001.json"); flowEngine.init(); FlowParam param = new FlowParam("action_interrupt_test_001"); FlowResult result = flowEngine.execute(param); logger.info("biz result:" + result.getResult()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/action/CompensateTest.java ================================================ package com.jd.easyflow.flow.cases.action; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.action.compensate.CompensateAction; import com.jd.easyflow.flow.model.action.compensate.CompensateFlowParseEventListener; import com.jd.easyflow.flow.model.action.compensate.CompensateHelper; import com.jd.easyflow.flow.model.parser.FlowParserImpl; import com.jd.easyflow.flow.util.FlowUtil; /** * @author liyuliang5 */ public class CompensateTest { private static final Logger logger = LoggerFactory.getLogger(CompensateTest.class); @Test public void testCompensate() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:/flow/cases/action/compensate_001.json"); flowEngine.init(); FlowParam param = new FlowParam("compensate_001"); FlowResult result = flowEngine.execute(param); assertEquals("node001", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testCompensate002() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:/flow/cases/action/compensate_002.json"); flowEngine.init(); FlowParam param = new FlowParam("compensate_002"); FlowResult result = flowEngine.execute(param); } @Test public void testCompensateFlow() { FlowEngineImpl flowEngine = new FlowEngineImpl(); FlowParserImpl flowParser = new FlowParserImpl(); flowParser.setPostListeners(Arrays.asList(new CompensateFlowParseEventListener())); flowEngine.setFlowParser(flowParser); flowEngine.setFlowPath("classpath:/flow/cases/action/compensate_flow_001.json"); flowEngine.init(); FlowParam param = new FlowParam("compensate_flow_001"); FlowResult result = flowEngine.execute(param); logger.info("start compensate flow"); List endNodes = result.getContext().getEndNodes(); List startNodes = new ArrayList(); for (NodeContext endNode : endNodes) { NodeContext startNode = CompensateHelper.createCompensateNode(endNode); startNodes.add(startNode); } FlowParam param2 = new FlowParam(); result.getContext().setStartNodes(startNodes); param2.setContext(result.getContext()); FlowResult compensateResult = result.getContext().getFlowEngine().execute(param2); } @Test public void testCompensateFlowFilter() { FlowEngineImpl flowEngine = new FlowEngineImpl(); FlowParserImpl flowParser = new FlowParserImpl(); flowParser.setPostListeners(Arrays.asList(new CompensateFlowParseEventListener())); flowEngine.setFlowParser(flowParser); flowEngine.setFlowPath("classpath:/flow/cases/action/compensate_flow_001.json"); flowEngine.init(); FlowParam param = new FlowParam("compensate_flow_001"); FlowResult result = flowEngine.execute(param); logger.info("start compensate flow"); CompensateHelper.compensate(result.getContext()); FlowParam param2 = new FlowParam(); param2.setContext(result.getContext()); FlowResult compensateResult = result.getContext().getFlowEngine().execute(param2); } public static class TestCompensateNodeAction implements NodeAction, CompensateAction { public TestCompensateNodeAction() { } @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("execute:" + FlowUtil.node(nodeContext, context).getId()); return null; } @Override public T compensate(NodeContext nodeContext, FlowContext context) { logger.info("compensate:" + FlowUtil.node(nodeContext, context).getId()); return null; } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/action/LoopNodeActionTest.java ================================================ package com.jd.easyflow.flow.cases.action; import java.util.Arrays; import org.junit.Test; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class LoopNodeActionTest { /** * Test customize action. */ @Test public void testLoop001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/action/loop_test_001.json"); flowEngine.init(); FlowParam param = new FlowParam("loop_test_001", null); param.put("loopMaxCount", 3); param.put("data", Arrays.asList("1","2","3","4")); FlowResult result = flowEngine.execute(param); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/action/MockActionClass.java ================================================ package com.jd.easyflow.flow.cases.action; import java.util.HashMap; import java.util.Map; /** * * @author liyuliang5 * */ public class MockActionClass { public Map method1(Map param) { Map result = new HashMap<>(); result.put("r1", "hello " + param.get("A")); result.put("r2", "hello r2"); return result; } public Map method3(Map param) { Map result = new HashMap<>(); result.put("r3", "hello " + param.get("A")); return result; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/action/MockClassMethodAction.java ================================================ package com.jd.easyflow.flow.cases.action; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ClassUtils; import com.jd.easyflow.flow.el.ElFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class MockClassMethodAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(MockClassMethodAction.class); private Object instance; private Method method; private List> paramList; private List> resultList; private String nodeId; public MockClassMethodAction(Map actionConf, FlowNode node) throws Exception { String classMethod = (String) actionConf.get("classMethod"); paramList = (List>) actionConf.get("param"); resultList = (List>) actionConf.get("result"); String[] info = classMethod.split("::"); String clazzName = info[0]; String methodName = info[1]; Class clazz = Class.forName(clazzName); method = ClassUtils.getMethod(clazz, methodName, Map.class); instance = clazz.newInstance(); nodeId = node.getId(); } @Override public T execute(NodeContext nodeContext, FlowContext context) { Map paramMap = new HashMap<>(); Map nodeActionMap = context.get("nodeActionInfoMap"); for (Map param : paramList) { String key = (String) param.get("key"); String valueExp = (String) param.get("value"); Map contextMap = new HashMap<>(); contextMap.put("node", nodeActionMap); String value = context.getElEvaluator().eval(valueExp, nodeContext, context, contextMap); paramMap.put(key, value); } try { logger.info("Param:" + paramMap); Map resultMap = (Map) method.invoke(instance, paramMap); logger.info("Result:" + resultMap); Map outputMap = new HashMap<>(); for (Map result : resultList) { String key = (String) result.get("key"); outputMap.put(key, result.get("key")); } Map info = new HashMap<>(); info.put("res", outputMap); nodeActionMap.put(nodeId, info); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new FlowException(e); } return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/action/MockInputOutputEventListener.java ================================================ package com.jd.easyflow.flow.cases.action; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.event.FlowEvent; import com.jd.easyflow.flow.engine.event.FlowEventListener; import com.jd.easyflow.flow.util.FlowEventTypes; import com.jd.easyflow.flow.util.Pair; /** * * @author liyuliang5 * */ public class MockInputOutputEventListener implements FlowEventListener { private List> paramList; private List> resultList; public MockInputOutputEventListener(Map conf) { paramList = (List>) conf.get("param"); resultList = (List>) conf.get("result"); } public Pair[] getAcceptedEvents() { return new Pair[] { Pair.of(FlowEventTypes.FLOW_START, 0), Pair.of(FlowEventTypes.FLOW_END, 0), Pair.of(FlowEventTypes.FLOW_COMPLETE, 0) }; } @Override public void on(FlowEvent flowEvent) { FlowContext context = flowEvent.getContext(); switch (flowEvent.getType()) { case FlowEventTypes.FLOW_START: { context.put("nodeActionInfoMap", new ConcurrentHashMap()); Map paramMap = new HashMap<>(); Map nodeActionMap = context.get("nodeActionInfoMap"); Map contextMap = new HashMap<>(); contextMap.put("node", nodeActionMap); for (Map param : paramList) { String key = (String) param.get("key"); String valueExp = (String) param.get("value"); String value = context.getElEvaluator().eval(valueExp, null, context, contextMap); paramMap.put(key, value); } context.getParam().setParam(paramMap); break; } case FlowEventTypes.FLOW_END: { Map outputMap = new HashMap<>(); Map contextMap = new HashMap<>(); contextMap.put("node", context.get("nodeActionInfoMap")); for (Map result : resultList) { String key = (String) result.get("key"); String valueExp = (String) result.get("value"); String value = context.getElEvaluator().eval(valueExp, null, context, contextMap); outputMap.put(key, value); } context.getResult().setResult(outputMap); break; } case FlowEventTypes.FLOW_COMPLETE: { break; } } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/action/MultipleActionTest.java ================================================ package com.jd.easyflow.flow.cases.action; import org.junit.Test; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.parser.param.ActionParseParam; /** * @author liyuliang5 */ public class MultipleActionTest { @Test public void testMultipleActionNodeAction() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/action/multiple_action_001.json"); flowEngine.init(); FlowParam param = new FlowParam("multi_action_001"); FlowResult result = flowEngine.execute(param); } public static class TestMultipleActionNodeAction implements NodeAction { // Action protected NodeAction action; @Override public T execute(NodeContext nodeContext, FlowContext context) { for (int i = 0; i < 10; i++) { action.execute(nodeContext, context); } return null; } @Override public void init(InitContext initContext, Object parent) { FlowNode node = (FlowNode) parent; Object actionConf = node.getProperty("action"); if (actionConf != null) { ActionParseParam param = new ActionParseParam(actionConf, initContext.getFlowList(), initContext.isParseEl(), initContext.getFlow(), node); action = initContext.getFlowParser().parseNodeAction(param); } if (action != null) { action.init(initContext, node); } if (initContext.isParseEl()) { if (action == null) { throw new FlowException("Action can not be null"); } } } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/common/TestNodeExecutor.java ================================================ package com.jd.easyflow.flow.cases.common; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeExecutor; /** * @author liyuliang5 */ public class TestNodeExecutor implements NodeExecutor { private Object value; public TestNodeExecutor(Object value) { this.value = value; } @Override public Object execute(NodeContext nodeContext, FlowContext context) { return value; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/event/EventFlowTest.java ================================================ package com.jd.easyflow.flow.cases.event; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.util.FlowConstants; /** * * @author liyuliang5 * */ public class EventFlowTest { public static final Logger logger = LoggerFactory.getLogger(EventFlowTest.class); @Test public void testEvent1() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/event/flow_event_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_event_001", "EMPTY_NODE", null); param.put(FlowConstants.PARAM_DATA_EVENT, "EVENT1"); FlowResult result = flowEngine.execute(param); logger.info("Result:" + result); assertEquals("EMPTY_NODE", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testEvent2() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/event/flow_event_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_event_001", "EMPTY_NODE", null); param.put(FlowConstants.PARAM_DATA_EVENT, "EVENT2"); FlowResult result = flowEngine.execute(param); logger.info("Result:" + result); assertEquals("EMPTY_NODE", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testEvent3() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/event/flow_event_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_event_001", "EMPTY_NODE", null); param.put(FlowConstants.PARAM_DATA_EVENT, "EVENT3"); FlowResult result = flowEngine.execute(param); logger.info("Result:" + result); assertEquals("EMPTY_NODE2", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testEvent3OfFlow002() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/event/flow_event_002.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_event_002", "EMPTY_NODE", null); param.put(FlowConstants.PARAM_DATA_EVENT, "EVENT3"); FlowResult result = flowEngine.execute(param); logger.info("Result:" + result); assertEquals("EMPTY_NODE2", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testEvent4() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/event/flow_event_003.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_event_003", "EMPTY_NODE", null); param.put(FlowConstants.PARAM_DATA_EVENT, "EVENT1"); FlowResult result = flowEngine.execute(param); logger.info("Result:" + result); assertEquals("EMPTY_NODE", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testEventNoListener001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/event/flow_event_nolistener_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_event_nolistener_001", "EMPTY_NODE", null); param.put(FlowConstants.PARAM_DATA_EVENT, "EVENT3"); FlowResult result = flowEngine.execute(param); logger.info("Result:" + result); assertEquals("EMPTY_NODE2", result.getContext().getEndNodes().get(0).getNodeId()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/event/TestEventNodeAction.java ================================================ package com.jd.easyflow.flow.cases.event; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * Test event node action * @author liyuliang5 * */ public class TestEventNodeAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(TestEventNodeAction.class); @Override public Map execute(NodeContext nodeContext, FlowContext context) { logger.info("Start execute event node action"); Map result = new HashMap<>(); result.put("result1", 1); return result; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/filter/FilterTest.java ================================================ package com.jd.easyflow.flow.cases.filter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ReflectionUtils; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.filter.impl.BaseFlowEngineFilter; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.filter.BaseFilter; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeContextAccessor; import com.jd.easyflow.flow.model.filter.FlowFilterManager; import com.jd.easyflow.flow.model.filter.impl.BaseFlowFilter; import com.jd.easyflow.flow.model.filter.impl.BaseNodeFilter; import com.jd.easyflow.flow.util.Pair; import com.jd.easyflow.flow.util.Triple; /** * @author liyuliang5 * */ public class FilterTest { @Test public void testInnerNodeActionFilter() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/filter/inner_node_action_filter_001.json"); flowEngine.init(); // false false FlowParam param = new FlowParam("innerNodeActionFilter001"); FlowResult result = flowEngine.execute(param); assertEquals("node002", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testInnerFlowEngineFilter() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/filter/inner_flow_engine_filter_001.json"); TestInnerFlowEngineFilter filter = new TestInnerFlowEngineFilter(); filter.setOrder(-1); flowEngine.setFilters(Arrays.asList(filter)); flowEngine.init(); // false false FlowParam param = new FlowParam("innerFlowEngineFilter001"); FlowResult result = flowEngine.execute(param); assertEquals(true, param.get("flowEngineFilter")); } @Test public void testInnerFlowFilter() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/filter/inner_flow_filter_001.json"); flowEngine.init(); // false false FlowParam param = new FlowParam("innerFlowFilter001"); FlowResult result = flowEngine.execute(param); assertEquals(true, param.get("flowFilter")); assertEquals(true, param.get("nodeFilter")); assertEquals("node002", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testAddFilter() throws Exception { Method method = FlowFilterManager.class.getDeclaredMethod("addFilter", Filter.class, List.class, List.class); method.setAccessible(true); List innerList = new ArrayList(); List outerList = new ArrayList(); /*0 {} {}*/ outerList.clear(); innerList.clear(); Filter filter = new TestEmptyFilter("", 0); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 1); assertTrue(innerList.size() == 0); /*1 {} {}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("", 1); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 1); assertTrue(innerList.size() == 0); /*-1 {} {}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("", -1); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 0); assertTrue(innerList.size() == 1); /*0 {} {0}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("new", 0); outerList.add(new TestEmptyFilter("0", 0)); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 2); assertTrue(innerList.size() == 0); assertTrue(outerList.get(1).id.equals("new")); /*2 {} {1}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("new", 2); outerList.add(new TestEmptyFilter("0", 1)); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 2); assertTrue(innerList.size() == 0); assertTrue(outerList.get(0).id.equals("new")); /*2 {} {3}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("new", 2); outerList.add(new TestEmptyFilter("0", 3)); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 2); assertTrue(innerList.size() == 0); assertTrue(outerList.get(1).id.equals("new")); /*-2 {-2} {}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("new", -2); innerList.add(new TestEmptyFilter("0", -2)); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 0); assertTrue(innerList.size() == 2); assertTrue(innerList.get(1).id.equals("new")); /*-2 {-1} {}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("new", -2); innerList.add(new TestEmptyFilter("0", -1)); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 0); assertTrue(innerList.size() == 2); assertTrue(innerList.get(1).id.equals("new")); /*-2 {-3} {}*/ outerList.clear(); innerList.clear(); filter = new TestEmptyFilter("new", -2); innerList.add(new TestEmptyFilter("0", -3)); ReflectionUtils.invokeMethod(method, new FlowFilterManager(), filter, innerList, outerList); assertTrue(outerList.size() == 0); assertTrue(innerList.size() == 2); assertTrue(innerList.get(0).id.equals("new")); } static class TestEmptyFilter implements Filter { private int order; private String id; public TestEmptyFilter(String id, int order) { this.id = id; this.order = order; } public int getOrder() { return order; } @Override public Object doFilter(Object request, FilterChain chain) { return null; } } public static class TestNodeActionFilter extends BaseFilter, Object> { private static final Logger logger = LoggerFactory.getLogger(TestNodeActionFilter.class); @Override public Object doFilter(Pair request, FilterChain, Object> chain) { Object result = chain.doFilter(request); logger.info("result:{}, nodeActionResult:{}", result, request.getLeft().getActionResult()); return 1; } } static class TestInnerFlowEngineFilter extends BaseFlowEngineFilter { @Override public FlowResult doFilter(Pair request, FilterChain, FlowResult> chain) { request.getLeft().put("flowEngineFilter", true); return chain.doFilter(request); } } public static class TestInnerFlowFilter extends BaseFlowFilter { public TestInnerFlowFilter(int order) { this.order = order; } @Override public FlowResult doFilter(FlowContext request, FilterChain chain) { request.getParam().put("flowFilter", true);; return chain.doFilter(request); } } public static class TestInnerNodeFilter extends BaseNodeFilter { @Override public NodeContext doFilter(Triple request, FilterChain, NodeContext> chain) { if (request.getLeft().getId().equals("node002")) { return chain.doFilter(request); } request.getRight().getParam().put("nodeFilter", true); NodeContext nodeContext = chain.doFilter(request); NodeContextAccessor.setNextNodeIds(nodeContext, new String[]{"node002"}); return nodeContext; } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/flowengine/FlowEngineImplTest.java ================================================ package com.jd.easyflow.flow.cases.flowengine; import static org.junit.Assert.assertEquals; import java.util.Arrays; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.builder.FlowParamBuilder; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.action.ExecutorNodeAction; import com.jd.easyflow.flow.model.builder.FlowBuilder; import com.jd.easyflow.flow.util.Pair; /** * * @author liyuliang5 * */ public class FlowEngineImplTest { public static final Logger logger = LoggerFactory.getLogger(FlowEngineImplTest.class); /** * * Basic flow engine test. * Steps: define flow, execute flow, validate result * */ @Test public void testSample() { //define flow Flow flow = FlowBuilder.create("test", "testName") //add node .addNode("node1", new ExecutorNodeAction((nc, c) -> {logger.info("hello"); return "hello";})) .build(); FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.addFlow(flow); flowEngine.init(); // create param FlowParam param = FlowParamBuilder.create("test", "node1").build(); // execute flow FlowResult result = flowEngine.execute(param); // print result logger.info("Result:" + result); } /** * Change flowId from test to test2 by filter. */ @Test public void testFlowEngineFilter() { Flow flow1 = FlowBuilder.create("test", null).build(); Flow flow2 = FlowBuilder.create("test2", null).build(); FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.addFlow(flow1); flowEngine.addFlow(flow2); flowEngine.getFilterManager().setFilters(Arrays.asList(new TestFlowEngineFilter())); flowEngine.init(); FlowParam param = new FlowParam("test", new String[] {}, null); flowEngine.execute(param); assertEquals("test2", param.getFlowId()); } } class TestFlowEngineFilter implements Filter, FlowResult> { @Override public FlowResult doFilter(Pair request, FilterChain, FlowResult> chain) { request.getLeft().setFlowId("test2"); return chain.doFilter(request); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/flowengine/PocFlowEngineImpl.java ================================================ package com.jd.easyflow.flow.cases.flowengine; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowContextImpl; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeContext; /** * This is only a POC demo of FlowEngine. * @author liyuliang5 */ public abstract class PocFlowEngineImpl implements FlowEngine { protected Map flowMap = new ConcurrentHashMap<>(); /** * Start flow engine, exeucte flow. */ @Override public FlowResult execute(FlowParam param) { // init flow context FlowContext context = new FlowContextImpl(); context.setParam(param); FlowResult result = new FlowResult(); context.setResult(result); result.setContext(context); context.setFlow(flowMap.get(param.getFlowId())); // init start nodes String[] nodeIds = context.getParam().getNodeIds(); NodeContext[] nodes = new NodeContext[nodeIds.length]; for (int i = 0; i < nodeIds.length; i++) { nodes[i] = new NodeContext(nodeIds[i]); } ((FlowContextImpl) context).addNodes(nodes); // run NodeContext currentNode; FlowContextImpl contextImpl = (FlowContextImpl) context; // Loop execute. while ((currentNode = contextImpl.getNextNode()) != null) { FlowNode node = context.getFlow().getNode(currentNode.getNodeId()); NodeContext[] nextNodes = null; // @see PocNodeImpl node.execute(currentNode, context); // get next nodes nextNodes = currentNode.getNextNodes(); if (nextNodes != null) { contextImpl.addNodes(nextNodes); } } // return result return context.getResult(); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/flowengine/PocNodeImpl.java ================================================ package com.jd.easyflow.flow.cases.flowengine; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeContextAccessor; import com.jd.easyflow.flow.model.NodePostHandler; import com.jd.easyflow.flow.model.NodePreHandler; /** * This is only a POC demo of FlowNode. * * @author liyuliang5 */ public abstract class PocNodeImpl implements FlowNode { protected NodePreHandler preHandler; protected NodeAction action; protected NodePostHandler postHandler; @Override public NodeContext execute(NodeContext nodeContext, FlowContext context) { boolean preResult = true; if (preHandler != null) { preResult = preHandler.preHandle(nodeContext, context); NodeContextAccessor.setPreResult(nodeContext, preResult); } if (!preResult) { return nodeContext; } if (action != null) { Object result = action.execute(nodeContext, context); NodeContextAccessor.setActionResult(nodeContext,result); } if (postHandler != null) { NodeContext[] nextNodes = postHandler.postHandle(nodeContext, context); if (nextNodes != null) { NodeContextAccessor.setNextNodes(nodeContext,nextNodes); } } return nodeContext; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/inclusive/InclusiveTest.java ================================================ package com.jd.easyflow.flow.cases.inclusive; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowConstants; import com.jd.easyflow.flow.util.FlowUtil; public class InclusiveTest { @Test public void testInclusive001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive201.json"); flowEngine.init(); // false false FlowParam param = new FlowParam("flow_inclusive201"); param.setParam(3); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(1, endNodeIds.size()); assertTrue(endNodeIds.contains("START_NODE")); } /** * test inclusive nodes. */ @Test public void testInclusive002() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive201.json"); flowEngine.init(); // true false FlowParam param = new FlowParam("flow_inclusive201"); param.setParam(1); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(1, endNodeIds.size()); assertTrue(endNodeIds.contains("END")); NodeContext endNode = result.getContext().getEndNodes().stream().filter(node->node.getNodeId().equals("END")).findFirst().get(); List previousNodeList = endNode.getPreviousNode().get(FlowConstants.NODECTX_PREVIOUS_NODES); assertEquals(1, previousNodeList.size()); assertEquals("NODE2", previousNodeList.get(0).getNodeId()); } /** * test inclusive nodes. */ @Test public void testInclusive003() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive201.json"); flowEngine.init(); // true true FlowParam param = new FlowParam("flow_inclusive201"); param.setParam(0); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(2, endNodeIds.size()); assertTrue(endNodeIds.contains("END")); } /** * test inclusive nodes. */ @Test public void testInclusive004() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive202.json"); flowEngine.init(); // true false FlowParam param = new FlowParam("flow_inclusive202"); param.setParam(1); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(1, endNodeIds.size()); assertTrue(endNodeIds.contains("END")); } /** * test inclusive nodes. */ @Test public void testInclusive005() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive202.json"); flowEngine.init(); // true true FlowParam param = new FlowParam("flow_inclusive202"); param.setParam(0); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(2, endNodeIds.size()); assertTrue(endNodeIds.contains("END")); } /** * test inclusive nodes. */ @Test public void testInclusive006() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive203.json"); flowEngine.init(); // true true true FlowParam param = new FlowParam("flow_inclusive203"); Map bizParam = new HashMap<>(); param.setParam(bizParam); bizParam.put("node1", true); bizParam.put("node2", true); bizParam.put("node3", true); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertTrue(endNodeIds.contains("END")); } /** * test inclusive nodes. */ @Test public void testInclusive007() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive203.json"); flowEngine.init(); // false true true FlowParam param = new FlowParam("flow_inclusive203"); Map bizParam = new HashMap<>(); param.setParam(bizParam); bizParam.put("node1", false); bizParam.put("node2", true); bizParam.put("node3", true); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertTrue(endNodeIds.contains("END")); } /** * test inclusive nodes. * dead loop */ @Test public void testInclusive008() { // FlowEngineImpl flowEngine = new FlowEngineImpl(); // flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive303.json"); // flowEngine.init(); // // true false // FlowParam param = new FlowParam("flow_inclusive303"); // Map bizParam = new HashMap<>(); // param.setParam(bizParam); // bizParam.put("I1", false); // bizParam.put("I2", true); // FlowResult result = flowEngine.execute(param); // List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); // assertTrue(endNodeIds.contains("END")); } /** * test inclusive nodes. * dependency each other, not inactive inclusive gateway. */ @Test public void testInclusive009() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive303.json"); flowEngine.init(); // true true FlowParam param = new FlowParam("flow_inclusive303"); Map bizParam = new HashMap<>(); param.setParam(bizParam); bizParam.put("I1", true); bizParam.put("I2", true); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(endNodeIds.size(), 2); assertTrue(endNodeIds.contains("I1")); assertTrue(endNodeIds.contains("I2")); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/interrupt/InterruptTest.java ================================================ package com.jd.easyflow.flow.cases.interrupt; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowUtil; /** * * @author liyuliang5 */ public class InterruptTest { private static final Logger logger = LoggerFactory.getLogger(InterruptTest.class); /** * Test interrupt. * node001 sleep 1000ms return false. * node0021 sleep 2000ms return true -> node0022 sleep 1000ms return false. * node003 sleep 3000ms return false. * expect false. * */ @Test public void testInterrupt001() throws Exception { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/interrupt/interrupt_001.json"); flowEngine.init(); FlowParam param = new FlowParam("interrupt_001"); AtomicInteger successBizCount = new AtomicInteger(0); param.putContextData("successBizCount", successBizCount); FlowResult result = flowEngine.execute(param); logger.info("successBizCount:" + successBizCount.get()); logger.info("final result:" + (successBizCount.get() == result.getContext().getFlow().getNodeList().size())); // sleep to see node002 and node003 log. //Thread.sleep(5000); } /** * Test interrupt. * node001 sleep 1000ms return true. * node002 sleep 2000ms return true. * node003 sleep 3000ms return true. * expect true. * */ @Test public void testInterrupt002() throws Exception { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/interrupt/interrupt_002.json"); flowEngine.init(); FlowParam param = new FlowParam("interrupt_002"); AtomicInteger successBizCount = new AtomicInteger(0); param.putContextData("successBizCount", successBizCount); FlowResult result = flowEngine.execute(param); logger.info("successBizCount:" + successBizCount.get()); logger.info("final result:" + ((successBizCount.get() == result.getContext().getFlow().getNodeList().size()))); } } class TestInterruptBizNodeAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(TestInterruptBizNodeAction.class); private long sleepMills; private boolean result; public TestInterruptBizNodeAction(long sleepMills, boolean result) { this.sleepMills = sleepMills; this.result = result; } @Override public Boolean execute(NodeContext nodeContext, FlowContext context) { try { logger.info(FlowUtil.node(nodeContext, context).getId() + " start"); // biz1 use 1000ms, return result. Thread.sleep(sleepMills); if (result) { ((AtomicInteger) context.get("successBizCount")).incrementAndGet(); } else { context.setInterrupted(); } logger.info(FlowUtil.node(nodeContext, context).getId() + " end"); return result; } catch (InterruptedException e) { throw new RuntimeException(e); } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/listener/InterruptFlowListenerTest.java ================================================ package com.jd.easyflow.flow.cases.listener; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class InterruptFlowListenerTest { private static final Logger logger = LoggerFactory.getLogger(InterruptFlowListenerTest.class); /** * Test interrupt. */ @Test public void testInterrupt001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/listener/interrupt_listener_001.json"); flowEngine.init(); FlowParam param = new FlowParam("interrupt_listener_test_001"); FlowResult result = flowEngine.execute(param); Assert.assertTrue(result.getContext().isInterrupted()); } /** * Test interrupt. */ @Test public void testInterrupt002() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/listener/interrupt_listener_002.json"); flowEngine.init(); FlowParam param = new FlowParam("interrupt_listener_test_002"); FlowResult result = flowEngine.execute(param); Assert.assertTrue(result.getContext().isInterrupted()); } /** * Test interrupt. */ @Test public void testInterrupt003() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/listener/interrupt_listener_003.json"); flowEngine.init(); FlowParam param = new FlowParam("interrupt_listener_test_003"); FlowResult result = flowEngine.execute(param); Assert.assertFalse(result.getContext().isInterrupted()); param.setParam(true); result = flowEngine.execute(param); Assert.assertTrue(result.getContext().isInterrupted()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/logflag/LogFlagTest.java ================================================ package com.jd.easyflow.flow.cases.logflag; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * @author liyuliang5 */ public class LogFlagTest { private static final Logger logger = LoggerFactory.getLogger(LogFlagTest.class); @Test(expected = Exception.class) public void testLogFlag1() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/logflag/logflag_001.json"); flowEngine.init(); FlowParam param = new FlowParam("logFlag_001"); param.setLogFlag(false); flowEngine.execute(param); } public void throwException() throws Exception { throw new Exception("testException"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/mockbiz/CheckBiz.java ================================================ package com.jd.easyflow.flow.cases.mockbiz; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * @version 1.0 * @since 1.0 */ public class CheckBiz { private static final Logger logger = LoggerFactory.getLogger(CheckBiz.class); public void paramCheck(FlowContext context, NodeContext nodeContext) { logger.info("paramCheck"); } public void authCheck(FlowContext context) { logger.info("authCheck"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/mockbiz/LimitBiz.java ================================================ package com.jd.easyflow.flow.cases.mockbiz; import java.math.BigDecimal; /** * * @author liyuliang5 * */ public class LimitBiz { public boolean judgeLimit(BigDecimal amount) { return amount.compareTo(new BigDecimal(100)) < 0; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/mockbiz/LoanBiz.java ================================================ package com.jd.easyflow.flow.cases.mockbiz; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author liyuliang5 * */ public class LoanBiz { private static final Logger logger = LoggerFactory.getLogger(LoanBiz.class); public void doLoan() { logger.info("finish loan command"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/mockbiz/LoanContractSignBiz.java ================================================ package com.jd.easyflow.flow.cases.mockbiz; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoanContractSignBiz { public static final Logger logger = LoggerFactory.getLogger(LoanContractSignBiz.class); public void sign() { logger.info("sign"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/mockbiz/MockFlowListener.java ================================================ package com.jd.easyflow.flow.cases.mockbiz; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.event.FlowEvent; import com.jd.easyflow.flow.engine.event.FlowEventListener; import com.jd.easyflow.flow.util.FlowEventTypes; /** * * Mock Flow Listener * * @author liyuliang5 * @version 1.0 * @since 1.0 */ public class MockFlowListener implements FlowEventListener { private static final Logger logger = LoggerFactory.getLogger(MockFlowListener.class); /** * */ @Override public void on(FlowEvent flowEvent) { switch (flowEvent.getType()) { case FlowEventTypes.FLOW_START: { logger.info("FLOW START"); break; } case FlowEventTypes.FLOW_COMPLETE : { logger.info("FLOW END"); break; } default : { // NOOP } } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/mockbiz/MockLoanTest.java ================================================ package com.jd.easyflow.flow.cases.mockbiz; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class MockLoanTest { public static final Logger logger = LoggerFactory.getLogger(MockLoanTest.class); @Test public void testFlow001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/mockbiz/mock_loan_001.json"); flowEngine.init(); Map paramData = new HashMap<>(); paramData.put("amount", new BigDecimal(80)); FlowParam param = new FlowParam("mock_loan_001", "CONTRACT_SIGN", paramData); FlowResult result = flowEngine.execute(param); logger.info("result:" + result); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/parallel/ParallelTest.java ================================================ package com.jd.easyflow.flow.cases.parallel; import static org.junit.Assert.assertEquals; import java.util.List; import org.junit.Test; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.FlowConstants; /** * * @author liyuliang5 * */ public class ParallelTest { /** * test parallel nodes. */ @Test public void testParallel001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/parallel/flow_parallel001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_parallel001"); FlowResult result = flowEngine.execute(param); List previousNodes = result.getContext().getEndNodes().stream() .map(node -> (List)node.get(FlowConstants.NODECTX_PREVIOUS_NODES)).filter(list -> list != null && list.size() > 0).findFirst() .get(); assertEquals(2, previousNodes.size()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/parser/FlowParserTest.java ================================================ package com.jd.easyflow.flow.cases.parser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.InputStream; import java.util.List; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.parser.FlowParser; import com.jd.easyflow.flow.model.parser.FlowParserImpl; import com.jd.easyflow.flow.util.FlowIOUtil; import com.jd.easyflow.flow.util.JsonUtil; /** * * * @author liyuliang5 * @version 1.0 * @since 1.0 */ public class FlowParserTest { private static final Logger logger = LoggerFactory.getLogger(FlowParserTest.class); @Test public void testParseFlow() throws Exception { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources; InputStream is = null; resources = resolver.getResources("classpath:flow/cases/parser/parser_test_001.json"); for (Resource resource : resources) { logger.info("Start parse flow definition:" + resource.getURI()); is = resource.getInputStream(); String flowConfigStr = FlowIOUtil.toString(is); List flowList = new FlowParserImpl().parse(flowConfigStr); logger.info("Parse end, model:" + JsonUtil.toJsonString(flowList)); is.close(); } } @Test public void testFlowParseListener() throws Exception { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources; InputStream is = null; Flow flow = null; resources = resolver.getResources("classpath:flow/cases/parser/parser_test_002.json"); for (Resource resource : resources) { logger.info("Start parse flow definition:" + resource.getURI()); is = resource.getInputStream(); String flowConfigStr = FlowIOUtil.toString(is); List flowList = new FlowParserImpl().parse(flowConfigStr); logger.info("Parse end, model:" + JsonUtil.toJsonString(flowList)); flow = flowList.get(0); } assertEquals("node001", flow.getNodeList().get(0).getName()); assertEquals("node001", flow.getStartNodeIds()[0]); } @Test public void testMultiple() throws Exception { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/parser/flow_multiple001.json"); flowEngine.init(); assertTrue(flowEngine.getFlowMap().size() == 2); FlowParam param1 = new FlowParam("flow_multiple0011"); flowEngine.execute(param1); FlowParam param2 = new FlowParam("flow_multiple0012"); flowEngine.execute(param2); } @Test public void testAddFilterForAllFlow() throws Exception { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/parser/parser_test_003.json"); flowEngine.setFlowParser(new TestFlowParserImpl()); flowEngine.init(); FlowParam param = new FlowParam("parser_test_003", new String[] {}, null); flowEngine.execute(param); } @Test public void testSubFlow001() throws Exception { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources; InputStream is = null; resources = resolver.getResources("classpath:flow/cases/parser/parser_test_subflow_001.json"); for (Resource resource : resources) { logger.info("Start parse flow definition:" + resource.getURI()); is = resource.getInputStream(); String flowConfigStr = FlowIOUtil.toString(is); List flowList = new FlowParserImpl().parse(flowConfigStr); is.close(); assertEquals(flowList.get(1).getProperty("_parent_flow_id"), "flow1"); assertEquals(flowList.get(2).getProperty("_parent_flow_id"), "flow11"); } } @Test public void testSubFlow002() throws Exception { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources; InputStream is = null; resources = resolver.getResources("classpath:flow/cases/parser/parser_test_subflow_002.json"); for (Resource resource : resources) { logger.info("Start parse flow definition:" + resource.getURI()); is = resource.getInputStream(); String flowConfigStr = FlowIOUtil.toString(is); List flowList = new FlowParserImpl().parse(flowConfigStr); is.close(); assertEquals(flowList.get(1).getProperty("_parent_flow_id"), "flow1"); assertEquals(flowList.get(2).getProperty("_parent_flow_id"), "flow11"); } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/parser/TestAddFilterParseListener.java ================================================ package com.jd.easyflow.flow.cases.parser; import com.jd.easyflow.flow.model.parser.event.FlowParseEvent; import com.jd.easyflow.flow.model.parser.event.FlowParseEventListener; import com.jd.easyflow.flow.model.parser.event.FlowParseEventTypes; /** * * @author liyuliang5 */ public class TestAddFilterParseListener implements FlowParseEventListener { @Override public void on(FlowParseEvent event) { switch (event.getType()) { case FlowParseEventTypes.INIT_FLOW_END: { event.getFlow().getFilterManager().addFilter(new TestFlowParamAndResultPrintFilter()); break; } } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/parser/TestFlowParamAndResultPrintFilter.java ================================================ package com.jd.easyflow.flow.cases.parser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.filter.FilterChain; /** * * @author liyuliang5 */ public class TestFlowParamAndResultPrintFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(TestFlowParamAndResultPrintFilter.class); @Override public FlowResult doFilter(FlowContext request, FilterChain chain) { logger.info("flowId is:" + request.getParam().getFlowId()); FlowResult result = chain.doFilter(request); logger.info("flow result is:" + result.getResult()); return result; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/parser/TestFlowParseListener.java ================================================ package com.jd.easyflow.flow.cases.parser; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.model.parser.event.FlowParseEvent; import com.jd.easyflow.flow.model.parser.event.FlowParseEventListener; import com.jd.easyflow.flow.model.parser.event.FlowParseEventTypes; /** * * @author liyuliang5 * */ public class TestFlowParseListener implements FlowParseEventListener { @Override public void on(FlowParseEvent event) { switch (event.getType()) { case FlowParseEventTypes.PARSE_FLOW_START: { List> nodeList = (List>) event.getFlowDef().get("nodes"); for (Map node : nodeList) { String name = (String) node.get("name"); if (name == null) { node.put("name", node.get("id")); } } break; } case FlowParseEventTypes.PARSE_FLOW_END: { event.getFlow().setStartNodeIds(new String[] { event.getFlow().getNodeList().get(0).getId() }); break; } } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/parser/TestFlowParserImpl.java ================================================ package com.jd.easyflow.flow.cases.parser; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.parser.FlowParserImpl; import com.jd.easyflow.flow.model.parser.event.FlowParseEventListener; /** * * @author liyuliang5 */ public class TestFlowParserImpl extends FlowParserImpl { @Override protected List parseParseListeners(Map map, Flow flow, boolean parseEl) { List list = super.parseParseListeners(map, flow, parseEl); if (list == null) { list = new ArrayList(); } list.add(new TestAddFilterParseListener()); return list; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/performance/EmptyStepAction.java ================================================ package com.jd.easyflow.flow.cases.performance; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class EmptyStepAction implements NodeAction { @Override public T execute(NodeContext nodeContext, FlowContext context) { int i = 0; i++; return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/performance/LoopStepAction.java ================================================ package com.jd.easyflow.flow.cases.performance; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.NodeContextAccessor; /** * * @author liyuliang5 * */ public class LoopStepAction implements NodeAction { private static final String COUNT_KEY = "count"; int i = 0; @Override public T execute(NodeContext nodeContext, FlowContext context) { if (i < (int) context.getParam().get(COUNT_KEY)) { i++; NodeContextAccessor.setNextNodeIds(nodeContext, new String[] {"EMPTY_NODE3"}); } return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/performance/MemoryTest.java ================================================ package com.jd.easyflow.flow.cases.performance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * Test memory usage * @author liyuliang5 * */ public class MemoryTest { private static final Logger log = LoggerFactory.getLogger(MemoryTest.class); /** * test one node exeucte multiple times. * If flow.recordHistory set to true, -Xmx=5M, OOM will occur. */ public void test1() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/performance/flow_memory001.json"); flowEngine.init(); int count = 1000000000; // start test log.info("start test"); long t1 = System.currentTimeMillis(); FlowParam param = new FlowParam("flow_memory_001", "EMPTY_NODE3", null); param.put("count", count); FlowResult result = flowEngine.execute(param); long time = System.currentTimeMillis() - t1; log.info("execute " + count + " times, elpase " + time + "ms"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/performance/PerformanceTest.java ================================================ package com.jd.easyflow.flow.cases.performance; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.el.ElFactory; import com.jd.easyflow.flow.el.SpelEvaluator; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class PerformanceTest { private static final Logger log = LoggerFactory.getLogger(PerformanceTest.class); /** * test one node exeucte multiple times */ @Test public void test1() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/performance/flow_performance001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_performance001", "EMPTY_NODE2", null); param.setLogFlag(false); FlowResult result = flowEngine.execute(param); // start test log.info("start test"); int count = 1000; param = new FlowParam("flow_performance001", "EMPTY_NODE2", null); param.setLogFlag(false); long t1 = System.currentTimeMillis(); for (int i = 0; i < count; i++) { result = flowEngine.execute(param); } long time = System.currentTimeMillis() - t1; log.info("execute " + count + "times, elapse " + time + "ms"); } /** * test one flow with one spel node */ @Test public void test2() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/performance/flow_performance001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_performance001", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); // start test log.info("start test"); param = new FlowParam("flow_performance001", "EMPTY_NODE", null); param.setLogFlag(false); SpelEvaluator evaluator = new SpelEvaluator(); evaluator.setRootType(1); ElFactory.setDefaultEvaluator(evaluator); long t1 = System.currentTimeMillis(); int count = 1000; for (int i = 0; i < count; i++) { result = flowEngine.execute(param); } long time = System.currentTimeMillis() - t1; log.info("execute " + count + " times, elapse" + time + "ms"); } /** * test one flow execute multiple times with one loop node. */ @Test public void test3() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/performance/flow_performance001.json"); flowEngine.init(); int count = 100; FlowParam param = new FlowParam("flow_performance001", "EMPTY_NODE3", null); param.put("count", 1); FlowResult result = flowEngine.execute(param); // start test log.info("start test"); long t1 = System.currentTimeMillis(); param = new FlowParam("flow_performance001", "EMPTY_NODE3", null); param.put("count", count); param.setLogFlag(false); result = flowEngine.execute(param); long time = System.currentTimeMillis() - t1; log.info("execute " + count + " times, elpase " + time + "ms"); } /** * test one flow with one SPEL post */ @Test public void test4() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/performance/flow_performance001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_performance001", "EMPTY_NODE4", null); FlowResult result = flowEngine.execute(param); // start test log.info("start test"); param = new FlowParam("flow_performance001", "EMPTY_NODE4", null); param.setLogFlag(false); SpelEvaluator evaluator = new SpelEvaluator(); evaluator.setRootType(0); ElFactory.setDefaultEvaluator(evaluator); long t1 = System.currentTimeMillis(); int count = 1000; for (int i = 0; i < count; i++) { result = flowEngine.execute(param); } long time = System.currentTimeMillis() - t1; log.info("execute " + count + " times, elapse" + time + "ms"); } /** * test one flow with one createExp SPEL post */ @Test public void test5() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/performance/flow_performance001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_performance001", "EMPTY_NODE5", null); FlowResult result = flowEngine.execute(param); // start test log.info("start test"); param = new FlowParam("flow_performance001", "EMPTY_NODE5", null); param.setLogFlag(false); SpelEvaluator evaluator = new SpelEvaluator(); evaluator.setRootType(1); ElFactory.setDefaultEvaluator(evaluator); long t1 = System.currentTimeMillis(); int count = 1000; for (int i = 0; i < count; i++) { result = flowEngine.execute(param); } long time = System.currentTimeMillis() - t1; log.info("execute " + count + " times, elapse" + time + "ms"); } // @Test // public void testBuildSpelRoot() { // NodeContext nodeContext = new NodeContext(); // FlowContext context = new FlowContextImpl(); // FlowParam param = new FlowParam(); // FlowResult result = new FlowResult(); // context.setParam(param); // context.setResult(result); // Map data = new HashMap<>(); // data.put("node", "node"); // // SpelEvaluator evaluator = new SpelEvaluator(); // long t1 = System.currentTimeMillis(); // for (int i = 0; i < 1000000; i++) { // evaluator.buildHashMapRoot(nodeContext, context, data); // } // long t2 = System.currentTimeMillis(); // for (int i = 0; i < 1000000; i++) { // evaluator.buildRootMapRoot(nodeContext, context, data); // } // long t3 = System.currentTimeMillis(); // log.info((t2 - t1) + " " + (t3 - t2)); // } // // @Test // public void testExecuteSpel() { // NodeContext nodeContext = new NodeContext(); // FlowContext context = new FlowContextImpl(); // FlowParam param = new FlowParam(); // FlowResult result = new FlowResult(); // context.setParam(param); // context.setResult(result); // Map data = new HashMap<>(); // data.put("node", "node"); // // SpelEvaluator evaluator = new SpelEvaluator(); // Object root1 = evaluator.buildHashMapRoot(nodeContext, context, data); // Object root2 = evaluator.buildRootMapRoot(nodeContext, context, data); // SpelHelper.evalWithDefaultContext("context.param", root1, true); // SpelHelper.evalWithDefaultContext("context.param", root2, true); // long t1 = System.currentTimeMillis(); // for (int i = 0; i < 1000000; i++) { // SpelHelper.evalWithDefaultContext("context.param", root1, true); // } // long t2 = System.currentTimeMillis(); // for (int i = 0; i < 1000000; i++) { // SpelHelper.evalWithDefaultContext("context.param", root1, true); // } // long t3 = System.currentTimeMillis(); // for (int i = 0; i < 1000000; i++) { // SpelHelper.evalWithDefaultContext("context.param", root1, true); // } // long t4 = System.currentTimeMillis(); // log.info((t2 - t1) + " " + (t3 - t2) + " " + (t4 - t3)); // } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/ConditionalPostHandlerTest.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowContextImpl; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.builder.FlowBuilder; import com.jd.easyflow.flow.model.post.ConditionalNodePostHandler; /** * @author liyuliang5 */ public class ConditionalPostHandlerTest { private static final Logger logger = LoggerFactory.getLogger(ConditionalPostHandlerTest.class); @Test public void testPerformance() { ConditionalNodePostHandler handler = new ConditionalNodePostHandler(); NodeContext nodeContext = new NodeContext("123"); FlowContextImpl flowContext = new FlowContextImpl(); Flow flow = FlowBuilder.create("001", "001").addNode("123", null).build(); flowContext.setFlow(flow); flowContext.setFlowEngine(new FlowEngineImpl()); Map branch = new HashMap(); branch.put("false", "NEXT"); handler.setBranchList(Arrays.asList(branch)); //handler.init(null, null); handler.postHandle(nodeContext, flowContext); long t1 = System.currentTimeMillis(); for (int i = 0; i < 1; i++) { handler.postHandle(nodeContext, flowContext); } logger.info("time cost" + (System.currentTimeMillis() - t1)); } @Test public void testCreateExpWhen() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/posthandler/flow_when_createexp_001.json"); flowEngine.init(); FlowResult result = flowEngine.execute(new FlowParam("flow_when_createexp_001")); assertEquals(result.getContext().getEndNodes().get(0).getNodeId(), "002"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/FlowIndexTest.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import static org.junit.Assert.assertEquals; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class FlowIndexTest { public static final Logger logger = LoggerFactory.getLogger(FlowIndexTest.class); @Test public void testFlow001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/posthandler/flow_index001.json"); flowEngine.init(); Map paramData = new HashMap<>(); paramData.put("amount", new BigDecimal(80)); FlowParam param = new FlowParam("flow_index001", "CONTRACT_SIGN", paramData); FlowResult result = flowEngine.execute(param); logger.info("Result:" + result); assertEquals("DO_LOAN", result.getContext().getEndNodes().get(0).getNodeId()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/NodePostHandlerTest.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.NodeContext; /** * Test NodePostHandler function * @author liyuliang5 * */ public class NodePostHandlerTest { private static final Logger logger = LoggerFactory.getLogger(NodePostHandlerTest.class); /** * Test exp post */ @Test public void testExpPostTo() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/posthandler/flow_exp_post_to_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_exp_post_to_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("STEP2", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testPostParam() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/posthandler/flow_post_param_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_post_param_001"); flowEngine.execute(param); } @Test public void testCreateExpPostTo() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/posthandler/flow_createexp_post_to_001.json"); flowEngine.init(); FlowResult result = flowEngine.execute(new FlowParam("flow_createexp_post_to_001")); assertEquals(result.getContext().getEndNodes().get(0).getNodeId(), "STEP2"); } @Test public void testPostData() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/posthandler/flow_postdata_001.json"); flowEngine.init(); FlowResult result = flowEngine.execute(new FlowParam("flow_postdata_001")); NodeContext nodeContext = result.getContext().getEndNodes().get(0); assertTrue((int)nodeContext.get("a") == 1); assertTrue((int)nodeContext.get("b") == 3); assertTrue((int) ((Map)nodeContext.get("c")).get("d") == 3); assertTrue(nodeContext.get("d").equals("dd")); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/PostHandlerFilterTest.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * Test PostHandlerFilter * * @author liyuliang5 * */ public class PostHandlerFilterTest { private static final Logger logger = LoggerFactory.getLogger(PostHandlerFilterTest.class); /** * Test exp post */ @Test public void testPostHandlerFilter() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/posthandler/flow_post_handler_filter_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_post_handler_filter_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("STEP3", result.getContext().getEndNodes().get(0).getNodeId()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/PostParamNode1Action.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class PostParamNode1Action implements NodeAction { @Override public T execute(NodeContext nodeContext, FlowContext context) { nodeContext.put("node1Param", 123); return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/PostParamNode2Action.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class PostParamNode2Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(PostParamNode2Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("node2 param " + nodeContext.getDataMap()); if (nodeContext.getDataMap().get("b").equals("123")) { throw new FlowException("context data error"); } return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/PostParamNode3Action.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class PostParamNode3Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(PostParamNode3Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("node3 param "); return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/posthandler/TestPostHandlerFilter.java ================================================ package com.jd.easyflow.flow.cases.posthandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.filter.Filter; import com.jd.easyflow.flow.filter.FilterChain; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.util.Pair; /** * Test node post handler filter. * * @author liyuliang5 * */ public class TestPostHandlerFilter implements Filter, NodeContext[]> { private static final Logger logger = LoggerFactory.getLogger(TestPostHandlerFilter.class); @Override public NodeContext[] doFilter(Pair request, FilterChain, NodeContext[]> chain) { NodeContext[] result = chain.doFilter(request); if ("STEP1".equals(request.getLeft().getNodeId())) { logger.info("origin result:" + result[0].getNodeId()); NodeContext nextNodeContext = new NodeContext("STEP3"); // request.getLeft().setNextNodes(new NodeContext[] { nextNodeContext }); return new NodeContext[] { nextNodeContext }; } return result; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/prehandler/PreHandlerTest.java ================================================ package com.jd.easyflow.flow.cases.prehandler; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class PreHandlerTest { private static final Logger logger = LoggerFactory.getLogger(PreHandlerTest.class); /** * Test exp post */ @Test public void testPreHandler001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/prehandler/flow_prehandler_001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_prehandler_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/pretty/FlowDefPrettyHelperTest.java ================================================ package com.jd.easyflow.flow.cases.pretty; import static org.junit.Assert.assertEquals; import java.lang.reflect.Method; import java.util.Map; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.util.FlowIOUtil; import com.jd.easyflow.flow.util.JsonPrettyHelper; import com.jd.easyflow.flow.util.JsonUtil; /** * flow definition pretty helper * * @author liyuliang5 * */ public class FlowDefPrettyHelperTest { private static final Logger logger = LoggerFactory.getLogger(FlowDefPrettyHelperTest.class); @Test public void testPrettyFile() throws Exception { String origin = FlowIOUtil.toString(FlowDefPrettyHelperTest.class.getResourceAsStream("/flow/cases/pretty/pretty_test.json")); String flowPrettyConf = FlowIOUtil.toString(FlowDefPrettyHelperTest.class.getResourceAsStream("/pretty/pretty-flow.json")); logger.info("Pretty conf:" + flowPrettyConf); logger.info("Input:" + JsonUtil.toJsonString(JsonUtil.parseObject(flowPrettyConf, Map.class))); String prettyStr = JsonPrettyHelper.pretty(origin, flowPrettyConf); logger.info("Output:\n" + prettyStr); } @Test public void testPretty1() throws Exception { String origin = FlowIOUtil.toString(FlowDefPrettyHelperTest.class.getResourceAsStream("/flow/cases/pretty/pretty_test.json")); String flowPrettyConf = FlowIOUtil.toString(FlowDefPrettyHelperTest.class.getResourceAsStream("/pretty/pretty-flow.json")); String prettyStr = JsonPrettyHelper.pretty(origin, flowPrettyConf); logger.info("Pretty result:\n" + prettyStr); logger.info("Start comparing result:"); String originJsonStr = JsonUtil.toJsonString(JsonUtil.parseObject(origin, Map.class)); String prettyJsonStr = JsonUtil.toJsonString(JsonUtil.parseObject(prettyStr, Map.class)); logger.info("Original result:" + originJsonStr); logger.info("Pretty result:" + prettyJsonStr); Assert.assertEquals(originJsonStr, prettyJsonStr); } @Test public void testPrettyList() throws Exception { String origin = FlowIOUtil.toString(FlowDefPrettyHelperTest.class.getResourceAsStream("/flow/cases/pretty/pretty_multiple_test.json")); String flowPrettyConf = FlowIOUtil.toString(FlowDefPrettyHelperTest.class.getResourceAsStream("/pretty/pretty-flow.json")); logger.info("Pretty conf:" + flowPrettyConf); logger.info("Input:" + JsonUtil.toJsonString(JsonUtil.parseObject(flowPrettyConf, Map.class))); String prettyStr = JsonPrettyHelper.pretty(origin, flowPrettyConf); logger.info("Output:\n" + prettyStr); } @Test public void testQuoteEscapse() throws Exception { Method method = JsonPrettyHelper.class.getDeclaredMethod("quote", new Class[] {String.class}); method.setAccessible(true); String result = (String) method.invoke(null, "a\\b\nc"); assertEquals(result, "\"a\\\\b\\nc\""); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/runner/MultiThreadTest.java ================================================ package com.jd.easyflow.flow.cases.runner; import static org.junit.Assert.assertFalse; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.util.FlowConstants; /** * * @author liyuliang5 * */ public class MultiThreadTest { public static final Logger logger = LoggerFactory.getLogger(MultiThreadTest.class); @Test public void test1() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_multi001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_multi001", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); logger.info("end"); } @Test(expected = Exception.class) public void testException() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_multi002.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_multi002", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); logger.info("end"); } @Test public void testTimeout() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_multi003.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_multi003", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); assertFalse(result.getContext().get(FlowConstants.FLOW_CTX_MULTI_AWAIT_RESULT)); logger.info("end"); } @Test(expected = FlowException.class) public void testTimeoutThrowException() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_multi004.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_multi004", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); logger.info("end"); } @Test public void testInterruptOnMultipleThread() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_interrupt001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_interrupt001"); FlowResult result = flowEngine.execute(param); logger.info("end"); } @Test public void testInterruptOnSingleThread() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_interrupt002.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_interrupt002"); FlowResult result = flowEngine.execute(param); logger.info("end"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/runner/ReusableThreadTest.java ================================================ package com.jd.easyflow.flow.cases.runner; import static org.junit.Assert.assertFalse; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.util.FlowConstants; /** * * @author liyuliang5 * */ public class ReusableThreadTest { public static final Logger logger = LoggerFactory.getLogger(ReusableThreadTest.class); @Test public void test1() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_reusable001.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_reusable001", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); logger.info("end"); } @Test(expected = Exception.class) public void testException() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_reusable002.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_reusable002", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); logger.info("end"); } @Test public void testTimeout() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_reusable003.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_reusable003", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); assertFalse(result.getContext().get(FlowConstants.FLOW_CTX_MULTI_AWAIT_RESULT)); logger.info("end"); } @Test public void testInterruptOnReusableThread() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_interrupt003.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_interrupt003"); FlowResult result = flowEngine.execute(param); logger.info("end"); } @Test public void testRunner() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/runner/flow_reusable004.json"); flowEngine.init(); FlowParam param = new FlowParam("flow_reusable004", "EMPTY_NODE", null); FlowResult result = flowEngine.execute(param); logger.info("end"); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/runner/TestInterruptNodeAction.java ================================================ package com.jd.easyflow.flow.cases.runner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class TestInterruptNodeAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(TestInterruptNodeAction.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("start execute node:" + nodeContext.getNodeId()); try { Thread.sleep(300); } catch (InterruptedException e) { throw new RuntimeException(e); } boolean interrupt = Boolean.TRUE.equals(context.getFlow().getNode(nodeContext.getNodeId()).getProperty("interrupt")); if (interrupt) { logger.info("interrupt"); context.setInterrupted();; } logger.info("end execute node:" + nodeContext.getNodeId()); return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/runner/TestMultiRunner.java ================================================ package com.jd.easyflow.flow.cases.runner; import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; import java.util.concurrent.TimeUnit; import com.jd.easyflow.flow.engine.impl.MultipleThreadFlowRunner; /** * * @author liyuliang5 * */ public class TestMultiRunner extends MultipleThreadFlowRunner { public TestMultiRunner() { this.executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Executors.defaultThreadFactory(), new AbortPolicy()); } public TestMultiRunner(long timeout) { this.executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Executors.defaultThreadFactory(), new AbortPolicy()); this.timeout = timeout; } public TestMultiRunner(long timeout, boolean throwExceptionOnTimeout) { this.executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Executors.defaultThreadFactory(), new AbortPolicy()); this.timeout = timeout; this.throwExceptionOnTimeout = throwExceptionOnTimeout; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/runner/TestReusableThreadRunner.java ================================================ package com.jd.easyflow.flow.cases.runner; import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; import java.util.concurrent.TimeUnit; import com.jd.easyflow.flow.engine.impl.ReusableThreadFlowRunner; /** * * @author liyuliang5 * */ public class TestReusableThreadRunner extends ReusableThreadFlowRunner { public TestReusableThreadRunner() { this.executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Executors.defaultThreadFactory(), new AbortPolicy()); } public TestReusableThreadRunner(long timeout) { this.executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Executors.defaultThreadFactory(), new AbortPolicy()); this.timeout = timeout; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/share/nodeaction/TestStepAction.java ================================================ package com.jd.easyflow.flow.cases.share.nodeaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class TestStepAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(TestStepAction.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { FlowNode flowNode = context.getFlow().getNode(nodeContext.getNodeId()); boolean exception = Boolean.TRUE.equals(flowNode.getProperty("exception")); if (exception) { logger.info("exception"); throw new RuntimeException("exception"); } Integer sleep = flowNode.getProperty("sleep"); if (sleep != null) { logger.info("Sleep time is:" + sleep); try { Thread.sleep(sleep); } catch (InterruptedException e) { throw new RuntimeException(e); } } return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/spring/SpringFlowTest.java ================================================ package com.jd.easyflow.flow.cases.spring; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.jd.easyflow.flow.engine.FlowEngine; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.quickstart.QuickStart001Node01Action; import com.jd.easyflow.flow.quickstart.QuickStart002Node01Action; import com.jd.easyflow.flow.quickstart.QuickStart003Node01Action; /** * * @author liyuliang5 */ public class SpringFlowTest { private static final Logger logger = LoggerFactory.getLogger(SpringFlowTest.class); @Test public void testIntegrationWithSpringXml() { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:flow/cases/spring/applicationContext-flow-test.xml"); FlowEngine flowEngine = context.getBean(FlowEngine.class); FlowParam param = new FlowParam("flow_spring_test_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("node003", result.getContext().getEndNodes().get(0).getNodeId()); } @Test public void testIntegrationWithSpringAnnotation() { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); FlowEngine flowEngine = context.getBean(FlowEngine.class); FlowParam param = new FlowParam("flow_spring_test_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("node003", result.getContext().getEndNodes().get(0).getNodeId()); } @Configuration public static class SpringConfig { @Bean public FlowEngine flowEngine() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/spring/flow_spring_001.json"); return flowEngine; } @Bean public QuickStart001Node01Action quickStart001Node01Action() { return new QuickStart001Node01Action(); } @Bean public QuickStart002Node01Action quickStart002Node01Action() { return new QuickStart002Node01Action(); } @Bean public QuickStart003Node01Action quickStart003Node01Action() { return new QuickStart003Node01Action(); } } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/subflow/SubFlowTest.java ================================================ package com.jd.easyflow.flow.cases.subflow; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * Sub flow test cases. * * @author liyuliang5 * */ public class SubFlowTest { private static final Logger logger = LoggerFactory.getLogger(SubFlowTest.class); /** * Test inner subflow. */ @Test public void testInnerSubFlow() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/subflow/flow_subflow_001.json"); flowEngine.init(); FlowParam param = new FlowParam("subflow_test_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("node003", result.getContext().getEndNodes().get(0).getNodeId()); } /** * Test out subflow. */ @Test public void testOutSubFlow() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath( "classpath:flow/cases/subflow/flow_subflow_002.json,classpath:flow/cases/subflow/flow_subflow_002_01.json"); flowEngine.init(); FlowParam param = new FlowParam("subflow_test_002"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("node003", result.getContext().getEndNodes().get(0).getNodeId()); } /** * Test self subflow. */ @Test public void testSelfSubFlow() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath( "classpath:flow/cases/subflow/flow_subflow_003.json,classpath:flow/cases/subflow/flow_subflow_003_01.json"); flowEngine.init(); FlowParam param = new FlowParam("subflow_test_003"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("node003", result.getContext().getEndNodes().get(0).getNodeId()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/cases/subflow/TestInvokeSubFlowNodeAction.java ================================================ package com.jd.easyflow.flow.cases.subflow; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class TestInvokeSubFlowNodeAction implements NodeAction { @Override public T execute(NodeContext nodeContext, FlowContext context) { String subFlowId = context.getFlow().getNode(nodeContext.getNodeId()).getProperty("subFlowId"); FlowParam flowParam = new FlowParam(subFlowId); context.getFlowEngine().execute(flowParam); return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/quickstart/QuickStart001Node01Action.java ================================================ package com.jd.easyflow.flow.quickstart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class QuickStart001Node01Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(QuickStart001Node01Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("Execute Node 001"); return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/quickstart/QuickStart002Node01Action.java ================================================ package com.jd.easyflow.flow.quickstart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class QuickStart002Node01Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(QuickStart002Node01Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("Execute Node 002"); return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/quickstart/QuickStart003Node01Action.java ================================================ package com.jd.easyflow.flow.quickstart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class QuickStart003Node01Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(QuickStart003Node01Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("Execute Node 003"); return null; } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/quickstart/QuickStartTest.java ================================================ package com.jd.easyflow.flow.quickstart; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class QuickStartTest { private static final Logger logger = LoggerFactory.getLogger(QuickStartTest.class); @Test public void testQuickStart001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/quickstart/quickstart_001.json"); flowEngine.init(); FlowParam param = new FlowParam("quickstart_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("node003", result.getContext().getEndNodes().get(0).getNodeId()); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/unit/util/JsonTest.java ================================================ package com.jd.easyflow.flow.unit.util; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowContextImpl; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class JsonTest { public static final Logger logger = LoggerFactory.getLogger(JsonTest.class); @Test public void test2St() { logger.info("null:" + JsonUtil.toJsonString(null)); logger.info("1" + JsonUtil.toJsonString(1)); logger.info("123:" + JsonUtil.toJsonString("123")); logger.info("date:" + JsonUtil.toJsonString(new Date())); logger.info("map:" + JsonUtil.toJsonString( new HashMap<>())); logger.info("object:" + JsonUtil.toJsonString(new Object())); } @Test public void test2Object() { logger.info("null:" + JsonUtil.parseObject("null", Object.class)); logger.info("1" + JsonUtil.parseObject("1", Integer.class)); logger.info("123:" + JsonUtil.parseObject("123", String.class)); logger.info("date:" + JsonUtil.parseObject("1591418665721", Date.class)); logger.info("map:" + JsonUtil.parseObject("{}", Map.class)); logger.info("object:" + JsonUtil.parseObject("{}", Object.class)); } @Test public void testJsonIgnore() { FlowParam param = new FlowParam(); FlowContextImpl context = new FlowContextImpl(); param.setContext(context); context.setParam(param); logger.info(JsonUtil.toJsonString(context)); } @Test public void testJsonIgnore2() { FlowResult result = new FlowResult(); FlowContextImpl context = new FlowContextImpl(); result.setContext(context); context.setResult(result); logger.info(JsonUtil.toJsonString(result)); } } ================================================ FILE: easyflow-flow/src/test/java/com/jd/easyflow/flow/unit/util/TreeMapTest.java ================================================ package com.jd.easyflow.flow.unit.util; import java.util.Comparator; import java.util.TreeMap; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author liyuliang5 * */ public class TreeMapTest { private static final Logger logger = LoggerFactory.getLogger(TreeMapTest.class); @Test public void testTreeMap1() { TreeMap map = new TreeMap<>(); map.put(1, "1"); map.put(0, "0"); map.put(-1, "-1"); map.put(2, "2"); map.put(-2, "-2"); logger.info("ascending order:"); for (Integer i : map.keySet()) { logger.info(i + ""); } logger.info("reverse order:"); for (Integer i : map.descendingKeySet()) { logger.info(i + ""); } } @Test public void testTreeMap2() { TreeMap map = new TreeMap<>(Comparator.reverseOrder()); map.put(1, "1"); map.put(0, "0"); map.put(-1, "-1"); map.put(2, "2"); map.put(-2, "-2"); logger.info("ascending order:"); for (Integer i : map.keySet()) { logger.info(i + ""); } logger.info("reverse order:"); for (Integer i : map.descendingKeySet()) { logger.info(i + ""); } } } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/action/action_interrupt_001.json ================================================ { "id": "action_interrupt_test_001", "name": "Action Interrupt Test 001", "nodes": [ {"id": "node001","name": "Node001","start": true, "post": {"to": ["node002", "node003"]}}, {"id": "node002","action":{"createExp":"new com.jd.easyflow.flow.model.action.InterruptNodeAction()"}, "name": "Node002"}, {"id": "node003", "name": "Node003"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/action/compensate_001.json ================================================ { "id":"compensate_001", "nodes":[ {"id":"node001", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":"node002"}}, {"id":"node002", "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":"node003"}}, {"id":"node003", "action":{"createExp":"new com.jd.easyflow.flow.model.action.CompensateNodeAction()"}} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/action/compensate_002.json ================================================ { "id":"compensate_002", "nodes":[ {"id":"node001", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":["node002","node003"]}}, {"id":"node002", "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":"node004"}}, {"id":"node003", "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":"node004"}}, {"id":"node004", "pre":{"preNodes":["node002","node003"]}, "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":"node005"}}, {"id":"node005", "action":{"createExp":"new com.jd.easyflow.flow.model.action.CompensateNodeAction()"}} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/action/compensate_flow_001.json ================================================ { "id":"compensate_flow_001", "nodes":[ {"id":"node001", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":["node002","node003"]}}, {"id":"node002", "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":"node004"}}, {"id":"node003", "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}, "post":{"to":"node004"}}, {"id":"node004", "pre":{"preNodes":["node002","node003"]}, "action":{"createExp":"new com.jd.easyflow.flow.cases.action.CompensateTest.TestCompensateNodeAction()"}} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/action/loop_test_001.json ================================================ { "id": "loop_test_001", "name": "Loop Test 001", "nodes": [ { "id": "node001", "name": "Node001", "start": true, "action": { "createExp": "new com.jd.easyflow.flow.model.action.LoopNodeAction()" }, "properties": { "loopTestBefore":false, "loopMaxCountExp":"param.get('loopMaxCount')", "loopAction":{ "exp":"T(java.lang.System).out.println(param.get('data').get(nodeContext.get('currentLoopIndex')))" } } } ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/action/multiple_action_001.json ================================================ {"id":"multi_action_001", "nodes":[ {"id":"node1", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.action.MultipleActionTest.TestMultipleActionNodeAction()"}, "properties":{"action":{"exp":"T(java.lang.System).out.println('subAction')"}}} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/event/flow_event_001.json ================================================ { "id": "flow_event_001","name": "Flow event 001", "nodes": [ {"id": "EMPTY_NODE","name": "Single Empty Node1","action":{"createExp":"new com.jd.easyflow.flow.model.action.EventNodeAction()"}, "post":{"createExp":"new com.jd.easyflow.flow.model.post.EventPostHandler()"}, "properties":{"events":{ "EVENT1":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction().execute(nodeContext, context)", "EVENT2":{"action":{"createExp":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction()"}}, "EVENT3":{"action":{"exp":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction().execute(nodeContext, context)"}, "post":{"to":"EMPTY_NODE2"}} }} }, {"id": "EMPTY_NODE2","name": "Single Empty Node2"} ], "listeners":[{"createExp":"new com.jd.easyflow.flow.engine.event.impl.EventFlowListener()"}] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/event/flow_event_002.json ================================================ { "id": "flow_event_002","name": "Flow event 002", "nodes": [ {"id": "EMPTY_NODE","name": "Single Empty Node1","action":{"exp":"new com.jd.easyflow.flow.model.action.EventNodeAction().execute(nodeContext, context)"}, "post":{"createExp":"new com.jd.easyflow.flow.model.post.EventPostHandler()"}, "properties":{"events":{ "EVENT1":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction().execute(nodeContext, context)", "EVENT2":{"action":{"createExp":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction()"}}, "EVENT3":{"action":{"exp":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction().execute(nodeContext, context)"}, "post":{"to":"EMPTY_NODE2"}} }} }, {"id": "EMPTY_NODE2","name": "Single Empty Node2"} ], "listeners":[{"createExp":"new com.jd.easyflow.flow.engine.event.impl.EventFlowListener()"}] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/event/flow_event_003.json ================================================ { "id": "flow_event_003","name": "Flow event 003", "nodes": [ {"id": "EMPTY_NODE","name": "Single Empty Node1","action":{"type":"event"}, "post":{"type":"event"}, "properties":{"events":{ "EVENT1":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction().execute(nodeContext, context)", "EVENT2":{"action":{"createExp":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction()"}}, "EVENT3":{"action":{"exp":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction().execute(nodeContext, context)"}, "post":{"to":"EMPTY_NODE2"}} }} }, {"id": "EMPTY_NODE2","name": "Single Empty Node2"} ], "listeners":[{"createExp":"new com.jd.easyflow.flow.engine.event.impl.EventFlowListener()"}] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/event/flow_event_nolistener_001.json ================================================ { "id": "flow_event_nolistener_001", "nodes": [ {"id": "EMPTY_NODE","name": "Single Empty Node1","action":{"type":"event"}, "post":{"type":"event"}, "properties":{"events":{ "EVENT3":{"action":{"exp":"new com.jd.easyflow.flow.cases.event.TestEventNodeAction().execute(nodeContext, context)"}, "post":{"to":"EMPTY_NODE2"}} }} }, {"id": "EMPTY_NODE2","name": "Single Empty Node2"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/filter/inner_flow_engine_filter_001.json ================================================ { "id":"innerFlowEngineFilter001", "nodes":[ {"id":"001", "start":true} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/filter/inner_flow_filter_001.json ================================================ { "id":"innerFlowFilter001", "nodes":[ {"id":"node001", "start":true, "action":"0", "post":{"when":"actionResult>0", "to":"node002"}}, {"id":"node002"} ], "filters":[{"createExp":"new com.jd.easyflow.flow.cases.filter.FilterTest.TestInnerFlowFilter(-1)"}], "nodeFilters":[{"createExp":"new com.jd.easyflow.flow.cases.filter.FilterTest.TestInnerNodeFilter()", "order":-1}] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/filter/inner_node_action_filter_001.json ================================================ { "id":"innerNodeActionFilter001", "nodes":[ {"id":"node001", "start":true, "action":"0", "post":{"when":"actionResult>0", "to":"node002"}}, {"id":"node002"} ], "nodeActionFilters":[{"createExp":"new com.jd.easyflow.flow.cases.filter.FilterTest.TestNodeActionFilter()", "order":-1}] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/inclusive/flow_inclusive201.json ================================================ { "id": "flow_inclusive201","name": "Inclusive nodes 001", "nodes": [ {"id": "START_NODE", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"conditionType":"inclusive", "conditions":[ {"when":"bizParam<1", "to":"NODE1"}, {"when":"bizParam<2", "to":"NODE2"} ]} }, {"id": "NODE1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE_NODE"}}, {"id": "NODE2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE_NODE"}}, {"id": "INCLUSIVE_NODE", "pre":{"createExp":"new com.jd.easyflow.flow.model.pre.InclusiveCheckPreHandler()"}, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"END"}, "properties":{"preNodes":["NODE1","NODE2"]}}, {"id":"END"} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestMultiRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/inclusive/flow_inclusive202.json ================================================ { "id": "flow_inclusive202","name": "Inclusive nodes 001", "nodes": [ {"id": "START_NODE", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"conditionType":"inclusive", "conditions":[ {"when":"bizParam<1", "to":"NODE1"}, {"when":"bizParam<2", "to":"NODE2"} ]} }, {"id": "NODE1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"NODE11"}}, {"id": "NODE2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"NODE21"}}, {"id": "NODE11","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE_NODE"}}, {"id": "NODE21","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE_NODE"}}, {"id": "INCLUSIVE_NODE", "pre":{"createExp":"new com.jd.easyflow.flow.model.pre.InclusiveCheckPreHandler()"}, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"END"}, "properties":{"preNodes":["NODE11","NODE21"]}}, {"id":"END"} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/inclusive/flow_inclusive203.json ================================================ { "id": "flow_inclusive203","name": "Inclusive nodes 001", "nodes": [ {"id": "START_NODE", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"conditionType":"inclusive", "conditions":[ {"when":"bizParam.node1", "to":"NODE1"}, {"when":"bizParam.node2", "to":"NODE2"}, {"when":"bizParam.node3", "to":"NODE3"} ]} }, {"id": "NODE1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE1"}}, {"id": "NODE2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE1"}}, {"id": "NODE3","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE2"}}, {"id": "INCLUSIVE1","pre":{"type":"inclusiveCheck", "preNodes":["NODE1","NODE2"]}, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"INCLUSIVE2"}}, {"id": "INCLUSIVE2", "pre":{"type":"inclusiveCheck","preNodes":["INCLUSIVE1","NODE3"]}, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"END"}}, {"id":"END"} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/inclusive/flow_inclusive303.json ================================================ { "id": "flow_inclusive303","name": "Inclusive nodes 001", "nodes": [ {"id": "S", "start":true, "post":{"conditionType":"inclusive", "conditions":[ {"when":"bizParam.I1", "to":"I1"}, {"when":"bizParam.I2", "to":"I2"} ]} }, {"id": "I1","pre":{"type":"inclusiveCheck", "preNodes":["S","I2"]}, "post":{"to":"I2"}}, {"id": "I2", "pre":{"type":"inclusiveCheck","preNodes":["S","I1"]},"post":{"to":"I1"}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/interrupt/interrupt_001.json ================================================ { "id": "interrupt_001", "nodes": [ {"id": "node001","action":{"createExp":"new com.jd.easyflow.flow.cases.interrupt.TestInterruptBizNodeAction(1000, false)"}, "start":true}, {"id": "node0021","action":{"createExp":"new com.jd.easyflow.flow.cases.interrupt.TestInterruptBizNodeAction(2000, true)"}, "post":{"when":"actionResult", "to":"node022"}, "start":true}, {"id": "node0022","action":{"createExp":"new com.jd.easyflow.flow.cases.interrupt.TestInterruptBizNodeAction(1000, false)"}}, {"id": "node003","action":{"createExp":"new com.jd.easyflow.flow.cases.interrupt.TestInterruptBizNodeAction(3000, false)"}, "start":true} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner(10000)"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/interrupt/interrupt_002.json ================================================ { "id": "interrupt_002", "nodes": [ {"id": "node001","action":{"createExp":"new com.jd.easyflow.flow.cases.interrupt.TestInterruptBizNodeAction(1000, true)"}, "start":true}, {"id": "node002","action":{"createExp":"new com.jd.easyflow.flow.cases.interrupt.TestInterruptBizNodeAction(2000, true)"}, "start":true}, {"id": "node003","action":{"createExp":"new com.jd.easyflow.flow.cases.interrupt.TestInterruptBizNodeAction(3000, true)"}, "start":true} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner(10000)"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/listener/interrupt_listener_001.json ================================================ { "id": "interrupt_listener_test_001", "name": "Interrupt Listener Test 001", "nodes": [ {"id": "node001","name": "Node001","start": true, "post": {"to": ["node002", "node003"]}}, {"id": "node002", "name": "Node002", "properties":{"interrupt":true}}, {"id": "node003", "name": "Node003"} ], "listeners":[ {"createExp":"new com.jd.easyflow.flow.engine.event.impl.InterruptFlowListener()"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/listener/interrupt_listener_002.json ================================================ { "id": "interrupt_listener_test_002", "name": "Interrupt Listener Test 002", "nodes": [ {"id": "node001","name": "Node001","start": true, "post": {"to": ["node002", "node003"]}}, {"id": "node002", "name": "Node002"}, {"id": "node003", "name": "Node003", "properties":{"interrupt":true}} ], "listeners":[ {"createExp":"new com.jd.easyflow.flow.engine.event.impl.InterruptFlowListener()"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/listener/interrupt_listener_003.json ================================================ { "id": "interrupt_listener_test_003", "name": "Interrupt Listener Test 003", "nodes": [ {"id": "node001","name": "Node001","start": true, "post": {"to": ["node002", "node003"]}}, {"id": "node002", "name": "Node002", "properties":{"interruptExp":"param.param==true"}}, {"id": "node003", "name": "Node003"} ], "listeners":[ {"createExp":"new com.jd.easyflow.flow.engine.event.impl.InterruptFlowListener()"} ]} ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/logflag/logflag_001.json ================================================ { "id":"logFlag_001", "nodes":[ {"id":"node001","action":"new com.jd.easyflow.flow.cases.logflag.LogFlagTest().throwException()","start":true} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/mockbiz/mock_loan_001.json ================================================ { "id": "mock_loan_001", "name": "Mock loan flow", "nodes": [ { "id": "CONTRACT_SIGN", "name": "sign contract", "properties": {"order": 1}, "pre":null, "action":{"exp":"new com.jd.easyflow.flow.cases.mockbiz.LoanContractSignBiz().sign()"}, "post":{"to":"LIMIT_JUDGE"} }, { "id": "LIMIT_JUDGE", "name": "Judge limit", "properties": {"order": 2}, "action":{"exp":"new com.jd.easyflow.flow.cases.mockbiz.LimitBiz().judgeLimit(param.param['amount'])"}, "post":{"conditions":[{"when":"nodeContext.actionResult==true", "to":"DO_LOAN"}]} }, { "id": "DO_LOAN", "name": "Do loan", "properties": {"order": 3}, "action":{"exp":"new com.jd.easyflow.flow.cases.mockbiz.LoanBiz().doLoan()"} } ], "listeners":[ {"createExp":"new com.jd.easyflow.flow.cases.mockbiz.MockFlowListener()"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/parallel/flow_parallel001.json ================================================ { "id": "flow_parallel001","name": "Parallel nodes 001", "nodes": [ {"id": "START_NODE", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":["PARALLEL_NODE1","PARALLEL_NODE2"]} }, {"id": "PARALLEL_NODE1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"END_NODE"}, "properties":{"sleep":100}}, {"id": "PARALLEL_NODE2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"END_NODE"}, "properties":{"sleep":300}}, {"id": "END_NODE", "pre":{"createExp":"new com.jd.easyflow.flow.model.pre.MultiCheckPreHandler()"}, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"preNodes":["PARALLEL_NODE1","PARALLEL_NODE2"]}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestMultiRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/parser/flow_multiple001.json ================================================ [ { "id": "flow_multiple0011","name": "0011", "nodes": [ {"id": "START_NODE1", "start":true} ] }, { "id": "flow_multiple0012","name": "0012", "nodes": [{"id": "START_NODE2", "start":true} ] } ] ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/parser/parser_test_001.json ================================================ { "id": "parser_test_001", "name": "Parser Test 001", "nodes": [ {"id": "node001","name": "Node001","start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart002Node01Action()"},"post": {"to": "node003"}}, {"id": "node003","name": "Node003"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/parser/parser_test_002.json ================================================ { "id": "parser_test_002", "name": "Parser Test 002", "nodes": [ {"id": "node001", "post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart002Node01Action()"},"post": {"to": "node003"}}, {"id": "node003","name": "Node003"} ], "parseListeners":[{"createExp":"new com.jd.easyflow.flow.cases.parser.TestFlowParseListener()"}] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/parser/parser_test_003.json ================================================ { "id": "parser_test_003", "name": "Parser Test 003", "nodes": [ {"id": "node001"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/parser/parser_test_subflow_001.json ================================================ { "id": "flow1", "nodes": [ { "id": "node1", "action": { "flow": { "id": "flow11", "nodes": [ { "id": "node11", "action": { "flow": { "id": "flow111" } } } ] } } } ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/parser/parser_test_subflow_002.json ================================================ { "id": "flow1", "nodes": [ { "id": "node1", "action":{"type":"flow"}, "properties": { "flow": { "id": "flow11", "nodes": [ { "id": "node11", "action":{"type":"flow"}, "properties": { "flow": { "id": "flow111" } } } ] } } } ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/performance/flow_memory001.json ================================================ { "id": "flow_memory_001","name": "Memory test flow 001", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"exp":"new com.jd.easyflow.flow.cases.performance.EmptyStepAction().execute(nodeContext, context)"}}, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.performance.EmptyStepAction()"}}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.performance.LoopStepAction()"}} ], "properties":{ "flow.recordHistory":false } } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/performance/flow_performance001.json ================================================ { "id": "flow_performance001","name": "Performance test flow 001", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"exp":"new com.jd.easyflow.flow.cases.performance.EmptyStepAction().execute(nodeContext, context)"}}, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.performance.EmptyStepAction()"}}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.performance.LoopStepAction()"}}, {"id": "EMPTY_NODE4","name": "Empty node 4", "post":{"when":"actionResult==true", "to":"EMPTY_NODE2"}}, {"id": "EMPTY_NODE5","name": "Empty node 5", "post":{"when":{"createExp":"T(com.jd.easyflow.flow.model.action.ActionResultEl).create('==',true)"}, "to":"EMPTY_NODE2"}} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/posthandler/flow_createexp_post_to_001.json ================================================ { "id": "flow_createexp_post_to_001", "nodes": [ { "id": "STEP1", "start": true, "post":{"to":{"createExp":"new com.jd.easyflow.flow.cases.common.TestNodeExecutor('STEP2')"}} }, { "id": "STEP2" } ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/posthandler/flow_exp_post_to_001.json ================================================ { "id": "flow_exp_post_to_001", "name": "text exp post to", "nodes": [ { "id": "STEP1", "start": true, "post":{"to":{"exp":"'STEP' + '2'"}} }, { "id": "STEP2" } ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/posthandler/flow_index001.json ================================================ { "id": "flow_index001", "name": "Mock loan flow", "nodes": [ { "id": "CONTRACT_SIGN", "name": "Sign contract", "properties": {"order": 1}, "pre":null, "action":{"exp":"new com.jd.easyflow.flow.cases.mockbiz.LoanContractSignBiz().sign()"}, "post":{"to":"$next"} }, { "id": "LIMIT_JUDGE", "name": "Juedge limit", "properties": {"order": 2}, "action":{"exp":"new com.jd.easyflow.flow.cases.mockbiz.LimitBiz().judgeLimit(param.param['amount'])"}, "post":{"conditions":[{"when":"nodeContext.actionResult==true", "to":2}]} }, { "id": "DO_LOAN", "name": "Do loan", "properties": {"order": 3}, "action":{"exp":"new com.jd.easyflow.flow.cases.mockbiz.LoanBiz().doLoan()"} } ], "listeners":[ {"createExp":"new com.jd.easyflow.flow.cases.mockbiz.MockFlowListener()"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/posthandler/flow_post_handler_filter_001.json ================================================ { "id": "flow_post_handler_filter_001", "name": "flow_post_handler_filter_001", "nodes": [ { "id": "STEP1", "start": true, "action":{"createExp":"new com.jd.easyflow.flow.cases.posthandler.PostParamNode1Action()"}, "post":{"to":{"node":"STEP2", "data":{"a":"5", "b":"nodeContext.dataMap['node1Param']"}}} }, { "id": "STEP2", "action":{"createExp":"new com.jd.easyflow.flow.cases.posthandler.PostParamNode2Action()"} }, { "id": "STEP3", "action":{"createExp":"new com.jd.easyflow.flow.cases.posthandler.PostParamNode3Action()"} } ], "nodePostHandlerFilters":[{"createExp":"new com.jd.easyflow.flow.cases.posthandler.TestPostHandlerFilter()"}] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/posthandler/flow_post_param_001.json ================================================ { "id": "flow_post_param_001", "name": "flow post param 001", "nodes": [ { "id": "STEP1", "start": true, "action":{"createExp":"new com.jd.easyflow.flow.cases.posthandler.PostParamNode1Action()"}, "post":{"to":{"node":"STEP2", "data":{"a":"5", "b":"nodeContext.dataMap['node1Param']"}}} }, { "id": "STEP2", "action":{"createExp":"new com.jd.easyflow.flow.cases.posthandler.PostParamNode2Action()"} } ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/posthandler/flow_postdata_001.json ================================================ { "id": "flow_postdata_001", "nodes": [ { "id": "STEP1", "start": true, "post":{"to":{"node":"STEP2", "data":{"a":"1", "b":{"exp":"1+2"}, "c":{"fixedValue":{"d":3}}, "d":{"createExp":"new com.jd.easyflow.flow.cases.common.TestNodeExecutor('dd')"}}} }}, { "id": "STEP2" } ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/posthandler/flow_when_createexp_001.json ================================================ { "id":"flow_when_createexp_001", "nodes":[ {"id":"001", "start":true, "post":{"when":{"createExp":"new com.jd.easyflow.flow.cases.common.TestNodeExecutor(true)"},"to":"002"}}, {"id":"002"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/prehandler/flow_prehandler_001.json ================================================ { "id":"flow_prehandler_001", "name":"prehandle flow 001", "nodes":[ {"id":"node_001", "name":"001 Node", "start":true, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":["node_002","node_003"]} }, {"id":"node_002", "name":"002 Node", "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"node_004"} }, {"id":"node_003", "name":"003 Node", "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"node_004"} }, {"id":"node_004", "name":"004 Node", "pre":{"preNodes":["node_002","node_003"]}, "action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":"end"} }, {"id":"end", "name":"End Node", "properties":{"end":true}} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/pretty/pretty_multiple_test.json ================================================ [ { "id": "flow_multiple0011","name": "0011", "nodes": [ {"id": "START_NODE1", "start":true} ] }, { "id": "flow_multiple0012","name": "0012", "nodes": [{"id": "START_NODE2", "start":true} ] } ] ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/pretty/pretty_test.json ================================================ { "id": "flow001", "name": "Mock loan flow", "pre":{"createExp":"null"}, "nodes": [ { "id": "CONTRACT_SIGN", "name": "sign contract", "pre":null, "action":{"exp":"new com.jd.easyflow.flow.mockbiz.loan.contract.LoanContractSignBiz().sign()"}, "post":{"to":"LIMIT_JUDGE"}, "properties": {"order": 1} }, { "id": "LIMIT_JUDGE", "name": "Judge limit", "action":{"exp":"new com.jd.easyflow.flow.mockbiz.limit.LimitBiz().judgeLimit(param.param['amount'])"}, "post":{"conditions":[{"when":"nodeContext.actionResult==true", "to":"DO_LOAN"}]}, "properties": {"order": 2} }, { "id": "DO_LOAN", "name": "Do loan\"only test\"", "action":{"exp":"new com.jd.easyflow.flow.mockbiz.loan.LoanBiz().doLoan()"}, "properties": {"order": 3} } ], "post":{"createExp":"null"}, "listeners":[ {"createExp":"new com.jd.easyflow.flow.mockbiz.MockFlowListener()"} ], "properties":{"process":{"checkStartNode":true, "dataFlushPolicy":"AFTER_PROCESS"}}, "extData":{"a":"123"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_interrupt001.json ================================================ { "id": "flow_interrupt001","name": "interrupt flow test 001", "nodes": [ {"id": "EMPTY_NODE1","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}, "start":true, "properties":{"interrupt":true}}, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}, "post":{"to":"EMPTY_NODE3"}, "start":true}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestMultiRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_interrupt002.json ================================================ { "id": "flow_interrupt002","name": "interrupt flow test 002", "nodes": [ {"id": "EMPTY_NODE1","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}, "start":true, "properties":{"interrupt":true}}, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}, "post":{"to":"EMPTY_NODE3"}, "start":true}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_interrupt003.json ================================================ { "id": "flow_interrupt003","name": "interrupt flow test 001", "nodes": [ {"id": "EMPTY_NODE1","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}, "start":true, "properties":{"interrupt":true}}, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}, "post":{"to":"EMPTY_NODE3"}, "start":true}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestInterruptNodeAction()"}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_multi001.json ================================================ { "id": "flow_multi001", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":["EMPTY_NODE2","EMPTY_NODE3"]}, "properties":{"sleep":300} }, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":100}}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":500}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestMultiRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_multi002.json ================================================ { "id": "flow_multi002", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":["EMPTY_NODE2","EMPTY_NODE3","EMPTY_NODE4"]}, "properties":{"sleep":3} }, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":1}}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"exception":true}}, {"id": "EMPTY_NODE4","name": "Empty node 4","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"exception":true}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestMultiRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_multi003.json ================================================ { "id": "flow_multi003", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":5} } ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestMultiRunner(2)"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_multi004.json ================================================ { "id": "flow_multi004", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":5} } ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestMultiRunner(2, true)"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_reusable001.json ================================================ { "id": "flow_reusable001", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":["EMPTY_NODE2","EMPTY_NODE3"]}, "properties":{"sleep":300} }, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":100}}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":500}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_reusable002.json ================================================ { "id": "flow_reusable002", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":["EMPTY_NODE2","EMPTY_NODE3"]}, "properties":{"sleep":300} }, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":1}}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"exception":true}} ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner()"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_reusable003.json ================================================ { "id": "flow_reusable003", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":5} } ], "runner":{"createExp":"new com.jd.easyflow.flow.cases.runner.TestReusableThreadRunner(2)"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/runner/flow_reusable004.json ================================================ { "id": "flow_reusable004", "nodes": [ {"id": "EMPTY_NODE","name": "Empty node 1","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "post":{"to":["EMPTY_NODE2","EMPTY_NODE3"]}, "properties":{"sleep":300} }, {"id": "EMPTY_NODE2","name": "Empty node 2","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":100}}, {"id": "EMPTY_NODE3","name": "Empty node 3","action":{"createExp":"new com.jd.easyflow.flow.cases.share.nodeaction.TestStepAction()"}, "properties":{"sleep":500}} ], "runner":{"createExp":"new com.jd.easyflow.flow.engine.impl.ReusableThreadFlowRunner(T(java.util.concurrent.Executors).newCachedThreadPool(),0,true)"} } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/spring/applicationContext-flow-test.xml ================================================ ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/spring/flow_spring_001.json ================================================ { "id": "flow_spring_test_001", "nodes": [ {"id": "node001","name": "Node001","action": {"createExp": "@quickStart001Node01Action"},"start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"exp": "@quickStart002Node01Action.execute(nodeContext, context)"},"post": {"to": "node003"}}, {"id": "node003","name": "Node003","action": "@quickStart003Node01Action.execute(nodeContext, context)"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/subflow/flow_subflow_001.json ================================================ { "id": "subflow_test_001", "name": "Sub Flow Test 001", "nodes": [ {"id": "node001","name": "Node001","start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": { "flow":{"id":"subflow_node002", "nodes":[ {"id": "node002_01","name": "Node002_01","start": true,"post": {"to": "node002_02"}}, {"id": "node002_02","name": "Node002_02"} ] } },"post": {"to": "node003"}}, {"id": "node003","name": "Node003"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/subflow/flow_subflow_002.json ================================================ { "id": "subflow_test_002", "name": "Sub Flow Test 002", "nodes": [ {"id": "node001","name": "Node001","start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"flowId":"subflow2_node002", "startNodeId":"node002_02"},"post": {"to": "node003"}}, {"id": "node003","name": "Node003"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/subflow/flow_subflow_002_01.json ================================================ {"id":"subflow2_node002", "nodes":[ {"id": "node002_01","name": "Node002_01","start": true,"post": {"to": "node002_02"}}, {"id": "node002_02","name": "Node002_02"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/subflow/flow_subflow_003.json ================================================ { "id": "subflow_test_003", "name": "Sub Flow Test 003", "nodes": [ {"id": "node001","name": "Node001","start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"createExp":"new com.jd.easyflow.flow.cases.subflow.TestInvokeSubFlowNodeAction()"},"post": {"to": "node003"}, "properties":{ "subFlowId":"subflow3_node002" }}, {"id": "node003","name": "Node003"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/cases/subflow/flow_subflow_003_01.json ================================================ {"id":"subflow3_node002", "nodes":[ {"id": "node002_01","name": "Node002_01","start": true,"post": {"to": "node002_02"}}, {"id": "node002_02","name": "Node002_02"} ] } ================================================ FILE: easyflow-flow/src/test/resources/flow/quickstart/quickstart_001.json ================================================ { "id": "quickstart_001", "name": "Quick Start 001", "nodes": [ {"id": "node001","name": "Node001","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart001Node01Action()"},"start": true,"post": {"to": "node002"}}, {"id": "node002","name": "Node002","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart002Node01Action()"},"post": {"to": "node003"}}, {"id": "node003","name": "Node003","action": {"createExp": "new com.jd.easyflow.flow.quickstart.QuickStart003Node01Action()"}} ] } ================================================ FILE: easyflow-flow/src/test/resources/logback.xml ================================================ %d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0} - %m%n ================================================ FILE: easyflow-flow-bpmn/BPMNDesigner.html ================================================ EasyFlow-BPMN Designer
================================================ FILE: easyflow-flow-bpmn/pom.xml ================================================ 4.0.0 easyflow-parent com.jd.easyflow 1.7.3 easyflow-flow-bpmn easyflow-flow-bpmn jar com.jd.easyflow easyflow-flow com.jd.easyflow easyflow-flow-extension test org.activiti activiti-bpmn-converter ch.qos.logback logback-classic test junit junit test ================================================ FILE: easyflow-flow-bpmn/src/main/java/.gitkeep ================================================ ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/BpmnFlowParser.java ================================================ package com.jd.easyflow.flow.bpmn; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.converter.BpmnConverter; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.parser.FlowParserImpl; import com.jd.easyflow.flow.model.parser.param.FlowParseParam; import com.jd.easyflow.flow.util.FlowIOUtil; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class BpmnFlowParser extends FlowParserImpl { public static final Logger logger = LoggerFactory.getLogger(BpmnFlowParser.class); private static final String FLOW_BPMN_STRING_KEY = "_flow_bpmn_string"; private static final String DEFAULT_FLOW_PRETTY_CONFIG_PATH = "/pretty/pretty-flow.json"; private static final String XML_PREFIX = " flowPrettyConfig; private String flowPrettyConfigPath; public BpmnFlowParser() { init(); } public void init() { if (flowPrettyConfigPath == null) { flowPrettyConfigPath = DEFAULT_FLOW_PRETTY_CONFIG_PATH; } String flowPrettyConfStr = null; InputStream inputStream = BpmnFlowParser.class.getResourceAsStream(flowPrettyConfigPath); if (inputStream == null) { flowPrettyConfStr = BpmnConverter.defaultFlowPrettyConfigStr; } else { try { flowPrettyConfStr = FlowIOUtil.toString(inputStream); inputStream.close(); } catch (IOException e) { throw new FlowException( "Pretty conf parse exception, path:" + flowPrettyConfigPath + " message:" + e.getMessage(), e); } } flowPrettyConfig = JsonUtil.parseObject(flowPrettyConfStr, Map.class); } @Override public List parse(FlowParseParam param) { String data = param.getStringDefinition(); if (data != null && data.trim().startsWith(XML_PREFIX)) { logger.info("BPMN Definition:\n" + data); String easyFlowDef = BpmnConverter.convert(data, flowPrettyConfig); logger.info("EasyFlow Definition:\n" + easyFlowDef); param.setStringDefinition(easyFlowDef); List flowList = super.parse(param); flowList.get(0).setProperty(FLOW_BPMN_STRING_KEY, data); return flowList; } else { return super.parse(param); } } /** * * BPMN Model to string * * @param flow * @return */ public static String bpmnStringify(Flow flow) { if (flow.getProperty(FLOW_BPMN_STRING_KEY) != null) { return flow.getProperty(FLOW_BPMN_STRING_KEY); } return null; } public String getFlowPrettyConfigPath() { return flowPrettyConfigPath; } public void setFlowPrettyConfigPath(String flowPrettyConfigPath) { this.flowPrettyConfigPath = flowPrettyConfigPath; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/BaseFlowNodeConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.activiti.bpmn.model.Activity; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.ExtensionElement; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.Gateway; import org.activiti.bpmn.model.SequenceFlow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.converter.util.BpmnXmlConstants; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.model.post.ConditionalNodePostHandler; import com.jd.easyflow.flow.util.FlowStringUtil; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class BaseFlowNodeConverter implements FlowNodeConverter { private static final Logger logger = LoggerFactory.getLogger(BaseFlowNodeConverter.class); private static final String CONDITION_TYPE_CREATE_EXP = "createExp"; private static final String CONDITION_TYPE_CREATE_EXP_PREFIX = CONDITION_TYPE_CREATE_EXP + ":"; @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = new HashMap<>(); // ID and Name node.put(DefConstants.COMMON_PROP_ID, flowNode.getId()); if (flowNode.getName() != null && flowNode.getName().length() > 0) { node.put(DefConstants.COMMON_PROP_NAME, flowNode.getName()); } // Properties Map properties = null; Map> extensionElementMap = flowNode.getExtensionElements(); if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.PROPERTIES)) { properties = ConvertUtil.getMapValue(node, DefConstants.COMMON_PROP_PROPERTIES); ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.PROPERTIES).get(0); String elementText = element.getElementText(); try { Map map = JsonUtil.parseObject(elementText, Map.class); properties.putAll(map); } catch (Exception e) { throw new FlowException("Property JSON parse error, Node:" + flowNode.getId() + ", Property:" + elementText + "." + e.getMessage(), e); } } // Start if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.START)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.START).get(0); String elementText = element.getElementText(); node.put(DefConstants.NODE_PROP_START, JsonUtil.parseObject(elementText, Boolean.class)); } // Pre // self first if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.PRE)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.PRE).get(0); String elementText = element.getElementText(); node.put(DefConstants.NODE_PROP_PRE, JsonUtil.parseObject(elementText, Map.class)); } // Action // self first if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.ACTION)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.ACTION).get(0); String elementText = element.getElementText(); node.put(DefConstants.NODE_PROP_ACTION, JsonUtil.parseObject(elementText, Map.class)); } // Post // self first. if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.POST)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.POST).get(0); String elementText = element.getElementText(); node.put(DefConstants.NODE_PROP_POST, JsonUtil.parseObject(elementText, Map.class)); } else { String defaultFlow = null; if (flowNode instanceof Activity) { defaultFlow = ((Activity) flowNode).getDefaultFlow(); } else if (flowNode instanceof Gateway) { defaultFlow = ((Gateway) flowNode).getDefaultFlow(); } List sequenceFlowList = flowNode.getOutgoingFlows(); if (sequenceFlowList.size() == 1) { Map post = new HashMap<>(); node.put(DefConstants.NODE_PROP_POST, post); SequenceFlow sequenceFlow = sequenceFlowList.get(0); String conditionExp = sequenceFlow.getConditionExpression(); if (FlowStringUtil.isNotEmpty(conditionExp)) { conditionExp = conditionExp.trim(); if (conditionExp.startsWith(CONDITION_TYPE_CREATE_EXP_PREFIX)) { Map condition = new HashMap(); condition.put(CONDITION_TYPE_CREATE_EXP, conditionExp.substring(CONDITION_TYPE_CREATE_EXP_PREFIX.length())); post.put(DefConstants.NODE_POST_PROP_WHEN, condition); } else { post.put(DefConstants.NODE_POST_PROP_WHEN, conditionExp); } } post.put(DefConstants.NODE_POST_PROP_TO, sequenceFlow.getTargetRef()); } else if (sequenceFlowList.size() > 1) { Map post = new HashMap<>(); node.put(DefConstants.NODE_PROP_POST, post); List> conditionList = new ArrayList<>(); post.put(DefConstants.NODE_POST_PROP_CONDITIONS, conditionList); for (SequenceFlow sequenceFlow : sequenceFlowList) { if (sequenceFlow.getId().equals(defaultFlow)) { post.put(DefConstants.NODE_POST_PROP_DEFAULT_TO, sequenceFlow.getTargetRef()); } else { Map condition = new HashMap<>(); String conditionExp = sequenceFlow.getConditionExpression(); if (FlowStringUtil.isNotEmpty(conditionExp)) { conditionExp = conditionExp.trim(); if (conditionExp.startsWith(CONDITION_TYPE_CREATE_EXP_PREFIX)) { Map conditionInfo = new HashMap(); conditionInfo.put(CONDITION_TYPE_CREATE_EXP, conditionExp.substring(CONDITION_TYPE_CREATE_EXP_PREFIX.length())); condition.put(DefConstants.NODE_POST_PROP_WHEN, conditionInfo); } else { condition.put(DefConstants.NODE_POST_PROP_WHEN, conditionExp); } } condition.put(DefConstants.NODE_POST_PROP_TO, sequenceFlow.getTargetRef()); conditionList.add(condition); } } // conditionType String conditionType = ConditionalNodePostHandler.INCLUSIVE_TYPE; if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.CONDITION_TYPE)) { conditionType = (String) (extensionElementMap.get(BpmnXmlConstants.CONDITION_TYPE)).get(0) .getElementText(); } if (!ConditionalNodePostHandler.EXCLUSIVE_TYPE.equals(conditionType)) { post.put(DefConstants.NODE_POST_PROP_CONDITION_TYPE, conditionType); } } } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/BpmnConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.activiti.bpmn.converter.BpmnXMLConverter; import org.activiti.bpmn.model.Activity; import org.activiti.bpmn.model.AdhocSubProcess; import org.activiti.bpmn.model.Association; import org.activiti.bpmn.model.BoundaryEvent; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.BusinessRuleTask; import org.activiti.bpmn.model.CallActivity; import org.activiti.bpmn.model.CompensateEventDefinition; import org.activiti.bpmn.model.ComplexGateway; import org.activiti.bpmn.model.DataObject; import org.activiti.bpmn.model.DataStoreReference; import org.activiti.bpmn.model.EndEvent; import org.activiti.bpmn.model.EventDefinition; import org.activiti.bpmn.model.EventGateway; import org.activiti.bpmn.model.EventSubProcess; import org.activiti.bpmn.model.ExclusiveGateway; import org.activiti.bpmn.model.ExtensionElement; import org.activiti.bpmn.model.FlowElement; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.InclusiveGateway; import org.activiti.bpmn.model.IntermediateCatchEvent; import org.activiti.bpmn.model.ManualTask; import org.activiti.bpmn.model.ParallelGateway; import org.activiti.bpmn.model.Process; import org.activiti.bpmn.model.ReceiveTask; import org.activiti.bpmn.model.ScriptTask; import org.activiti.bpmn.model.SendTask; import org.activiti.bpmn.model.SequenceFlow; import org.activiti.bpmn.model.ServiceTask; import org.activiti.bpmn.model.StartEvent; import org.activiti.bpmn.model.SubProcess; import org.activiti.bpmn.model.Task; import org.activiti.bpmn.model.ThrowEvent; import org.activiti.bpmn.model.Transaction; import org.activiti.bpmn.model.UserTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.converter.activity.AdhocSubProcessConverter; import com.jd.easyflow.flow.bpmn.converter.activity.BusinessRuleTaskConverter; import com.jd.easyflow.flow.bpmn.converter.activity.CallActivityConverter; import com.jd.easyflow.flow.bpmn.converter.activity.EventSubProcessConverter; import com.jd.easyflow.flow.bpmn.converter.activity.ManualTaskConverter; import com.jd.easyflow.flow.bpmn.converter.activity.ReceiveTaskConverter; import com.jd.easyflow.flow.bpmn.converter.activity.ScriptTaskConverter; import com.jd.easyflow.flow.bpmn.converter.activity.SendTaskConverter; import com.jd.easyflow.flow.bpmn.converter.activity.ServiceTaskConverter; import com.jd.easyflow.flow.bpmn.converter.activity.SubProcessConverter; import com.jd.easyflow.flow.bpmn.converter.activity.TaskConverter; import com.jd.easyflow.flow.bpmn.converter.activity.TransactionConverter; import com.jd.easyflow.flow.bpmn.converter.activity.UserTaskConverter; import com.jd.easyflow.flow.bpmn.converter.event.EndEventConverter; import com.jd.easyflow.flow.bpmn.converter.event.IntermediateCatchEventConverter; import com.jd.easyflow.flow.bpmn.converter.event.StartEventConverter; import com.jd.easyflow.flow.bpmn.converter.event.ThrowEventConverter; import com.jd.easyflow.flow.bpmn.converter.gateway.ComplexGatewayConverter; import com.jd.easyflow.flow.bpmn.converter.gateway.EventGatewayConverter; import com.jd.easyflow.flow.bpmn.converter.gateway.ExclusiveGatewayConverter; import com.jd.easyflow.flow.bpmn.converter.gateway.InclusiveGatewayConverter; import com.jd.easyflow.flow.bpmn.converter.gateway.ParallelGatewayConverter; import com.jd.easyflow.flow.bpmn.converter.util.BpmnXmlConstants; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.util.FlowStringUtil; import com.jd.easyflow.flow.util.JsonPrettyHelper; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class BpmnConverter { private static final Logger logger = LoggerFactory.getLogger(BpmnConverter.class); private static Map flowNodeConverterMap = new HashMap<>(); private static String defaultFlowPrettyConfigPath = "/pretty/pretty-flow.json"; public static String defaultFlowPrettyConfigStr = "{\"endNewLine\":true,\"subList\":[{\"newLine\":true,\"subList\":[{\"key\":\"id\"},{\"key\":\"name\",\"newLine\":true},{\"key\":\"pre\",\"newLine\":true},{\"key\":\"nodes\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true,\"subList\":[{\"key\":\"id\"},{\"key\":\"name\"},{\"key\":\"start\"},{\"key\":\"pre\"},{\"key\":\"action\",\"subList\":[{\"key\":\"flow\",\"subList\":[{\"key\":\"id\"},{\"key\":\"name\",\"newLine\":true},{\"key\":\"pre\",\"newLine\":true},{\"key\":\"nodes\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true,\"subList\":[{\"key\":\"id\"},{\"key\":\"name\"},{\"key\":\"start\"},{\"key\":\"pre\"},{\"key\":\"action\"},{\"key\":\"post\"},{\"key\":\"properties\"}]}]},{\"key\":\"post\",\"newLine\":true}]}]},{\"key\":\"post\",\"subList\":[{\"key\":\"when\"},{\"key\":\"to\"},{\"key\":\"conditions\",\"subList\":[{\"key\":\"default\",\"subList\":[{\"key\":\"when\"},{\"key\":\"to\"}]}]}]},{\"key\":\"properties\",\"default\":{\"newLine\":true,\"endNewLine\":true}}]}]},{\"key\":\"post\",\"newLine\":true},{\"key\":\"listeners\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"filters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodeFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodePreHandlerFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodeActionFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodePostHandlerFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"properties\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"keyType\":\"OTHER\",\"newLine\":true}],\"default\":{\"newLine\":true,\"endNewLine\":true}},{\"key\":\"parseListeners\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"keyType\":\"OTHER\",\"default\":{\"newLine\":true,\"endNewLine\":true}}]},{\"key\":\"id\"},{\"key\":\"name\",\"newLine\":true},{\"key\":\"pre\",\"newLine\":true},{\"key\":\"nodes\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true,\"subList\":[{\"key\":\"id\"},{\"key\":\"name\"},{\"key\":\"start\"},{\"key\":\"pre\"},{\"key\":\"action\",\"subList\":[{\"key\":\"flow\",\"subList\":[{\"key\":\"id\"},{\"key\":\"name\",\"newLine\":true},{\"key\":\"pre\",\"newLine\":true},{\"key\":\"nodes\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true,\"subList\":[{\"key\":\"id\"},{\"key\":\"name\"},{\"key\":\"start\"},{\"key\":\"pre\"},{\"key\":\"action\"},{\"key\":\"post\"},{\"key\":\"properties\"}]}]},{\"key\":\"post\",\"newLine\":true}]}]},{\"key\":\"post\",\"subList\":[{\"key\":\"when\"},{\"key\":\"to\"},{\"key\":\"conditions\",\"subList\":[{\"subList\":[{\"key\":\"when\"},{\"key\":\"to\"}]}]},{\"key\":\"defaultTo\"}]},{\"key\":\"properties\",\"default\":{\"newLine\":true,\"endNewLine\":true}}]}]},{\"key\":\"post\",\"newLine\":true},{\"key\":\"listeners\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"filters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodeFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodePreHandlerFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodeActionFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"nodePostHandlerFilters\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"key\":\"properties\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"keyType\":\"OTHER\",\"newLine\":true}],\"default\":{\"newLine\":true,\"endNewLine\":true}},{\"key\":\"parseListeners\",\"newLine\":true,\"endNewLine\":true,\"subList\":[{\"newLine\":true}]},{\"keyType\":\"OTHER\",\"default\":{\"newLine\":true,\"endNewLine\":true}}]}"; private static Map defaultFlowPrettyConfig; static { flowNodeConverterMap.put(StartEvent.class, new StartEventConverter()); flowNodeConverterMap.put(IntermediateCatchEvent.class, new IntermediateCatchEventConverter()); flowNodeConverterMap.put(ThrowEvent.class, new ThrowEventConverter()); flowNodeConverterMap.put(EndEvent.class, new EndEventConverter()); flowNodeConverterMap.put(ScriptTask.class, new ScriptTaskConverter()); flowNodeConverterMap.put(UserTask.class, new UserTaskConverter()); flowNodeConverterMap.put(ReceiveTask.class, new ReceiveTaskConverter()); flowNodeConverterMap.put(SubProcess.class, new SubProcessConverter()); flowNodeConverterMap.put(CallActivity.class, new CallActivityConverter()); flowNodeConverterMap.put(Transaction.class, new TransactionConverter()); flowNodeConverterMap.put(AdhocSubProcess.class, new AdhocSubProcessConverter()); flowNodeConverterMap.put(EventSubProcess.class, new EventSubProcessConverter()); flowNodeConverterMap.put(BusinessRuleTask.class, new BusinessRuleTaskConverter()); flowNodeConverterMap.put(ManualTask.class, new ManualTaskConverter()); flowNodeConverterMap.put(SendTask.class, new SendTaskConverter()); flowNodeConverterMap.put(ServiceTask.class, new ServiceTaskConverter()); flowNodeConverterMap.put(Task.class, new TaskConverter()); flowNodeConverterMap.put(ExclusiveGateway.class, new ExclusiveGatewayConverter()); flowNodeConverterMap.put(InclusiveGateway.class, new InclusiveGatewayConverter()); flowNodeConverterMap.put(ParallelGateway.class, new ParallelGatewayConverter()); flowNodeConverterMap.put(ComplexGateway.class, new ComplexGatewayConverter()); flowNodeConverterMap.put(EventGateway.class, new EventGatewayConverter()); defaultFlowPrettyConfig = JsonUtil.parseObject(defaultFlowPrettyConfigStr, Map.class); //Steps: Modify pretty-flow.json, then use JSONUtil to convert to string. // try { // InputStream inputStream = BpmnFlowParser.class.getResourceAsStream(defaultFlowPrettyConfigPath); // if (inputStream == null) { // logger.warn("No pretty config:" + defaultFlowPrettyConfigPath + ",JSON format without prettying"); // } // String flowPrettyConf = IOUtils.toString(inputStream); // defaultFlowPrettyConfig = JsonUtil.parseObject(flowPrettyConf, Map.class); // } catch (IOException e) { // throw new FlowException("Pretty config parse error," + e.getMessage(), e); // } } public static String convert(String bpmnXmlData) { return convert(bpmnXmlData, defaultFlowPrettyConfig); } /** * Convert BPMN Model to EasyFlow Model. * * @param bpmnXmlData * @return */ public static String convert(String bpmnXmlData, Map flowPrettyConfig) { List> model; try { model = convert(new ByteArrayInputStream(bpmnXmlData.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { throw new FlowException(e); } if (flowPrettyConfig == null || flowPrettyConfig.isEmpty()) { return JsonUtil.toJsonString(model.size() == 1 ? model.get(0) : model); } else { return JsonPrettyHelper.pretty(model.size() == 1 ? model.get(0) : model, flowPrettyConfig); } } /** * Convert BPMN Model to EasyFlow Model. * * @param inputStream * @return */ public static List> convert(InputStream inputStream) { // Get xml data. XMLStreamReader reader = null; try { XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory(); xmlInputFactory.setProperty("javax.xml.stream.isCoalescing", true); reader = xmlInputFactory.createXMLStreamReader(inputStream); } catch (XMLStreamException | FactoryConfigurationError e) { throw new FlowException("BPMN Parse Exception", e); } // Convert to java BPMN model leveraging the activiti ability. BpmnXMLConverter bpmnXmlConverter = new BpmnXMLConverter(); BpmnModel bpmnModel = bpmnXmlConverter.convertToBpmnModel(reader); // Convert java BPMN model to EasyFlow model. List> flowDefList = new ArrayList<>(); for (Process process : bpmnModel.getProcesses()) { Map flowDef = new HashMap(); convertProcess(process, bpmnModel, flowDef); flowDefList.add(flowDef); } return flowDefList; } /** * Convert process definition. * * @param process * @param bpmnModel * @param flowDef */ private static void convertProcess(Process process, BpmnModel bpmnModel, Map flowDef) { // ID,name and properties String processId = process.getId(); String processName = process.getName(); flowDef.put(DefConstants.COMMON_PROP_ID, processId); if (FlowStringUtil.isNotEmpty(processName)) { flowDef.put(DefConstants.COMMON_PROP_NAME, processName); } Map> extensionElementMap = process.getExtensionElements(); // flow pre handler if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.PRE)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.PRE).get(0); String elementText = element.getElementText(); Object preHandlerDef = JsonUtil.parseObject(elementText, Map.class); flowDef.put(DefConstants.FLOW_PROP_PRE, preHandlerDef); } // Process flow element, convert Gateway,Event,Activity to Node. List> nodeList = new ArrayList<>(); flowDef.put(DefConstants.FLOW_PROP_NODES, nodeList); List boundaryEvents = new ArrayList(); Map> compensateActionMap = new HashMap<>(); for (FlowElement flowElement : process.getFlowElements()) { // Flow element if (flowElement instanceof FlowNode) { if (flowElement instanceof BoundaryEvent) { boundaryEvents.add((BoundaryEvent) flowElement); continue; } FlowNodeConverter nodeConverter = flowNodeConverterMap.get(flowElement.getClass()); if (nodeConverter == null) { throw new FlowException("Unsupported BPMN element:ID" + flowElement.getId() + " TYPE:" + flowElement.getClass().getCanonicalName()); } Map node = nodeConverter.convert((FlowNode) flowElement, bpmnModel, flowDef); if (flowElement instanceof Activity && ((Activity) flowElement).isForCompensation()) { compensateActionMap.put((String) node.get(DefConstants.COMMON_PROP_ID), node); } else { nodeList.add(node); } // Sequence flow } else if (flowElement instanceof SequenceFlow) { continue; } else if (flowElement instanceof DataStoreReference || flowElement instanceof DataObject) { continue; } else { throw new FlowException( "Unsupported BPMN element:ID" + flowElement.getId() + " TYPE:" + flowElement.getClass().getCanonicalName()); } } // process BoundaryEvent processBoundaryEvent(boundaryEvents, compensateActionMap, nodeList, process); // flow post handler if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.POST)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.POST).get(0); String elementText = element.getElementText(); Object postHandlerDef = JsonUtil.parseObject(elementText, Map.class); flowDef.put(DefConstants.FLOW_PROP_POST, postHandlerDef); } // properties if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.PROPERTIES)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.PROPERTIES).get(0); String elementText = element.getElementText(); Map map = JsonUtil.parseObject(elementText, Map.class); flowDef.put(DefConstants.COMMON_PROP_PROPERTIES, map); } // listeners if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.LISTENERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.LISTENERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_LISTENERS, list); } // filters if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.FILTERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.FILTERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_FILTERS, list); } // nodeFilters if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.NODE_FILTERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.NODE_FILTERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_NODE_FILTERS, list); } // nodePreHandlerFilters if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.NODE_PRE_HANDLER_FILTERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.NODE_PRE_HANDLER_FILTERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_NODE_PRE_HANDLER_FILTERS, list); } // nodeActionFilters if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.NODE_ACTION_FILTERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.NODE_ACTION_FILTERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_NODE_ACTION_FILTERS, list); } // nodePostHandlerFilters if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.NODE_POST_HANDLER_FILTERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.NODE_POST_HANDLER_FILTERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_NODE_POST_HANDLER_FILTERS, list); } // flowPreHandlerFilters if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.FLOW_PRE_HANDLER_FILTERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.FLOW_PRE_HANDLER_FILTERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_FLOW_PRE_HANDLER_FILTERS, list); } // flowPostHandlerFilters if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.FLOW_POST_HANDLER_FILTERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.FLOW_POST_HANDLER_FILTERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_FLOW_POST_HANDLER_FILTERS, list); } // runner if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.RUNNER)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.RUNNER).get(0); String elementText = element.getElementText(); Map runner = JsonUtil.parseObject(elementText, Map.class); flowDef.put(DefConstants.FLOW_PROP_RUNNER, runner); } // parseListeners if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.PARSE_LISTENERS)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.PARSE_LISTENERS).get(0); String elementText = element.getElementText(); List list = JsonUtil.parseObject(elementText, List.class); flowDef.put(DefConstants.FLOW_PROP_PARSE_LISTENERS, list); } // logFlag if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.LOG_FLAG)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.LOG_FLAG).get(0); String elementText = element.getElementText(); if (FlowStringUtil.isNotEmpty(elementText)) { flowDef.put(DefConstants.FLOW_PROP_LOG_FLAG, Boolean.valueOf(elementText)); } } } private static void processBoundaryEvent(List boundaryEvents, Map> compensateActionMap, List> nodeList, Process process) { for (BoundaryEvent event : boundaryEvents) { boolean isCompensate = false; String compensateActionId = null; for (EventDefinition definition : event.getEventDefinitions()) { if (definition instanceof CompensateEventDefinition) { isCompensate = true; List associationList = process.findAssociationsWithSourceRefRecursive(event.getId()); if (associationList.size() == 1) { compensateActionId = associationList.get(0).getTargetRef(); } break; } } if (isCompensate) { if (compensateActionId != null) { String nodeId = event.getAttachedToRefId(); Map compensateInfo = compensateActionMap.get(compensateActionId); Object actionInfo = compensateInfo.get(DefConstants.NODE_PROP_ACTION); Map nodeInfo = null; for (Map info : nodeList) { if (nodeId.equals(info.get(DefConstants.COMMON_PROP_ID))) { nodeInfo = info; break; } } Map properties = ConvertUtil.getMapValue(nodeInfo, DefConstants.COMMON_PROP_PROPERTIES); if (properties.get(DefConstants.NODE_PROPERTIES_PROP_COMPENSATE_ACTION) == null) { properties.put(DefConstants.NODE_PROPERTIES_PROP_COMPENSATE_ACTION, actionInfo); } } } else { throw new FlowException( "Unsupported BPMN element:ID" + event.getId() + " TYPE:" + event.getClass().getCanonicalName()); } } } private static Map getNodeDef(String nodeId, Map flowDef) { List> list = (List>) flowDef.get(DefConstants.FLOW_PROP_NODES); if (list == null) { return null; } for (Map node : list) { if (Objects.equals(nodeId, node.get(DefConstants.COMMON_PROP_ID))) { return node; } } return null; } public static Map getFlowNodeConverterMap() { return flowNodeConverterMap; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/FlowNodeConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; /** * * @author liyuliang5 * */ public interface FlowNodeConverter { /** * Do convert. * @param flowNode * @param bpmnModel * @param flowDef * @return */ Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef); } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/AdhocSubProcessConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; /** * * @author liyuliang5 */ public class AdhocSubProcessConverter extends SubProcessConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/BusinessRuleTaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; /** * * @author liyuliang5 */ public class BusinessRuleTaskConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/CallActivityConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.HashMap; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.CallActivity; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.util.FlowStringUtil; /** * * @author liyuliang5 * */ public class CallActivityConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); CallActivity callActivity = (CallActivity) flowNode; String flowId = callActivity.getCalledElement(); if (node.get(DefConstants.NODE_PROP_ACTION) == null) { Map action = new HashMap<>(); action.put(DefConstants.COMMON_PROP_FLOW_ID, flowId); node.put(DefConstants.NODE_PROP_ACTION, action); } else { if (FlowStringUtil.isNotEmpty(flowId)) { Map properties = ConvertUtil.getMapValue(node, DefConstants.COMMON_PROP_PROPERTIES); properties.put(DefConstants.COMMON_PROP_FLOW_ID, flowId); } } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/EventSubProcessConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; /** * * @author liyuliang5 */ public class EventSubProcessConverter extends SubProcessConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/ManualTaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; /** * * @author liyuliang5 */ public class ManualTaskConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/ReceiveTaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.HashMap; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.model.action.EventNodeAction; import com.jd.easyflow.flow.model.definition.DefConstants; /** * Receive Task Converter. * * @author liyuliang5 * */ public class ReceiveTaskConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); if (node.get(DefConstants.NODE_PROP_ACTION) == null) { Map action = new HashMap<>(); action.put(DefConstants.COMMON_PROP_TYPE, DefConstants.NODE_ACTION_TYPE_EVENT); node.put(DefConstants.NODE_PROP_ACTION, action); } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/ScriptTaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.HashMap; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.ScriptTask; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.util.FlowStringUtil; /** * Script Task Converter. * * @author liyuliang5 * */ public class ScriptTaskConverter extends BaseFlowNodeConverter { private static final String EXP_FORMAT = "exp"; // private static final String CREATE_EXP_FORMAT = "createExp"; @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); ScriptTask scriptTask = (ScriptTask) flowNode; String format = scriptTask.getScriptFormat(); String script = scriptTask.getScript(); Map action = new HashMap<>(); if (FlowStringUtil.isNotEmpty(script)) { if (format == null) { format = EXP_FORMAT; } action.put(format, script); } if (node.get(DefConstants.NODE_PROP_ACTION) == null) { if (!action.isEmpty()) { node.put(DefConstants.NODE_PROP_ACTION, action); } } else { if (!action.isEmpty()) { Map properties = ConvertUtil.getMapValue(node, DefConstants.COMMON_PROP_PROPERTIES); properties.put(DefConstants.NODE_PROP_ACTION, action); } } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/SendTaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; /** * * @author liyuliang5 */ public class SendTaskConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/ServiceTaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; /** * * @author liyuliang5 */ public class ServiceTaskConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/SubProcessConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.ExtensionElement; import org.activiti.bpmn.model.FlowElement; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.SequenceFlow; import org.activiti.bpmn.model.SubProcess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.BpmnConverter; import com.jd.easyflow.flow.bpmn.converter.FlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.BpmnXmlConstants; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class SubProcessConverter extends BaseFlowNodeConverter { private static final Logger logger = LoggerFactory.getLogger(SubProcessConverter.class); @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); SubProcess subProcess = (SubProcess) flowNode; Map subFlowDef = new HashMap<>(); Map> extensionElementMap = flowNode.getExtensionElements(); if (extensionElementMap != null && extensionElementMap.containsKey(BpmnXmlConstants.FLOW)) { ExtensionElement element = extensionElementMap.get(BpmnXmlConstants.FLOW).get(0); String elementText = element.getElementText(); subFlowDef = JsonUtil.parseObject(elementText, Map.class); } if (subFlowDef.get(DefConstants.COMMON_PROP_ID) == null) { throw new FlowException("flow id of sub process is necessary!"); } convertSubProcess(subProcess, bpmnModel, subFlowDef); if (node.get(DefConstants.NODE_PROP_ACTION) == null) { Map action = new HashMap<>(); action.put(DefConstants.COMMON_PROP_FLOW, subFlowDef); node.put(DefConstants.NODE_PROP_ACTION, action); } else { if (subFlowDef != null && ! subFlowDef.isEmpty()) { // put to properties, customized use. Map properties = ConvertUtil.getMapValue(node, DefConstants.COMMON_PROP_PROPERTIES); properties.put(DefConstants.COMMON_PROP_FLOW, subFlowDef); } } return node; } /** * Convert process definition. * * @param process * @param bpmnModel * @param flowDef */ private static void convertSubProcess(SubProcess process, BpmnModel bpmnModel, Map flowDef) { // Process flow element, convert Gateway,Event,Activity to Node. List> nodeList = (List>) flowDef.get(DefConstants.FLOW_PROP_NODES); if (nodeList != null) { return; } nodeList = new ArrayList<>(); flowDef.put(DefConstants.FLOW_PROP_NODES, nodeList); for (FlowElement flowElement : process.getFlowElements()) { // Flow element if (flowElement instanceof FlowNode) { FlowNodeConverter nodeConverter = BpmnConverter.getFlowNodeConverterMap().get(flowElement.getClass()); if (nodeConverter == null) { throw new FlowException("Unsupported BPMN elmenet:ID" + flowElement.getId() + " TYPE:" + flowElement.getClass().getCanonicalName()); } Map node = nodeConverter.convert((FlowNode) flowElement, bpmnModel, flowDef); nodeList.add(node); // Sequence flow } else if (flowElement instanceof SequenceFlow) { continue; } else { throw new FlowException("Unsupported BPMN element:ID" + flowElement.getId() + " TYPE:" + flowElement.getClass().getCanonicalName()); } } } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/TaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; /** * * @author liyuliang5 */ public class TaskConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/TransactionConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; /** * Transaction converter * @author liyuliang5 * */ public class TransactionConverter extends SubProcessConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/activity/UserTaskConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.activity; import java.util.HashMap; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.model.action.EventNodeAction; import com.jd.easyflow.flow.model.definition.DefConstants; /** * User Task Converter. * * @author liyuliang5 * */ public class UserTaskConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); if (node.get(DefConstants.NODE_PROP_ACTION) == null) { Map action = new HashMap<>(); action.put(DefConstants.COMMON_PROP_TYPE, DefConstants.NODE_ACTION_TYPE_EVENT); node.put(DefConstants.NODE_PROP_ACTION, action); } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/event/EndEventConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.event; import java.util.List; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.CompensateEventDefinition; import org.activiti.bpmn.model.EndEvent; import org.activiti.bpmn.model.EventDefinition; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.TerminateEventDefinition; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.model.definition.DefConstants; /** * End Event Converter. * * @author liyuliang5 * */ public class EndEventConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); // Properties Map properties = ConvertUtil.getMapValue(node, DefConstants.COMMON_PROP_PROPERTIES); properties.put(DefConstants.NODE_PROPERTIES_PROP_END, true); List events = ((EndEvent) flowNode).getEventDefinitions(); boolean isTerminateEvent = false; boolean isCompensateEvent = false; if (events != null) { for (EventDefinition definition : events) { if (definition instanceof TerminateEventDefinition) { isTerminateEvent = true; } else if (definition instanceof CompensateEventDefinition) { isCompensateEvent = true; } } } if (node.get(DefConstants.NODE_PROP_ACTION) == null) { if (isTerminateEvent) { Map action = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_ACTION); action.put(DefConstants.COMMON_PROP_TYPE, DefConstants.NODE_ACTION_TYPE_INTERRUPT); } else if (isCompensateEvent) { Map action = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_ACTION); action.put(DefConstants.COMMON_PROP_TYPE, DefConstants.NODE_ACTION_TYPE_COMPENSATE); } } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/event/IntermediateCatchEventConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.event; import java.util.HashMap; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.model.definition.DefConstants; /** * Intermediate Catch Event Converter. * * @author liyuliang5 * */ public class IntermediateCatchEventConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); if (node.get(DefConstants.NODE_PROP_ACTION) == null) { Map action = new HashMap<>(); action.put(DefConstants.COMMON_PROP_TYPE, DefConstants.NODE_ACTION_TYPE_EVENT); node.put(DefConstants.NODE_PROP_ACTION, action); } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/event/StartEventConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.event; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.model.definition.DefConstants; /** * Start Event Converter. * @author liyuliang5 * */ public class StartEventConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); node.put(DefConstants.NODE_PROP_START, true); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/event/ThrowEventConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.event; import java.util.List; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.CompensateEventDefinition; import org.activiti.bpmn.model.EventDefinition; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.ThrowEvent; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.model.definition.DefConstants; /** * Throw event converter. * * @author liyuliang5 */ public class ThrowEventConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); List events = ((ThrowEvent) flowNode).getEventDefinitions(); boolean isCompensateEvent = false; if (events != null) { for (EventDefinition definition : events) { if (definition instanceof CompensateEventDefinition) { isCompensateEvent = true; } } } if (node.get(DefConstants.NODE_PROP_ACTION) == null) { if (isCompensateEvent) { Map action = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_ACTION); action.put(DefConstants.COMMON_PROP_TYPE, DefConstants.NODE_ACTION_TYPE_COMPENSATE); } } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/gateway/ComplexGatewayConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.gateway; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; /** * Complex gateway. * @author liyuliang5 */ public class ComplexGatewayConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/gateway/EventGatewayConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.gateway; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; /** * * @author liyuliang5 */ public class EventGatewayConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/gateway/ExclusiveGatewayConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.gateway; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.ExclusiveGateway; import org.activiti.bpmn.model.FlowNode; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.model.definition.DefConstants; /** * Exclusive Gateway Converter. * @author liyuliang5 * */ public class ExclusiveGatewayConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); ExclusiveGateway exclusiveGateway = (ExclusiveGateway) flowNode; Map post = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_POST); post.remove(DefConstants.NODE_POST_PROP_CONDITION_TYPE); return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/gateway/InclusiveGatewayConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.gateway; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.InclusiveGateway; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.model.post.ConditionalNodePostHandler; /** * Inclusive Gateway Converter. * * @author liyuliang5 * */ public class InclusiveGatewayConverter extends BaseFlowNodeConverter { private static final Logger logger = LoggerFactory.getLogger(InclusiveGatewayConverter.class); @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { Map node = super.convert(flowNode, bpmnModel, flowDef); InclusiveGateway inclusiveGateway = (InclusiveGateway) flowNode; Map post = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_POST); post.put(DefConstants.NODE_POST_PROP_CONDITION_TYPE, ConditionalNodePostHandler.INCLUSIVE_TYPE); if (inclusiveGateway.getIncomingFlows().size() > 1) { if (node.get(DefConstants.NODE_PROP_PRE) == null) { Map pre = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_PRE); List preNodes = new ArrayList<>(); flowNode.getIncomingFlows().forEach(incomingFlow -> preNodes.add(incomingFlow.getSourceRef())); pre.put(DefConstants.COMMON_PROP_TYPE, DefConstants.NODE_PRE_TYPE_INCLUSIVECHECK); pre.put(DefConstants.NODE_PRE_PROP_PRE_NODES, preNodes); } } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/gateway/ParallelGatewayConverter.java ================================================ package com.jd.easyflow.flow.bpmn.converter.gateway; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.ParallelGateway; import org.activiti.bpmn.model.SequenceFlow; import com.jd.easyflow.flow.bpmn.converter.BaseFlowNodeConverter; import com.jd.easyflow.flow.bpmn.converter.util.ConvertUtil; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.model.post.ConditionalNodePostHandler; /** * Parallel Gateway Converter. * * @author liyuliang5 * */ public class ParallelGatewayConverter extends BaseFlowNodeConverter { @Override public Map convert(FlowNode flowNode, BpmnModel bpmnModel, Map flowDef) { ParallelGateway parallelGateway = (ParallelGateway) flowNode; Map node = super.convert(flowNode, bpmnModel, flowDef); Map post = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_POST); post.put(DefConstants.NODE_POST_PROP_CONDITION_TYPE, ConditionalNodePostHandler.INCLUSIVE_TYPE); // Set pre nodes. List list = parallelGateway.getIncomingFlows(); if (list.size() == 0) { throw new FlowException("Parallel gateway:" + flowNode.getId() + " no incoming flows"); } if (list.size() > 1) { if (node.get(DefConstants.NODE_PROP_PRE) == null) { Map pre = ConvertUtil.getMapValue(node, DefConstants.NODE_PROP_PRE); List preNodes = new ArrayList<>(); flowNode.getIncomingFlows().forEach(incomingFlow -> preNodes.add(incomingFlow.getSourceRef())); pre.put(DefConstants.NODE_PRE_PROP_PRE_NODES, preNodes); } } return node; } } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/util/BpmnXmlConstants.java ================================================ package com.jd.easyflow.flow.bpmn.converter.util; /** * * @author liyuliang5 * */ public class BpmnXmlConstants { public static final String START = "start"; public static final String PROPERTIES = "properties"; public static final String LISTENERS = "listeners"; public static final String FILTERS = "filters"; public static final String NODE_FILTERS = "nodeFilters"; public static final String NODE_PRE_HANDLER_FILTERS = "nodePreHandlerFilters"; public static final String NODE_ACTION_FILTERS = "nodeActionFilters"; public static final String NODE_POST_HANDLER_FILTERS = "nodePostHandlerFilters"; public static final String FLOW_PRE_HANDLER_FILTERS = "flowPreHandlerFilters"; public static final String FLOW_POST_HANDLER_FILTERS = "flowPostHandlerFilters"; public static final String RUNNER = "runner"; public static final String PARSE_LISTENERS = "parseListeners"; public static final String CONDITION_TYPE = "conditionType"; public static final String PRE = "pre"; public static final String ACTION = "action"; public static final String POST = "post"; public static final String FLOW = "flow"; public static final String LOG_FLAG = "logFlag"; } ================================================ FILE: easyflow-flow-bpmn/src/main/java/com/jd/easyflow/flow/bpmn/converter/util/ConvertUtil.java ================================================ package com.jd.easyflow.flow.bpmn.converter.util; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * * @author liyuliang5 * */ public class ConvertUtil { public static Map getMapValue(Map obj, String key) { Map value = (Map) obj.get(key); if (value == null) { value = new HashMap<>(); obj.put(key, value); } return value; } public static List getListValue(Map obj, String key) { List value = (List) obj.get(key); if (value == null) { value = new ArrayList<>(); obj.put(key, value); } return value; } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/FlowBpmnTestSuite.java ================================================ package com.jd.easyflow.flow.bpmn; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import com.jd.easyflow.flow.bpmn.cases.callactivity.BpmnCallActivityTest; import com.jd.easyflow.flow.bpmn.cases.converter.BpmnConverterTest; import com.jd.easyflow.flow.bpmn.cases.defaultflow.DefaultFlowTest; import com.jd.easyflow.flow.bpmn.cases.extension.BpmnExtensionTest; import com.jd.easyflow.flow.bpmn.cases.inclusive.BpmnInclusiveTest; import com.jd.easyflow.flow.bpmn.cases.logflag.LogFlagTest; import com.jd.easyflow.flow.bpmn.cases.parallel.BpmnParallelTest; import com.jd.easyflow.flow.bpmn.cases.subprocess.BpmnSubProcessTest; import com.jd.easyflow.flow.bpmn.cases.terminate.BpmnTerminateTest; import com.jd.easyflow.flow.bpmn.cases.throwevent.BpmnThrowEventTest; import com.jd.easyflow.flow.bpmn.ext.cases.chain.ChainTest; @RunWith(Suite.class) @SuiteClasses({ BpmnConverterTest.class, BpmnExtensionTest.class, BpmnParallelTest.class, BpmnSubProcessTest.class, BpmnInclusiveTest.class, BpmnExtensionTest.class, BpmnCallActivityTest.class, BpmnSubProcessTest.class, ChainTest.class, BpmnThrowEventTest.class, BpmnCallActivityTest.class, DefaultFlowTest.class, LogFlagTest.class, BpmnTerminateTest.class }) public class FlowBpmnTestSuite { } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/callactivity/BpmnCallActivityTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.callactivity; import org.junit.Test; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class BpmnCallActivityTest { /** * test bpmn call activity. */ @Test public void testCallActivity001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/callactivity/*.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("callactivity_test_001"); FlowResult result = flowEngine.execute(param); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/chain/FlowChainTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.chain; import java.util.HashMap; import java.util.Map; import java.util.function.Function; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.ext.chain.BaseChainPlugin; import com.jd.easyflow.flow.ext.chain.ChainInvoker; import com.jd.easyflow.flow.model.NodeContext; /** * @author liyuliang5 * */ public class FlowChainTest { private static final Logger log = LoggerFactory.getLogger(FlowChainTest.class); /** */ @Test public void testNormal() { // Init flow engine. FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowPath("classpath:flow/cases/chain/flow_chaintest1.bpmn"); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.init(); ChainInvoker chainInvoker = new ChainInvoker(); chainInvoker.setFlowEngine(flowEngine); Function invoker = (t) -> { log.info("Execute invoker, Param:" + t); Map result = new HashMap<>(); result.put("output", 456); return result; }; Map param = new HashMap<>(); param.put("input", 123); Object result = chainInvoker.invoke("flow_chaintest1", param, invoker); log.info("Result:" + result); } public static class TestPlugin extends BaseChainPlugin { private static final Logger logger = LoggerFactory.getLogger(TestPlugin.class); private String name; public TestPlugin(String name) { this.name = name; } @Override public boolean preHandle(NodeContext nodeContext, FlowContext context) { logger.info(name + " pre handle"); return true; } @Override public void postHandleNormal(NodeContext nodeContext, FlowContext context) { logger.info(name + " post handle normal"); } @Override public void postHandleException(Throwable t, NodeContext nodeContext, FlowContext context) { logger.info(name + " post handle exception"); } } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/converter/BpmnConverterTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.converter; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.List; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.bpmn.converter.BpmnConverter; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.util.FlowIOUtil; import com.jd.easyflow.flow.util.JsonPrettyHelper; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class BpmnConverterTest { private static final Logger logger = LoggerFactory.getLogger(BpmnConverterTest.class); /** * test convert. * @throws IOException */ @Test public void testConvert() throws IOException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:flow/cases/converter/process_1.bpmn"); List> model = BpmnConverter.convert(resource.getInputStream()); String flowPrettyConf = FlowIOUtil.toString(BpmnConverterTest.class.getResourceAsStream("/pretty/pretty-flow.json")); String pretty = JsonPrettyHelper.pretty(model, JsonUtil.parseObject(flowPrettyConf, Map.class)); logger.info("Model is:" + pretty); } /** * Validate convert. * @throws IOException */ @Test public void testConvertSimple() throws IOException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:flow/cases/converter/process_1_simple.bpmn"); List> model = BpmnConverter.convert(resource.getInputStream()); logger.info("Model is:" + JsonUtil.toJsonString(model)); } /** * Converter bpmn with multiple process. * @throws IOException */ @Test public void testConvertMultiple() throws IOException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:flow/cases/converter/flow_multiple_001.bpmn"); String modelString = BpmnConverter.convert(FlowIOUtil.toString(resource.getInputStream())); logger.info("Model is:" + modelString); List> model = JsonUtil.parseObject(modelString, List.class); } /** * Validate Convert result. */ @Test public void testConvertResult() { // Init flow engine FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/converter/flow001.json, classpath:flow/cases/converter/process_1.bpmn"); flowEngine.init(); // Execute flow instance 1. FlowParam param = new FlowParam("process_1"); param.put("input", 1); FlowResult result = flowEngine.execute(param); logger.info("Execute flow instance 1 result:" + result); // Execute flow instance 2. FlowParam param2 = new FlowParam("process_1"); param2.put("input", 10); FlowResult result2 = flowEngine.execute(param2); logger.info("Execute flow instance 2 result:" + result2); } @Test public void testCompensate() throws Exception { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:flow/cases/converter/compensate001.bpmn"); List> model = BpmnConverter.convert(resource.getInputStream()); logger.info("Model is:" + JsonUtil.toJsonString(model)); List> nodeList = (List>)model.get(0).get("nodes"); Map node001 = nodeList.stream().filter(map -> map.get("id").equals("node001")).findFirst().get(); assertEquals(((Map)((Map)node001.get("properties")).get("compensateAction")).get("createExp"), "@compensate1Action"); Map node003 = nodeList.stream().filter(map -> map.get("id").equals("node003")).findFirst().get(); assertEquals(((Map)node003.get("action")).get("type"), "compensate"); Map node004 = nodeList.stream().filter(map -> map.get("id").equals("node004")).findFirst().get(); assertEquals(((Map)node004.get("action")).get("type"), "compensate"); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/converter/BpmnTestService.java ================================================ package com.jd.easyflow.flow.bpmn.cases.converter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BpmnTestService { private static final Logger logger = LoggerFactory.getLogger(BpmnTestService.class); public void doScriptTask1() { logger.info("Script task1 execute"); } public void doScriptTask2() { logger.info("Script task2 execute"); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/defaultflow/DefaultFlowTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.defaultflow; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.List; import org.junit.Test; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.util.FlowUtil; /** * * @author liyuliang5 */ public class DefaultFlowTest { /** * test bpmn default flow. */ @Test public void testDefaultFlow001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/defaultflow/default_flow_001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("default_flow_001"); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(1, endNodeIds.size()); assertTrue(endNodeIds.contains("node1")); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/extension/BpmnExtensionTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.extension; import java.io.IOException; import java.util.List; import java.util.Map; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.activiti.bpmn.converter.BpmnXMLConverter; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.Process; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.jd.easyflow.flow.bpmn.converter.BpmnConverter; import com.jd.easyflow.flow.exception.FlowException; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 * */ public class BpmnExtensionTest { private static final Logger logger = LoggerFactory.getLogger(BpmnExtensionTest.class); @Test public void testExtension() { XMLStreamReader reader = null; try { reader = XMLInputFactory.newFactory().createXMLStreamReader(this.getClass().getResourceAsStream("/flow/cases/extension/process_1.bpmn")); } catch (XMLStreamException | FactoryConfigurationError e) { throw new FlowException("BPMN parse error", e); } // Parse BPMN BpmnXMLConverter bpmnXmlConverter = new BpmnXMLConverter(); BpmnModel bpmnModel = bpmnXmlConverter.convertToBpmnModel(reader); // BPMN to Easy Flow Process process = bpmnModel.getProcesses().get(0); } @Test public void testConvert() throws IOException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:flow/cases/extension/process_1.bpmn"); List> model = BpmnConverter.convert(resource.getInputStream()); logger.info("Model is:" + JsonUtil.toJsonString(model)); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/inclusive/BpmnInclusiveTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.inclusive; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.util.FlowUtil; /** * BPMN inclusive test. * @author liyuliang5 * */ public class BpmnInclusiveTest { /** * test bpmn inclusive nodes. */ @Test public void testInclusive001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("flow_bpmn_inclusive001"); param.setParam(1); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertEquals(1, endNodeIds.size()); assertTrue(endNodeIds.contains("END")); } /** * test bpmn inclusive nodes. */ @Test public void testInclusive002() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive002.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("Inclusive002"); Map bizParam = new HashMap<>(); param.setParam(bizParam); bizParam.put("C1", true); bizParam.put("C2", true); bizParam.put("C3", true); bizParam.put("C4", false); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertTrue(endNodeIds.contains("End")); } /** * test bpmn inclusive nodes. */ @Test public void testInclusive003() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive002.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("Inclusive002"); Map bizParam = new HashMap<>(); param.setParam(bizParam); bizParam.put("C1", true); bizParam.put("C2", false); bizParam.put("C3", true); bizParam.put("C4", false); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertTrue(endNodeIds.contains("End")); } /** * test bpmn inclusive nodes. */ @Test public void testInclusive004() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive002.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("Inclusive002"); Map bizParam = new HashMap<>(); param.setParam(bizParam); bizParam.put("C1", false); bizParam.put("C2", true); bizParam.put("C3", true); bizParam.put("C4", false); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertTrue(endNodeIds.contains("End")); } /** * test bpmn inclusive nodes. */ @Test public void testInclusive005() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/inclusive/flow_inclusive002.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("Inclusive002"); Map bizParam = new HashMap<>(); param.setParam(bizParam); bizParam.put("C1", false); bizParam.put("C2", false); bizParam.put("C3", true); bizParam.put("C4", false); FlowResult result = flowEngine.execute(param); List endNodeIds = FlowUtil.nodeIdsOfNodeContextList(result.getContext().getEndNodes()); assertTrue(! endNodeIds.contains("End")); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/logflag/LogFlagTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.logflag; import org.junit.Test; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 */ public class LogFlagTest { /** * test log flag. */ @Test public void testLogFlag001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/logflag/logflag_001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("Process_1"); FlowResult result = flowEngine.execute(param); } /** * test log flag. */ @Test public void testLogFlag002() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/logflag/logflag_001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("Process_1"); param.setLogFlag(true); FlowResult result = flowEngine.execute(param); } /** * test log flag. */ @Test public void testLogFlag003() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/logflag/logflag_001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("Process_1"); param.setLogFlag(false); FlowResult result = flowEngine.execute(param); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/parallel/BpmnParallelTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.parallel; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowContextImpl; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.model.Flow; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.InitContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; import com.jd.easyflow.flow.model.definition.DefConstants; import com.jd.easyflow.flow.model.parser.param.FlowParseParam; import com.jd.easyflow.flow.util.FlowConstants; /** * * @author liyuliang5 * */ public class BpmnParallelTest { private static final Logger logger = LoggerFactory.getLogger(BpmnParallelTest.class); /** * test bpmn parallel nodes. */ @Test public void testParallel001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/parallel/flow_parallel001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("flow_bpmn_parallel001"); FlowResult result = flowEngine.execute(param); } @Test public void testMultiParallelSubFlow001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/parallel/flow_multi_parallel_subflow_001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("multi_parallel_subflow_001"); param.put("recordList", Arrays.asList(1, 2, 3, 4, 5, 6)); FlowResult result = flowEngine.execute(param); } public static class MainFlowNodeAction implements NodeAction { private Flow flow; @Override public T execute(NodeContext nodeContext, FlowContext context) { List recordList = context.getParam().get("recordList"); Map result = new ConcurrentHashMap(); CountDownLatch latch = new CountDownLatch(recordList.size()); Executor executor = Executors.newFixedThreadPool(3); for (Integer i : recordList) { executor.execute(() -> { try { FlowParam subParam = new FlowParam(); subParam.put("record", i); FlowContext subContext = new FlowContextImpl(); subContext.setFlow(flow); subParam.setContext(subContext); FlowResult flowResult = context.getFlowEngine().execute(subParam); result.put(i, flowResult.get("subResult")); } finally { latch.countDown(); } }); } try { latch.await(1, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } logger.info("all subResult:" + result); return null; } @Override public void init(InitContext initContext, Object parent) { FlowNode node = (FlowNode) parent; Map flowDef = (Map) node.getProperty(DefConstants.COMMON_PROP_FLOW); FlowParseParam param = new FlowParseParam(); param.setObjectDefinition(flowDef); param.setParseEl(initContext.isParseEl()); List flowList = initContext.getFlowParser().parse(param); initContext.getFlowList().addAll(flowList); Flow subFlow = flowList.get(0); flow = subFlow; } } public static class SubFlowNodeAction implements NodeAction { @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("subFlow"); int param = context.getParam().get("record"); context.getResult().put("subResult", param * param); return null; } } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/share/nodeaction/TestEventNodeAction.java ================================================ package com.jd.easyflow.flow.bpmn.cases.share.nodeaction; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * Test event node action * @author liyuliang5 * */ public class TestEventNodeAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(TestEventNodeAction.class); @Override public Map execute(NodeContext nodeContext, FlowContext context) { logger.info("Start execute event node action"); Map result = new HashMap<>(); result.put("result1", 1); return result; } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/share/nodeaction/TestStepAction.java ================================================ package com.jd.easyflow.flow.bpmn.cases.share.nodeaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.FlowNode; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class TestStepAction implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(TestStepAction.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { FlowNode flowNode = context.getFlow().getNode(nodeContext.getNodeId()); boolean exception = Boolean.TRUE.equals(flowNode.getProperty("exception")); if (exception) { logger.info("exception"); throw new RuntimeException("exception"); } Integer sleep = flowNode.getProperty("sleep"); if (sleep != null) { logger.info("Sleep time is:" + sleep); try { Thread.sleep(sleep); } catch (InterruptedException e) { throw new RuntimeException(e); } } return null; } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/subprocess/BpmnSubProcessTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.subprocess; import org.junit.Test; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * Bpmn sub flow test. * @author liyuliang5 * */ public class BpmnSubProcessTest { /** * test bpmn sub flow. */ @Test public void testSubFlow001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/subprocess/*.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("subprocess_test_001"); FlowResult result = flowEngine.execute(param); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/terminate/BpmnTerminateTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.terminate; import org.junit.Test; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * BPMN Terminate test. * @author liyuliang5 * */ public class BpmnTerminateTest { /** * test bpmn terminate end event. */ @Test public void testTerminate001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/cases/terminate/flow_terminate001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("flow_bpmn_terminate001"); FlowResult result = flowEngine.execute(param); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/cases/throwevent/BpmnThrowEventTest.java ================================================ package com.jd.easyflow.flow.bpmn.cases.throwevent; import java.io.IOException; import java.util.List; import java.util.Map; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.jd.easyflow.flow.bpmn.cases.converter.BpmnConverterTest; import com.jd.easyflow.flow.bpmn.converter.BpmnConverter; import com.jd.easyflow.flow.util.FlowIOUtil; import com.jd.easyflow.flow.util.JsonPrettyHelper; import com.jd.easyflow.flow.util.JsonUtil; /** * * @author liyuliang5 */ public class BpmnThrowEventTest { private static final Logger logger = LoggerFactory.getLogger(BpmnThrowEventTest.class); /** * test convert. * @throws IOException */ @Test public void testConvert() throws IOException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:flow/cases/throwevent/flow_throwevent001.bpmn"); List> model = BpmnConverter.convert(resource.getInputStream()); String flowPrettyConf = FlowIOUtil.toString(BpmnConverterTest.class.getResourceAsStream("/pretty/pretty-flow.json")); String pretty = JsonPrettyHelper.pretty(model, JsonUtil.parseObject(flowPrettyConf, Map.class)); logger.info("Model is:" + pretty); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/ext/cases/chain/ChainTest.java ================================================ package com.jd.easyflow.flow.bpmn.ext.cases.chain; import static org.junit.Assert.assertEquals; import java.util.function.Function; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; import com.jd.easyflow.flow.ext.chain.ChainInvoker; public class ChainTest { private static final Logger logger = LoggerFactory.getLogger(ChainTest.class); /** * Test orchestrate plugins by nodes. */ @Test public void testChain1() { // Init flow engine. FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/ext/cases/chain/flow_chaintest1.bpmn"); flowEngine.init(); ChainInvoker invoker = new ChainInvoker(); invoker.setFlowEngine(flowEngine); // Execute flow instance1. Function targetAction = (o) -> { return new ChainTestService().execute(); }; Object result = invoker.invoke("flow_chaintest1", null, targetAction); logger.info("Execute result:" + result); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/ext/cases/chain/ChainTestService.java ================================================ package com.jd.easyflow.flow.bpmn.ext.cases.chain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ChainTestService { private static final Logger logger = LoggerFactory.getLogger(ChainTestService.class); public Object execute() { logger.info("ChainTestService execute"); return "abc"; } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/ext/cases/chain/Plugin1.java ================================================ package com.jd.easyflow.flow.bpmn.ext.cases.chain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.ext.chain.BaseChainPlugin; import com.jd.easyflow.flow.model.NodeContext; public class Plugin1 extends BaseChainPlugin { private static final Logger logger = LoggerFactory.getLogger(Plugin1.class); @Override public boolean preHandle(NodeContext nodeContext, FlowContext context) { logger.info("Plugin1 pre handle"); return true; } @Override public void postHandleNormal(NodeContext nodeContext, FlowContext context) { logger.info("Plugin1 post handle normal"); } @Override public void postHandleException(Throwable t, NodeContext nodeContext, FlowContext context) { logger.info("Plugin1 post handle exception"); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/ext/cases/chain/Plugin2.java ================================================ package com.jd.easyflow.flow.bpmn.ext.cases.chain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.ext.chain.BaseChainPlugin; import com.jd.easyflow.flow.model.NodeContext; public class Plugin2 extends BaseChainPlugin { private static final Logger logger = LoggerFactory.getLogger(Plugin2.class); @Override public boolean preHandle(NodeContext nodeContext, FlowContext context) { logger.info("Plugin2 pre handle"); return true; } @Override public void postHandleNormal(NodeContext nodeContext, FlowContext context) { logger.info("Plugin2 post handle normal"); } @Override public void postHandleException(Throwable t, NodeContext nodeContext, FlowContext context) { logger.info("Plugin2 post handle exception"); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/ext/cases/chain/Plugin3.java ================================================ package com.jd.easyflow.flow.bpmn.ext.cases.chain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.ext.chain.BaseChainPlugin; import com.jd.easyflow.flow.model.NodeContext; public class Plugin3 extends BaseChainPlugin { private static final Logger logger = LoggerFactory.getLogger(Plugin3.class); @Override public boolean preHandle(NodeContext nodeContext, FlowContext context) { logger.info("Plugin3 pre handle"); return true; } @Override public void postHandleNormal(NodeContext nodeContext, FlowContext context) { logger.info("Plugin3 post handle normal"); } @Override public void postHandleException(Throwable t, NodeContext nodeContext, FlowContext context) { logger.info("Plugin3 post handle exception"); } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/quickstart/QuickStart001Node01Action.java ================================================ package com.jd.easyflow.flow.bpmn.quickstart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class QuickStart001Node01Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(QuickStart001Node01Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("Execute Node 001"); return null; } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/quickstart/QuickStart002Node01Action.java ================================================ package com.jd.easyflow.flow.bpmn.quickstart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class QuickStart002Node01Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(QuickStart002Node01Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("Execute Node 002"); return null; } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/quickstart/QuickStart003Node01Action.java ================================================ package com.jd.easyflow.flow.bpmn.quickstart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.engine.FlowContext; import com.jd.easyflow.flow.model.NodeAction; import com.jd.easyflow.flow.model.NodeContext; /** * * @author liyuliang5 * */ public class QuickStart003Node01Action implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(QuickStart003Node01Action.class); @Override public T execute(NodeContext nodeContext, FlowContext context) { logger.info("Execute Node 003"); return null; } } ================================================ FILE: easyflow-flow-bpmn/src/test/java/com/jd/easyflow/flow/bpmn/quickstart/QuickStartTest.java ================================================ package com.jd.easyflow.flow.bpmn.quickstart; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jd.easyflow.flow.bpmn.BpmnFlowParser; import com.jd.easyflow.flow.engine.FlowParam; import com.jd.easyflow.flow.engine.FlowResult; import com.jd.easyflow.flow.engine.impl.FlowEngineImpl; /** * * @author liyuliang5 * */ public class QuickStartTest { private static final Logger logger = LoggerFactory.getLogger(QuickStartTest.class); @Test public void testQuickStart001() { FlowEngineImpl flowEngine = new FlowEngineImpl(); flowEngine.setFlowParser(new BpmnFlowParser()); flowEngine.setFlowPath("classpath:flow/quickstart/quickstart_001.bpmn"); flowEngine.init(); FlowParam param = new FlowParam("quickstart_001"); FlowResult result = flowEngine.execute(param); logger.info("Execute finish, current node is:" + result.getContext().getEndNodes().get(0).getNodeId()); assertEquals("node003", result.getContext().getEndNodes().get(0).getNodeId()); } } ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/callactivity/flow_callactivity_test_001.bpmn ================================================ Flow_057eq7h Flow_057eq7h Flow_0u94yvk Flow_0u94yvk ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/callactivity/flow_sub_of_called.bpmn ================================================ Flow_01stv9z Flow_01stv9z Flow_0jjnarz T(java.lang.System).out.println("hello") Flow_0jjnarz ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/chain/flow_chaintest1.bpmn ================================================ {"commentWidth":159,"commentPosition":{"top":18,"left":71}} exclusive Flow_0modaq2 Flow_1h77pr5 Flow_161s0ml Flow_1ipaej0 new com.jd.easyflow.flow.bpmn.cases.chain.FlowChainTest.TestPlugin("Plugin1") {"commentWidth":155,"commentPosition":{"top":21,"left":68}} exclusive Flow_161s0ml Flow_0hype9i Flow_0nnnvyb Flow_1h77pr5 new com.jd.easyflow.flow.bpmn.cases.chain.FlowChainTest.TestPlugin("Plugin2") {"commentPosition":{"top":-52,"left":4},"commentWidth":256} context.get('chainStage')=='PRE' {"commentWidth":152,"commentPosition":{"top":19,"left":70}} exclusive Flow_0nnnvyb Flow_0woq951 Flow_1rtbe85 Flow_0hype9i new com.jd.easyflow.flow.bpmn.cases.chain.FlowChainTest.TestPlugin("Plugin3") {"commentWidth":252,"commentPosition":{"top":-51,"left":0}} context.get('chainStage')=='PRE' {"commentWidth":164,"commentPosition":{"top":87,"left":4}} exclusive Flow_1rtbe85 Flow_0woq951 new com.jd.easyflow.flow.ext.chain.TargetAction() {"commentPosition":{"top":-52,"left":12},"commentWidth":248} context.get('chainStage')=='PRE' Flow_0modaq2 Flow_1ipaej0 {"commentWidth":239,"commentPosition":{"top":21,"left":1}} context.get('chainStage')=='POST' {"commentWidth":239,"commentPosition":{"top":18,"left":2}} context.get('chainStage')=='POST' {"commentWidth":237,"commentPosition":{"top":10,"left":1}} context.get('chainStage')=='POST' ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/converter/compensate001.bpmn ================================================ Flow_14hzcpx Flow_1myfz66 Flow_15q116s Flow_1myfz66 Flow_14hzcpx Flow_15q116s @compensate1Action ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/converter/flow001.json ================================================ { "id": "flow001", "name": "Mock loan flow", "nodes": [ { "id": "CONTRACT_SIGN", "name": "Sign contract", "properties": {"order": 1}, "pre":null, "action":{"exp":"new com.jd.easyflow.flow.mockbiz.loan.contract.LoanContractSignBiz().sign()"}, "post":{"to":"LIMIT_JUDGE"} }, { "id": "LIMIT_JUDGE", "name": "Judge limit", "properties": {"order": 2}, "action":{"exp":"new com.jd.easyflow.flow.mockbiz.limit.LimitBiz().judgeLimit(param.param['amount'])"}, "post":{"conditions":[{"when":"nodeContext.actionResult==true", "to":"DO_LOAN"}]} }, { "id": "DO_LOAN", "name": "Do loan", "properties": {"order": 3}, "action":{"exp":"new com.jd.easyflow.flow.mockbiz.loan.LoanBiz().doLoan()"} } ] } ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/converter/flow_multiple_001.bpmn ================================================ ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/converter/process_1.bpmn ================================================ SequenceFlow_2 SequenceFlow_4 SequenceFlow_2 SequenceFlow_5 new com.jd.easyflow.flow.bpmn.cases.converter.BpmnTestService().doScriptTask1() SequenceFlow_6 SequenceFlow_4 new com.jd.easyflow.flow.bpmn.cases.converter.BpmnTestService().doScriptTask2() SequenceFlow_5 SequenceFlow_6 SequenceFlow_10 param.get('input')>5 SequenceFlow_10 ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/converter/process_1_simple.bpmn ================================================ SequenceFlow_3 SequenceFlow_3 ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/defaultflow/default_flow_001.bpmn ================================================ Flow_0smvq7t Flow_0og7tvg Flow_1d63jfc 1>2 Flow_0smvq7t Flow_0og7tvg Flow_1d63jfc ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/extension/process_1.bpmn ================================================ SequenceFlow_2 SequenceFlow_4 SequenceFlow_2 SequenceFlow_5 new com.jd.easyflow.flow.bpmn.BpmnTestService().doScriptTask1() SequenceFlow_6 SequenceFlow_4 new com.jd.easyflow.flow.bpmn.BpmnTestService().doScriptTask2() SequenceFlow_5 SequenceFlow_6 SequenceFlow_10 param.get('input')>5 SequenceFlow_10 ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/inclusive/flow_inclusive001.bpmn ================================================ Flow_1wpx3u4 Flow_0a5nli9 Flow_186e14f bizParam<1 Flow_1hk491y Flow_03vcyf2 bizParam<2 Flow_1fv7f8g Flow_1wpx3u4 Flow_0a5nli9 Flow_1hk491y Flow_186e14f Flow_03vcyf2 Flow_1fv7f8g ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/inclusive/flow_inclusive002.bpmn ================================================ Flow_009yno9 Flow_19z9ihs Flow_01vevpz bizParam.C1 Flow_10a11ob Flow_0guprom bizParam.C2 Flow_01vevpz Flow_0kgrau1 Flow_14vadzk Flow_0kgrau1 Flow_02ojoe6 bizParam.C3 Flow_14vadzk Flow_09dvd22 bizParam.C4 Flow_07b2jxo Flow_009yno9 Flow_19z9ihs Flow_10a11ob Flow_0guprom Flow_02ojoe6 Flow_09dvd22 Flow_07b2jxo ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/logflag/logflag_001.bpmn ================================================ false ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/parallel/flow_multi_parallel_subflow_001.bpmn ================================================ Flow_0vzdjlp Flow_0d9losl {"id":"subFlow001"} {"createExp":"new com.jd.easyflow.flow.bpmn.cases.parallel.BpmnParallelTest.MainFlowNodeAction()"} Flow_0vzdjlp Flow_0d9losl Flow_1uq36dk Flow_1uq36dk Flow_1kfxj59 new com.jd.easyflow.flow.bpmn.cases.parallel.BpmnParallelTest.SubFlowNodeAction() Flow_1kfxj59 ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/parallel/flow_parallel001.bpmn ================================================ Flow_08hz986 {"sleep":100} Flow_1xkqwtr Flow_0jod389 new com.jd.easyflow.flow.bpmn.cases.share.nodeaction.TestStepAction() {"sleep":300} Flow_0rnxuqo Flow_0lreky6 new com.jd.easyflow.flow.bpmn.cases.share.nodeaction.TestStepAction() Flow_1992ddw Flow_08hz986 Flow_1xkqwtr Flow_0rnxuqo Flow_0jod389 Flow_0lreky6 Flow_1992ddw ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/subprocess/flow_subprocess_test_001.bpmn ================================================ Flow_0zkxi10 {"id":"subprocess"} Flow_0zkxi10 Flow_1g0nlgh Flow_1sv8pyg Flow_1sv8pyg Flow_1oc2lcj T(java.lang.System).out.println("hello") Flow_1oc2lcj Flow_1g0nlgh ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/terminate/flow_terminate001.bpmn ================================================ Flow_0w91l7g Flow_0w91l7g ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/cases/throwevent/flow_throwevent001.bpmn ================================================ {"exp":"@throwEventNodeAction.execute()"} Flow_10hbpzz Flow_1axfbpv {"exp":"@startNodeAction.execute()"} Flow_10hbpzz {"exp":"@endNodeAction.execute()"} Flow_1axfbpv ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/ext/cases/chain/flow_chaintest1.bpmn ================================================ {"commentWidth":159,"commentPosition":{"top":18,"left":71}} exclusive Flow_0modaq2 Flow_1h77pr5 Flow_161s0ml Flow_1ipaej0 new com.jd.easyflow.flow.bpmn.ext.cases.chain.Plugin1() {"commentWidth":155,"commentPosition":{"top":21,"left":68}} exclusive Flow_161s0ml Flow_0hype9i Flow_0nnnvyb Flow_1h77pr5 new com.jd.easyflow.flow.bpmn.ext.cases.chain.Plugin2() {"commentPosition":{"top":-52,"left":4},"commentWidth":256} context.get('chainStage')=='PRE' {"commentWidth":152,"commentPosition":{"top":19,"left":70}} exclusive Flow_0nnnvyb Flow_0woq951 Flow_1rtbe85 Flow_0hype9i new com.jd.easyflow.flow.bpmn.ext.cases.chain.Plugin3() {"commentWidth":252,"commentPosition":{"top":-51,"left":0}} context.get('chainStage')=='PRE' {"commentWidth":164,"commentPosition":{"top":87,"left":4}} exclusive Flow_1rtbe85 Flow_0woq951 new com.jd.easyflow.flow.ext.chain.TargetAction() {"commentPosition":{"top":-52,"left":12},"commentWidth":248} context.get('chainStage')=='PRE' Flow_0modaq2 Flow_1ipaej0 {"commentWidth":239,"commentPosition":{"top":21,"left":1}} context.get('chainStage')=='POST' {"commentWidth":239,"commentPosition":{"top":18,"left":2}} context.get('chainStage')=='POST' {"commentWidth":237,"commentPosition":{"top":10,"left":1}} context.get('chainStage')=='POST' ================================================ FILE: easyflow-flow-bpmn/src/test/resources/flow/quickstart/quickstart_001.bpmn ================================================ true Flow_0iohk2p new com.jd.easyflow.flow.bpmn.quickstart.QuickStart001Node01Action() Flow_0iohk2p Flow_0a49msy new com.jd.easyflow.flow.bpmn.quickstart.QuickStart002Node01Action() Flow_0a49msy new com.jd.easyflow.flow.bpmn.quickstart.QuickStart003Node01Action() ================================================ FILE: easyflow-flow-bpmn/src/test/resources/logback.xml ================================================ %d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0} - %m%n ================================================ FILE: easyflow-flow-bpmn/static/plugins/bootstrap/css/bootstrap-grid.css ================================================ /*! * Bootstrap Grid v4.3.1 (https://getbootstrap.com/) * Copyright 2011-2019 The Bootstrap Authors * Copyright 2011-2019 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ html { box-sizing: border-box; -ms-overflow-style: scrollbar; } *, *::before, *::after { box-sizing: inherit; } .container { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } @media (min-width: 576px) { .container { max-width: 540px; } } @media (min-width: 768px) { .container { max-width: 720px; } } @media (min-width: 992px) { .container { max-width: 960px; } } @media (min-width: 1200px) { .container { max-width: 1140px; } } .container-fluid { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } .row { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: -15px; margin-left: -15px; } .no-gutters { margin-right: 0; margin-left: 0; } .no-gutters > .col, .no-gutters > [class*="col-"] { padding-right: 0; padding-left: 0; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, .col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, .col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, .col-xl-auto { position: relative; width: 100%; padding-right: 15px; padding-left: 15px; } .col { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-first { -ms-flex-order: -1; order: -1; } .order-last { -ms-flex-order: 13; order: 13; } .order-0 { -ms-flex-order: 0; order: 0; } .order-1 { -ms-flex-order: 1; order: 1; } .order-2 { -ms-flex-order: 2; order: 2; } .order-3 { -ms-flex-order: 3; order: 3; } .order-4 { -ms-flex-order: 4; order: 4; } .order-5 { -ms-flex-order: 5; order: 5; } .order-6 { -ms-flex-order: 6; order: 6; } .order-7 { -ms-flex-order: 7; order: 7; } .order-8 { -ms-flex-order: 8; order: 8; } .order-9 { -ms-flex-order: 9; order: 9; } .order-10 { -ms-flex-order: 10; order: 10; } .order-11 { -ms-flex-order: 11; order: 11; } .order-12 { -ms-flex-order: 12; order: 12; } .offset-1 { margin-left: 8.333333%; } .offset-2 { margin-left: 16.666667%; } .offset-3 { margin-left: 25%; } .offset-4 { margin-left: 33.333333%; } .offset-5 { margin-left: 41.666667%; } .offset-6 { margin-left: 50%; } .offset-7 { margin-left: 58.333333%; } .offset-8 { margin-left: 66.666667%; } .offset-9 { margin-left: 75%; } .offset-10 { margin-left: 83.333333%; } .offset-11 { margin-left: 91.666667%; } @media (min-width: 576px) { .col-sm { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-sm-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-sm-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-sm-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-sm-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-sm-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-sm-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-sm-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-sm-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-sm-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-sm-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-sm-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-sm-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-sm-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-sm-first { -ms-flex-order: -1; order: -1; } .order-sm-last { -ms-flex-order: 13; order: 13; } .order-sm-0 { -ms-flex-order: 0; order: 0; } .order-sm-1 { -ms-flex-order: 1; order: 1; } .order-sm-2 { -ms-flex-order: 2; order: 2; } .order-sm-3 { -ms-flex-order: 3; order: 3; } .order-sm-4 { -ms-flex-order: 4; order: 4; } .order-sm-5 { -ms-flex-order: 5; order: 5; } .order-sm-6 { -ms-flex-order: 6; order: 6; } .order-sm-7 { -ms-flex-order: 7; order: 7; } .order-sm-8 { -ms-flex-order: 8; order: 8; } .order-sm-9 { -ms-flex-order: 9; order: 9; } .order-sm-10 { -ms-flex-order: 10; order: 10; } .order-sm-11 { -ms-flex-order: 11; order: 11; } .order-sm-12 { -ms-flex-order: 12; order: 12; } .offset-sm-0 { margin-left: 0; } .offset-sm-1 { margin-left: 8.333333%; } .offset-sm-2 { margin-left: 16.666667%; } .offset-sm-3 { margin-left: 25%; } .offset-sm-4 { margin-left: 33.333333%; } .offset-sm-5 { margin-left: 41.666667%; } .offset-sm-6 { margin-left: 50%; } .offset-sm-7 { margin-left: 58.333333%; } .offset-sm-8 { margin-left: 66.666667%; } .offset-sm-9 { margin-left: 75%; } .offset-sm-10 { margin-left: 83.333333%; } .offset-sm-11 { margin-left: 91.666667%; } } @media (min-width: 768px) { .col-md { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-md-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-md-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-md-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-md-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-md-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-md-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-md-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-md-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-md-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-md-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-md-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-md-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-md-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-md-first { -ms-flex-order: -1; order: -1; } .order-md-last { -ms-flex-order: 13; order: 13; } .order-md-0 { -ms-flex-order: 0; order: 0; } .order-md-1 { -ms-flex-order: 1; order: 1; } .order-md-2 { -ms-flex-order: 2; order: 2; } .order-md-3 { -ms-flex-order: 3; order: 3; } .order-md-4 { -ms-flex-order: 4; order: 4; } .order-md-5 { -ms-flex-order: 5; order: 5; } .order-md-6 { -ms-flex-order: 6; order: 6; } .order-md-7 { -ms-flex-order: 7; order: 7; } .order-md-8 { -ms-flex-order: 8; order: 8; } .order-md-9 { -ms-flex-order: 9; order: 9; } .order-md-10 { -ms-flex-order: 10; order: 10; } .order-md-11 { -ms-flex-order: 11; order: 11; } .order-md-12 { -ms-flex-order: 12; order: 12; } .offset-md-0 { margin-left: 0; } .offset-md-1 { margin-left: 8.333333%; } .offset-md-2 { margin-left: 16.666667%; } .offset-md-3 { margin-left: 25%; } .offset-md-4 { margin-left: 33.333333%; } .offset-md-5 { margin-left: 41.666667%; } .offset-md-6 { margin-left: 50%; } .offset-md-7 { margin-left: 58.333333%; } .offset-md-8 { margin-left: 66.666667%; } .offset-md-9 { margin-left: 75%; } .offset-md-10 { margin-left: 83.333333%; } .offset-md-11 { margin-left: 91.666667%; } } @media (min-width: 992px) { .col-lg { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-lg-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-lg-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-lg-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-lg-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-lg-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-lg-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-lg-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-lg-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-lg-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-lg-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-lg-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-lg-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-lg-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-lg-first { -ms-flex-order: -1; order: -1; } .order-lg-last { -ms-flex-order: 13; order: 13; } .order-lg-0 { -ms-flex-order: 0; order: 0; } .order-lg-1 { -ms-flex-order: 1; order: 1; } .order-lg-2 { -ms-flex-order: 2; order: 2; } .order-lg-3 { -ms-flex-order: 3; order: 3; } .order-lg-4 { -ms-flex-order: 4; order: 4; } .order-lg-5 { -ms-flex-order: 5; order: 5; } .order-lg-6 { -ms-flex-order: 6; order: 6; } .order-lg-7 { -ms-flex-order: 7; order: 7; } .order-lg-8 { -ms-flex-order: 8; order: 8; } .order-lg-9 { -ms-flex-order: 9; order: 9; } .order-lg-10 { -ms-flex-order: 10; order: 10; } .order-lg-11 { -ms-flex-order: 11; order: 11; } .order-lg-12 { -ms-flex-order: 12; order: 12; } .offset-lg-0 { margin-left: 0; } .offset-lg-1 { margin-left: 8.333333%; } .offset-lg-2 { margin-left: 16.666667%; } .offset-lg-3 { margin-left: 25%; } .offset-lg-4 { margin-left: 33.333333%; } .offset-lg-5 { margin-left: 41.666667%; } .offset-lg-6 { margin-left: 50%; } .offset-lg-7 { margin-left: 58.333333%; } .offset-lg-8 { margin-left: 66.666667%; } .offset-lg-9 { margin-left: 75%; } .offset-lg-10 { margin-left: 83.333333%; } .offset-lg-11 { margin-left: 91.666667%; } } @media (min-width: 1200px) { .col-xl { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-xl-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-xl-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-xl-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-xl-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-xl-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-xl-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-xl-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-xl-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-xl-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-xl-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-xl-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-xl-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-xl-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-xl-first { -ms-flex-order: -1; order: -1; } .order-xl-last { -ms-flex-order: 13; order: 13; } .order-xl-0 { -ms-flex-order: 0; order: 0; } .order-xl-1 { -ms-flex-order: 1; order: 1; } .order-xl-2 { -ms-flex-order: 2; order: 2; } .order-xl-3 { -ms-flex-order: 3; order: 3; } .order-xl-4 { -ms-flex-order: 4; order: 4; } .order-xl-5 { -ms-flex-order: 5; order: 5; } .order-xl-6 { -ms-flex-order: 6; order: 6; } .order-xl-7 { -ms-flex-order: 7; order: 7; } .order-xl-8 { -ms-flex-order: 8; order: 8; } .order-xl-9 { -ms-flex-order: 9; order: 9; } .order-xl-10 { -ms-flex-order: 10; order: 10; } .order-xl-11 { -ms-flex-order: 11; order: 11; } .order-xl-12 { -ms-flex-order: 12; order: 12; } .offset-xl-0 { margin-left: 0; } .offset-xl-1 { margin-left: 8.333333%; } .offset-xl-2 { margin-left: 16.666667%; } .offset-xl-3 { margin-left: 25%; } .offset-xl-4 { margin-left: 33.333333%; } .offset-xl-5 { margin-left: 41.666667%; } .offset-xl-6 { margin-left: 50%; } .offset-xl-7 { margin-left: 58.333333%; } .offset-xl-8 { margin-left: 66.666667%; } .offset-xl-9 { margin-left: 75%; } .offset-xl-10 { margin-left: 83.333333%; } .offset-xl-11 { margin-left: 91.666667%; } } .d-none { display: none !important; } .d-inline { display: inline !important; } .d-inline-block { display: inline-block !important; } .d-block { display: block !important; } .d-table { display: table !important; } .d-table-row { display: table-row !important; } .d-table-cell { display: table-cell !important; } .d-flex { display: -ms-flexbox !important; display: flex !important; } .d-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } @media (min-width: 576px) { .d-sm-none { display: none !important; } .d-sm-inline { display: inline !important; } .d-sm-inline-block { display: inline-block !important; } .d-sm-block { display: block !important; } .d-sm-table { display: table !important; } .d-sm-table-row { display: table-row !important; } .d-sm-table-cell { display: table-cell !important; } .d-sm-flex { display: -ms-flexbox !important; display: flex !important; } .d-sm-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 768px) { .d-md-none { display: none !important; } .d-md-inline { display: inline !important; } .d-md-inline-block { display: inline-block !important; } .d-md-block { display: block !important; } .d-md-table { display: table !important; } .d-md-table-row { display: table-row !important; } .d-md-table-cell { display: table-cell !important; } .d-md-flex { display: -ms-flexbox !important; display: flex !important; } .d-md-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 992px) { .d-lg-none { display: none !important; } .d-lg-inline { display: inline !important; } .d-lg-inline-block { display: inline-block !important; } .d-lg-block { display: block !important; } .d-lg-table { display: table !important; } .d-lg-table-row { display: table-row !important; } .d-lg-table-cell { display: table-cell !important; } .d-lg-flex { display: -ms-flexbox !important; display: flex !important; } .d-lg-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 1200px) { .d-xl-none { display: none !important; } .d-xl-inline { display: inline !important; } .d-xl-inline-block { display: inline-block !important; } .d-xl-block { display: block !important; } .d-xl-table { display: table !important; } .d-xl-table-row { display: table-row !important; } .d-xl-table-cell { display: table-cell !important; } .d-xl-flex { display: -ms-flexbox !important; display: flex !important; } .d-xl-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media print { .d-print-none { display: none !important; } .d-print-inline { display: inline !important; } .d-print-inline-block { display: inline-block !important; } .d-print-block { display: block !important; } .d-print-table { display: table !important; } .d-print-table-row { display: table-row !important; } .d-print-table-cell { display: table-cell !important; } .d-print-flex { display: -ms-flexbox !important; display: flex !important; } .d-print-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } .flex-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } @media (min-width: 576px) { .flex-sm-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-sm-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-sm-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-sm-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-sm-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-sm-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-sm-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-sm-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-sm-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-sm-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-sm-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-sm-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-sm-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-sm-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-sm-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-sm-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-sm-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-sm-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-sm-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-sm-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-sm-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-sm-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-sm-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-sm-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-sm-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-sm-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-sm-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-sm-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-sm-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-sm-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-sm-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-sm-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-sm-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-sm-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 768px) { .flex-md-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-md-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-md-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-md-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-md-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-md-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-md-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-md-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-md-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-md-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-md-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-md-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-md-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-md-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-md-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-md-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-md-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-md-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-md-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-md-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-md-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-md-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-md-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-md-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-md-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-md-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-md-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-md-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-md-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-md-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-md-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-md-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-md-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-md-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 992px) { .flex-lg-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-lg-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-lg-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-lg-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-lg-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-lg-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-lg-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-lg-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-lg-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-lg-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-lg-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-lg-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-lg-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-lg-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-lg-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-lg-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-lg-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-lg-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-lg-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-lg-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-lg-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-lg-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-lg-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-lg-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-lg-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-lg-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-lg-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-lg-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-lg-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-lg-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-lg-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-lg-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-lg-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-lg-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 1200px) { .flex-xl-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-xl-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-xl-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-xl-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-xl-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-xl-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-xl-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-xl-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-xl-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-xl-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-xl-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-xl-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-xl-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-xl-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-xl-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-xl-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-xl-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-xl-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-xl-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-xl-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-xl-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-xl-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-xl-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-xl-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-xl-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-xl-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-xl-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-xl-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-xl-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-xl-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-xl-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-xl-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-xl-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-xl-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } .m-0 { margin: 0 !important; } .mt-0, .my-0 { margin-top: 0 !important; } .mr-0, .mx-0 { margin-right: 0 !important; } .mb-0, .my-0 { margin-bottom: 0 !important; } .ml-0, .mx-0 { margin-left: 0 !important; } .m-1 { margin: 0.25rem !important; } .mt-1, .my-1 { margin-top: 0.25rem !important; } .mr-1, .mx-1 { margin-right: 0.25rem !important; } .mb-1, .my-1 { margin-bottom: 0.25rem !important; } .ml-1, .mx-1 { margin-left: 0.25rem !important; } .m-2 { margin: 0.5rem !important; } .mt-2, .my-2 { margin-top: 0.5rem !important; } .mr-2, .mx-2 { margin-right: 0.5rem !important; } .mb-2, .my-2 { margin-bottom: 0.5rem !important; } .ml-2, .mx-2 { margin-left: 0.5rem !important; } .m-3 { margin: 1rem !important; } .mt-3, .my-3 { margin-top: 1rem !important; } .mr-3, .mx-3 { margin-right: 1rem !important; } .mb-3, .my-3 { margin-bottom: 1rem !important; } .ml-3, .mx-3 { margin-left: 1rem !important; } .m-4 { margin: 1.5rem !important; } .mt-4, .my-4 { margin-top: 1.5rem !important; } .mr-4, .mx-4 { margin-right: 1.5rem !important; } .mb-4, .my-4 { margin-bottom: 1.5rem !important; } .ml-4, .mx-4 { margin-left: 1.5rem !important; } .m-5 { margin: 3rem !important; } .mt-5, .my-5 { margin-top: 3rem !important; } .mr-5, .mx-5 { margin-right: 3rem !important; } .mb-5, .my-5 { margin-bottom: 3rem !important; } .ml-5, .mx-5 { margin-left: 3rem !important; } .p-0 { padding: 0 !important; } .pt-0, .py-0 { padding-top: 0 !important; } .pr-0, .px-0 { padding-right: 0 !important; } .pb-0, .py-0 { padding-bottom: 0 !important; } .pl-0, .px-0 { padding-left: 0 !important; } .p-1 { padding: 0.25rem !important; } .pt-1, .py-1 { padding-top: 0.25rem !important; } .pr-1, .px-1 { padding-right: 0.25rem !important; } .pb-1, .py-1 { padding-bottom: 0.25rem !important; } .pl-1, .px-1 { padding-left: 0.25rem !important; } .p-2 { padding: 0.5rem !important; } .pt-2, .py-2 { padding-top: 0.5rem !important; } .pr-2, .px-2 { padding-right: 0.5rem !important; } .pb-2, .py-2 { padding-bottom: 0.5rem !important; } .pl-2, .px-2 { padding-left: 0.5rem !important; } .p-3 { padding: 1rem !important; } .pt-3, .py-3 { padding-top: 1rem !important; } .pr-3, .px-3 { padding-right: 1rem !important; } .pb-3, .py-3 { padding-bottom: 1rem !important; } .pl-3, .px-3 { padding-left: 1rem !important; } .p-4 { padding: 1.5rem !important; } .pt-4, .py-4 { padding-top: 1.5rem !important; } .pr-4, .px-4 { padding-right: 1.5rem !important; } .pb-4, .py-4 { padding-bottom: 1.5rem !important; } .pl-4, .px-4 { padding-left: 1.5rem !important; } .p-5 { padding: 3rem !important; } .pt-5, .py-5 { padding-top: 3rem !important; } .pr-5, .px-5 { padding-right: 3rem !important; } .pb-5, .py-5 { padding-bottom: 3rem !important; } .pl-5, .px-5 { padding-left: 3rem !important; } .m-n1 { margin: -0.25rem !important; } .mt-n1, .my-n1 { margin-top: -0.25rem !important; } .mr-n1, .mx-n1 { margin-right: -0.25rem !important; } .mb-n1, .my-n1 { margin-bottom: -0.25rem !important; } .ml-n1, .mx-n1 { margin-left: -0.25rem !important; } .m-n2 { margin: -0.5rem !important; } .mt-n2, .my-n2 { margin-top: -0.5rem !important; } .mr-n2, .mx-n2 { margin-right: -0.5rem !important; } .mb-n2, .my-n2 { margin-bottom: -0.5rem !important; } .ml-n2, .mx-n2 { margin-left: -0.5rem !important; } .m-n3 { margin: -1rem !important; } .mt-n3, .my-n3 { margin-top: -1rem !important; } .mr-n3, .mx-n3 { margin-right: -1rem !important; } .mb-n3, .my-n3 { margin-bottom: -1rem !important; } .ml-n3, .mx-n3 { margin-left: -1rem !important; } .m-n4 { margin: -1.5rem !important; } .mt-n4, .my-n4 { margin-top: -1.5rem !important; } .mr-n4, .mx-n4 { margin-right: -1.5rem !important; } .mb-n4, .my-n4 { margin-bottom: -1.5rem !important; } .ml-n4, .mx-n4 { margin-left: -1.5rem !important; } .m-n5 { margin: -3rem !important; } .mt-n5, .my-n5 { margin-top: -3rem !important; } .mr-n5, .mx-n5 { margin-right: -3rem !important; } .mb-n5, .my-n5 { margin-bottom: -3rem !important; } .ml-n5, .mx-n5 { margin-left: -3rem !important; } .m-auto { margin: auto !important; } .mt-auto, .my-auto { margin-top: auto !important; } .mr-auto, .mx-auto { margin-right: auto !important; } .mb-auto, .my-auto { margin-bottom: auto !important; } .ml-auto, .mx-auto { margin-left: auto !important; } @media (min-width: 576px) { .m-sm-0 { margin: 0 !important; } .mt-sm-0, .my-sm-0 { margin-top: 0 !important; } .mr-sm-0, .mx-sm-0 { margin-right: 0 !important; } .mb-sm-0, .my-sm-0 { margin-bottom: 0 !important; } .ml-sm-0, .mx-sm-0 { margin-left: 0 !important; } .m-sm-1 { margin: 0.25rem !important; } .mt-sm-1, .my-sm-1 { margin-top: 0.25rem !important; } .mr-sm-1, .mx-sm-1 { margin-right: 0.25rem !important; } .mb-sm-1, .my-sm-1 { margin-bottom: 0.25rem !important; } .ml-sm-1, .mx-sm-1 { margin-left: 0.25rem !important; } .m-sm-2 { margin: 0.5rem !important; } .mt-sm-2, .my-sm-2 { margin-top: 0.5rem !important; } .mr-sm-2, .mx-sm-2 { margin-right: 0.5rem !important; } .mb-sm-2, .my-sm-2 { margin-bottom: 0.5rem !important; } .ml-sm-2, .mx-sm-2 { margin-left: 0.5rem !important; } .m-sm-3 { margin: 1rem !important; } .mt-sm-3, .my-sm-3 { margin-top: 1rem !important; } .mr-sm-3, .mx-sm-3 { margin-right: 1rem !important; } .mb-sm-3, .my-sm-3 { margin-bottom: 1rem !important; } .ml-sm-3, .mx-sm-3 { margin-left: 1rem !important; } .m-sm-4 { margin: 1.5rem !important; } .mt-sm-4, .my-sm-4 { margin-top: 1.5rem !important; } .mr-sm-4, .mx-sm-4 { margin-right: 1.5rem !important; } .mb-sm-4, .my-sm-4 { margin-bottom: 1.5rem !important; } .ml-sm-4, .mx-sm-4 { margin-left: 1.5rem !important; } .m-sm-5 { margin: 3rem !important; } .mt-sm-5, .my-sm-5 { margin-top: 3rem !important; } .mr-sm-5, .mx-sm-5 { margin-right: 3rem !important; } .mb-sm-5, .my-sm-5 { margin-bottom: 3rem !important; } .ml-sm-5, .mx-sm-5 { margin-left: 3rem !important; } .p-sm-0 { padding: 0 !important; } .pt-sm-0, .py-sm-0 { padding-top: 0 !important; } .pr-sm-0, .px-sm-0 { padding-right: 0 !important; } .pb-sm-0, .py-sm-0 { padding-bottom: 0 !important; } .pl-sm-0, .px-sm-0 { padding-left: 0 !important; } .p-sm-1 { padding: 0.25rem !important; } .pt-sm-1, .py-sm-1 { padding-top: 0.25rem !important; } .pr-sm-1, .px-sm-1 { padding-right: 0.25rem !important; } .pb-sm-1, .py-sm-1 { padding-bottom: 0.25rem !important; } .pl-sm-1, .px-sm-1 { padding-left: 0.25rem !important; } .p-sm-2 { padding: 0.5rem !important; } .pt-sm-2, .py-sm-2 { padding-top: 0.5rem !important; } .pr-sm-2, .px-sm-2 { padding-right: 0.5rem !important; } .pb-sm-2, .py-sm-2 { padding-bottom: 0.5rem !important; } .pl-sm-2, .px-sm-2 { padding-left: 0.5rem !important; } .p-sm-3 { padding: 1rem !important; } .pt-sm-3, .py-sm-3 { padding-top: 1rem !important; } .pr-sm-3, .px-sm-3 { padding-right: 1rem !important; } .pb-sm-3, .py-sm-3 { padding-bottom: 1rem !important; } .pl-sm-3, .px-sm-3 { padding-left: 1rem !important; } .p-sm-4 { padding: 1.5rem !important; } .pt-sm-4, .py-sm-4 { padding-top: 1.5rem !important; } .pr-sm-4, .px-sm-4 { padding-right: 1.5rem !important; } .pb-sm-4, .py-sm-4 { padding-bottom: 1.5rem !important; } .pl-sm-4, .px-sm-4 { padding-left: 1.5rem !important; } .p-sm-5 { padding: 3rem !important; } .pt-sm-5, .py-sm-5 { padding-top: 3rem !important; } .pr-sm-5, .px-sm-5 { padding-right: 3rem !important; } .pb-sm-5, .py-sm-5 { padding-bottom: 3rem !important; } .pl-sm-5, .px-sm-5 { padding-left: 3rem !important; } .m-sm-n1 { margin: -0.25rem !important; } .mt-sm-n1, .my-sm-n1 { margin-top: -0.25rem !important; } .mr-sm-n1, .mx-sm-n1 { margin-right: -0.25rem !important; } .mb-sm-n1, .my-sm-n1 { margin-bottom: -0.25rem !important; } .ml-sm-n1, .mx-sm-n1 { margin-left: -0.25rem !important; } .m-sm-n2 { margin: -0.5rem !important; } .mt-sm-n2, .my-sm-n2 { margin-top: -0.5rem !important; } .mr-sm-n2, .mx-sm-n2 { margin-right: -0.5rem !important; } .mb-sm-n2, .my-sm-n2 { margin-bottom: -0.5rem !important; } .ml-sm-n2, .mx-sm-n2 { margin-left: -0.5rem !important; } .m-sm-n3 { margin: -1rem !important; } .mt-sm-n3, .my-sm-n3 { margin-top: -1rem !important; } .mr-sm-n3, .mx-sm-n3 { margin-right: -1rem !important; } .mb-sm-n3, .my-sm-n3 { margin-bottom: -1rem !important; } .ml-sm-n3, .mx-sm-n3 { margin-left: -1rem !important; } .m-sm-n4 { margin: -1.5rem !important; } .mt-sm-n4, .my-sm-n4 { margin-top: -1.5rem !important; } .mr-sm-n4, .mx-sm-n4 { margin-right: -1.5rem !important; } .mb-sm-n4, .my-sm-n4 { margin-bottom: -1.5rem !important; } .ml-sm-n4, .mx-sm-n4 { margin-left: -1.5rem !important; } .m-sm-n5 { margin: -3rem !important; } .mt-sm-n5, .my-sm-n5 { margin-top: -3rem !important; } .mr-sm-n5, .mx-sm-n5 { margin-right: -3rem !important; } .mb-sm-n5, .my-sm-n5 { margin-bottom: -3rem !important; } .ml-sm-n5, .mx-sm-n5 { margin-left: -3rem !important; } .m-sm-auto { margin: auto !important; } .mt-sm-auto, .my-sm-auto { margin-top: auto !important; } .mr-sm-auto, .mx-sm-auto { margin-right: auto !important; } .mb-sm-auto, .my-sm-auto { margin-bottom: auto !important; } .ml-sm-auto, .mx-sm-auto { margin-left: auto !important; } } @media (min-width: 768px) { .m-md-0 { margin: 0 !important; } .mt-md-0, .my-md-0 { margin-top: 0 !important; } .mr-md-0, .mx-md-0 { margin-right: 0 !important; } .mb-md-0, .my-md-0 { margin-bottom: 0 !important; } .ml-md-0, .mx-md-0 { margin-left: 0 !important; } .m-md-1 { margin: 0.25rem !important; } .mt-md-1, .my-md-1 { margin-top: 0.25rem !important; } .mr-md-1, .mx-md-1 { margin-right: 0.25rem !important; } .mb-md-1, .my-md-1 { margin-bottom: 0.25rem !important; } .ml-md-1, .mx-md-1 { margin-left: 0.25rem !important; } .m-md-2 { margin: 0.5rem !important; } .mt-md-2, .my-md-2 { margin-top: 0.5rem !important; } .mr-md-2, .mx-md-2 { margin-right: 0.5rem !important; } .mb-md-2, .my-md-2 { margin-bottom: 0.5rem !important; } .ml-md-2, .mx-md-2 { margin-left: 0.5rem !important; } .m-md-3 { margin: 1rem !important; } .mt-md-3, .my-md-3 { margin-top: 1rem !important; } .mr-md-3, .mx-md-3 { margin-right: 1rem !important; } .mb-md-3, .my-md-3 { margin-bottom: 1rem !important; } .ml-md-3, .mx-md-3 { margin-left: 1rem !important; } .m-md-4 { margin: 1.5rem !important; } .mt-md-4, .my-md-4 { margin-top: 1.5rem !important; } .mr-md-4, .mx-md-4 { margin-right: 1.5rem !important; } .mb-md-4, .my-md-4 { margin-bottom: 1.5rem !important; } .ml-md-4, .mx-md-4 { margin-left: 1.5rem !important; } .m-md-5 { margin: 3rem !important; } .mt-md-5, .my-md-5 { margin-top: 3rem !important; } .mr-md-5, .mx-md-5 { margin-right: 3rem !important; } .mb-md-5, .my-md-5 { margin-bottom: 3rem !important; } .ml-md-5, .mx-md-5 { margin-left: 3rem !important; } .p-md-0 { padding: 0 !important; } .pt-md-0, .py-md-0 { padding-top: 0 !important; } .pr-md-0, .px-md-0 { padding-right: 0 !important; } .pb-md-0, .py-md-0 { padding-bottom: 0 !important; } .pl-md-0, .px-md-0 { padding-left: 0 !important; } .p-md-1 { padding: 0.25rem !important; } .pt-md-1, .py-md-1 { padding-top: 0.25rem !important; } .pr-md-1, .px-md-1 { padding-right: 0.25rem !important; } .pb-md-1, .py-md-1 { padding-bottom: 0.25rem !important; } .pl-md-1, .px-md-1 { padding-left: 0.25rem !important; } .p-md-2 { padding: 0.5rem !important; } .pt-md-2, .py-md-2 { padding-top: 0.5rem !important; } .pr-md-2, .px-md-2 { padding-right: 0.5rem !important; } .pb-md-2, .py-md-2 { padding-bottom: 0.5rem !important; } .pl-md-2, .px-md-2 { padding-left: 0.5rem !important; } .p-md-3 { padding: 1rem !important; } .pt-md-3, .py-md-3 { padding-top: 1rem !important; } .pr-md-3, .px-md-3 { padding-right: 1rem !important; } .pb-md-3, .py-md-3 { padding-bottom: 1rem !important; } .pl-md-3, .px-md-3 { padding-left: 1rem !important; } .p-md-4 { padding: 1.5rem !important; } .pt-md-4, .py-md-4 { padding-top: 1.5rem !important; } .pr-md-4, .px-md-4 { padding-right: 1.5rem !important; } .pb-md-4, .py-md-4 { padding-bottom: 1.5rem !important; } .pl-md-4, .px-md-4 { padding-left: 1.5rem !important; } .p-md-5 { padding: 3rem !important; } .pt-md-5, .py-md-5 { padding-top: 3rem !important; } .pr-md-5, .px-md-5 { padding-right: 3rem !important; } .pb-md-5, .py-md-5 { padding-bottom: 3rem !important; } .pl-md-5, .px-md-5 { padding-left: 3rem !important; } .m-md-n1 { margin: -0.25rem !important; } .mt-md-n1, .my-md-n1 { margin-top: -0.25rem !important; } .mr-md-n1, .mx-md-n1 { margin-right: -0.25rem !important; } .mb-md-n1, .my-md-n1 { margin-bottom: -0.25rem !important; } .ml-md-n1, .mx-md-n1 { margin-left: -0.25rem !important; } .m-md-n2 { margin: -0.5rem !important; } .mt-md-n2, .my-md-n2 { margin-top: -0.5rem !important; } .mr-md-n2, .mx-md-n2 { margin-right: -0.5rem !important; } .mb-md-n2, .my-md-n2 { margin-bottom: -0.5rem !important; } .ml-md-n2, .mx-md-n2 { margin-left: -0.5rem !important; } .m-md-n3 { margin: -1rem !important; } .mt-md-n3, .my-md-n3 { margin-top: -1rem !important; } .mr-md-n3, .mx-md-n3 { margin-right: -1rem !important; } .mb-md-n3, .my-md-n3 { margin-bottom: -1rem !important; } .ml-md-n3, .mx-md-n3 { margin-left: -1rem !important; } .m-md-n4 { margin: -1.5rem !important; } .mt-md-n4, .my-md-n4 { margin-top: -1.5rem !important; } .mr-md-n4, .mx-md-n4 { margin-right: -1.5rem !important; } .mb-md-n4, .my-md-n4 { margin-bottom: -1.5rem !important; } .ml-md-n4, .mx-md-n4 { margin-left: -1.5rem !important; } .m-md-n5 { margin: -3rem !important; } .mt-md-n5, .my-md-n5 { margin-top: -3rem !important; } .mr-md-n5, .mx-md-n5 { margin-right: -3rem !important; } .mb-md-n5, .my-md-n5 { margin-bottom: -3rem !important; } .ml-md-n5, .mx-md-n5 { margin-left: -3rem !important; } .m-md-auto { margin: auto !important; } .mt-md-auto, .my-md-auto { margin-top: auto !important; } .mr-md-auto, .mx-md-auto { margin-right: auto !important; } .mb-md-auto, .my-md-auto { margin-bottom: auto !important; } .ml-md-auto, .mx-md-auto { margin-left: auto !important; } } @media (min-width: 992px) { .m-lg-0 { margin: 0 !important; } .mt-lg-0, .my-lg-0 { margin-top: 0 !important; } .mr-lg-0, .mx-lg-0 { margin-right: 0 !important; } .mb-lg-0, .my-lg-0 { margin-bottom: 0 !important; } .ml-lg-0, .mx-lg-0 { margin-left: 0 !important; } .m-lg-1 { margin: 0.25rem !important; } .mt-lg-1, .my-lg-1 { margin-top: 0.25rem !important; } .mr-lg-1, .mx-lg-1 { margin-right: 0.25rem !important; } .mb-lg-1, .my-lg-1 { margin-bottom: 0.25rem !important; } .ml-lg-1, .mx-lg-1 { margin-left: 0.25rem !important; } .m-lg-2 { margin: 0.5rem !important; } .mt-lg-2, .my-lg-2 { margin-top: 0.5rem !important; } .mr-lg-2, .mx-lg-2 { margin-right: 0.5rem !important; } .mb-lg-2, .my-lg-2 { margin-bottom: 0.5rem !important; } .ml-lg-2, .mx-lg-2 { margin-left: 0.5rem !important; } .m-lg-3 { margin: 1rem !important; } .mt-lg-3, .my-lg-3 { margin-top: 1rem !important; } .mr-lg-3, .mx-lg-3 { margin-right: 1rem !important; } .mb-lg-3, .my-lg-3 { margin-bottom: 1rem !important; } .ml-lg-3, .mx-lg-3 { margin-left: 1rem !important; } .m-lg-4 { margin: 1.5rem !important; } .mt-lg-4, .my-lg-4 { margin-top: 1.5rem !important; } .mr-lg-4, .mx-lg-4 { margin-right: 1.5rem !important; } .mb-lg-4, .my-lg-4 { margin-bottom: 1.5rem !important; } .ml-lg-4, .mx-lg-4 { margin-left: 1.5rem !important; } .m-lg-5 { margin: 3rem !important; } .mt-lg-5, .my-lg-5 { margin-top: 3rem !important; } .mr-lg-5, .mx-lg-5 { margin-right: 3rem !important; } .mb-lg-5, .my-lg-5 { margin-bottom: 3rem !important; } .ml-lg-5, .mx-lg-5 { margin-left: 3rem !important; } .p-lg-0 { padding: 0 !important; } .pt-lg-0, .py-lg-0 { padding-top: 0 !important; } .pr-lg-0, .px-lg-0 { padding-right: 0 !important; } .pb-lg-0, .py-lg-0 { padding-bottom: 0 !important; } .pl-lg-0, .px-lg-0 { padding-left: 0 !important; } .p-lg-1 { padding: 0.25rem !important; } .pt-lg-1, .py-lg-1 { padding-top: 0.25rem !important; } .pr-lg-1, .px-lg-1 { padding-right: 0.25rem !important; } .pb-lg-1, .py-lg-1 { padding-bottom: 0.25rem !important; } .pl-lg-1, .px-lg-1 { padding-left: 0.25rem !important; } .p-lg-2 { padding: 0.5rem !important; } .pt-lg-2, .py-lg-2 { padding-top: 0.5rem !important; } .pr-lg-2, .px-lg-2 { padding-right: 0.5rem !important; } .pb-lg-2, .py-lg-2 { padding-bottom: 0.5rem !important; } .pl-lg-2, .px-lg-2 { padding-left: 0.5rem !important; } .p-lg-3 { padding: 1rem !important; } .pt-lg-3, .py-lg-3 { padding-top: 1rem !important; } .pr-lg-3, .px-lg-3 { padding-right: 1rem !important; } .pb-lg-3, .py-lg-3 { padding-bottom: 1rem !important; } .pl-lg-3, .px-lg-3 { padding-left: 1rem !important; } .p-lg-4 { padding: 1.5rem !important; } .pt-lg-4, .py-lg-4 { padding-top: 1.5rem !important; } .pr-lg-4, .px-lg-4 { padding-right: 1.5rem !important; } .pb-lg-4, .py-lg-4 { padding-bottom: 1.5rem !important; } .pl-lg-4, .px-lg-4 { padding-left: 1.5rem !important; } .p-lg-5 { padding: 3rem !important; } .pt-lg-5, .py-lg-5 { padding-top: 3rem !important; } .pr-lg-5, .px-lg-5 { padding-right: 3rem !important; } .pb-lg-5, .py-lg-5 { padding-bottom: 3rem !important; } .pl-lg-5, .px-lg-5 { padding-left: 3rem !important; } .m-lg-n1 { margin: -0.25rem !important; } .mt-lg-n1, .my-lg-n1 { margin-top: -0.25rem !important; } .mr-lg-n1, .mx-lg-n1 { margin-right: -0.25rem !important; } .mb-lg-n1, .my-lg-n1 { margin-bottom: -0.25rem !important; } .ml-lg-n1, .mx-lg-n1 { margin-left: -0.25rem !important; } .m-lg-n2 { margin: -0.5rem !important; } .mt-lg-n2, .my-lg-n2 { margin-top: -0.5rem !important; } .mr-lg-n2, .mx-lg-n2 { margin-right: -0.5rem !important; } .mb-lg-n2, .my-lg-n2 { margin-bottom: -0.5rem !important; } .ml-lg-n2, .mx-lg-n2 { margin-left: -0.5rem !important; } .m-lg-n3 { margin: -1rem !important; } .mt-lg-n3, .my-lg-n3 { margin-top: -1rem !important; } .mr-lg-n3, .mx-lg-n3 { margin-right: -1rem !important; } .mb-lg-n3, .my-lg-n3 { margin-bottom: -1rem !important; } .ml-lg-n3, .mx-lg-n3 { margin-left: -1rem !important; } .m-lg-n4 { margin: -1.5rem !important; } .mt-lg-n4, .my-lg-n4 { margin-top: -1.5rem !important; } .mr-lg-n4, .mx-lg-n4 { margin-right: -1.5rem !important; } .mb-lg-n4, .my-lg-n4 { margin-bottom: -1.5rem !important; } .ml-lg-n4, .mx-lg-n4 { margin-left: -1.5rem !important; } .m-lg-n5 { margin: -3rem !important; } .mt-lg-n5, .my-lg-n5 { margin-top: -3rem !important; } .mr-lg-n5, .mx-lg-n5 { margin-right: -3rem !important; } .mb-lg-n5, .my-lg-n5 { margin-bottom: -3rem !important; } .ml-lg-n5, .mx-lg-n5 { margin-left: -3rem !important; } .m-lg-auto { margin: auto !important; } .mt-lg-auto, .my-lg-auto { margin-top: auto !important; } .mr-lg-auto, .mx-lg-auto { margin-right: auto !important; } .mb-lg-auto, .my-lg-auto { margin-bottom: auto !important; } .ml-lg-auto, .mx-lg-auto { margin-left: auto !important; } } @media (min-width: 1200px) { .m-xl-0 { margin: 0 !important; } .mt-xl-0, .my-xl-0 { margin-top: 0 !important; } .mr-xl-0, .mx-xl-0 { margin-right: 0 !important; } .mb-xl-0, .my-xl-0 { margin-bottom: 0 !important; } .ml-xl-0, .mx-xl-0 { margin-left: 0 !important; } .m-xl-1 { margin: 0.25rem !important; } .mt-xl-1, .my-xl-1 { margin-top: 0.25rem !important; } .mr-xl-1, .mx-xl-1 { margin-right: 0.25rem !important; } .mb-xl-1, .my-xl-1 { margin-bottom: 0.25rem !important; } .ml-xl-1, .mx-xl-1 { margin-left: 0.25rem !important; } .m-xl-2 { margin: 0.5rem !important; } .mt-xl-2, .my-xl-2 { margin-top: 0.5rem !important; } .mr-xl-2, .mx-xl-2 { margin-right: 0.5rem !important; } .mb-xl-2, .my-xl-2 { margin-bottom: 0.5rem !important; } .ml-xl-2, .mx-xl-2 { margin-left: 0.5rem !important; } .m-xl-3 { margin: 1rem !important; } .mt-xl-3, .my-xl-3 { margin-top: 1rem !important; } .mr-xl-3, .mx-xl-3 { margin-right: 1rem !important; } .mb-xl-3, .my-xl-3 { margin-bottom: 1rem !important; } .ml-xl-3, .mx-xl-3 { margin-left: 1rem !important; } .m-xl-4 { margin: 1.5rem !important; } .mt-xl-4, .my-xl-4 { margin-top: 1.5rem !important; } .mr-xl-4, .mx-xl-4 { margin-right: 1.5rem !important; } .mb-xl-4, .my-xl-4 { margin-bottom: 1.5rem !important; } .ml-xl-4, .mx-xl-4 { margin-left: 1.5rem !important; } .m-xl-5 { margin: 3rem !important; } .mt-xl-5, .my-xl-5 { margin-top: 3rem !important; } .mr-xl-5, .mx-xl-5 { margin-right: 3rem !important; } .mb-xl-5, .my-xl-5 { margin-bottom: 3rem !important; } .ml-xl-5, .mx-xl-5 { margin-left: 3rem !important; } .p-xl-0 { padding: 0 !important; } .pt-xl-0, .py-xl-0 { padding-top: 0 !important; } .pr-xl-0, .px-xl-0 { padding-right: 0 !important; } .pb-xl-0, .py-xl-0 { padding-bottom: 0 !important; } .pl-xl-0, .px-xl-0 { padding-left: 0 !important; } .p-xl-1 { padding: 0.25rem !important; } .pt-xl-1, .py-xl-1 { padding-top: 0.25rem !important; } .pr-xl-1, .px-xl-1 { padding-right: 0.25rem !important; } .pb-xl-1, .py-xl-1 { padding-bottom: 0.25rem !important; } .pl-xl-1, .px-xl-1 { padding-left: 0.25rem !important; } .p-xl-2 { padding: 0.5rem !important; } .pt-xl-2, .py-xl-2 { padding-top: 0.5rem !important; } .pr-xl-2, .px-xl-2 { padding-right: 0.5rem !important; } .pb-xl-2, .py-xl-2 { padding-bottom: 0.5rem !important; } .pl-xl-2, .px-xl-2 { padding-left: 0.5rem !important; } .p-xl-3 { padding: 1rem !important; } .pt-xl-3, .py-xl-3 { padding-top: 1rem !important; } .pr-xl-3, .px-xl-3 { padding-right: 1rem !important; } .pb-xl-3, .py-xl-3 { padding-bottom: 1rem !important; } .pl-xl-3, .px-xl-3 { padding-left: 1rem !important; } .p-xl-4 { padding: 1.5rem !important; } .pt-xl-4, .py-xl-4 { padding-top: 1.5rem !important; } .pr-xl-4, .px-xl-4 { padding-right: 1.5rem !important; } .pb-xl-4, .py-xl-4 { padding-bottom: 1.5rem !important; } .pl-xl-4, .px-xl-4 { padding-left: 1.5rem !important; } .p-xl-5 { padding: 3rem !important; } .pt-xl-5, .py-xl-5 { padding-top: 3rem !important; } .pr-xl-5, .px-xl-5 { padding-right: 3rem !important; } .pb-xl-5, .py-xl-5 { padding-bottom: 3rem !important; } .pl-xl-5, .px-xl-5 { padding-left: 3rem !important; } .m-xl-n1 { margin: -0.25rem !important; } .mt-xl-n1, .my-xl-n1 { margin-top: -0.25rem !important; } .mr-xl-n1, .mx-xl-n1 { margin-right: -0.25rem !important; } .mb-xl-n1, .my-xl-n1 { margin-bottom: -0.25rem !important; } .ml-xl-n1, .mx-xl-n1 { margin-left: -0.25rem !important; } .m-xl-n2 { margin: -0.5rem !important; } .mt-xl-n2, .my-xl-n2 { margin-top: -0.5rem !important; } .mr-xl-n2, .mx-xl-n2 { margin-right: -0.5rem !important; } .mb-xl-n2, .my-xl-n2 { margin-bottom: -0.5rem !important; } .ml-xl-n2, .mx-xl-n2 { margin-left: -0.5rem !important; } .m-xl-n3 { margin: -1rem !important; } .mt-xl-n3, .my-xl-n3 { margin-top: -1rem !important; } .mr-xl-n3, .mx-xl-n3 { margin-right: -1rem !important; } .mb-xl-n3, .my-xl-n3 { margin-bottom: -1rem !important; } .ml-xl-n3, .mx-xl-n3 { margin-left: -1rem !important; } .m-xl-n4 { margin: -1.5rem !important; } .mt-xl-n4, .my-xl-n4 { margin-top: -1.5rem !important; } .mr-xl-n4, .mx-xl-n4 { margin-right: -1.5rem !important; } .mb-xl-n4, .my-xl-n4 { margin-bottom: -1.5rem !important; } .ml-xl-n4, .mx-xl-n4 { margin-left: -1.5rem !important; } .m-xl-n5 { margin: -3rem !important; } .mt-xl-n5, .my-xl-n5 { margin-top: -3rem !important; } .mr-xl-n5, .mx-xl-n5 { margin-right: -3rem !important; } .mb-xl-n5, .my-xl-n5 { margin-bottom: -3rem !important; } .ml-xl-n5, .mx-xl-n5 { margin-left: -3rem !important; } .m-xl-auto { margin: auto !important; } .mt-xl-auto, .my-xl-auto { margin-top: auto !important; } .mr-xl-auto, .mx-xl-auto { margin-right: auto !important; } .mb-xl-auto, .my-xl-auto { margin-bottom: auto !important; } .ml-xl-auto, .mx-xl-auto { margin-left: auto !important; } } /*# sourceMappingURL=bootstrap-grid.css.map */ ================================================ FILE: easyflow-flow-bpmn/static/plugins/bootstrap/css/bootstrap-reboot.css ================================================ /*! * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) * Copyright 2011-2019 The Bootstrap Authors * Copyright 2011-2019 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) */ *, *::before, *::after { box-sizing: border-box; } html { font-family: sans-serif; line-height: 1.15; -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { display: block; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; text-align: left; background-color: #fff; } [tabindex="-1"]:focus { outline: 0 !important; } hr { box-sizing: content-box; height: 0; overflow: visible; } h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5rem; } p { margin-top: 0; margin-bottom: 1rem; } abbr[title], abbr[data-original-title] { text-decoration: underline; -webkit-text-decoration: underline dotted; text-decoration: underline dotted; cursor: help; border-bottom: 0; -webkit-text-decoration-skip-ink: none; text-decoration-skip-ink: none; } address { margin-bottom: 1rem; font-style: normal; line-height: inherit; } ol, ul, dl { margin-top: 0; margin-bottom: 1rem; } ol ol, ul ul, ol ul, ul ol { margin-bottom: 0; } dt { font-weight: 700; } dd { margin-bottom: .5rem; margin-left: 0; } blockquote { margin: 0 0 1rem; } b, strong { font-weight: bolder; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sub { bottom: -.25em; } sup { top: -.5em; } a { color: #007bff; text-decoration: none; background-color: transparent; } a:hover { color: #0056b3; text-decoration: underline; } a:not([href]):not([tabindex]) { color: inherit; text-decoration: none; } a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { color: inherit; text-decoration: none; } a:not([href]):not([tabindex]):focus { outline: 0; } pre, code, kbd, samp { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 1em; } pre { margin-top: 0; margin-bottom: 1rem; overflow: auto; } figure { margin: 0 0 1rem; } img { vertical-align: middle; border-style: none; } svg { overflow: hidden; vertical-align: middle; } table { border-collapse: collapse; } caption { padding-top: 0.75rem; padding-bottom: 0.75rem; color: #6c757d; text-align: left; caption-side: bottom; } th { text-align: inherit; } label { display: inline-block; margin-bottom: 0.5rem; } button { border-radius: 0; } button:focus { outline: 1px dotted; outline: 5px auto -webkit-focus-ring-color; } input, button, select, optgroup, textarea { margin: 0; font-family: inherit; font-size: inherit; line-height: inherit; } button, input { overflow: visible; } button, select { text-transform: none; } select { word-wrap: normal; } button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } button:not(:disabled), [type="button"]:not(:disabled), [type="reset"]:not(:disabled), [type="submit"]:not(:disabled) { cursor: pointer; } button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { padding: 0; border-style: none; } input[type="radio"], input[type="checkbox"] { box-sizing: border-box; padding: 0; } input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"] { -webkit-appearance: listbox; } textarea { overflow: auto; resize: vertical; } fieldset { min-width: 0; padding: 0; margin: 0; border: 0; } legend { display: block; width: 100%; max-width: 100%; padding: 0; margin-bottom: .5rem; font-size: 1.5rem; line-height: inherit; color: inherit; white-space: normal; } progress { vertical-align: baseline; } [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } [type="search"] { outline-offset: -2px; -webkit-appearance: none; } [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-file-upload-button { font: inherit; -webkit-appearance: button; } output { display: inline-block; } summary { display: list-item; cursor: pointer; } template { display: none; } [hidden] { display: none !important; } /*# sourceMappingURL=bootstrap-reboot.css.map */ ================================================ FILE: easyflow-flow-bpmn/static/plugins/bootstrap/css/bootstrap.css ================================================ /*! * Bootstrap v4.3.1 (https://getbootstrap.com/) * Copyright 2011-2019 The Bootstrap Authors * Copyright 2011-2019 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ :root { --blue: #007bff; --indigo: #6610f2; --purple: #6f42c1; --pink: #e83e8c; --red: #dc3545; --orange: #fd7e14; --yellow: #ffc107; --green: #28a745; --teal: #20c997; --cyan: #17a2b8; --white: #fff; --gray: #6c757d; --gray-dark: #343a40; --primary: #007bff; --secondary: #6c757d; --success: #28a745; --info: #17a2b8; --warning: #ffc107; --danger: #dc3545; --light: #f8f9fa; --dark: #343a40; --breakpoint-xs: 0; --breakpoint-sm: 576px; --breakpoint-md: 768px; --breakpoint-lg: 992px; --breakpoint-xl: 1200px; --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } *, *::before, *::after { box-sizing: border-box; } html { font-family: sans-serif; line-height: 1.15; -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { display: block; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; text-align: left; background-color: #fff; } [tabindex="-1"]:focus { outline: 0 !important; } hr { box-sizing: content-box; height: 0; overflow: visible; } h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5rem; } p { margin-top: 0; margin-bottom: 1rem; } abbr[title], abbr[data-original-title] { text-decoration: underline; -webkit-text-decoration: underline dotted; text-decoration: underline dotted; cursor: help; border-bottom: 0; -webkit-text-decoration-skip-ink: none; text-decoration-skip-ink: none; } address { margin-bottom: 1rem; font-style: normal; line-height: inherit; } ol, ul, dl { margin-top: 0; margin-bottom: 1rem; } ol ol, ul ul, ol ul, ul ol { margin-bottom: 0; } dt { font-weight: 700; } dd { margin-bottom: .5rem; margin-left: 0; } blockquote { margin: 0 0 1rem; } b, strong { font-weight: bolder; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sub { bottom: -.25em; } sup { top: -.5em; } a { color: #007bff; text-decoration: none; background-color: transparent; } a:hover { color: #0056b3; text-decoration: underline; } a:not([href]):not([tabindex]) { color: inherit; text-decoration: none; } a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { color: inherit; text-decoration: none; } a:not([href]):not([tabindex]):focus { outline: 0; } pre, code, kbd, samp { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 1em; } pre { margin-top: 0; margin-bottom: 1rem; overflow: auto; } figure { margin: 0 0 1rem; } img { vertical-align: middle; border-style: none; } svg { overflow: hidden; vertical-align: middle; } table { border-collapse: collapse; } caption { padding-top: 0.75rem; padding-bottom: 0.75rem; color: #6c757d; text-align: left; caption-side: bottom; } th { text-align: inherit; } label { display: inline-block; margin-bottom: 0.5rem; } button { border-radius: 0; } button:focus { outline: 1px dotted; outline: 5px auto -webkit-focus-ring-color; } input, button, select, optgroup, textarea { margin: 0; font-family: inherit; font-size: inherit; line-height: inherit; } button, input { overflow: visible; } button, select { text-transform: none; } select { word-wrap: normal; } button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } button:not(:disabled), [type="button"]:not(:disabled), [type="reset"]:not(:disabled), [type="submit"]:not(:disabled) { cursor: pointer; } button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { padding: 0; border-style: none; } input[type="radio"], input[type="checkbox"] { box-sizing: border-box; padding: 0; } input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"] { -webkit-appearance: listbox; } textarea { overflow: auto; resize: vertical; } fieldset { min-width: 0; padding: 0; margin: 0; border: 0; } legend { display: block; width: 100%; max-width: 100%; padding: 0; margin-bottom: .5rem; font-size: 1.5rem; line-height: inherit; color: inherit; white-space: normal; } progress { vertical-align: baseline; } [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } [type="search"] { outline-offset: -2px; -webkit-appearance: none; } [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-file-upload-button { font: inherit; -webkit-appearance: button; } output { display: inline-block; } summary { display: list-item; cursor: pointer; } template { display: none; } [hidden] { display: none !important; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { margin-bottom: 0.5rem; font-weight: 500; line-height: 1.2; } h1, .h1 { font-size: 2.5rem; } h2, .h2 { font-size: 2rem; } h3, .h3 { font-size: 1.75rem; } h4, .h4 { font-size: 1.5rem; } h5, .h5 { font-size: 1.25rem; } h6, .h6 { font-size: 1rem; } .lead { font-size: 1.25rem; font-weight: 300; } .display-1 { font-size: 6rem; font-weight: 300; line-height: 1.2; } .display-2 { font-size: 5.5rem; font-weight: 300; line-height: 1.2; } .display-3 { font-size: 4.5rem; font-weight: 300; line-height: 1.2; } .display-4 { font-size: 3.5rem; font-weight: 300; line-height: 1.2; } hr { margin-top: 1rem; margin-bottom: 1rem; border: 0; border-top: 1px solid rgba(0, 0, 0, 0.1); } small, .small { font-size: 80%; font-weight: 400; } mark, .mark { padding: 0.2em; background-color: #fcf8e3; } .list-unstyled { padding-left: 0; list-style: none; } .list-inline { padding-left: 0; list-style: none; } .list-inline-item { display: inline-block; } .list-inline-item:not(:last-child) { margin-right: 0.5rem; } .initialism { font-size: 90%; text-transform: uppercase; } .blockquote { margin-bottom: 1rem; font-size: 1.25rem; } .blockquote-footer { display: block; font-size: 80%; color: #6c757d; } .blockquote-footer::before { content: "\2014\00A0"; } .img-fluid { max-width: 100%; height: auto; } .img-thumbnail { padding: 0.25rem; background-color: #fff; border: 1px solid #dee2e6; border-radius: 0.25rem; max-width: 100%; height: auto; } .figure { display: inline-block; } .figure-img { margin-bottom: 0.5rem; line-height: 1; } .figure-caption { font-size: 90%; color: #6c757d; } code { font-size: 87.5%; color: #e83e8c; word-break: break-word; } a > code { color: inherit; } kbd { padding: 0.2rem 0.4rem; font-size: 87.5%; color: #fff; background-color: #212529; border-radius: 0.2rem; } kbd kbd { padding: 0; font-size: 100%; font-weight: 700; } pre { display: block; font-size: 87.5%; color: #212529; } pre code { font-size: inherit; color: inherit; word-break: normal; } .pre-scrollable { max-height: 340px; overflow-y: scroll; } .container { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } @media (min-width: 576px) { .container { max-width: 540px; } } @media (min-width: 768px) { .container { max-width: 720px; } } @media (min-width: 992px) { .container { max-width: 960px; } } @media (min-width: 1200px) { .container { max-width: 1140px; } } .container-fluid { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } .row { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: -15px; margin-left: -15px; } .no-gutters { margin-right: 0; margin-left: 0; } .no-gutters > .col, .no-gutters > [class*="col-"] { padding-right: 0; padding-left: 0; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, .col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, .col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, .col-xl-auto { position: relative; width: 100%; padding-right: 15px; padding-left: 15px; } .col { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-first { -ms-flex-order: -1; order: -1; } .order-last { -ms-flex-order: 13; order: 13; } .order-0 { -ms-flex-order: 0; order: 0; } .order-1 { -ms-flex-order: 1; order: 1; } .order-2 { -ms-flex-order: 2; order: 2; } .order-3 { -ms-flex-order: 3; order: 3; } .order-4 { -ms-flex-order: 4; order: 4; } .order-5 { -ms-flex-order: 5; order: 5; } .order-6 { -ms-flex-order: 6; order: 6; } .order-7 { -ms-flex-order: 7; order: 7; } .order-8 { -ms-flex-order: 8; order: 8; } .order-9 { -ms-flex-order: 9; order: 9; } .order-10 { -ms-flex-order: 10; order: 10; } .order-11 { -ms-flex-order: 11; order: 11; } .order-12 { -ms-flex-order: 12; order: 12; } .offset-1 { margin-left: 8.333333%; } .offset-2 { margin-left: 16.666667%; } .offset-3 { margin-left: 25%; } .offset-4 { margin-left: 33.333333%; } .offset-5 { margin-left: 41.666667%; } .offset-6 { margin-left: 50%; } .offset-7 { margin-left: 58.333333%; } .offset-8 { margin-left: 66.666667%; } .offset-9 { margin-left: 75%; } .offset-10 { margin-left: 83.333333%; } .offset-11 { margin-left: 91.666667%; } @media (min-width: 576px) { .col-sm { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-sm-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-sm-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-sm-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-sm-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-sm-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-sm-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-sm-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-sm-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-sm-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-sm-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-sm-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-sm-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-sm-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-sm-first { -ms-flex-order: -1; order: -1; } .order-sm-last { -ms-flex-order: 13; order: 13; } .order-sm-0 { -ms-flex-order: 0; order: 0; } .order-sm-1 { -ms-flex-order: 1; order: 1; } .order-sm-2 { -ms-flex-order: 2; order: 2; } .order-sm-3 { -ms-flex-order: 3; order: 3; } .order-sm-4 { -ms-flex-order: 4; order: 4; } .order-sm-5 { -ms-flex-order: 5; order: 5; } .order-sm-6 { -ms-flex-order: 6; order: 6; } .order-sm-7 { -ms-flex-order: 7; order: 7; } .order-sm-8 { -ms-flex-order: 8; order: 8; } .order-sm-9 { -ms-flex-order: 9; order: 9; } .order-sm-10 { -ms-flex-order: 10; order: 10; } .order-sm-11 { -ms-flex-order: 11; order: 11; } .order-sm-12 { -ms-flex-order: 12; order: 12; } .offset-sm-0 { margin-left: 0; } .offset-sm-1 { margin-left: 8.333333%; } .offset-sm-2 { margin-left: 16.666667%; } .offset-sm-3 { margin-left: 25%; } .offset-sm-4 { margin-left: 33.333333%; } .offset-sm-5 { margin-left: 41.666667%; } .offset-sm-6 { margin-left: 50%; } .offset-sm-7 { margin-left: 58.333333%; } .offset-sm-8 { margin-left: 66.666667%; } .offset-sm-9 { margin-left: 75%; } .offset-sm-10 { margin-left: 83.333333%; } .offset-sm-11 { margin-left: 91.666667%; } } @media (min-width: 768px) { .col-md { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-md-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-md-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-md-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-md-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-md-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-md-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-md-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-md-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-md-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-md-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-md-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-md-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-md-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-md-first { -ms-flex-order: -1; order: -1; } .order-md-last { -ms-flex-order: 13; order: 13; } .order-md-0 { -ms-flex-order: 0; order: 0; } .order-md-1 { -ms-flex-order: 1; order: 1; } .order-md-2 { -ms-flex-order: 2; order: 2; } .order-md-3 { -ms-flex-order: 3; order: 3; } .order-md-4 { -ms-flex-order: 4; order: 4; } .order-md-5 { -ms-flex-order: 5; order: 5; } .order-md-6 { -ms-flex-order: 6; order: 6; } .order-md-7 { -ms-flex-order: 7; order: 7; } .order-md-8 { -ms-flex-order: 8; order: 8; } .order-md-9 { -ms-flex-order: 9; order: 9; } .order-md-10 { -ms-flex-order: 10; order: 10; } .order-md-11 { -ms-flex-order: 11; order: 11; } .order-md-12 { -ms-flex-order: 12; order: 12; } .offset-md-0 { margin-left: 0; } .offset-md-1 { margin-left: 8.333333%; } .offset-md-2 { margin-left: 16.666667%; } .offset-md-3 { margin-left: 25%; } .offset-md-4 { margin-left: 33.333333%; } .offset-md-5 { margin-left: 41.666667%; } .offset-md-6 { margin-left: 50%; } .offset-md-7 { margin-left: 58.333333%; } .offset-md-8 { margin-left: 66.666667%; } .offset-md-9 { margin-left: 75%; } .offset-md-10 { margin-left: 83.333333%; } .offset-md-11 { margin-left: 91.666667%; } } @media (min-width: 992px) { .col-lg { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-lg-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-lg-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-lg-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-lg-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-lg-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-lg-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-lg-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-lg-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-lg-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-lg-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-lg-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-lg-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-lg-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-lg-first { -ms-flex-order: -1; order: -1; } .order-lg-last { -ms-flex-order: 13; order: 13; } .order-lg-0 { -ms-flex-order: 0; order: 0; } .order-lg-1 { -ms-flex-order: 1; order: 1; } .order-lg-2 { -ms-flex-order: 2; order: 2; } .order-lg-3 { -ms-flex-order: 3; order: 3; } .order-lg-4 { -ms-flex-order: 4; order: 4; } .order-lg-5 { -ms-flex-order: 5; order: 5; } .order-lg-6 { -ms-flex-order: 6; order: 6; } .order-lg-7 { -ms-flex-order: 7; order: 7; } .order-lg-8 { -ms-flex-order: 8; order: 8; } .order-lg-9 { -ms-flex-order: 9; order: 9; } .order-lg-10 { -ms-flex-order: 10; order: 10; } .order-lg-11 { -ms-flex-order: 11; order: 11; } .order-lg-12 { -ms-flex-order: 12; order: 12; } .offset-lg-0 { margin-left: 0; } .offset-lg-1 { margin-left: 8.333333%; } .offset-lg-2 { margin-left: 16.666667%; } .offset-lg-3 { margin-left: 25%; } .offset-lg-4 { margin-left: 33.333333%; } .offset-lg-5 { margin-left: 41.666667%; } .offset-lg-6 { margin-left: 50%; } .offset-lg-7 { margin-left: 58.333333%; } .offset-lg-8 { margin-left: 66.666667%; } .offset-lg-9 { margin-left: 75%; } .offset-lg-10 { margin-left: 83.333333%; } .offset-lg-11 { margin-left: 91.666667%; } } @media (min-width: 1200px) { .col-xl { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; max-width: 100%; } .col-xl-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-xl-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-xl-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-xl-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-xl-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-xl-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-xl-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-xl-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-xl-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-xl-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-xl-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-xl-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-xl-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-xl-first { -ms-flex-order: -1; order: -1; } .order-xl-last { -ms-flex-order: 13; order: 13; } .order-xl-0 { -ms-flex-order: 0; order: 0; } .order-xl-1 { -ms-flex-order: 1; order: 1; } .order-xl-2 { -ms-flex-order: 2; order: 2; } .order-xl-3 { -ms-flex-order: 3; order: 3; } .order-xl-4 { -ms-flex-order: 4; order: 4; } .order-xl-5 { -ms-flex-order: 5; order: 5; } .order-xl-6 { -ms-flex-order: 6; order: 6; } .order-xl-7 { -ms-flex-order: 7; order: 7; } .order-xl-8 { -ms-flex-order: 8; order: 8; } .order-xl-9 { -ms-flex-order: 9; order: 9; } .order-xl-10 { -ms-flex-order: 10; order: 10; } .order-xl-11 { -ms-flex-order: 11; order: 11; } .order-xl-12 { -ms-flex-order: 12; order: 12; } .offset-xl-0 { margin-left: 0; } .offset-xl-1 { margin-left: 8.333333%; } .offset-xl-2 { margin-left: 16.666667%; } .offset-xl-3 { margin-left: 25%; } .offset-xl-4 { margin-left: 33.333333%; } .offset-xl-5 { margin-left: 41.666667%; } .offset-xl-6 { margin-left: 50%; } .offset-xl-7 { margin-left: 58.333333%; } .offset-xl-8 { margin-left: 66.666667%; } .offset-xl-9 { margin-left: 75%; } .offset-xl-10 { margin-left: 83.333333%; } .offset-xl-11 { margin-left: 91.666667%; } } .table { width: 100%; margin-bottom: 1rem; color: #212529; } .table th, .table td { padding: 0.75rem; vertical-align: top; border-top: 1px solid #dee2e6; } .table thead th { vertical-align: bottom; border-bottom: 2px solid #dee2e6; } .table tbody + tbody { border-top: 2px solid #dee2e6; } .table-sm th, .table-sm td { padding: 0.3rem; } .table-bordered { border: 1px solid #dee2e6; } .table-bordered th, .table-bordered td { border: 1px solid #dee2e6; } .table-bordered thead th, .table-bordered thead td { border-bottom-width: 2px; } .table-borderless th, .table-borderless td, .table-borderless thead th, .table-borderless tbody + tbody { border: 0; } .table-striped tbody tr:nth-of-type(odd) { background-color: rgba(0, 0, 0, 0.05); } .table-hover tbody tr:hover { color: #212529; background-color: rgba(0, 0, 0, 0.075); } .table-primary, .table-primary > th, .table-primary > td { background-color: #b8daff; } .table-primary th, .table-primary td, .table-primary thead th, .table-primary tbody + tbody { border-color: #7abaff; } .table-hover .table-primary:hover { background-color: #9fcdff; } .table-hover .table-primary:hover > td, .table-hover .table-primary:hover > th { background-color: #9fcdff; } .table-secondary, .table-secondary > th, .table-secondary > td { background-color: #d6d8db; } .table-secondary th, .table-secondary td, .table-secondary thead th, .table-secondary tbody + tbody { border-color: #b3b7bb; } .table-hover .table-secondary:hover { background-color: #c8cbcf; } .table-hover .table-secondary:hover > td, .table-hover .table-secondary:hover > th { background-color: #c8cbcf; } .table-success, .table-success > th, .table-success > td { background-color: #c3e6cb; } .table-success th, .table-success td, .table-success thead th, .table-success tbody + tbody { border-color: #8fd19e; } .table-hover .table-success:hover { background-color: #b1dfbb; } .table-hover .table-success:hover > td, .table-hover .table-success:hover > th { background-color: #b1dfbb; } .table-info, .table-info > th, .table-info > td { background-color: #bee5eb; } .table-info th, .table-info td, .table-info thead th, .table-info tbody + tbody { border-color: #86cfda; } .table-hover .table-info:hover { background-color: #abdde5; } .table-hover .table-info:hover > td, .table-hover .table-info:hover > th { background-color: #abdde5; } .table-warning, .table-warning > th, .table-warning > td { background-color: #ffeeba; } .table-warning th, .table-warning td, .table-warning thead th, .table-warning tbody + tbody { border-color: #ffdf7e; } .table-hover .table-warning:hover { background-color: #ffe8a1; } .table-hover .table-warning:hover > td, .table-hover .table-warning:hover > th { background-color: #ffe8a1; } .table-danger, .table-danger > th, .table-danger > td { background-color: #f5c6cb; } .table-danger th, .table-danger td, .table-danger thead th, .table-danger tbody + tbody { border-color: #ed969e; } .table-hover .table-danger:hover { background-color: #f1b0b7; } .table-hover .table-danger:hover > td, .table-hover .table-danger:hover > th { background-color: #f1b0b7; } .table-light, .table-light > th, .table-light > td { background-color: #fdfdfe; } .table-light th, .table-light td, .table-light thead th, .table-light tbody + tbody { border-color: #fbfcfc; } .table-hover .table-light:hover { background-color: #ececf6; } .table-hover .table-light:hover > td, .table-hover .table-light:hover > th { background-color: #ececf6; } .table-dark, .table-dark > th, .table-dark > td { background-color: #c6c8ca; } .table-dark th, .table-dark td, .table-dark thead th, .table-dark tbody + tbody { border-color: #95999c; } .table-hover .table-dark:hover { background-color: #b9bbbe; } .table-hover .table-dark:hover > td, .table-hover .table-dark:hover > th { background-color: #b9bbbe; } .table-active, .table-active > th, .table-active > td { background-color: rgba(0, 0, 0, 0.075); } .table-hover .table-active:hover { background-color: rgba(0, 0, 0, 0.075); } .table-hover .table-active:hover > td, .table-hover .table-active:hover > th { background-color: rgba(0, 0, 0, 0.075); } .table .thead-dark th { color: #fff; background-color: #343a40; border-color: #454d55; } .table .thead-light th { color: #495057; background-color: #e9ecef; border-color: #dee2e6; } .table-dark { color: #fff; background-color: #343a40; } .table-dark th, .table-dark td, .table-dark thead th { border-color: #454d55; } .table-dark.table-bordered { border: 0; } .table-dark.table-striped tbody tr:nth-of-type(odd) { background-color: rgba(255, 255, 255, 0.05); } .table-dark.table-hover tbody tr:hover { color: #fff; background-color: rgba(255, 255, 255, 0.075); } @media (max-width: 575.98px) { .table-responsive-sm { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-sm > .table-bordered { border: 0; } } @media (max-width: 767.98px) { .table-responsive-md { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-md > .table-bordered { border: 0; } } @media (max-width: 991.98px) { .table-responsive-lg { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-lg > .table-bordered { border: 0; } } @media (max-width: 1199.98px) { .table-responsive-xl { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-xl > .table-bordered { border: 0; } } .table-responsive { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive > .table-bordered { border: 0; } .form-control { display: block; width: 100%; height: calc(1.5em + 0.75rem + 2px); padding: 0.375rem 0.75rem; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #495057; background-color: #fff; background-clip: padding-box; border: 1px solid #ced4da; border-radius: 0.25rem; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .form-control { transition: none; } } .form-control::-ms-expand { background-color: transparent; border: 0; } .form-control:focus { color: #495057; background-color: #fff; border-color: #80bdff; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .form-control::-webkit-input-placeholder { color: #6c757d; opacity: 1; } .form-control::-moz-placeholder { color: #6c757d; opacity: 1; } .form-control:-ms-input-placeholder { color: #6c757d; opacity: 1; } .form-control::-ms-input-placeholder { color: #6c757d; opacity: 1; } .form-control::placeholder { color: #6c757d; opacity: 1; } .form-control:disabled, .form-control[readonly] { background-color: #e9ecef; opacity: 1; } select.form-control:focus::-ms-value { color: #495057; background-color: #fff; } .form-control-file, .form-control-range { display: block; width: 100%; } .col-form-label { padding-top: calc(0.375rem + 1px); padding-bottom: calc(0.375rem + 1px); margin-bottom: 0; font-size: inherit; line-height: 1.5; } .col-form-label-lg { padding-top: calc(0.5rem + 1px); padding-bottom: calc(0.5rem + 1px); font-size: 1.25rem; line-height: 1.5; } .col-form-label-sm { padding-top: calc(0.25rem + 1px); padding-bottom: calc(0.25rem + 1px); font-size: 0.875rem; line-height: 1.5; } .form-control-plaintext { display: block; width: 100%; padding-top: 0.375rem; padding-bottom: 0.375rem; margin-bottom: 0; line-height: 1.5; color: #212529; background-color: transparent; border: solid transparent; border-width: 1px 0; } .form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { padding-right: 0; padding-left: 0; } .form-control-sm { height: calc(1.5em + 0.5rem + 2px); padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; border-radius: 0.2rem; } .form-control-lg { height: calc(1.5em + 1rem + 2px); padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0.3rem; } select.form-control[size], select.form-control[multiple] { height: auto; } textarea.form-control { height: auto; } .form-group { margin-bottom: 1rem; } .form-text { display: block; margin-top: 0.25rem; } .form-row { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: -5px; margin-left: -5px; } .form-row > .col, .form-row > [class*="col-"] { padding-right: 5px; padding-left: 5px; } .form-check { position: relative; display: block; padding-left: 1.25rem; } .form-check-input { position: absolute; margin-top: 0.3rem; margin-left: -1.25rem; } .form-check-input:disabled ~ .form-check-label { color: #6c757d; } .form-check-label { margin-bottom: 0; } .form-check-inline { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-align: center; align-items: center; padding-left: 0; margin-right: 0.75rem; } .form-check-inline .form-check-input { position: static; margin-top: 0; margin-right: 0.3125rem; margin-left: 0; } .valid-feedback { display: none; width: 100%; margin-top: 0.25rem; font-size: 80%; color: #28a745; } .valid-tooltip { position: absolute; top: 100%; z-index: 5; display: none; max-width: 100%; padding: 0.25rem 0.5rem; margin-top: .1rem; font-size: 0.875rem; line-height: 1.5; color: #fff; background-color: rgba(40, 167, 69, 0.9); border-radius: 0.25rem; } .was-validated .form-control:valid, .form-control.is-valid { border-color: #28a745; padding-right: calc(1.5em + 0.75rem); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: center right calc(0.375em + 0.1875rem); background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .form-control:valid:focus, .form-control.is-valid:focus { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .was-validated .form-control:valid ~ .valid-feedback, .was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, .form-control.is-valid ~ .valid-tooltip { display: block; } .was-validated textarea.form-control:valid, textarea.form-control.is-valid { padding-right: calc(1.5em + 0.75rem); background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); } .was-validated .custom-select:valid, .custom-select.is-valid { border-color: #28a745; padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem); background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .was-validated .custom-select:valid ~ .valid-feedback, .was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback, .custom-select.is-valid ~ .valid-tooltip { display: block; } .was-validated .form-control-file:valid ~ .valid-feedback, .was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback, .form-control-file.is-valid ~ .valid-tooltip { display: block; } .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { color: #28a745; } .was-validated .form-check-input:valid ~ .valid-feedback, .was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, .form-check-input.is-valid ~ .valid-tooltip { display: block; } .was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { color: #28a745; } .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { border-color: #28a745; } .was-validated .custom-control-input:valid ~ .valid-feedback, .was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, .custom-control-input.is-valid ~ .valid-tooltip { display: block; } .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { border-color: #34ce57; background-color: #34ce57; } .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before { border-color: #28a745; } .was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { border-color: #28a745; } .was-validated .custom-file-input:valid ~ .valid-feedback, .was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, .custom-file-input.is-valid ~ .valid-tooltip { display: block; } .was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .invalid-feedback { display: none; width: 100%; margin-top: 0.25rem; font-size: 80%; color: #dc3545; } .invalid-tooltip { position: absolute; top: 100%; z-index: 5; display: none; max-width: 100%; padding: 0.25rem 0.5rem; margin-top: .1rem; font-size: 0.875rem; line-height: 1.5; color: #fff; background-color: rgba(220, 53, 69, 0.9); border-radius: 0.25rem; } .was-validated .form-control:invalid, .form-control.is-invalid { border-color: #dc3545; padding-right: calc(1.5em + 0.75rem); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E"); background-repeat: no-repeat; background-position: center right calc(0.375em + 0.1875rem); background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .was-validated .form-control:invalid ~ .invalid-feedback, .was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, .form-control.is-invalid ~ .invalid-tooltip { display: block; } .was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { padding-right: calc(1.5em + 0.75rem); background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); } .was-validated .custom-select:invalid, .custom-select.is-invalid { border-color: #dc3545; padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem); background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .was-validated .custom-select:invalid ~ .invalid-feedback, .was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback, .custom-select.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .form-control-file:invalid ~ .invalid-feedback, .was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback, .form-control-file.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { color: #dc3545; } .was-validated .form-check-input:invalid ~ .invalid-feedback, .was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, .form-check-input.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { color: #dc3545; } .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { border-color: #dc3545; } .was-validated .custom-control-input:invalid ~ .invalid-feedback, .was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, .custom-control-input.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { border-color: #e4606d; background-color: #e4606d; } .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before { border-color: #dc3545; } .was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { border-color: #dc3545; } .was-validated .custom-file-input:invalid ~ .invalid-feedback, .was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, .custom-file-input.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .form-inline { display: -ms-flexbox; display: flex; -ms-flex-flow: row wrap; flex-flow: row wrap; -ms-flex-align: center; align-items: center; } .form-inline .form-check { width: 100%; } @media (min-width: 576px) { .form-inline label { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; margin-bottom: 0; } .form-inline .form-group { display: -ms-flexbox; display: flex; -ms-flex: 0 0 auto; flex: 0 0 auto; -ms-flex-flow: row wrap; flex-flow: row wrap; -ms-flex-align: center; align-items: center; margin-bottom: 0; } .form-inline .form-control { display: inline-block; width: auto; vertical-align: middle; } .form-inline .form-control-plaintext { display: inline-block; } .form-inline .input-group, .form-inline .custom-select { width: auto; } .form-inline .form-check { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; width: auto; padding-left: 0; } .form-inline .form-check-input { position: relative; -ms-flex-negative: 0; flex-shrink: 0; margin-top: 0; margin-right: 0.25rem; margin-left: 0; } .form-inline .custom-control { -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; } .form-inline .custom-control-label { margin-bottom: 0; } } .btn { display: inline-block; font-weight: 400; color: #212529; text-align: center; vertical-align: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: transparent; border: 1px solid transparent; padding: 0.375rem 0.75rem; font-size: 1rem; line-height: 1.5; border-radius: 0.25rem; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .btn { transition: none; } } .btn:hover { color: #212529; text-decoration: none; } .btn:focus, .btn.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .btn.disabled, .btn:disabled { opacity: 0.65; } a.btn.disabled, fieldset:disabled a.btn { pointer-events: none; } .btn-primary { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-primary:hover { color: #fff; background-color: #0069d9; border-color: #0062cc; } .btn-primary:focus, .btn-primary.focus { box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); } .btn-primary.disabled, .btn-primary:disabled { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { color: #fff; background-color: #0062cc; border-color: #005cbf; } .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); } .btn-secondary { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-secondary:hover { color: #fff; background-color: #5a6268; border-color: #545b62; } .btn-secondary:focus, .btn-secondary.focus { box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } .btn-secondary.disabled, .btn-secondary:disabled { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show > .btn-secondary.dropdown-toggle { color: #fff; background-color: #545b62; border-color: #4e555b; } .btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-secondary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } .btn-success { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-success:hover { color: #fff; background-color: #218838; border-color: #1e7e34; } .btn-success:focus, .btn-success.focus { box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); } .btn-success.disabled, .btn-success:disabled { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, .show > .btn-success.dropdown-toggle { color: #fff; background-color: #1e7e34; border-color: #1c7430; } .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, .show > .btn-success.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); } .btn-info { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-info:hover { color: #fff; background-color: #138496; border-color: #117a8b; } .btn-info:focus, .btn-info.focus { box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } .btn-info.disabled, .btn-info:disabled { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle { color: #fff; background-color: #117a8b; border-color: #10707f; } .btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, .show > .btn-info.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } .btn-warning { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-warning:hover { color: #212529; background-color: #e0a800; border-color: #d39e00; } .btn-warning:focus, .btn-warning.focus { box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); } .btn-warning.disabled, .btn-warning:disabled { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle { color: #212529; background-color: #d39e00; border-color: #c69500; } .btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-warning.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); } .btn-danger { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-danger:hover { color: #fff; background-color: #c82333; border-color: #bd2130; } .btn-danger:focus, .btn-danger.focus { box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); } .btn-danger.disabled, .btn-danger:disabled { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, .show > .btn-danger.dropdown-toggle { color: #fff; background-color: #bd2130; border-color: #b21f2d; } .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-danger.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); } .btn-light { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-light:hover { color: #212529; background-color: #e2e6ea; border-color: #dae0e5; } .btn-light:focus, .btn-light.focus { box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } .btn-light.disabled, .btn-light:disabled { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle { color: #212529; background-color: #dae0e5; border-color: #d3d9df; } .btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, .show > .btn-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } .btn-dark { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-dark:hover { color: #fff; background-color: #23272b; border-color: #1d2124; } .btn-dark:focus, .btn-dark.focus { box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } .btn-dark.disabled, .btn-dark:disabled { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, .show > .btn-dark.dropdown-toggle { color: #fff; background-color: #1d2124; border-color: #171a1d; } .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } .btn-outline-primary { color: #007bff; border-color: #007bff; } .btn-outline-primary:hover { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-outline-primary:focus, .btn-outline-primary.focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .btn-outline-primary.disabled, .btn-outline-primary:disabled { color: #007bff; background-color: transparent; } .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, .show > .btn-outline-primary.dropdown-toggle { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-primary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .btn-outline-secondary { color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:hover { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:focus, .btn-outline-secondary.focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { color: #6c757d; background-color: transparent; } .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, .show > .btn-outline-secondary.dropdown-toggle { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .btn-outline-success { color: #28a745; border-color: #28a745; } .btn-outline-success:hover { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-success:focus, .btn-outline-success.focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .btn-outline-success.disabled, .btn-outline-success:disabled { color: #28a745; background-color: transparent; } .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .btn-outline-info { color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:hover { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:focus, .btn-outline-info.focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .btn-outline-info.disabled, .btn-outline-info:disabled { color: #17a2b8; background-color: transparent; } .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-info.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .btn-outline-warning { color: #ffc107; border-color: #ffc107; } .btn-outline-warning:hover { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-outline-warning:focus, .btn-outline-warning.focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .btn-outline-warning.disabled, .btn-outline-warning:disabled { color: #ffc107; background-color: transparent; } .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, .show > .btn-outline-warning.dropdown-toggle { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-warning.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .btn-outline-danger { color: #dc3545; border-color: #dc3545; } .btn-outline-danger:hover { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-danger:focus, .btn-outline-danger.focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .btn-outline-danger.disabled, .btn-outline-danger:disabled { color: #dc3545; background-color: transparent; } .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, .show > .btn-outline-danger.dropdown-toggle { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-danger.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .btn-outline-light { color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:hover { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:focus, .btn-outline-light.focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .btn-outline-light.disabled, .btn-outline-light:disabled { color: #f8f9fa; background-color: transparent; } .btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, .show > .btn-outline-light.dropdown-toggle { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .btn-outline-dark { color: #343a40; border-color: #343a40; } .btn-outline-dark:hover { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-outline-dark:focus, .btn-outline-dark.focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } .btn-outline-dark.disabled, .btn-outline-dark:disabled { color: #343a40; background-color: transparent; } .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, .show > .btn-outline-dark.dropdown-toggle { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } .btn-link { font-weight: 400; color: #007bff; text-decoration: none; } .btn-link:hover { color: #0056b3; text-decoration: underline; } .btn-link:focus, .btn-link.focus { text-decoration: underline; box-shadow: none; } .btn-link:disabled, .btn-link.disabled { color: #6c757d; pointer-events: none; } .btn-lg, .btn-group-lg > .btn { padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0.3rem; } .btn-sm, .btn-group-sm > .btn { padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; border-radius: 0.2rem; } .btn-block { display: block; width: 100%; } .btn-block + .btn-block { margin-top: 0.5rem; } input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { width: 100%; } .fade { transition: opacity 0.15s linear; } @media (prefers-reduced-motion: reduce) { .fade { transition: none; } } .fade:not(.show) { opacity: 0; } .collapse:not(.show) { display: none; } .collapsing { position: relative; height: 0; overflow: hidden; transition: height 0.35s ease; } @media (prefers-reduced-motion: reduce) { .collapsing { transition: none; } } .dropup, .dropright, .dropdown, .dropleft { position: relative; } .dropdown-toggle { white-space: nowrap; } .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid; border-right: 0.3em solid transparent; border-bottom: 0; border-left: 0.3em solid transparent; } .dropdown-toggle:empty::after { margin-left: 0; } .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: 1000; display: none; float: left; min-width: 10rem; padding: 0.5rem 0; margin: 0.125rem 0 0; font-size: 1rem; color: #212529; text-align: left; list-style: none; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 0.25rem; } .dropdown-menu-left { right: auto; left: 0; } .dropdown-menu-right { right: 0; left: auto; } @media (min-width: 576px) { .dropdown-menu-sm-left { right: auto; left: 0; } .dropdown-menu-sm-right { right: 0; left: auto; } } @media (min-width: 768px) { .dropdown-menu-md-left { right: auto; left: 0; } .dropdown-menu-md-right { right: 0; left: auto; } } @media (min-width: 992px) { .dropdown-menu-lg-left { right: auto; left: 0; } .dropdown-menu-lg-right { right: 0; left: auto; } } @media (min-width: 1200px) { .dropdown-menu-xl-left { right: auto; left: 0; } .dropdown-menu-xl-right { right: 0; left: auto; } } .dropup .dropdown-menu { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 0.125rem; } .dropup .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0; border-right: 0.3em solid transparent; border-bottom: 0.3em solid; border-left: 0.3em solid transparent; } .dropup .dropdown-toggle:empty::after { margin-left: 0; } .dropright .dropdown-menu { top: 0; right: auto; left: 100%; margin-top: 0; margin-left: 0.125rem; } .dropright .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid transparent; border-right: 0; border-bottom: 0.3em solid transparent; border-left: 0.3em solid; } .dropright .dropdown-toggle:empty::after { margin-left: 0; } .dropright .dropdown-toggle::after { vertical-align: 0; } .dropleft .dropdown-menu { top: 0; right: 100%; left: auto; margin-top: 0; margin-right: 0.125rem; } .dropleft .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; } .dropleft .dropdown-toggle::after { display: none; } .dropleft .dropdown-toggle::before { display: inline-block; margin-right: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid transparent; border-right: 0.3em solid; border-bottom: 0.3em solid transparent; } .dropleft .dropdown-toggle:empty::after { margin-left: 0; } .dropleft .dropdown-toggle::before { vertical-align: 0; } .dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] { right: auto; bottom: auto; } .dropdown-divider { height: 0; margin: 0.5rem 0; overflow: hidden; border-top: 1px solid #e9ecef; } .dropdown-item { display: block; width: 100%; padding: 0.25rem 1.5rem; clear: both; font-weight: 400; color: #212529; text-align: inherit; white-space: nowrap; background-color: transparent; border: 0; } .dropdown-item:hover, .dropdown-item:focus { color: #16181b; text-decoration: none; background-color: #f8f9fa; } .dropdown-item.active, .dropdown-item:active { color: #fff; text-decoration: none; background-color: #007bff; } .dropdown-item.disabled, .dropdown-item:disabled { color: #6c757d; pointer-events: none; background-color: transparent; } .dropdown-menu.show { display: block; } .dropdown-header { display: block; padding: 0.5rem 1.5rem; margin-bottom: 0; font-size: 0.875rem; color: #6c757d; white-space: nowrap; } .dropdown-item-text { display: block; padding: 0.25rem 1.5rem; color: #212529; } .btn-group, .btn-group-vertical { position: relative; display: -ms-inline-flexbox; display: inline-flex; vertical-align: middle; } .btn-group > .btn, .btn-group-vertical > .btn { position: relative; -ms-flex: 1 1 auto; flex: 1 1 auto; } .btn-group > .btn:hover, .btn-group-vertical > .btn:hover { z-index: 1; } .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn.active { z-index: 1; } .btn-toolbar { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-pack: start; justify-content: flex-start; } .btn-toolbar .input-group { width: auto; } .btn-group > .btn:not(:first-child), .btn-group > .btn-group:not(:first-child) { margin-left: -1px; } .btn-group > .btn:not(:last-child):not(.dropdown-toggle), .btn-group > .btn-group:not(:last-child) > .btn { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group > .btn:not(:first-child), .btn-group > .btn-group:not(:first-child) > .btn { border-top-left-radius: 0; border-bottom-left-radius: 0; } .dropdown-toggle-split { padding-right: 0.5625rem; padding-left: 0.5625rem; } .dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropright .dropdown-toggle-split::after { margin-left: 0; } .dropleft .dropdown-toggle-split::before { margin-right: 0; } .btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { padding-right: 0.375rem; padding-left: 0.375rem; } .btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { padding-right: 0.75rem; padding-left: 0.75rem; } .btn-group-vertical { -ms-flex-direction: column; flex-direction: column; -ms-flex-align: start; align-items: flex-start; -ms-flex-pack: center; justify-content: center; } .btn-group-vertical > .btn, .btn-group-vertical > .btn-group { width: 100%; } .btn-group-vertical > .btn:not(:first-child), .btn-group-vertical > .btn-group:not(:first-child) { margin-top: -1px; } .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), .btn-group-vertical > .btn-group:not(:last-child) > .btn { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn:not(:first-child), .btn-group-vertical > .btn-group:not(:first-child) > .btn { border-top-left-radius: 0; border-top-right-radius: 0; } .btn-group-toggle > .btn, .btn-group-toggle > .btn-group > .btn { margin-bottom: 0; } .btn-group-toggle > .btn input[type="radio"], .btn-group-toggle > .btn input[type="checkbox"], .btn-group-toggle > .btn-group > .btn input[type="radio"], .btn-group-toggle > .btn-group > .btn input[type="checkbox"] { position: absolute; clip: rect(0, 0, 0, 0); pointer-events: none; } .input-group { position: relative; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: stretch; align-items: stretch; width: 100%; } .input-group > .form-control, .input-group > .form-control-plaintext, .input-group > .custom-select, .input-group > .custom-file { position: relative; -ms-flex: 1 1 auto; flex: 1 1 auto; width: 1%; margin-bottom: 0; } .input-group > .form-control + .form-control, .input-group > .form-control + .custom-select, .input-group > .form-control + .custom-file, .input-group > .form-control-plaintext + .form-control, .input-group > .form-control-plaintext + .custom-select, .input-group > .form-control-plaintext + .custom-file, .input-group > .custom-select + .form-control, .input-group > .custom-select + .custom-select, .input-group > .custom-select + .custom-file, .input-group > .custom-file + .form-control, .input-group > .custom-file + .custom-select, .input-group > .custom-file + .custom-file { margin-left: -1px; } .input-group > .form-control:focus, .input-group > .custom-select:focus, .input-group > .custom-file .custom-file-input:focus ~ .custom-file-label { z-index: 3; } .input-group > .custom-file .custom-file-input:focus { z-index: 4; } .input-group > .form-control:not(:last-child), .input-group > .custom-select:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .form-control:not(:first-child), .input-group > .custom-select:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .input-group > .custom-file { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; } .input-group > .custom-file:not(:last-child) .custom-file-label, .input-group > .custom-file:not(:last-child) .custom-file-label::after { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .custom-file:not(:first-child) .custom-file-label { border-top-left-radius: 0; border-bottom-left-radius: 0; } .input-group-prepend, .input-group-append { display: -ms-flexbox; display: flex; } .input-group-prepend .btn, .input-group-append .btn { position: relative; z-index: 2; } .input-group-prepend .btn:focus, .input-group-append .btn:focus { z-index: 3; } .input-group-prepend .btn + .btn, .input-group-prepend .btn + .input-group-text, .input-group-prepend .input-group-text + .input-group-text, .input-group-prepend .input-group-text + .btn, .input-group-append .btn + .btn, .input-group-append .btn + .input-group-text, .input-group-append .input-group-text + .input-group-text, .input-group-append .input-group-text + .btn { margin-left: -1px; } .input-group-prepend { margin-right: -1px; } .input-group-append { margin-left: -1px; } .input-group-text { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; padding: 0.375rem 0.75rem; margin-bottom: 0; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #495057; text-align: center; white-space: nowrap; background-color: #e9ecef; border: 1px solid #ced4da; border-radius: 0.25rem; } .input-group-text input[type="radio"], .input-group-text input[type="checkbox"] { margin-top: 0; } .input-group-lg > .form-control:not(textarea), .input-group-lg > .custom-select { height: calc(1.5em + 1rem + 2px); } .input-group-lg > .form-control, .input-group-lg > .custom-select, .input-group-lg > .input-group-prepend > .input-group-text, .input-group-lg > .input-group-append > .input-group-text, .input-group-lg > .input-group-prepend > .btn, .input-group-lg > .input-group-append > .btn { padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0.3rem; } .input-group-sm > .form-control:not(textarea), .input-group-sm > .custom-select { height: calc(1.5em + 0.5rem + 2px); } .input-group-sm > .form-control, .input-group-sm > .custom-select, .input-group-sm > .input-group-prepend > .input-group-text, .input-group-sm > .input-group-append > .input-group-text, .input-group-sm > .input-group-prepend > .btn, .input-group-sm > .input-group-append > .btn { padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; border-radius: 0.2rem; } .input-group-lg > .custom-select, .input-group-sm > .custom-select { padding-right: 1.75rem; } .input-group > .input-group-prepend > .btn, .input-group > .input-group-prepend > .input-group-text, .input-group > .input-group-append:not(:last-child) > .btn, .input-group > .input-group-append:not(:last-child) > .input-group-text, .input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .input-group-append > .btn, .input-group > .input-group-append > .input-group-text, .input-group > .input-group-prepend:not(:first-child) > .btn, .input-group > .input-group-prepend:not(:first-child) > .input-group-text, .input-group > .input-group-prepend:first-child > .btn:not(:first-child), .input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .custom-control { position: relative; display: block; min-height: 1.5rem; padding-left: 1.5rem; } .custom-control-inline { display: -ms-inline-flexbox; display: inline-flex; margin-right: 1rem; } .custom-control-input { position: absolute; z-index: -1; opacity: 0; } .custom-control-input:checked ~ .custom-control-label::before { color: #fff; border-color: #007bff; background-color: #007bff; } .custom-control-input:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-control-input:focus:not(:checked) ~ .custom-control-label::before { border-color: #80bdff; } .custom-control-input:not(:disabled):active ~ .custom-control-label::before { color: #fff; background-color: #b3d7ff; border-color: #b3d7ff; } .custom-control-input:disabled ~ .custom-control-label { color: #6c757d; } .custom-control-input:disabled ~ .custom-control-label::before { background-color: #e9ecef; } .custom-control-label { position: relative; margin-bottom: 0; vertical-align: top; } .custom-control-label::before { position: absolute; top: 0.25rem; left: -1.5rem; display: block; width: 1rem; height: 1rem; pointer-events: none; content: ""; background-color: #fff; border: #adb5bd solid 1px; } .custom-control-label::after { position: absolute; top: 0.25rem; left: -1.5rem; display: block; width: 1rem; height: 1rem; content: ""; background: no-repeat 50% / 50% 50%; } .custom-checkbox .custom-control-label::before { border-radius: 0.25rem; } .custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e"); } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { border-color: #007bff; background-color: #007bff; } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); } .custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-radio .custom-control-label::before { border-radius: 50%; } .custom-radio .custom-control-input:checked ~ .custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); } .custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-switch { padding-left: 2.25rem; } .custom-switch .custom-control-label::before { left: -2.25rem; width: 1.75rem; pointer-events: all; border-radius: 0.5rem; } .custom-switch .custom-control-label::after { top: calc(0.25rem + 2px); left: calc(-2.25rem + 2px); width: calc(1rem - 4px); height: calc(1rem - 4px); background-color: #adb5bd; border-radius: 0.5rem; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .custom-switch .custom-control-label::after { transition: none; } } .custom-switch .custom-control-input:checked ~ .custom-control-label::after { background-color: #fff; -webkit-transform: translateX(0.75rem); transform: translateX(0.75rem); } .custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-select { display: inline-block; width: 100%; height: calc(1.5em + 0.75rem + 2px); padding: 0.375rem 1.75rem 0.375rem 0.75rem; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #495057; vertical-align: middle; background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; background-color: #fff; border: 1px solid #ced4da; border-radius: 0.25rem; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .custom-select:focus { border-color: #80bdff; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-select:focus::-ms-value { color: #495057; background-color: #fff; } .custom-select[multiple], .custom-select[size]:not([size="1"]) { height: auto; padding-right: 0.75rem; background-image: none; } .custom-select:disabled { color: #6c757d; background-color: #e9ecef; } .custom-select::-ms-expand { display: none; } .custom-select-sm { height: calc(1.5em + 0.5rem + 2px); padding-top: 0.25rem; padding-bottom: 0.25rem; padding-left: 0.5rem; font-size: 0.875rem; } .custom-select-lg { height: calc(1.5em + 1rem + 2px); padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; font-size: 1.25rem; } .custom-file { position: relative; display: inline-block; width: 100%; height: calc(1.5em + 0.75rem + 2px); margin-bottom: 0; } .custom-file-input { position: relative; z-index: 2; width: 100%; height: calc(1.5em + 0.75rem + 2px); margin: 0; opacity: 0; } .custom-file-input:focus ~ .custom-file-label { border-color: #80bdff; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-file-input:disabled ~ .custom-file-label { background-color: #e9ecef; } .custom-file-input:lang(en) ~ .custom-file-label::after { content: "Browse"; } .custom-file-input ~ .custom-file-label[data-browse]::after { content: attr(data-browse); } .custom-file-label { position: absolute; top: 0; right: 0; left: 0; z-index: 1; height: calc(1.5em + 0.75rem + 2px); padding: 0.375rem 0.75rem; font-weight: 400; line-height: 1.5; color: #495057; background-color: #fff; border: 1px solid #ced4da; border-radius: 0.25rem; } .custom-file-label::after { position: absolute; top: 0; right: 0; bottom: 0; z-index: 3; display: block; height: calc(1.5em + 0.75rem); padding: 0.375rem 0.75rem; line-height: 1.5; color: #495057; content: "Browse"; background-color: #e9ecef; border-left: inherit; border-radius: 0 0.25rem 0.25rem 0; } .custom-range { width: 100%; height: calc(1rem + 0.4rem); padding: 0; background-color: transparent; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .custom-range:focus { outline: none; } .custom-range:focus::-webkit-slider-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range:focus::-moz-range-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range:focus::-ms-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range::-moz-focus-outer { border: 0; } .custom-range::-webkit-slider-thumb { width: 1rem; height: 1rem; margin-top: -0.25rem; background-color: #007bff; border: 0; border-radius: 1rem; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -webkit-appearance: none; appearance: none; } @media (prefers-reduced-motion: reduce) { .custom-range::-webkit-slider-thumb { transition: none; } } .custom-range::-webkit-slider-thumb:active { background-color: #b3d7ff; } .custom-range::-webkit-slider-runnable-track { width: 100%; height: 0.5rem; color: transparent; cursor: pointer; background-color: #dee2e6; border-color: transparent; border-radius: 1rem; } .custom-range::-moz-range-thumb { width: 1rem; height: 1rem; background-color: #007bff; border: 0; border-radius: 1rem; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -moz-appearance: none; appearance: none; } @media (prefers-reduced-motion: reduce) { .custom-range::-moz-range-thumb { transition: none; } } .custom-range::-moz-range-thumb:active { background-color: #b3d7ff; } .custom-range::-moz-range-track { width: 100%; height: 0.5rem; color: transparent; cursor: pointer; background-color: #dee2e6; border-color: transparent; border-radius: 1rem; } .custom-range::-ms-thumb { width: 1rem; height: 1rem; margin-top: 0; margin-right: 0.2rem; margin-left: 0.2rem; background-color: #007bff; border: 0; border-radius: 1rem; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; appearance: none; } @media (prefers-reduced-motion: reduce) { .custom-range::-ms-thumb { transition: none; } } .custom-range::-ms-thumb:active { background-color: #b3d7ff; } .custom-range::-ms-track { width: 100%; height: 0.5rem; color: transparent; cursor: pointer; background-color: transparent; border-color: transparent; border-width: 0.5rem; } .custom-range::-ms-fill-lower { background-color: #dee2e6; border-radius: 1rem; } .custom-range::-ms-fill-upper { margin-right: 15px; background-color: #dee2e6; border-radius: 1rem; } .custom-range:disabled::-webkit-slider-thumb { background-color: #adb5bd; } .custom-range:disabled::-webkit-slider-runnable-track { cursor: default; } .custom-range:disabled::-moz-range-thumb { background-color: #adb5bd; } .custom-range:disabled::-moz-range-track { cursor: default; } .custom-range:disabled::-ms-thumb { background-color: #adb5bd; } .custom-control-label::before, .custom-file-label, .custom-select { transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .custom-control-label::before, .custom-file-label, .custom-select { transition: none; } } .nav { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; padding-left: 0; margin-bottom: 0; list-style: none; } .nav-link { display: block; padding: 0.5rem 1rem; } .nav-link:hover, .nav-link:focus { text-decoration: none; } .nav-link.disabled { color: #6c757d; pointer-events: none; cursor: default; } .nav-tabs { border-bottom: 1px solid #dee2e6; } .nav-tabs .nav-item { margin-bottom: -1px; } .nav-tabs .nav-link { border: 1px solid transparent; border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { border-color: #e9ecef #e9ecef #dee2e6; } .nav-tabs .nav-link.disabled { color: #6c757d; background-color: transparent; border-color: transparent; } .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { color: #495057; background-color: #fff; border-color: #dee2e6 #dee2e6 #fff; } .nav-tabs .dropdown-menu { margin-top: -1px; border-top-left-radius: 0; border-top-right-radius: 0; } .nav-pills .nav-link { border-radius: 0.25rem; } .nav-pills .nav-link.active, .nav-pills .show > .nav-link { color: #fff; background-color: #007bff; } .nav-fill .nav-item { -ms-flex: 1 1 auto; flex: 1 1 auto; text-align: center; } .nav-justified .nav-item { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; text-align: center; } .tab-content > .tab-pane { display: none; } .tab-content > .active { display: block; } .navbar { position: relative; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: center; align-items: center; -ms-flex-pack: justify; justify-content: space-between; padding: 0.5rem 1rem; } .navbar > .container, .navbar > .container-fluid { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: center; align-items: center; -ms-flex-pack: justify; justify-content: space-between; } .navbar-brand { display: inline-block; padding-top: 0.3125rem; padding-bottom: 0.3125rem; margin-right: 1rem; font-size: 1.25rem; line-height: inherit; white-space: nowrap; } .navbar-brand:hover, .navbar-brand:focus { text-decoration: none; } .navbar-nav { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; padding-left: 0; margin-bottom: 0; list-style: none; } .navbar-nav .nav-link { padding-right: 0; padding-left: 0; } .navbar-nav .dropdown-menu { position: static; float: none; } .navbar-text { display: inline-block; padding-top: 0.5rem; padding-bottom: 0.5rem; } .navbar-collapse { -ms-flex-preferred-size: 100%; flex-basis: 100%; -ms-flex-positive: 1; flex-grow: 1; -ms-flex-align: center; align-items: center; } .navbar-toggler { padding: 0.25rem 0.75rem; font-size: 1.25rem; line-height: 1; background-color: transparent; border: 1px solid transparent; border-radius: 0.25rem; } .navbar-toggler:hover, .navbar-toggler:focus { text-decoration: none; } .navbar-toggler-icon { display: inline-block; width: 1.5em; height: 1.5em; vertical-align: middle; content: ""; background: no-repeat center center; background-size: 100% 100%; } @media (max-width: 575.98px) { .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 576px) { .navbar-expand-sm { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-sm .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-sm .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-sm .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-sm .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-sm .navbar-toggler { display: none; } } @media (max-width: 767.98px) { .navbar-expand-md > .container, .navbar-expand-md > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 768px) { .navbar-expand-md { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-md .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-md .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-md .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-md > .container, .navbar-expand-md > .container-fluid { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-md .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-md .navbar-toggler { display: none; } } @media (max-width: 991.98px) { .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 992px) { .navbar-expand-lg { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-lg .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-lg .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-lg .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-lg .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-lg .navbar-toggler { display: none; } } @media (max-width: 1199.98px) { .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid { padding-right: 0; padding-left: 0; } } @media (min-width: 1200px) { .navbar-expand-xl { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-xl .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-xl .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-xl .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-xl .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-xl .navbar-toggler { display: none; } } .navbar-expand { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand > .container, .navbar-expand > .container-fluid { padding-right: 0; padding-left: 0; } .navbar-expand .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand > .container, .navbar-expand > .container-fluid { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand .navbar-toggler { display: none; } .navbar-light .navbar-brand { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-nav .nav-link { color: rgba(0, 0, 0, 0.5); } .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { color: rgba(0, 0, 0, 0.7); } .navbar-light .navbar-nav .nav-link.disabled { color: rgba(0, 0, 0, 0.3); } .navbar-light .navbar-nav .show > .nav-link, .navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .nav-link.active { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-toggler { color: rgba(0, 0, 0, 0.5); border-color: rgba(0, 0, 0, 0.1); } .navbar-light .navbar-toggler-icon { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } .navbar-light .navbar-text { color: rgba(0, 0, 0, 0.5); } .navbar-light .navbar-text a { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { color: rgba(0, 0, 0, 0.9); } .navbar-dark .navbar-brand { color: #fff; } .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { color: #fff; } .navbar-dark .navbar-nav .nav-link { color: rgba(255, 255, 255, 0.5); } .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { color: rgba(255, 255, 255, 0.75); } .navbar-dark .navbar-nav .nav-link.disabled { color: rgba(255, 255, 255, 0.25); } .navbar-dark .navbar-nav .show > .nav-link, .navbar-dark .navbar-nav .active > .nav-link, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .nav-link.active { color: #fff; } .navbar-dark .navbar-toggler { color: rgba(255, 255, 255, 0.5); border-color: rgba(255, 255, 255, 0.1); } .navbar-dark .navbar-toggler-icon { background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } .navbar-dark .navbar-text { color: rgba(255, 255, 255, 0.5); } .navbar-dark .navbar-text a { color: #fff; } .navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { color: #fff; } .card { position: relative; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; min-width: 0; word-wrap: break-word; background-color: #fff; background-clip: border-box; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 0.25rem; } .card > hr { margin-right: 0; margin-left: 0; } .card > .list-group:first-child .list-group-item:first-child { border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .card > .list-group:last-child .list-group-item:last-child { border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .card-body { -ms-flex: 1 1 auto; flex: 1 1 auto; padding: 1.25rem; } .card-title { margin-bottom: 0.75rem; } .card-subtitle { margin-top: -0.375rem; margin-bottom: 0; } .card-text:last-child { margin-bottom: 0; } .card-link:hover { text-decoration: none; } .card-link + .card-link { margin-left: 1.25rem; } .card-header { padding: 0.75rem 1.25rem; margin-bottom: 0; background-color: rgba(0, 0, 0, 0.03); border-bottom: 1px solid rgba(0, 0, 0, 0.125); } .card-header:first-child { border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; } .card-header + .list-group .list-group-item:first-child { border-top: 0; } .card-footer { padding: 0.75rem 1.25rem; background-color: rgba(0, 0, 0, 0.03); border-top: 1px solid rgba(0, 0, 0, 0.125); } .card-footer:last-child { border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); } .card-header-tabs { margin-right: -0.625rem; margin-bottom: -0.75rem; margin-left: -0.625rem; border-bottom: 0; } .card-header-pills { margin-right: -0.625rem; margin-left: -0.625rem; } .card-img-overlay { position: absolute; top: 0; right: 0; bottom: 0; left: 0; padding: 1.25rem; } .card-img { width: 100%; border-radius: calc(0.25rem - 1px); } .card-img-top { width: 100%; border-top-left-radius: calc(0.25rem - 1px); border-top-right-radius: calc(0.25rem - 1px); } .card-img-bottom { width: 100%; border-bottom-right-radius: calc(0.25rem - 1px); border-bottom-left-radius: calc(0.25rem - 1px); } .card-deck { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; } .card-deck .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-deck { -ms-flex-flow: row wrap; flex-flow: row wrap; margin-right: -15px; margin-left: -15px; } .card-deck .card { display: -ms-flexbox; display: flex; -ms-flex: 1 0 0%; flex: 1 0 0%; -ms-flex-direction: column; flex-direction: column; margin-right: 15px; margin-bottom: 0; margin-left: 15px; } } .card-group { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; } .card-group > .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-group { -ms-flex-flow: row wrap; flex-flow: row wrap; } .card-group > .card { -ms-flex: 1 0 0%; flex: 1 0 0%; margin-bottom: 0; } .card-group > .card + .card { margin-left: 0; border-left: 0; } .card-group > .card:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .card-group > .card:not(:last-child) .card-img-top, .card-group > .card:not(:last-child) .card-header { border-top-right-radius: 0; } .card-group > .card:not(:last-child) .card-img-bottom, .card-group > .card:not(:last-child) .card-footer { border-bottom-right-radius: 0; } .card-group > .card:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .card-group > .card:not(:first-child) .card-img-top, .card-group > .card:not(:first-child) .card-header { border-top-left-radius: 0; } .card-group > .card:not(:first-child) .card-img-bottom, .card-group > .card:not(:first-child) .card-footer { border-bottom-left-radius: 0; } } .card-columns .card { margin-bottom: 0.75rem; } @media (min-width: 576px) { .card-columns { -webkit-column-count: 3; -moz-column-count: 3; column-count: 3; -webkit-column-gap: 1.25rem; -moz-column-gap: 1.25rem; column-gap: 1.25rem; orphans: 1; widows: 1; } .card-columns .card { display: inline-block; width: 100%; } } .accordion > .card { overflow: hidden; } .accordion > .card:not(:first-of-type) .card-header:first-child { border-radius: 0; } .accordion > .card:not(:first-of-type):not(:last-of-type) { border-bottom: 0; border-radius: 0; } .accordion > .card:first-of-type { border-bottom: 0; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .accordion > .card:last-of-type { border-top-left-radius: 0; border-top-right-radius: 0; } .accordion > .card .card-header { margin-bottom: -1px; } .breadcrumb { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; padding: 0.75rem 1rem; margin-bottom: 1rem; list-style: none; background-color: #e9ecef; border-radius: 0.25rem; } .breadcrumb-item + .breadcrumb-item { padding-left: 0.5rem; } .breadcrumb-item + .breadcrumb-item::before { display: inline-block; padding-right: 0.5rem; color: #6c757d; content: "/"; } .breadcrumb-item + .breadcrumb-item:hover::before { text-decoration: underline; } .breadcrumb-item + .breadcrumb-item:hover::before { text-decoration: none; } .breadcrumb-item.active { color: #6c757d; } .pagination { display: -ms-flexbox; display: flex; padding-left: 0; list-style: none; border-radius: 0.25rem; } .page-link { position: relative; display: block; padding: 0.5rem 0.75rem; margin-left: -1px; line-height: 1.25; color: #007bff; background-color: #fff; border: 1px solid #dee2e6; } .page-link:hover { z-index: 2; color: #0056b3; text-decoration: none; background-color: #e9ecef; border-color: #dee2e6; } .page-link:focus { z-index: 2; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .page-item:first-child .page-link { margin-left: 0; border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .page-item:last-child .page-link { border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; } .page-item.active .page-link { z-index: 1; color: #fff; background-color: #007bff; border-color: #007bff; } .page-item.disabled .page-link { color: #6c757d; pointer-events: none; cursor: auto; background-color: #fff; border-color: #dee2e6; } .pagination-lg .page-link { padding: 0.75rem 1.5rem; font-size: 1.25rem; line-height: 1.5; } .pagination-lg .page-item:first-child .page-link { border-top-left-radius: 0.3rem; border-bottom-left-radius: 0.3rem; } .pagination-lg .page-item:last-child .page-link { border-top-right-radius: 0.3rem; border-bottom-right-radius: 0.3rem; } .pagination-sm .page-link { padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; } .pagination-sm .page-item:first-child .page-link { border-top-left-radius: 0.2rem; border-bottom-left-radius: 0.2rem; } .pagination-sm .page-item:last-child .page-link { border-top-right-radius: 0.2rem; border-bottom-right-radius: 0.2rem; } .badge { display: inline-block; padding: 0.25em 0.4em; font-size: 75%; font-weight: 700; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: 0.25rem; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .badge { transition: none; } } a.badge:hover, a.badge:focus { text-decoration: none; } .badge:empty { display: none; } .btn .badge { position: relative; top: -1px; } .badge-pill { padding-right: 0.6em; padding-left: 0.6em; border-radius: 10rem; } .badge-primary { color: #fff; background-color: #007bff; } a.badge-primary:hover, a.badge-primary:focus { color: #fff; background-color: #0062cc; } a.badge-primary:focus, a.badge-primary.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .badge-secondary { color: #fff; background-color: #6c757d; } a.badge-secondary:hover, a.badge-secondary:focus { color: #fff; background-color: #545b62; } a.badge-secondary:focus, a.badge-secondary.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .badge-success { color: #fff; background-color: #28a745; } a.badge-success:hover, a.badge-success:focus { color: #fff; background-color: #1e7e34; } a.badge-success:focus, a.badge-success.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .badge-info { color: #fff; background-color: #17a2b8; } a.badge-info:hover, a.badge-info:focus { color: #fff; background-color: #117a8b; } a.badge-info:focus, a.badge-info.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .badge-warning { color: #212529; background-color: #ffc107; } a.badge-warning:hover, a.badge-warning:focus { color: #212529; background-color: #d39e00; } a.badge-warning:focus, a.badge-warning.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .badge-danger { color: #fff; background-color: #dc3545; } a.badge-danger:hover, a.badge-danger:focus { color: #fff; background-color: #bd2130; } a.badge-danger:focus, a.badge-danger.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .badge-light { color: #212529; background-color: #f8f9fa; } a.badge-light:hover, a.badge-light:focus { color: #212529; background-color: #dae0e5; } a.badge-light:focus, a.badge-light.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .badge-dark { color: #fff; background-color: #343a40; } a.badge-dark:hover, a.badge-dark:focus { color: #fff; background-color: #1d2124; } a.badge-dark:focus, a.badge-dark.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } .jumbotron { padding: 2rem 1rem; margin-bottom: 2rem; background-color: #e9ecef; border-radius: 0.3rem; } @media (min-width: 576px) { .jumbotron { padding: 4rem 2rem; } } .jumbotron-fluid { padding-right: 0; padding-left: 0; border-radius: 0; } .alert { position: relative; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.25rem; } .alert-heading { color: inherit; } .alert-link { font-weight: 700; } .alert-dismissible { padding-right: 4rem; } .alert-dismissible .close { position: absolute; top: 0; right: 0; padding: 0.75rem 1.25rem; color: inherit; } .alert-primary { color: #004085; background-color: #cce5ff; border-color: #b8daff; } .alert-primary hr { border-top-color: #9fcdff; } .alert-primary .alert-link { color: #002752; } .alert-secondary { color: #383d41; background-color: #e2e3e5; border-color: #d6d8db; } .alert-secondary hr { border-top-color: #c8cbcf; } .alert-secondary .alert-link { color: #202326; } .alert-success { color: #155724; background-color: #d4edda; border-color: #c3e6cb; } .alert-success hr { border-top-color: #b1dfbb; } .alert-success .alert-link { color: #0b2e13; } .alert-info { color: #0c5460; background-color: #d1ecf1; border-color: #bee5eb; } .alert-info hr { border-top-color: #abdde5; } .alert-info .alert-link { color: #062c33; } .alert-warning { color: #856404; background-color: #fff3cd; border-color: #ffeeba; } .alert-warning hr { border-top-color: #ffe8a1; } .alert-warning .alert-link { color: #533f03; } .alert-danger { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; } .alert-danger hr { border-top-color: #f1b0b7; } .alert-danger .alert-link { color: #491217; } .alert-light { color: #818182; background-color: #fefefe; border-color: #fdfdfe; } .alert-light hr { border-top-color: #ececf6; } .alert-light .alert-link { color: #686868; } .alert-dark { color: #1b1e21; background-color: #d6d8d9; border-color: #c6c8ca; } .alert-dark hr { border-top-color: #b9bbbe; } .alert-dark .alert-link { color: #040505; } @-webkit-keyframes progress-bar-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } } @keyframes progress-bar-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } } .progress { display: -ms-flexbox; display: flex; height: 1rem; overflow: hidden; font-size: 0.75rem; background-color: #e9ecef; border-radius: 0.25rem; } .progress-bar { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; -ms-flex-pack: center; justify-content: center; color: #fff; text-align: center; white-space: nowrap; background-color: #007bff; transition: width 0.6s ease; } @media (prefers-reduced-motion: reduce) { .progress-bar { transition: none; } } .progress-bar-striped { background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-size: 1rem 1rem; } .progress-bar-animated { -webkit-animation: progress-bar-stripes 1s linear infinite; animation: progress-bar-stripes 1s linear infinite; } @media (prefers-reduced-motion: reduce) { .progress-bar-animated { -webkit-animation: none; animation: none; } } .media { display: -ms-flexbox; display: flex; -ms-flex-align: start; align-items: flex-start; } .media-body { -ms-flex: 1; flex: 1; } .list-group { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; padding-left: 0; margin-bottom: 0; } .list-group-item-action { width: 100%; color: #495057; text-align: inherit; } .list-group-item-action:hover, .list-group-item-action:focus { z-index: 1; color: #495057; text-decoration: none; background-color: #f8f9fa; } .list-group-item-action:active { color: #212529; background-color: #e9ecef; } .list-group-item { position: relative; display: block; padding: 0.75rem 1.25rem; margin-bottom: -1px; background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.125); } .list-group-item:first-child { border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .list-group-item:last-child { margin-bottom: 0; border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .list-group-item.disabled, .list-group-item:disabled { color: #6c757d; pointer-events: none; background-color: #fff; } .list-group-item.active { z-index: 2; color: #fff; background-color: #007bff; border-color: #007bff; } .list-group-horizontal { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal .list-group-item { margin-right: -1px; margin-bottom: 0; } .list-group-horizontal .list-group-item:first-child { border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal .list-group-item:last-child { margin-right: 0; border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } @media (min-width: 576px) { .list-group-horizontal-sm { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-sm .list-group-item { margin-right: -1px; margin-bottom: 0; } .list-group-horizontal-sm .list-group-item:first-child { border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-sm .list-group-item:last-child { margin-right: 0; border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } } @media (min-width: 768px) { .list-group-horizontal-md { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-md .list-group-item { margin-right: -1px; margin-bottom: 0; } .list-group-horizontal-md .list-group-item:first-child { border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-md .list-group-item:last-child { margin-right: 0; border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } } @media (min-width: 992px) { .list-group-horizontal-lg { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-lg .list-group-item { margin-right: -1px; margin-bottom: 0; } .list-group-horizontal-lg .list-group-item:first-child { border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-lg .list-group-item:last-child { margin-right: 0; border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } } @media (min-width: 1200px) { .list-group-horizontal-xl { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-xl .list-group-item { margin-right: -1px; margin-bottom: 0; } .list-group-horizontal-xl .list-group-item:first-child { border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-xl .list-group-item:last-child { margin-right: 0; border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0; } } .list-group-flush .list-group-item { border-right: 0; border-left: 0; border-radius: 0; } .list-group-flush .list-group-item:last-child { margin-bottom: -1px; } .list-group-flush:first-child .list-group-item:first-child { border-top: 0; } .list-group-flush:last-child .list-group-item:last-child { margin-bottom: 0; border-bottom: 0; } .list-group-item-primary { color: #004085; background-color: #b8daff; } .list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { color: #004085; background-color: #9fcdff; } .list-group-item-primary.list-group-item-action.active { color: #fff; background-color: #004085; border-color: #004085; } .list-group-item-secondary { color: #383d41; background-color: #d6d8db; } .list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { color: #383d41; background-color: #c8cbcf; } .list-group-item-secondary.list-group-item-action.active { color: #fff; background-color: #383d41; border-color: #383d41; } .list-group-item-success { color: #155724; background-color: #c3e6cb; } .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { color: #155724; background-color: #b1dfbb; } .list-group-item-success.list-group-item-action.active { color: #fff; background-color: #155724; border-color: #155724; } .list-group-item-info { color: #0c5460; background-color: #bee5eb; } .list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { color: #0c5460; background-color: #abdde5; } .list-group-item-info.list-group-item-action.active { color: #fff; background-color: #0c5460; border-color: #0c5460; } .list-group-item-warning { color: #856404; background-color: #ffeeba; } .list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { color: #856404; background-color: #ffe8a1; } .list-group-item-warning.list-group-item-action.active { color: #fff; background-color: #856404; border-color: #856404; } .list-group-item-danger { color: #721c24; background-color: #f5c6cb; } .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { color: #721c24; background-color: #f1b0b7; } .list-group-item-danger.list-group-item-action.active { color: #fff; background-color: #721c24; border-color: #721c24; } .list-group-item-light { color: #818182; background-color: #fdfdfe; } .list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { color: #818182; background-color: #ececf6; } .list-group-item-light.list-group-item-action.active { color: #fff; background-color: #818182; border-color: #818182; } .list-group-item-dark { color: #1b1e21; background-color: #c6c8ca; } .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { color: #1b1e21; background-color: #b9bbbe; } .list-group-item-dark.list-group-item-action.active { color: #fff; background-color: #1b1e21; border-color: #1b1e21; } .close { float: right; font-size: 1.5rem; font-weight: 700; line-height: 1; color: #000; text-shadow: 0 1px 0 #fff; opacity: .5; } .close:hover { color: #000; text-decoration: none; } .close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus { opacity: .75; } button.close { padding: 0; background-color: transparent; border: 0; -webkit-appearance: none; -moz-appearance: none; appearance: none; } a.close.disabled { pointer-events: none; } .toast { max-width: 350px; overflow: hidden; font-size: 0.875rem; background-color: rgba(255, 255, 255, 0.85); background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); opacity: 0; border-radius: 0.25rem; } .toast:not(:last-child) { margin-bottom: 0.75rem; } .toast.showing { opacity: 1; } .toast.show { display: block; opacity: 1; } .toast.hide { display: none; } .toast-header { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; padding: 0.25rem 0.75rem; color: #6c757d; background-color: rgba(255, 255, 255, 0.85); background-clip: padding-box; border-bottom: 1px solid rgba(0, 0, 0, 0.05); } .toast-body { padding: 0.75rem; } .modal-open { overflow: hidden; } .modal-open .modal { overflow-x: hidden; overflow-y: auto; } .modal { position: fixed; top: 0; left: 0; z-index: 1050; display: none; width: 100%; height: 100%; overflow: hidden; outline: 0; } .modal-dialog { position: relative; width: auto; margin: 0.5rem; pointer-events: none; } .modal.fade .modal-dialog { transition: -webkit-transform 0.3s ease-out; transition: transform 0.3s ease-out; transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; -webkit-transform: translate(0, -50px); transform: translate(0, -50px); } @media (prefers-reduced-motion: reduce) { .modal.fade .modal-dialog { transition: none; } } .modal.show .modal-dialog { -webkit-transform: none; transform: none; } .modal-dialog-scrollable { display: -ms-flexbox; display: flex; max-height: calc(100% - 1rem); } .modal-dialog-scrollable .modal-content { max-height: calc(100vh - 1rem); overflow: hidden; } .modal-dialog-scrollable .modal-header, .modal-dialog-scrollable .modal-footer { -ms-flex-negative: 0; flex-shrink: 0; } .modal-dialog-scrollable .modal-body { overflow-y: auto; } .modal-dialog-centered { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; min-height: calc(100% - 1rem); } .modal-dialog-centered::before { display: block; height: calc(100vh - 1rem); content: ""; } .modal-dialog-centered.modal-dialog-scrollable { -ms-flex-direction: column; flex-direction: column; -ms-flex-pack: center; justify-content: center; height: 100%; } .modal-dialog-centered.modal-dialog-scrollable .modal-content { max-height: none; } .modal-dialog-centered.modal-dialog-scrollable::before { content: none; } .modal-content { position: relative; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; width: 100%; pointer-events: auto; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 0.3rem; outline: 0; } .modal-backdrop { position: fixed; top: 0; left: 0; z-index: 1040; width: 100vw; height: 100vh; background-color: #000; } .modal-backdrop.fade { opacity: 0; } .modal-backdrop.show { opacity: 0.5; } .modal-header { display: -ms-flexbox; display: flex; -ms-flex-align: start; align-items: flex-start; -ms-flex-pack: justify; justify-content: space-between; padding: 1rem 1rem; border-bottom: 1px solid #dee2e6; border-top-left-radius: 0.3rem; border-top-right-radius: 0.3rem; } .modal-header .close { padding: 1rem 1rem; margin: -1rem -1rem -1rem auto; } .modal-title { margin-bottom: 0; line-height: 1.5; } .modal-body { position: relative; -ms-flex: 1 1 auto; flex: 1 1 auto; padding: 1rem; } .modal-footer { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: end; justify-content: flex-end; padding: 1rem; border-top: 1px solid #dee2e6; border-bottom-right-radius: 0.3rem; border-bottom-left-radius: 0.3rem; } .modal-footer > :not(:first-child) { margin-left: .25rem; } .modal-footer > :not(:last-child) { margin-right: .25rem; } .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } @media (min-width: 576px) { .modal-dialog { max-width: 500px; margin: 1.75rem auto; } .modal-dialog-scrollable { max-height: calc(100% - 3.5rem); } .modal-dialog-scrollable .modal-content { max-height: calc(100vh - 3.5rem); } .modal-dialog-centered { min-height: calc(100% - 3.5rem); } .modal-dialog-centered::before { height: calc(100vh - 3.5rem); } .modal-sm { max-width: 300px; } } @media (min-width: 992px) { .modal-lg, .modal-xl { max-width: 800px; } } @media (min-width: 1200px) { .modal-xl { max-width: 1140px; } } .tooltip { position: absolute; z-index: 1070; display: block; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-style: normal; font-weight: 400; line-height: 1.5; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; white-space: normal; line-break: auto; font-size: 0.875rem; word-wrap: break-word; opacity: 0; } .tooltip.show { opacity: 0.9; } .tooltip .arrow { position: absolute; display: block; width: 0.8rem; height: 0.4rem; } .tooltip .arrow::before { position: absolute; content: ""; border-color: transparent; border-style: solid; } .bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { padding: 0.4rem 0; } .bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { bottom: 0; } .bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { top: 0; border-width: 0.4rem 0.4rem 0; border-top-color: #000; } .bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { padding: 0 0.4rem; } .bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { left: 0; width: 0.4rem; height: 0.8rem; } .bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { right: 0; border-width: 0.4rem 0.4rem 0.4rem 0; border-right-color: #000; } .bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { padding: 0.4rem 0; } .bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { top: 0; } .bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { bottom: 0; border-width: 0 0.4rem 0.4rem; border-bottom-color: #000; } .bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { padding: 0 0.4rem; } .bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { right: 0; width: 0.4rem; height: 0.8rem; } .bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { left: 0; border-width: 0.4rem 0 0.4rem 0.4rem; border-left-color: #000; } .tooltip-inner { max-width: 200px; padding: 0.25rem 0.5rem; color: #fff; text-align: center; background-color: #000; border-radius: 0.25rem; } .popover { position: absolute; top: 0; left: 0; z-index: 1060; display: block; max-width: 276px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-style: normal; font-weight: 400; line-height: 1.5; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; white-space: normal; line-break: auto; font-size: 0.875rem; word-wrap: break-word; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 0.3rem; } .popover .arrow { position: absolute; display: block; width: 1rem; height: 0.5rem; margin: 0 0.3rem; } .popover .arrow::before, .popover .arrow::after { position: absolute; display: block; content: ""; border-color: transparent; border-style: solid; } .bs-popover-top, .bs-popover-auto[x-placement^="top"] { margin-bottom: 0.5rem; } .bs-popover-top > .arrow, .bs-popover-auto[x-placement^="top"] > .arrow { bottom: calc((0.5rem + 1px) * -1); } .bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^="top"] > .arrow::before { bottom: 0; border-width: 0.5rem 0.5rem 0; border-top-color: rgba(0, 0, 0, 0.25); } .bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^="top"] > .arrow::after { bottom: 1px; border-width: 0.5rem 0.5rem 0; border-top-color: #fff; } .bs-popover-right, .bs-popover-auto[x-placement^="right"] { margin-left: 0.5rem; } .bs-popover-right > .arrow, .bs-popover-auto[x-placement^="right"] > .arrow { left: calc((0.5rem + 1px) * -1); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } .bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^="right"] > .arrow::before { left: 0; border-width: 0.5rem 0.5rem 0.5rem 0; border-right-color: rgba(0, 0, 0, 0.25); } .bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^="right"] > .arrow::after { left: 1px; border-width: 0.5rem 0.5rem 0.5rem 0; border-right-color: #fff; } .bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { margin-top: 0.5rem; } .bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^="bottom"] > .arrow { top: calc((0.5rem + 1px) * -1); } .bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before { top: 0; border-width: 0 0.5rem 0.5rem 0.5rem; border-bottom-color: rgba(0, 0, 0, 0.25); } .bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^="bottom"] > .arrow::after { top: 1px; border-width: 0 0.5rem 0.5rem 0.5rem; border-bottom-color: #fff; } .bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { position: absolute; top: 0; left: 50%; display: block; width: 1rem; margin-left: -0.5rem; content: ""; border-bottom: 1px solid #f7f7f7; } .bs-popover-left, .bs-popover-auto[x-placement^="left"] { margin-right: 0.5rem; } .bs-popover-left > .arrow, .bs-popover-auto[x-placement^="left"] > .arrow { right: calc((0.5rem + 1px) * -1); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } .bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^="left"] > .arrow::before { right: 0; border-width: 0.5rem 0 0.5rem 0.5rem; border-left-color: rgba(0, 0, 0, 0.25); } .bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^="left"] > .arrow::after { right: 1px; border-width: 0.5rem 0 0.5rem 0.5rem; border-left-color: #fff; } .popover-header { padding: 0.5rem 0.75rem; margin-bottom: 0; font-size: 1rem; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-top-left-radius: calc(0.3rem - 1px); border-top-right-radius: calc(0.3rem - 1px); } .popover-header:empty { display: none; } .popover-body { padding: 0.5rem 0.75rem; color: #212529; } .carousel { position: relative; } .carousel.pointer-event { -ms-touch-action: pan-y; touch-action: pan-y; } .carousel-inner { position: relative; width: 100%; overflow: hidden; } .carousel-inner::after { display: block; clear: both; content: ""; } .carousel-item { position: relative; display: none; float: left; width: 100%; margin-right: -100%; -webkit-backface-visibility: hidden; backface-visibility: hidden; transition: -webkit-transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out; } @media (prefers-reduced-motion: reduce) { .carousel-item { transition: none; } } .carousel-item.active, .carousel-item-next, .carousel-item-prev { display: block; } .carousel-item-next:not(.carousel-item-left), .active.carousel-item-right { -webkit-transform: translateX(100%); transform: translateX(100%); } .carousel-item-prev:not(.carousel-item-right), .active.carousel-item-left { -webkit-transform: translateX(-100%); transform: translateX(-100%); } .carousel-fade .carousel-item { opacity: 0; transition-property: opacity; -webkit-transform: none; transform: none; } .carousel-fade .carousel-item.active, .carousel-fade .carousel-item-next.carousel-item-left, .carousel-fade .carousel-item-prev.carousel-item-right { z-index: 1; opacity: 1; } .carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right { z-index: 0; opacity: 0; transition: 0s 0.6s opacity; } @media (prefers-reduced-motion: reduce) { .carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right { transition: none; } } .carousel-control-prev, .carousel-control-next { position: absolute; top: 0; bottom: 0; z-index: 1; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; width: 15%; color: #fff; text-align: center; opacity: 0.5; transition: opacity 0.15s ease; } @media (prefers-reduced-motion: reduce) { .carousel-control-prev, .carousel-control-next { transition: none; } } .carousel-control-prev:hover, .carousel-control-prev:focus, .carousel-control-next:hover, .carousel-control-next:focus { color: #fff; text-decoration: none; outline: 0; opacity: 0.9; } .carousel-control-prev { left: 0; } .carousel-control-next { right: 0; } .carousel-control-prev-icon, .carousel-control-next-icon { display: inline-block; width: 20px; height: 20px; background: no-repeat 50% / 100% 100%; } .carousel-control-prev-icon { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e"); } .carousel-control-next-icon { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e"); } .carousel-indicators { position: absolute; right: 0; bottom: 0; left: 0; z-index: 15; display: -ms-flexbox; display: flex; -ms-flex-pack: center; justify-content: center; padding-left: 0; margin-right: 15%; margin-left: 15%; list-style: none; } .carousel-indicators li { box-sizing: content-box; -ms-flex: 0 1 auto; flex: 0 1 auto; width: 30px; height: 3px; margin-right: 3px; margin-left: 3px; text-indent: -999px; cursor: pointer; background-color: #fff; background-clip: padding-box; border-top: 10px solid transparent; border-bottom: 10px solid transparent; opacity: .5; transition: opacity 0.6s ease; } @media (prefers-reduced-motion: reduce) { .carousel-indicators li { transition: none; } } .carousel-indicators .active { opacity: 1; } .carousel-caption { position: absolute; right: 15%; bottom: 20px; left: 15%; z-index: 10; padding-top: 20px; padding-bottom: 20px; color: #fff; text-align: center; } @-webkit-keyframes spinner-border { to { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes spinner-border { to { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } .spinner-border { display: inline-block; width: 2rem; height: 2rem; vertical-align: text-bottom; border: 0.25em solid currentColor; border-right-color: transparent; border-radius: 50%; -webkit-animation: spinner-border .75s linear infinite; animation: spinner-border .75s linear infinite; } .spinner-border-sm { width: 1rem; height: 1rem; border-width: 0.2em; } @-webkit-keyframes spinner-grow { 0% { -webkit-transform: scale(0); transform: scale(0); } 50% { opacity: 1; } } @keyframes spinner-grow { 0% { -webkit-transform: scale(0); transform: scale(0); } 50% { opacity: 1; } } .spinner-grow { display: inline-block; width: 2rem; height: 2rem; vertical-align: text-bottom; background-color: currentColor; border-radius: 50%; opacity: 0; -webkit-animation: spinner-grow .75s linear infinite; animation: spinner-grow .75s linear infinite; } .spinner-grow-sm { width: 1rem; height: 1rem; } .align-baseline { vertical-align: baseline !important; } .align-top { vertical-align: top !important; } .align-middle { vertical-align: middle !important; } .align-bottom { vertical-align: bottom !important; } .align-text-bottom { vertical-align: text-bottom !important; } .align-text-top { vertical-align: text-top !important; } .bg-primary { background-color: #007bff !important; } a.bg-primary:hover, a.bg-primary:focus, button.bg-primary:hover, button.bg-primary:focus { background-color: #0062cc !important; } .bg-secondary { background-color: #6c757d !important; } a.bg-secondary:hover, a.bg-secondary:focus, button.bg-secondary:hover, button.bg-secondary:focus { background-color: #545b62 !important; } .bg-success { background-color: #28a745 !important; } a.bg-success:hover, a.bg-success:focus, button.bg-success:hover, button.bg-success:focus { background-color: #1e7e34 !important; } .bg-info { background-color: #17a2b8 !important; } a.bg-info:hover, a.bg-info:focus, button.bg-info:hover, button.bg-info:focus { background-color: #117a8b !important; } .bg-warning { background-color: #ffc107 !important; } a.bg-warning:hover, a.bg-warning:focus, button.bg-warning:hover, button.bg-warning:focus { background-color: #d39e00 !important; } .bg-danger { background-color: #dc3545 !important; } a.bg-danger:hover, a.bg-danger:focus, button.bg-danger:hover, button.bg-danger:focus { background-color: #bd2130 !important; } .bg-light { background-color: #f8f9fa !important; } a.bg-light:hover, a.bg-light:focus, button.bg-light:hover, button.bg-light:focus { background-color: #dae0e5 !important; } .bg-dark { background-color: #343a40 !important; } a.bg-dark:hover, a.bg-dark:focus, button.bg-dark:hover, button.bg-dark:focus { background-color: #1d2124 !important; } .bg-white { background-color: #fff !important; } .bg-transparent { background-color: transparent !important; } .border { border: 1px solid #dee2e6 !important; } .border-top { border-top: 1px solid #dee2e6 !important; } .border-right { border-right: 1px solid #dee2e6 !important; } .border-bottom { border-bottom: 1px solid #dee2e6 !important; } .border-left { border-left: 1px solid #dee2e6 !important; } .border-0 { border: 0 !important; } .border-top-0 { border-top: 0 !important; } .border-right-0 { border-right: 0 !important; } .border-bottom-0 { border-bottom: 0 !important; } .border-left-0 { border-left: 0 !important; } .border-primary { border-color: #007bff !important; } .border-secondary { border-color: #6c757d !important; } .border-success { border-color: #28a745 !important; } .border-info { border-color: #17a2b8 !important; } .border-warning { border-color: #ffc107 !important; } .border-danger { border-color: #dc3545 !important; } .border-light { border-color: #f8f9fa !important; } .border-dark { border-color: #343a40 !important; } .border-white { border-color: #fff !important; } .rounded-sm { border-radius: 0.2rem !important; } .rounded { border-radius: 0.25rem !important; } .rounded-top { border-top-left-radius: 0.25rem !important; border-top-right-radius: 0.25rem !important; } .rounded-right { border-top-right-radius: 0.25rem !important; border-bottom-right-radius: 0.25rem !important; } .rounded-bottom { border-bottom-right-radius: 0.25rem !important; border-bottom-left-radius: 0.25rem !important; } .rounded-left { border-top-left-radius: 0.25rem !important; border-bottom-left-radius: 0.25rem !important; } .rounded-lg { border-radius: 0.3rem !important; } .rounded-circle { border-radius: 50% !important; } .rounded-pill { border-radius: 50rem !important; } .rounded-0 { border-radius: 0 !important; } .clearfix::after { display: block; clear: both; content: ""; } .d-none { display: none !important; } .d-inline { display: inline !important; } .d-inline-block { display: inline-block !important; } .d-block { display: block !important; } .d-table { display: table !important; } .d-table-row { display: table-row !important; } .d-table-cell { display: table-cell !important; } .d-flex { display: -ms-flexbox !important; display: flex !important; } .d-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } @media (min-width: 576px) { .d-sm-none { display: none !important; } .d-sm-inline { display: inline !important; } .d-sm-inline-block { display: inline-block !important; } .d-sm-block { display: block !important; } .d-sm-table { display: table !important; } .d-sm-table-row { display: table-row !important; } .d-sm-table-cell { display: table-cell !important; } .d-sm-flex { display: -ms-flexbox !important; display: flex !important; } .d-sm-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 768px) { .d-md-none { display: none !important; } .d-md-inline { display: inline !important; } .d-md-inline-block { display: inline-block !important; } .d-md-block { display: block !important; } .d-md-table { display: table !important; } .d-md-table-row { display: table-row !important; } .d-md-table-cell { display: table-cell !important; } .d-md-flex { display: -ms-flexbox !important; display: flex !important; } .d-md-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 992px) { .d-lg-none { display: none !important; } .d-lg-inline { display: inline !important; } .d-lg-inline-block { display: inline-block !important; } .d-lg-block { display: block !important; } .d-lg-table { display: table !important; } .d-lg-table-row { display: table-row !important; } .d-lg-table-cell { display: table-cell !important; } .d-lg-flex { display: -ms-flexbox !important; display: flex !important; } .d-lg-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 1200px) { .d-xl-none { display: none !important; } .d-xl-inline { display: inline !important; } .d-xl-inline-block { display: inline-block !important; } .d-xl-block { display: block !important; } .d-xl-table { display: table !important; } .d-xl-table-row { display: table-row !important; } .d-xl-table-cell { display: table-cell !important; } .d-xl-flex { display: -ms-flexbox !important; display: flex !important; } .d-xl-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media print { .d-print-none { display: none !important; } .d-print-inline { display: inline !important; } .d-print-inline-block { display: inline-block !important; } .d-print-block { display: block !important; } .d-print-table { display: table !important; } .d-print-table-row { display: table-row !important; } .d-print-table-cell { display: table-cell !important; } .d-print-flex { display: -ms-flexbox !important; display: flex !important; } .d-print-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } .embed-responsive { position: relative; display: block; width: 100%; padding: 0; overflow: hidden; } .embed-responsive::before { display: block; content: ""; } .embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video { position: absolute; top: 0; bottom: 0; left: 0; width: 100%; height: 100%; border: 0; } .embed-responsive-21by9::before { padding-top: 42.857143%; } .embed-responsive-16by9::before { padding-top: 56.25%; } .embed-responsive-4by3::before { padding-top: 75%; } .embed-responsive-1by1::before { padding-top: 100%; } .flex-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } @media (min-width: 576px) { .flex-sm-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-sm-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-sm-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-sm-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-sm-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-sm-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-sm-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-sm-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-sm-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-sm-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-sm-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-sm-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-sm-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-sm-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-sm-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-sm-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-sm-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-sm-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-sm-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-sm-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-sm-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-sm-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-sm-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-sm-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-sm-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-sm-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-sm-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-sm-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-sm-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-sm-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-sm-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-sm-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-sm-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-sm-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 768px) { .flex-md-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-md-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-md-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-md-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-md-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-md-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-md-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-md-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-md-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-md-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-md-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-md-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-md-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-md-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-md-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-md-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-md-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-md-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-md-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-md-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-md-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-md-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-md-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-md-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-md-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-md-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-md-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-md-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-md-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-md-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-md-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-md-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-md-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-md-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 992px) { .flex-lg-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-lg-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-lg-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-lg-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-lg-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-lg-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-lg-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-lg-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-lg-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-lg-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-lg-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-lg-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-lg-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-lg-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-lg-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-lg-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-lg-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-lg-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-lg-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-lg-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-lg-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-lg-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-lg-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-lg-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-lg-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-lg-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-lg-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-lg-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-lg-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-lg-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-lg-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-lg-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-lg-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-lg-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 1200px) { .flex-xl-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-xl-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-xl-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-xl-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-xl-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-xl-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-xl-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-xl-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-xl-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-xl-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-xl-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-xl-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-xl-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-xl-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-xl-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-xl-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-xl-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-xl-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-xl-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-xl-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-xl-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-xl-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-xl-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-xl-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-xl-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-xl-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-xl-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-xl-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-xl-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-xl-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-xl-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-xl-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-xl-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-xl-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } .float-left { float: left !important; } .float-right { float: right !important; } .float-none { float: none !important; } @media (min-width: 576px) { .float-sm-left { float: left !important; } .float-sm-right { float: right !important; } .float-sm-none { float: none !important; } } @media (min-width: 768px) { .float-md-left { float: left !important; } .float-md-right { float: right !important; } .float-md-none { float: none !important; } } @media (min-width: 992px) { .float-lg-left { float: left !important; } .float-lg-right { float: right !important; } .float-lg-none { float: none !important; } } @media (min-width: 1200px) { .float-xl-left { float: left !important; } .float-xl-right { float: right !important; } .float-xl-none { float: none !important; } } .overflow-auto { overflow: auto !important; } .overflow-hidden { overflow: hidden !important; } .position-static { position: static !important; } .position-relative { position: relative !important; } .position-absolute { position: absolute !important; } .position-fixed { position: fixed !important; } .position-sticky { position: -webkit-sticky !important; position: sticky !important; } .fixed-top { position: fixed; top: 0; right: 0; left: 0; z-index: 1030; } .fixed-bottom { position: fixed; right: 0; bottom: 0; left: 0; z-index: 1030; } @supports ((position: -webkit-sticky) or (position: sticky)) { .sticky-top { position: -webkit-sticky; position: sticky; top: 0; z-index: 1020; } } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; overflow: visible; clip: auto; white-space: normal; } .shadow-sm { box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; } .shadow { box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } .shadow-lg { box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; } .shadow-none { box-shadow: none !important; } .w-25 { width: 25% !important; } .w-50 { width: 50% !important; } .w-75 { width: 75% !important; } .w-100 { width: 100% !important; } .w-auto { width: auto !important; } .h-25 { height: 25% !important; } .h-50 { height: 50% !important; } .h-75 { height: 75% !important; } .h-100 { height: 100% !important; } .h-auto { height: auto !important; } .mw-100 { max-width: 100% !important; } .mh-100 { max-height: 100% !important; } .min-vw-100 { min-width: 100vw !important; } .min-vh-100 { min-height: 100vh !important; } .vw-100 { width: 100vw !important; } .vh-100 { height: 100vh !important; } .stretched-link::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: auto; content: ""; background-color: rgba(0, 0, 0, 0); } .m-0 { margin: 0 !important; } .mt-0, .my-0 { margin-top: 0 !important; } .mr-0, .mx-0 { margin-right: 0 !important; } .mb-0, .my-0 { margin-bottom: 0 !important; } .ml-0, .mx-0 { margin-left: 0 !important; } .m-1 { margin: 0.25rem !important; } .mt-1, .my-1 { margin-top: 0.25rem !important; } .mr-1, .mx-1 { margin-right: 0.25rem !important; } .mb-1, .my-1 { margin-bottom: 0.25rem !important; } .ml-1, .mx-1 { margin-left: 0.25rem !important; } .m-2 { margin: 0.5rem !important; } .mt-2, .my-2 { margin-top: 0.5rem !important; } .mr-2, .mx-2 { margin-right: 0.5rem !important; } .mb-2, .my-2 { margin-bottom: 0.5rem !important; } .ml-2, .mx-2 { margin-left: 0.5rem !important; } .m-3 { margin: 1rem !important; } .mt-3, .my-3 { margin-top: 1rem !important; } .mr-3, .mx-3 { margin-right: 1rem !important; } .mb-3, .my-3 { margin-bottom: 1rem !important; } .ml-3, .mx-3 { margin-left: 1rem !important; } .m-4 { margin: 1.5rem !important; } .mt-4, .my-4 { margin-top: 1.5rem !important; } .mr-4, .mx-4 { margin-right: 1.5rem !important; } .mb-4, .my-4 { margin-bottom: 1.5rem !important; } .ml-4, .mx-4 { margin-left: 1.5rem !important; } .m-5 { margin: 3rem !important; } .mt-5, .my-5 { margin-top: 3rem !important; } .mr-5, .mx-5 { margin-right: 3rem !important; } .mb-5, .my-5 { margin-bottom: 3rem !important; } .ml-5, .mx-5 { margin-left: 3rem !important; } .p-0 { padding: 0 !important; } .pt-0, .py-0 { padding-top: 0 !important; } .pr-0, .px-0 { padding-right: 0 !important; } .pb-0, .py-0 { padding-bottom: 0 !important; } .pl-0, .px-0 { padding-left: 0 !important; } .p-1 { padding: 0.25rem !important; } .pt-1, .py-1 { padding-top: 0.25rem !important; } .pr-1, .px-1 { padding-right: 0.25rem !important; } .pb-1, .py-1 { padding-bottom: 0.25rem !important; } .pl-1, .px-1 { padding-left: 0.25rem !important; } .p-2 { padding: 0.5rem !important; } .pt-2, .py-2 { padding-top: 0.5rem !important; } .pr-2, .px-2 { padding-right: 0.5rem !important; } .pb-2, .py-2 { padding-bottom: 0.5rem !important; } .pl-2, .px-2 { padding-left: 0.5rem !important; } .p-3 { padding: 1rem !important; } .pt-3, .py-3 { padding-top: 1rem !important; } .pr-3, .px-3 { padding-right: 1rem !important; } .pb-3, .py-3 { padding-bottom: 1rem !important; } .pl-3, .px-3 { padding-left: 1rem !important; } .p-4 { padding: 1.5rem !important; } .pt-4, .py-4 { padding-top: 1.5rem !important; } .pr-4, .px-4 { padding-right: 1.5rem !important; } .pb-4, .py-4 { padding-bottom: 1.5rem !important; } .pl-4, .px-4 { padding-left: 1.5rem !important; } .p-5 { padding: 3rem !important; } .pt-5, .py-5 { padding-top: 3rem !important; } .pr-5, .px-5 { padding-right: 3rem !important; } .pb-5, .py-5 { padding-bottom: 3rem !important; } .pl-5, .px-5 { padding-left: 3rem !important; } .m-n1 { margin: -0.25rem !important; } .mt-n1, .my-n1 { margin-top: -0.25rem !important; } .mr-n1, .mx-n1 { margin-right: -0.25rem !important; } .mb-n1, .my-n1 { margin-bottom: -0.25rem !important; } .ml-n1, .mx-n1 { margin-left: -0.25rem !important; } .m-n2 { margin: -0.5rem !important; } .mt-n2, .my-n2 { margin-top: -0.5rem !important; } .mr-n2, .mx-n2 { margin-right: -0.5rem !important; } .mb-n2, .my-n2 { margin-bottom: -0.5rem !important; } .ml-n2, .mx-n2 { margin-left: -0.5rem !important; } .m-n3 { margin: -1rem !important; } .mt-n3, .my-n3 { margin-top: -1rem !important; } .mr-n3, .mx-n3 { margin-right: -1rem !important; } .mb-n3, .my-n3 { margin-bottom: -1rem !important; } .ml-n3, .mx-n3 { margin-left: -1rem !important; } .m-n4 { margin: -1.5rem !important; } .mt-n4, .my-n4 { margin-top: -1.5rem !important; } .mr-n4, .mx-n4 { margin-right: -1.5rem !important; } .mb-n4, .my-n4 { margin-bottom: -1.5rem !important; } .ml-n4, .mx-n4 { margin-left: -1.5rem !important; } .m-n5 { margin: -3rem !important; } .mt-n5, .my-n5 { margin-top: -3rem !important; } .mr-n5, .mx-n5 { margin-right: -3rem !important; } .mb-n5, .my-n5 { margin-bottom: -3rem !important; } .ml-n5, .mx-n5 { margin-left: -3rem !important; } .m-auto { margin: auto !important; } .mt-auto, .my-auto { margin-top: auto !important; } .mr-auto, .mx-auto { margin-right: auto !important; } .mb-auto, .my-auto { margin-bottom: auto !important; } .ml-auto, .mx-auto { margin-left: auto !important; } @media (min-width: 576px) { .m-sm-0 { margin: 0 !important; } .mt-sm-0, .my-sm-0 { margin-top: 0 !important; } .mr-sm-0, .mx-sm-0 { margin-right: 0 !important; } .mb-sm-0, .my-sm-0 { margin-bottom: 0 !important; } .ml-sm-0, .mx-sm-0 { margin-left: 0 !important; } .m-sm-1 { margin: 0.25rem !important; } .mt-sm-1, .my-sm-1 { margin-top: 0.25rem !important; } .mr-sm-1, .mx-sm-1 { margin-right: 0.25rem !important; } .mb-sm-1, .my-sm-1 { margin-bottom: 0.25rem !important; } .ml-sm-1, .mx-sm-1 { margin-left: 0.25rem !important; } .m-sm-2 { margin: 0.5rem !important; } .mt-sm-2, .my-sm-2 { margin-top: 0.5rem !important; } .mr-sm-2, .mx-sm-2 { margin-right: 0.5rem !important; } .mb-sm-2, .my-sm-2 { margin-bottom: 0.5rem !important; } .ml-sm-2, .mx-sm-2 { margin-left: 0.5rem !important; } .m-sm-3 { margin: 1rem !important; } .mt-sm-3, .my-sm-3 { margin-top: 1rem !important; } .mr-sm-3, .mx-sm-3 { margin-right: 1rem !important; } .mb-sm-3, .my-sm-3 { margin-bottom: 1rem !important; } .ml-sm-3, .mx-sm-3 { margin-left: 1rem !important; } .m-sm-4 { margin: 1.5rem !important; } .mt-sm-4, .my-sm-4 { margin-top: 1.5rem !important; } .mr-sm-4, .mx-sm-4 { margin-right: 1.5rem !important; } .mb-sm-4, .my-sm-4 { margin-bottom: 1.5rem !important; } .ml-sm-4, .mx-sm-4 { margin-left: 1.5rem !important; } .m-sm-5 { margin: 3rem !important; } .mt-sm-5, .my-sm-5 { margin-top: 3rem !important; } .mr-sm-5, .mx-sm-5 { margin-right: 3rem !important; } .mb-sm-5, .my-sm-5 { margin-bottom: 3rem !important; } .ml-sm-5, .mx-sm-5 { margin-left: 3rem !important; } .p-sm-0 { padding: 0 !important; } .pt-sm-0, .py-sm-0 { padding-top: 0 !important; } .pr-sm-0, .px-sm-0 { padding-right: 0 !important; } .pb-sm-0, .py-sm-0 { padding-bottom: 0 !important; } .pl-sm-0, .px-sm-0 { padding-left: 0 !important; } .p-sm-1 { padding: 0.25rem !important; } .pt-sm-1, .py-sm-1 { padding-top: 0.25rem !important; } .pr-sm-1, .px-sm-1 { padding-right: 0.25rem !important; } .pb-sm-1, .py-sm-1 { padding-bottom: 0.25rem !important; } .pl-sm-1, .px-sm-1 { padding-left: 0.25rem !important; } .p-sm-2 { padding: 0.5rem !important; } .pt-sm-2, .py-sm-2 { padding-top: 0.5rem !important; } .pr-sm-2, .px-sm-2 { padding-right: 0.5rem !important; } .pb-sm-2, .py-sm-2 { padding-bottom: 0.5rem !important; } .pl-sm-2, .px-sm-2 { padding-left: 0.5rem !important; } .p-sm-3 { padding: 1rem !important; } .pt-sm-3, .py-sm-3 { padding-top: 1rem !important; } .pr-sm-3, .px-sm-3 { padding-right: 1rem !important; } .pb-sm-3, .py-sm-3 { padding-bottom: 1rem !important; } .pl-sm-3, .px-sm-3 { padding-left: 1rem !important; } .p-sm-4 { padding: 1.5rem !important; } .pt-sm-4, .py-sm-4 { padding-top: 1.5rem !important; } .pr-sm-4, .px-sm-4 { padding-right: 1.5rem !important; } .pb-sm-4, .py-sm-4 { padding-bottom: 1.5rem !important; } .pl-sm-4, .px-sm-4 { padding-left: 1.5rem !important; } .p-sm-5 { padding: 3rem !important; } .pt-sm-5, .py-sm-5 { padding-top: 3rem !important; } .pr-sm-5, .px-sm-5 { padding-right: 3rem !important; } .pb-sm-5, .py-sm-5 { padding-bottom: 3rem !important; } .pl-sm-5, .px-sm-5 { padding-left: 3rem !important; } .m-sm-n1 { margin: -0.25rem !important; } .mt-sm-n1, .my-sm-n1 { margin-top: -0.25rem !important; } .mr-sm-n1, .mx-sm-n1 { margin-right: -0.25rem !important; } .mb-sm-n1, .my-sm-n1 { margin-bottom: -0.25rem !important; } .ml-sm-n1, .mx-sm-n1 { margin-left: -0.25rem !important; } .m-sm-n2 { margin: -0.5rem !important; } .mt-sm-n2, .my-sm-n2 { margin-top: -0.5rem !important; } .mr-sm-n2, .mx-sm-n2 { margin-right: -0.5rem !important; } .mb-sm-n2, .my-sm-n2 { margin-bottom: -0.5rem !important; } .ml-sm-n2, .mx-sm-n2 { margin-left: -0.5rem !important; } .m-sm-n3 { margin: -1rem !important; } .mt-sm-n3, .my-sm-n3 { margin-top: -1rem !important; } .mr-sm-n3, .mx-sm-n3 { margin-right: -1rem !important; } .mb-sm-n3, .my-sm-n3 { margin-bottom: -1rem !important; } .ml-sm-n3, .mx-sm-n3 { margin-left: -1rem !important; } .m-sm-n4 { margin: -1.5rem !important; } .mt-sm-n4, .my-sm-n4 { margin-top: -1.5rem !important; } .mr-sm-n4, .mx-sm-n4 { margin-right: -1.5rem !important; } .mb-sm-n4, .my-sm-n4 { margin-bottom: -1.5rem !important; } .ml-sm-n4, .mx-sm-n4 { margin-left: -1.5rem !important; } .m-sm-n5 { margin: -3rem !important; } .mt-sm-n5, .my-sm-n5 { margin-top: -3rem !important; } .mr-sm-n5, .mx-sm-n5 { margin-right: -3rem !important; } .mb-sm-n5, .my-sm-n5 { margin-bottom: -3rem !important; } .ml-sm-n5, .mx-sm-n5 { margin-left: -3rem !important; } .m-sm-auto { margin: auto !important; } .mt-sm-auto, .my-sm-auto { margin-top: auto !important; } .mr-sm-auto, .mx-sm-auto { margin-right: auto !important; } .mb-sm-auto, .my-sm-auto { margin-bottom: auto !important; } .ml-sm-auto, .mx-sm-auto { margin-left: auto !important; } } @media (min-width: 768px) { .m-md-0 { margin: 0 !important; } .mt-md-0, .my-md-0 { margin-top: 0 !important; } .mr-md-0, .mx-md-0 { margin-right: 0 !important; } .mb-md-0, .my-md-0 { margin-bottom: 0 !important; } .ml-md-0, .mx-md-0 { margin-left: 0 !important; } .m-md-1 { margin: 0.25rem !important; } .mt-md-1, .my-md-1 { margin-top: 0.25rem !important; } .mr-md-1, .mx-md-1 { margin-right: 0.25rem !important; } .mb-md-1, .my-md-1 { margin-bottom: 0.25rem !important; } .ml-md-1, .mx-md-1 { margin-left: 0.25rem !important; } .m-md-2 { margin: 0.5rem !important; } .mt-md-2, .my-md-2 { margin-top: 0.5rem !important; } .mr-md-2, .mx-md-2 { margin-right: 0.5rem !important; } .mb-md-2, .my-md-2 { margin-bottom: 0.5rem !important; } .ml-md-2, .mx-md-2 { margin-left: 0.5rem !important; } .m-md-3 { margin: 1rem !important; } .mt-md-3, .my-md-3 { margin-top: 1rem !important; } .mr-md-3, .mx-md-3 { margin-right: 1rem !important; } .mb-md-3, .my-md-3 { margin-bottom: 1rem !important; } .ml-md-3, .mx-md-3 { margin-left: 1rem !important; } .m-md-4 { margin: 1.5rem !important; } .mt-md-4, .my-md-4 { margin-top: 1.5rem !important; } .mr-md-4, .mx-md-4 { margin-right: 1.5rem !important; } .mb-md-4, .my-md-4 { margin-bottom: 1.5rem !important; } .ml-md-4, .mx-md-4 { margin-left: 1.5rem !important; } .m-md-5 { margin: 3rem !important; } .mt-md-5, .my-md-5 { margin-top: 3rem !important; } .mr-md-5, .mx-md-5 { margin-right: 3rem !important; } .mb-md-5, .my-md-5 { margin-bottom: 3rem !important; } .ml-md-5, .mx-md-5 { margin-left: 3rem !important; } .p-md-0 { padding: 0 !important; } .pt-md-0, .py-md-0 { padding-top: 0 !important; } .pr-md-0, .px-md-0 { padding-right: 0 !important; } .pb-md-0, .py-md-0 { padding-bottom: 0 !important; } .pl-md-0, .px-md-0 { padding-left: 0 !important; } .p-md-1 { padding: 0.25rem !important; } .pt-md-1, .py-md-1 { padding-top: 0.25rem !important; } .pr-md-1, .px-md-1 { padding-right: 0.25rem !important; } .pb-md-1, .py-md-1 { padding-bottom: 0.25rem !important; } .pl-md-1, .px-md-1 { padding-left: 0.25rem !important; } .p-md-2 { padding: 0.5rem !important; } .pt-md-2, .py-md-2 { padding-top: 0.5rem !important; } .pr-md-2, .px-md-2 { padding-right: 0.5rem !important; } .pb-md-2, .py-md-2 { padding-bottom: 0.5rem !important; } .pl-md-2, .px-md-2 { padding-left: 0.5rem !important; } .p-md-3 { padding: 1rem !important; } .pt-md-3, .py-md-3 { padding-top: 1rem !important; } .pr-md-3, .px-md-3 { padding-right: 1rem !important; } .pb-md-3, .py-md-3 { padding-bottom: 1rem !important; } .pl-md-3, .px-md-3 { padding-left: 1rem !important; } .p-md-4 { padding: 1.5rem !important; } .pt-md-4, .py-md-4 { padding-top: 1.5rem !important; } .pr-md-4, .px-md-4 { padding-right: 1.5rem !important; } .pb-md-4, .py-md-4 { padding-bottom: 1.5rem !important; } .pl-md-4, .px-md-4 { padding-left: 1.5rem !important; } .p-md-5 { padding: 3rem !important; } .pt-md-5, .py-md-5 { padding-top: 3rem !important; } .pr-md-5, .px-md-5 { padding-right: 3rem !important; } .pb-md-5, .py-md-5 { padding-bottom: 3rem !important; } .pl-md-5, .px-md-5 { padding-left: 3rem !important; } .m-md-n1 { margin: -0.25rem !important; } .mt-md-n1, .my-md-n1 { margin-top: -0.25rem !important; } .mr-md-n1, .mx-md-n1 { margin-right: -0.25rem !important; } .mb-md-n1, .my-md-n1 { margin-bottom: -0.25rem !important; } .ml-md-n1, .mx-md-n1 { margin-left: -0.25rem !important; } .m-md-n2 { margin: -0.5rem !important; } .mt-md-n2, .my-md-n2 { margin-top: -0.5rem !important; } .mr-md-n2, .mx-md-n2 { margin-right: -0.5rem !important; } .mb-md-n2, .my-md-n2 { margin-bottom: -0.5rem !important; } .ml-md-n2, .mx-md-n2 { margin-left: -0.5rem !important; } .m-md-n3 { margin: -1rem !important; } .mt-md-n3, .my-md-n3 { margin-top: -1rem !important; } .mr-md-n3, .mx-md-n3 { margin-right: -1rem !important; } .mb-md-n3, .my-md-n3 { margin-bottom: -1rem !important; } .ml-md-n3, .mx-md-n3 { margin-left: -1rem !important; } .m-md-n4 { margin: -1.5rem !important; } .mt-md-n4, .my-md-n4 { margin-top: -1.5rem !important; } .mr-md-n4, .mx-md-n4 { margin-right: -1.5rem !important; } .mb-md-n4, .my-md-n4 { margin-bottom: -1.5rem !important; } .ml-md-n4, .mx-md-n4 { margin-left: -1.5rem !important; } .m-md-n5 { margin: -3rem !important; } .mt-md-n5, .my-md-n5 { margin-top: -3rem !important; } .mr-md-n5, .mx-md-n5 { margin-right: -3rem !important; } .mb-md-n5, .my-md-n5 { margin-bottom: -3rem !important; } .ml-md-n5, .mx-md-n5 { margin-left: -3rem !important; } .m-md-auto { margin: auto !important; } .mt-md-auto, .my-md-auto { margin-top: auto !important; } .mr-md-auto, .mx-md-auto { margin-right: auto !important; } .mb-md-auto, .my-md-auto { margin-bottom: auto !important; } .ml-md-auto, .mx-md-auto { margin-left: auto !important; } } @media (min-width: 992px) { .m-lg-0 { margin: 0 !important; } .mt-lg-0, .my-lg-0 { margin-top: 0 !important; } .mr-lg-0, .mx-lg-0 { margin-right: 0 !important; } .mb-lg-0, .my-lg-0 { margin-bottom: 0 !important; } .ml-lg-0, .mx-lg-0 { margin-left: 0 !important; } .m-lg-1 { margin: 0.25rem !important; } .mt-lg-1, .my-lg-1 { margin-top: 0.25rem !important; } .mr-lg-1, .mx-lg-1 { margin-right: 0.25rem !important; } .mb-lg-1, .my-lg-1 { margin-bottom: 0.25rem !important; } .ml-lg-1, .mx-lg-1 { margin-left: 0.25rem !important; } .m-lg-2 { margin: 0.5rem !important; } .mt-lg-2, .my-lg-2 { margin-top: 0.5rem !important; } .mr-lg-2, .mx-lg-2 { margin-right: 0.5rem !important; } .mb-lg-2, .my-lg-2 { margin-bottom: 0.5rem !important; } .ml-lg-2, .mx-lg-2 { margin-left: 0.5rem !important; } .m-lg-3 { margin: 1rem !important; } .mt-lg-3, .my-lg-3 { margin-top: 1rem !important; } .mr-lg-3, .mx-lg-3 { margin-right: 1rem !important; } .mb-lg-3, .my-lg-3 { margin-bottom: 1rem !important; } .ml-lg-3, .mx-lg-3 { margin-left: 1rem !important; } .m-lg-4 { margin: 1.5rem !important; } .mt-lg-4, .my-lg-4 { margin-top: 1.5rem !important; } .mr-lg-4, .mx-lg-4 { margin-right: 1.5rem !important; } .mb-lg-4, .my-lg-4 { margin-bottom: 1.5rem !important; } .ml-lg-4, .mx-lg-4 { margin-left: 1.5rem !important; } .m-lg-5 { margin: 3rem !important; } .mt-lg-5, .my-lg-5 { margin-top: 3rem !important; } .mr-lg-5, .mx-lg-5 { margin-right: 3rem !important; } .mb-lg-5, .my-lg-5 { margin-bottom: 3rem !important; } .ml-lg-5, .mx-lg-5 { margin-left: 3rem !important; } .p-lg-0 { padding: 0 !important; } .pt-lg-0, .py-lg-0 { padding-top: 0 !important; } .pr-lg-0, .px-lg-0 { padding-right: 0 !important; } .pb-lg-0, .py-lg-0 { padding-bottom: 0 !important; } .pl-lg-0, .px-lg-0 { padding-left: 0 !important; } .p-lg-1 { padding: 0.25rem !important; } .pt-lg-1, .py-lg-1 { padding-top: 0.25rem !important; } .pr-lg-1, .px-lg-1 { padding-right: 0.25rem !important; } .pb-lg-1, .py-lg-1 { padding-bottom: 0.25rem !important; } .pl-lg-1, .px-lg-1 { padding-left: 0.25rem !important; } .p-lg-2 { padding: 0.5rem !important; } .pt-lg-2, .py-lg-2 { padding-top: 0.5rem !important; } .pr-lg-2, .px-lg-2 { padding-right: 0.5rem !important; } .pb-lg-2, .py-lg-2 { padding-bottom: 0.5rem !important; } .pl-lg-2, .px-lg-2 { padding-left: 0.5rem !important; } .p-lg-3 { padding: 1rem !important; } .pt-lg-3, .py-lg-3 { padding-top: 1rem !important; } .pr-lg-3, .px-lg-3 { padding-right: 1rem !important; } .pb-lg-3, .py-lg-3 { padding-bottom: 1rem !important; } .pl-lg-3, .px-lg-3 { padding-left: 1rem !important; } .p-lg-4 { padding: 1.5rem !important; } .pt-lg-4, .py-lg-4 { padding-top: 1.5rem !important; } .pr-lg-4, .px-lg-4 { padding-right: 1.5rem !important; } .pb-lg-4, .py-lg-4 { padding-bottom: 1.5rem !important; } .pl-lg-4, .px-lg-4 { padding-left: 1.5rem !important; } .p-lg-5 { padding: 3rem !important; } .pt-lg-5, .py-lg-5 { padding-top: 3rem !important; } .pr-lg-5, .px-lg-5 { padding-right: 3rem !important; } .pb-lg-5, .py-lg-5 { padding-bottom: 3rem !important; } .pl-lg-5, .px-lg-5 { padding-left: 3rem !important; } .m-lg-n1 { margin: -0.25rem !important; } .mt-lg-n1, .my-lg-n1 { margin-top: -0.25rem !important; } .mr-lg-n1, .mx-lg-n1 { margin-right: -0.25rem !important; } .mb-lg-n1, .my-lg-n1 { margin-bottom: -0.25rem !important; } .ml-lg-n1, .mx-lg-n1 { margin-left: -0.25rem !important; } .m-lg-n2 { margin: -0.5rem !important; } .mt-lg-n2, .my-lg-n2 { margin-top: -0.5rem !important; } .mr-lg-n2, .mx-lg-n2 { margin-right: -0.5rem !important; } .mb-lg-n2, .my-lg-n2 { margin-bottom: -0.5rem !important; } .ml-lg-n2, .mx-lg-n2 { margin-left: -0.5rem !important; } .m-lg-n3 { margin: -1rem !important; } .mt-lg-n3, .my-lg-n3 { margin-top: -1rem !important; } .mr-lg-n3, .mx-lg-n3 { margin-right: -1rem !important; } .mb-lg-n3, .my-lg-n3 { margin-bottom: -1rem !important; } .ml-lg-n3, .mx-lg-n3 { margin-left: -1rem !important; } .m-lg-n4 { margin: -1.5rem !important; } .mt-lg-n4, .my-lg-n4 { margin-top: -1.5rem !important; } .mr-lg-n4, .mx-lg-n4 { margin-right: -1.5rem !important; } .mb-lg-n4, .my-lg-n4 { margin-bottom: -1.5rem !important; } .ml-lg-n4, .mx-lg-n4 { margin-left: -1.5rem !important; } .m-lg-n5 { margin: -3rem !important; } .mt-lg-n5, .my-lg-n5 { margin-top: -3rem !important; } .mr-lg-n5, .mx-lg-n5 { margin-right: -3rem !important; } .mb-lg-n5, .my-lg-n5 { margin-bottom: -3rem !important; } .ml-lg-n5, .mx-lg-n5 { margin-left: -3rem !important; } .m-lg-auto { margin: auto !important; } .mt-lg-auto, .my-lg-auto { margin-top: auto !important; } .mr-lg-auto, .mx-lg-auto { margin-right: auto !important; } .mb-lg-auto, .my-lg-auto { margin-bottom: auto !important; } .ml-lg-auto, .mx-lg-auto { margin-left: auto !important; } } @media (min-width: 1200px) { .m-xl-0 { margin: 0 !important; } .mt-xl-0, .my-xl-0 { margin-top: 0 !important; } .mr-xl-0, .mx-xl-0 { margin-right: 0 !important; } .mb-xl-0, .my-xl-0 { margin-bottom: 0 !important; } .ml-xl-0, .mx-xl-0 { margin-left: 0 !important; } .m-xl-1 { margin: 0.25rem !important; } .mt-xl-1, .my-xl-1 { margin-top: 0.25rem !important; } .mr-xl-1, .mx-xl-1 { margin-right: 0.25rem !important; } .mb-xl-1, .my-xl-1 { margin-bottom: 0.25rem !important; } .ml-xl-1, .mx-xl-1 { margin-left: 0.25rem !important; } .m-xl-2 { margin: 0.5rem !important; } .mt-xl-2, .my-xl-2 { margin-top: 0.5rem !important; } .mr-xl-2, .mx-xl-2 { margin-right: 0.5rem !important; } .mb-xl-2, .my-xl-2 { margin-bottom: 0.5rem !important; } .ml-xl-2, .mx-xl-2 { margin-left: 0.5rem !important; } .m-xl-3 { margin: 1rem !important; } .mt-xl-3, .my-xl-3 { margin-top: 1rem !important; } .mr-xl-3, .mx-xl-3 { margin-right: 1rem !important; } .mb-xl-3, .my-xl-3 { margin-bottom: 1rem !important; } .ml-xl-3, .mx-xl-3 { margin-left: 1rem !important; } .m-xl-4 { margin: 1.5rem !important; } .mt-xl-4, .my-xl-4 { margin-top: 1.5rem !important; } .mr-xl-4, .mx-xl-4 { margin-right: 1.5rem !important; } .mb-xl-4, .my-xl-4 { margin-bottom: 1.5rem !important; } .ml-xl-4, .mx-xl-4 { margin-left: 1.5rem !important; } .m-xl-5 { margin: 3rem !important; } .mt-xl-5, .my-xl-5 { margin-top: 3rem !important; } .mr-xl-5, .mx-xl-5 { margin-right: 3rem !important; } .mb-xl-5, .my-xl-5 { margin-bottom: 3rem !important; } .ml-xl-5, .mx-xl-5 { margin-left: 3rem !important; } .p-xl-0 { padding: 0 !important; } .pt-xl-0, .py-xl-0 { padding-top: 0 !important; } .pr-xl-0, .px-xl-0 { padding-right: 0 !important; } .pb-xl-0, .py-xl-0 { padding-bottom: 0 !important; } .pl-xl-0, .px-xl-0 { padding-left: 0 !important; } .p-xl-1 { padding: 0.25rem !important; } .pt-xl-1, .py-xl-1 { padding-top: 0.25rem !important; } .pr-xl-1, .px-xl-1 { padding-right: 0.25rem !important; } .pb-xl-1, .py-xl-1 { padding-bottom: 0.25rem !important; } .pl-xl-1, .px-xl-1 { padding-left: 0.25rem !important; } .p-xl-2 { padding: 0.5rem !important; } .pt-xl-2, .py-xl-2 { padding-top: 0.5rem !important; } .pr-xl-2, .px-xl-2 { padding-right: 0.5rem !important; } .pb-xl-2, .py-xl-2 { padding-bottom: 0.5rem !important; } .pl-xl-2, .px-xl-2 { padding-left: 0.5rem !important; } .p-xl-3 { padding: 1rem !important; } .pt-xl-3, .py-xl-3 { padding-top: 1rem !important; } .pr-xl-3, .px-xl-3 { padding-right: 1rem !important; } .pb-xl-3, .py-xl-3 { padding-bottom: 1rem !important; } .pl-xl-3, .px-xl-3 { padding-left: 1rem !important; } .p-xl-4 { padding: 1.5rem !important; } .pt-xl-4, .py-xl-4 { padding-top: 1.5rem !important; } .pr-xl-4, .px-xl-4 { padding-right: 1.5rem !important; } .pb-xl-4, .py-xl-4 { padding-bottom: 1.5rem !important; } .pl-xl-4, .px-xl-4 { padding-left: 1.5rem !important; } .p-xl-5 { padding: 3rem !important; } .pt-xl-5, .py-xl-5 { padding-top: 3rem !important; } .pr-xl-5, .px-xl-5 { padding-right: 3rem !important; } .pb-xl-5, .py-xl-5 { padding-bottom: 3rem !important; } .pl-xl-5, .px-xl-5 { padding-left: 3rem !important; } .m-xl-n1 { margin: -0.25rem !important; } .mt-xl-n1, .my-xl-n1 { margin-top: -0.25rem !important; } .mr-xl-n1, .mx-xl-n1 { margin-right: -0.25rem !important; } .mb-xl-n1, .my-xl-n1 { margin-bottom: -0.25rem !important; } .ml-xl-n1, .mx-xl-n1 { margin-left: -0.25rem !important; } .m-xl-n2 { margin: -0.5rem !important; } .mt-xl-n2, .my-xl-n2 { margin-top: -0.5rem !important; } .mr-xl-n2, .mx-xl-n2 { margin-right: -0.5rem !important; } .mb-xl-n2, .my-xl-n2 { margin-bottom: -0.5rem !important; } .ml-xl-n2, .mx-xl-n2 { margin-left: -0.5rem !important; } .m-xl-n3 { margin: -1rem !important; } .mt-xl-n3, .my-xl-n3 { margin-top: -1rem !important; } .mr-xl-n3, .mx-xl-n3 { margin-right: -1rem !important; } .mb-xl-n3, .my-xl-n3 { margin-bottom: -1rem !important; } .ml-xl-n3, .mx-xl-n3 { margin-left: -1rem !important; } .m-xl-n4 { margin: -1.5rem !important; } .mt-xl-n4, .my-xl-n4 { margin-top: -1.5rem !important; } .mr-xl-n4, .mx-xl-n4 { margin-right: -1.5rem !important; } .mb-xl-n4, .my-xl-n4 { margin-bottom: -1.5rem !important; } .ml-xl-n4, .mx-xl-n4 { margin-left: -1.5rem !important; } .m-xl-n5 { margin: -3rem !important; } .mt-xl-n5, .my-xl-n5 { margin-top: -3rem !important; } .mr-xl-n5, .mx-xl-n5 { margin-right: -3rem !important; } .mb-xl-n5, .my-xl-n5 { margin-bottom: -3rem !important; } .ml-xl-n5, .mx-xl-n5 { margin-left: -3rem !important; } .m-xl-auto { margin: auto !important; } .mt-xl-auto, .my-xl-auto { margin-top: auto !important; } .mr-xl-auto, .mx-xl-auto { margin-right: auto !important; } .mb-xl-auto, .my-xl-auto { margin-bottom: auto !important; } .ml-xl-auto, .mx-xl-auto { margin-left: auto !important; } } .text-monospace { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; } .text-justify { text-align: justify !important; } .text-wrap { white-space: normal !important; } .text-nowrap { white-space: nowrap !important; } .text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .text-left { text-align: left !important; } .text-right { text-align: right !important; } .text-center { text-align: center !important; } @media (min-width: 576px) { .text-sm-left { text-align: left !important; } .text-sm-right { text-align: right !important; } .text-sm-center { text-align: center !important; } } @media (min-width: 768px) { .text-md-left { text-align: left !important; } .text-md-right { text-align: right !important; } .text-md-center { text-align: center !important; } } @media (min-width: 992px) { .text-lg-left { text-align: left !important; } .text-lg-right { text-align: right !important; } .text-lg-center { text-align: center !important; } } @media (min-width: 1200px) { .text-xl-left { text-align: left !important; } .text-xl-right { text-align: right !important; } .text-xl-center { text-align: center !important; } } .text-lowercase { text-transform: lowercase !important; } .text-uppercase { text-transform: uppercase !important; } .text-capitalize { text-transform: capitalize !important; } .font-weight-light { font-weight: 300 !important; } .font-weight-lighter { font-weight: lighter !important; } .font-weight-normal { font-weight: 400 !important; } .font-weight-bold { font-weight: 700 !important; } .font-weight-bolder { font-weight: bolder !important; } .font-italic { font-style: italic !important; } .text-white { color: #fff !important; } .text-primary { color: #007bff !important; } a.text-primary:hover, a.text-primary:focus { color: #0056b3 !important; } .text-secondary { color: #6c757d !important; } a.text-secondary:hover, a.text-secondary:focus { color: #494f54 !important; } .text-success { color: #28a745 !important; } a.text-success:hover, a.text-success:focus { color: #19692c !important; } .text-info { color: #17a2b8 !important; } a.text-info:hover, a.text-info:focus { color: #0f6674 !important; } .text-warning { color: #ffc107 !important; } a.text-warning:hover, a.text-warning:focus { color: #ba8b00 !important; } .text-danger { color: #dc3545 !important; } a.text-danger:hover, a.text-danger:focus { color: #a71d2a !important; } .text-light { color: #f8f9fa !important; } a.text-light:hover, a.text-light:focus { color: #cbd3da !important; } .text-dark { color: #343a40 !important; } a.text-dark:hover, a.text-dark:focus { color: #121416 !important; } .text-body { color: #212529 !important; } .text-muted { color: #6c757d !important; } .text-black-50 { color: rgba(0, 0, 0, 0.5) !important; } .text-white-50 { color: rgba(255, 255, 255, 0.5) !important; } .text-hide { font: 0/0 a; color: transparent; text-shadow: none; background-color: transparent; border: 0; } .text-decoration-none { text-decoration: none !important; } .text-break { word-break: break-word !important; overflow-wrap: break-word !important; } .text-reset { color: inherit !important; } .visible { visibility: visible !important; } .invisible { visibility: hidden !important; } @media print { *, *::before, *::after { text-shadow: none !important; box-shadow: none !important; } a:not(.btn) { text-decoration: underline; } abbr[title]::after { content: " (" attr(title) ")"; } pre { white-space: pre-wrap !important; } pre, blockquote { border: 1px solid #adb5bd; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } @page { size: a3; } body { min-width: 992px !important; } .container { min-width: 992px !important; } .navbar { display: none; } .badge { border: 1px solid #000; } .table { border-collapse: collapse !important; } .table td, .table th { background-color: #fff !important; } .table-bordered th, .table-bordered td { border: 1px solid #dee2e6 !important; } .table-dark { color: inherit; } .table-dark th, .table-dark td, .table-dark thead th, .table-dark tbody + tbody { border-color: #dee2e6; } .table .thead-dark th { color: inherit; border-color: #dee2e6; } } /*# sourceMappingURL=bootstrap.css.map */ ================================================ FILE: easyflow-flow-bpmn/static/plugins/bootstrap/js/bootstrap.bundle.js ================================================ /*! * Bootstrap v4.3.1 (https://getbootstrap.com/) * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jquery')) : typeof define === 'function' && define.amd ? define(['exports', 'jquery'], factory) : (global = global || self, factory(global.bootstrap = {}, global.jQuery)); }(this, function (exports, $) { 'use strict'; $ = $ && $.hasOwnProperty('default') ? $['default'] : $; function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } /** * -------------------------------------------------------------------------- * Bootstrap (v4.3.1): util.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ /** * ------------------------------------------------------------------------ * Private TransitionEnd Helpers * ------------------------------------------------------------------------ */ var TRANSITION_END = 'transitionend'; var MAX_UID = 1000000; var MILLISECONDS_MULTIPLIER = 1000; // Shoutout AngusCroll (https://goo.gl/pxwQGp) function toType(obj) { return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase(); } function getSpecialTransitionEndEvent() { return { bindType: TRANSITION_END, delegateType: TRANSITION_END, handle: function handle(event) { if ($(event.target).is(this)) { return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params } return undefined; // eslint-disable-line no-undefined } }; } function transitionEndEmulator(duration) { var _this = this; var called = false; $(this).one(Util.TRANSITION_END, function () { called = true; }); setTimeout(function () { if (!called) { Util.triggerTransitionEnd(_this); } }, duration); return this; } function setTransitionEndSupport() { $.fn.emulateTransitionEnd = transitionEndEmulator; $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent(); } /** * -------------------------------------------------------------------------- * Public Util Api * -------------------------------------------------------------------------- */ var Util = { TRANSITION_END: 'bsTransitionEnd', getUID: function getUID(prefix) { do { // eslint-disable-next-line no-bitwise prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here } while (document.getElementById(prefix)); return prefix; }, getSelectorFromElement: function getSelectorFromElement(element) { var selector = element.getAttribute('data-target'); if (!selector || selector === '#') { var hrefAttr = element.getAttribute('href'); selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''; } try { return document.querySelector(selector) ? selector : null; } catch (err) { return null; } }, getTransitionDurationFromElement: function getTransitionDurationFromElement(element) { if (!element) { return 0; } // Get transition-duration of the element var transitionDuration = $(element).css('transition-duration'); var transitionDelay = $(element).css('transition-delay'); var floatTransitionDuration = parseFloat(transitionDuration); var floatTransitionDelay = parseFloat(transitionDelay); // Return 0 if element or transition duration is not found if (!floatTransitionDuration && !floatTransitionDelay) { return 0; } // If multiple durations are defined, take the first transitionDuration = transitionDuration.split(',')[0]; transitionDelay = transitionDelay.split(',')[0]; return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; }, reflow: function reflow(element) { return element.offsetHeight; }, triggerTransitionEnd: function triggerTransitionEnd(element) { $(element).trigger(TRANSITION_END); }, // TODO: Remove in v5 supportsTransitionEnd: function supportsTransitionEnd() { return Boolean(TRANSITION_END); }, isElement: function isElement(obj) { return (obj[0] || obj).nodeType; }, typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) { for (var property in configTypes) { if (Object.prototype.hasOwnProperty.call(configTypes, property)) { var expectedTypes = configTypes[property]; var value = config[property]; var valueType = value && Util.isElement(value) ? 'element' : toType(value); if (!new RegExp(expectedTypes).test(valueType)) { throw new Error(componentName.toUpperCase() + ": " + ("Option \"" + property + "\" provided type \"" + valueType + "\" ") + ("but expected type \"" + expectedTypes + "\".")); } } } }, findShadowRoot: function findShadowRoot(element) { if (!document.documentElement.attachShadow) { return null; } // Can find the shadow root otherwise it'll return the document if (typeof element.getRootNode === 'function') { var root = element.getRootNode(); return root instanceof ShadowRoot ? root : null; } if (element instanceof ShadowRoot) { return element; } // when we don't find a shadow root if (!element.parentNode) { return null; } return Util.findShadowRoot(element.parentNode); } }; setTransitionEndSupport(); /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME = 'alert'; var VERSION = '4.3.1'; var DATA_KEY = 'bs.alert'; var EVENT_KEY = "." + DATA_KEY; var DATA_API_KEY = '.data-api'; var JQUERY_NO_CONFLICT = $.fn[NAME]; var Selector = { DISMISS: '[data-dismiss="alert"]' }; var Event = { CLOSE: "close" + EVENT_KEY, CLOSED: "closed" + EVENT_KEY, CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY }; var ClassName = { ALERT: 'alert', FADE: 'fade', SHOW: 'show' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Alert = /*#__PURE__*/ function () { function Alert(element) { this._element = element; } // Getters var _proto = Alert.prototype; // Public _proto.close = function close(element) { var rootElement = this._element; if (element) { rootElement = this._getRootElement(element); } var customEvent = this._triggerCloseEvent(rootElement); if (customEvent.isDefaultPrevented()) { return; } this._removeElement(rootElement); }; _proto.dispose = function dispose() { $.removeData(this._element, DATA_KEY); this._element = null; } // Private ; _proto._getRootElement = function _getRootElement(element) { var selector = Util.getSelectorFromElement(element); var parent = false; if (selector) { parent = document.querySelector(selector); } if (!parent) { parent = $(element).closest("." + ClassName.ALERT)[0]; } return parent; }; _proto._triggerCloseEvent = function _triggerCloseEvent(element) { var closeEvent = $.Event(Event.CLOSE); $(element).trigger(closeEvent); return closeEvent; }; _proto._removeElement = function _removeElement(element) { var _this = this; $(element).removeClass(ClassName.SHOW); if (!$(element).hasClass(ClassName.FADE)) { this._destroyElement(element); return; } var transitionDuration = Util.getTransitionDurationFromElement(element); $(element).one(Util.TRANSITION_END, function (event) { return _this._destroyElement(element, event); }).emulateTransitionEnd(transitionDuration); }; _proto._destroyElement = function _destroyElement(element) { $(element).detach().trigger(Event.CLOSED).remove(); } // Static ; Alert._jQueryInterface = function _jQueryInterface(config) { return this.each(function () { var $element = $(this); var data = $element.data(DATA_KEY); if (!data) { data = new Alert(this); $element.data(DATA_KEY, data); } if (config === 'close') { data[config](this); } }); }; Alert._handleDismiss = function _handleDismiss(alertInstance) { return function (event) { if (event) { event.preventDefault(); } alertInstance.close(this); }; }; _createClass(Alert, null, [{ key: "VERSION", get: function get() { return VERSION; } }]); return Alert; }(); /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ $(document).on(Event.CLICK_DATA_API, Selector.DISMISS, Alert._handleDismiss(new Alert())); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME] = Alert._jQueryInterface; $.fn[NAME].Constructor = Alert; $.fn[NAME].noConflict = function () { $.fn[NAME] = JQUERY_NO_CONFLICT; return Alert._jQueryInterface; }; /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$1 = 'button'; var VERSION$1 = '4.3.1'; var DATA_KEY$1 = 'bs.button'; var EVENT_KEY$1 = "." + DATA_KEY$1; var DATA_API_KEY$1 = '.data-api'; var JQUERY_NO_CONFLICT$1 = $.fn[NAME$1]; var ClassName$1 = { ACTIVE: 'active', BUTTON: 'btn', FOCUS: 'focus' }; var Selector$1 = { DATA_TOGGLE_CARROT: '[data-toggle^="button"]', DATA_TOGGLE: '[data-toggle="buttons"]', INPUT: 'input:not([type="hidden"])', ACTIVE: '.active', BUTTON: '.btn' }; var Event$1 = { CLICK_DATA_API: "click" + EVENT_KEY$1 + DATA_API_KEY$1, FOCUS_BLUR_DATA_API: "focus" + EVENT_KEY$1 + DATA_API_KEY$1 + " " + ("blur" + EVENT_KEY$1 + DATA_API_KEY$1) /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Button = /*#__PURE__*/ function () { function Button(element) { this._element = element; } // Getters var _proto = Button.prototype; // Public _proto.toggle = function toggle() { var triggerChangeEvent = true; var addAriaPressed = true; var rootElement = $(this._element).closest(Selector$1.DATA_TOGGLE)[0]; if (rootElement) { var input = this._element.querySelector(Selector$1.INPUT); if (input) { if (input.type === 'radio') { if (input.checked && this._element.classList.contains(ClassName$1.ACTIVE)) { triggerChangeEvent = false; } else { var activeElement = rootElement.querySelector(Selector$1.ACTIVE); if (activeElement) { $(activeElement).removeClass(ClassName$1.ACTIVE); } } } if (triggerChangeEvent) { if (input.hasAttribute('disabled') || rootElement.hasAttribute('disabled') || input.classList.contains('disabled') || rootElement.classList.contains('disabled')) { return; } input.checked = !this._element.classList.contains(ClassName$1.ACTIVE); $(input).trigger('change'); } input.focus(); addAriaPressed = false; } } if (addAriaPressed) { this._element.setAttribute('aria-pressed', !this._element.classList.contains(ClassName$1.ACTIVE)); } if (triggerChangeEvent) { $(this._element).toggleClass(ClassName$1.ACTIVE); } }; _proto.dispose = function dispose() { $.removeData(this._element, DATA_KEY$1); this._element = null; } // Static ; Button._jQueryInterface = function _jQueryInterface(config) { return this.each(function () { var data = $(this).data(DATA_KEY$1); if (!data) { data = new Button(this); $(this).data(DATA_KEY$1, data); } if (config === 'toggle') { data[config](); } }); }; _createClass(Button, null, [{ key: "VERSION", get: function get() { return VERSION$1; } }]); return Button; }(); /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ $(document).on(Event$1.CLICK_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) { event.preventDefault(); var button = event.target; if (!$(button).hasClass(ClassName$1.BUTTON)) { button = $(button).closest(Selector$1.BUTTON); } Button._jQueryInterface.call($(button), 'toggle'); }).on(Event$1.FOCUS_BLUR_DATA_API, Selector$1.DATA_TOGGLE_CARROT, function (event) { var button = $(event.target).closest(Selector$1.BUTTON)[0]; $(button).toggleClass(ClassName$1.FOCUS, /^focus(in)?$/.test(event.type)); }); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME$1] = Button._jQueryInterface; $.fn[NAME$1].Constructor = Button; $.fn[NAME$1].noConflict = function () { $.fn[NAME$1] = JQUERY_NO_CONFLICT$1; return Button._jQueryInterface; }; /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$2 = 'carousel'; var VERSION$2 = '4.3.1'; var DATA_KEY$2 = 'bs.carousel'; var EVENT_KEY$2 = "." + DATA_KEY$2; var DATA_API_KEY$2 = '.data-api'; var JQUERY_NO_CONFLICT$2 = $.fn[NAME$2]; var ARROW_LEFT_KEYCODE = 37; // KeyboardEvent.which value for left arrow key var ARROW_RIGHT_KEYCODE = 39; // KeyboardEvent.which value for right arrow key var TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch var SWIPE_THRESHOLD = 40; var Default = { interval: 5000, keyboard: true, slide: false, pause: 'hover', wrap: true, touch: true }; var DefaultType = { interval: '(number|boolean)', keyboard: 'boolean', slide: '(boolean|string)', pause: '(string|boolean)', wrap: 'boolean', touch: 'boolean' }; var Direction = { NEXT: 'next', PREV: 'prev', LEFT: 'left', RIGHT: 'right' }; var Event$2 = { SLIDE: "slide" + EVENT_KEY$2, SLID: "slid" + EVENT_KEY$2, KEYDOWN: "keydown" + EVENT_KEY$2, MOUSEENTER: "mouseenter" + EVENT_KEY$2, MOUSELEAVE: "mouseleave" + EVENT_KEY$2, TOUCHSTART: "touchstart" + EVENT_KEY$2, TOUCHMOVE: "touchmove" + EVENT_KEY$2, TOUCHEND: "touchend" + EVENT_KEY$2, POINTERDOWN: "pointerdown" + EVENT_KEY$2, POINTERUP: "pointerup" + EVENT_KEY$2, DRAG_START: "dragstart" + EVENT_KEY$2, LOAD_DATA_API: "load" + EVENT_KEY$2 + DATA_API_KEY$2, CLICK_DATA_API: "click" + EVENT_KEY$2 + DATA_API_KEY$2 }; var ClassName$2 = { CAROUSEL: 'carousel', ACTIVE: 'active', SLIDE: 'slide', RIGHT: 'carousel-item-right', LEFT: 'carousel-item-left', NEXT: 'carousel-item-next', PREV: 'carousel-item-prev', ITEM: 'carousel-item', POINTER_EVENT: 'pointer-event' }; var Selector$2 = { ACTIVE: '.active', ACTIVE_ITEM: '.active.carousel-item', ITEM: '.carousel-item', ITEM_IMG: '.carousel-item img', NEXT_PREV: '.carousel-item-next, .carousel-item-prev', INDICATORS: '.carousel-indicators', DATA_SLIDE: '[data-slide], [data-slide-to]', DATA_RIDE: '[data-ride="carousel"]' }; var PointerType = { TOUCH: 'touch', PEN: 'pen' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Carousel = /*#__PURE__*/ function () { function Carousel(element, config) { this._items = null; this._interval = null; this._activeElement = null; this._isPaused = false; this._isSliding = false; this.touchTimeout = null; this.touchStartX = 0; this.touchDeltaX = 0; this._config = this._getConfig(config); this._element = element; this._indicatorsElement = this._element.querySelector(Selector$2.INDICATORS); this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent); this._addEventListeners(); } // Getters var _proto = Carousel.prototype; // Public _proto.next = function next() { if (!this._isSliding) { this._slide(Direction.NEXT); } }; _proto.nextWhenVisible = function nextWhenVisible() { // Don't call next when the page isn't visible // or the carousel or its parent isn't visible if (!document.hidden && $(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden') { this.next(); } }; _proto.prev = function prev() { if (!this._isSliding) { this._slide(Direction.PREV); } }; _proto.pause = function pause(event) { if (!event) { this._isPaused = true; } if (this._element.querySelector(Selector$2.NEXT_PREV)) { Util.triggerTransitionEnd(this._element); this.cycle(true); } clearInterval(this._interval); this._interval = null; }; _proto.cycle = function cycle(event) { if (!event) { this._isPaused = false; } if (this._interval) { clearInterval(this._interval); this._interval = null; } if (this._config.interval && !this._isPaused) { this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval); } }; _proto.to = function to(index) { var _this = this; this._activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM); var activeIndex = this._getItemIndex(this._activeElement); if (index > this._items.length - 1 || index < 0) { return; } if (this._isSliding) { $(this._element).one(Event$2.SLID, function () { return _this.to(index); }); return; } if (activeIndex === index) { this.pause(); this.cycle(); return; } var direction = index > activeIndex ? Direction.NEXT : Direction.PREV; this._slide(direction, this._items[index]); }; _proto.dispose = function dispose() { $(this._element).off(EVENT_KEY$2); $.removeData(this._element, DATA_KEY$2); this._items = null; this._config = null; this._element = null; this._interval = null; this._isPaused = null; this._isSliding = null; this._activeElement = null; this._indicatorsElement = null; } // Private ; _proto._getConfig = function _getConfig(config) { config = _objectSpread({}, Default, config); Util.typeCheckConfig(NAME$2, config, DefaultType); return config; }; _proto._handleSwipe = function _handleSwipe() { var absDeltax = Math.abs(this.touchDeltaX); if (absDeltax <= SWIPE_THRESHOLD) { return; } var direction = absDeltax / this.touchDeltaX; // swipe left if (direction > 0) { this.prev(); } // swipe right if (direction < 0) { this.next(); } }; _proto._addEventListeners = function _addEventListeners() { var _this2 = this; if (this._config.keyboard) { $(this._element).on(Event$2.KEYDOWN, function (event) { return _this2._keydown(event); }); } if (this._config.pause === 'hover') { $(this._element).on(Event$2.MOUSEENTER, function (event) { return _this2.pause(event); }).on(Event$2.MOUSELEAVE, function (event) { return _this2.cycle(event); }); } if (this._config.touch) { this._addTouchEventListeners(); } }; _proto._addTouchEventListeners = function _addTouchEventListeners() { var _this3 = this; if (!this._touchSupported) { return; } var start = function start(event) { if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { _this3.touchStartX = event.originalEvent.clientX; } else if (!_this3._pointerEvent) { _this3.touchStartX = event.originalEvent.touches[0].clientX; } }; var move = function move(event) { // ensure swiping with one touch and not pinching if (event.originalEvent.touches && event.originalEvent.touches.length > 1) { _this3.touchDeltaX = 0; } else { _this3.touchDeltaX = event.originalEvent.touches[0].clientX - _this3.touchStartX; } }; var end = function end(event) { if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) { _this3.touchDeltaX = event.originalEvent.clientX - _this3.touchStartX; } _this3._handleSwipe(); if (_this3._config.pause === 'hover') { // If it's a touch-enabled device, mouseenter/leave are fired as // part of the mouse compatibility events on first tap - the carousel // would stop cycling until user tapped out of it; // here, we listen for touchend, explicitly pause the carousel // (as if it's the second time we tap on it, mouseenter compat event // is NOT fired) and after a timeout (to allow for mouse compatibility // events to fire) we explicitly restart cycling _this3.pause(); if (_this3.touchTimeout) { clearTimeout(_this3.touchTimeout); } _this3.touchTimeout = setTimeout(function (event) { return _this3.cycle(event); }, TOUCHEVENT_COMPAT_WAIT + _this3._config.interval); } }; $(this._element.querySelectorAll(Selector$2.ITEM_IMG)).on(Event$2.DRAG_START, function (e) { return e.preventDefault(); }); if (this._pointerEvent) { $(this._element).on(Event$2.POINTERDOWN, function (event) { return start(event); }); $(this._element).on(Event$2.POINTERUP, function (event) { return end(event); }); this._element.classList.add(ClassName$2.POINTER_EVENT); } else { $(this._element).on(Event$2.TOUCHSTART, function (event) { return start(event); }); $(this._element).on(Event$2.TOUCHMOVE, function (event) { return move(event); }); $(this._element).on(Event$2.TOUCHEND, function (event) { return end(event); }); } }; _proto._keydown = function _keydown(event) { if (/input|textarea/i.test(event.target.tagName)) { return; } switch (event.which) { case ARROW_LEFT_KEYCODE: event.preventDefault(); this.prev(); break; case ARROW_RIGHT_KEYCODE: event.preventDefault(); this.next(); break; default: } }; _proto._getItemIndex = function _getItemIndex(element) { this._items = element && element.parentNode ? [].slice.call(element.parentNode.querySelectorAll(Selector$2.ITEM)) : []; return this._items.indexOf(element); }; _proto._getItemByDirection = function _getItemByDirection(direction, activeElement) { var isNextDirection = direction === Direction.NEXT; var isPrevDirection = direction === Direction.PREV; var activeIndex = this._getItemIndex(activeElement); var lastItemIndex = this._items.length - 1; var isGoingToWrap = isPrevDirection && activeIndex === 0 || isNextDirection && activeIndex === lastItemIndex; if (isGoingToWrap && !this._config.wrap) { return activeElement; } var delta = direction === Direction.PREV ? -1 : 1; var itemIndex = (activeIndex + delta) % this._items.length; return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex]; }; _proto._triggerSlideEvent = function _triggerSlideEvent(relatedTarget, eventDirectionName) { var targetIndex = this._getItemIndex(relatedTarget); var fromIndex = this._getItemIndex(this._element.querySelector(Selector$2.ACTIVE_ITEM)); var slideEvent = $.Event(Event$2.SLIDE, { relatedTarget: relatedTarget, direction: eventDirectionName, from: fromIndex, to: targetIndex }); $(this._element).trigger(slideEvent); return slideEvent; }; _proto._setActiveIndicatorElement = function _setActiveIndicatorElement(element) { if (this._indicatorsElement) { var indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector$2.ACTIVE)); $(indicators).removeClass(ClassName$2.ACTIVE); var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)]; if (nextIndicator) { $(nextIndicator).addClass(ClassName$2.ACTIVE); } } }; _proto._slide = function _slide(direction, element) { var _this4 = this; var activeElement = this._element.querySelector(Selector$2.ACTIVE_ITEM); var activeElementIndex = this._getItemIndex(activeElement); var nextElement = element || activeElement && this._getItemByDirection(direction, activeElement); var nextElementIndex = this._getItemIndex(nextElement); var isCycling = Boolean(this._interval); var directionalClassName; var orderClassName; var eventDirectionName; if (direction === Direction.NEXT) { directionalClassName = ClassName$2.LEFT; orderClassName = ClassName$2.NEXT; eventDirectionName = Direction.LEFT; } else { directionalClassName = ClassName$2.RIGHT; orderClassName = ClassName$2.PREV; eventDirectionName = Direction.RIGHT; } if (nextElement && $(nextElement).hasClass(ClassName$2.ACTIVE)) { this._isSliding = false; return; } var slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName); if (slideEvent.isDefaultPrevented()) { return; } if (!activeElement || !nextElement) { // Some weirdness is happening, so we bail return; } this._isSliding = true; if (isCycling) { this.pause(); } this._setActiveIndicatorElement(nextElement); var slidEvent = $.Event(Event$2.SLID, { relatedTarget: nextElement, direction: eventDirectionName, from: activeElementIndex, to: nextElementIndex }); if ($(this._element).hasClass(ClassName$2.SLIDE)) { $(nextElement).addClass(orderClassName); Util.reflow(nextElement); $(activeElement).addClass(directionalClassName); $(nextElement).addClass(directionalClassName); var nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10); if (nextElementInterval) { this._config.defaultInterval = this._config.defaultInterval || this._config.interval; this._config.interval = nextElementInterval; } else { this._config.interval = this._config.defaultInterval || this._config.interval; } var transitionDuration = Util.getTransitionDurationFromElement(activeElement); $(activeElement).one(Util.TRANSITION_END, function () { $(nextElement).removeClass(directionalClassName + " " + orderClassName).addClass(ClassName$2.ACTIVE); $(activeElement).removeClass(ClassName$2.ACTIVE + " " + orderClassName + " " + directionalClassName); _this4._isSliding = false; setTimeout(function () { return $(_this4._element).trigger(slidEvent); }, 0); }).emulateTransitionEnd(transitionDuration); } else { $(activeElement).removeClass(ClassName$2.ACTIVE); $(nextElement).addClass(ClassName$2.ACTIVE); this._isSliding = false; $(this._element).trigger(slidEvent); } if (isCycling) { this.cycle(); } } // Static ; Carousel._jQueryInterface = function _jQueryInterface(config) { return this.each(function () { var data = $(this).data(DATA_KEY$2); var _config = _objectSpread({}, Default, $(this).data()); if (typeof config === 'object') { _config = _objectSpread({}, _config, config); } var action = typeof config === 'string' ? config : _config.slide; if (!data) { data = new Carousel(this, _config); $(this).data(DATA_KEY$2, data); } if (typeof config === 'number') { data.to(config); } else if (typeof action === 'string') { if (typeof data[action] === 'undefined') { throw new TypeError("No method named \"" + action + "\""); } data[action](); } else if (_config.interval && _config.ride) { data.pause(); data.cycle(); } }); }; Carousel._dataApiClickHandler = function _dataApiClickHandler(event) { var selector = Util.getSelectorFromElement(this); if (!selector) { return; } var target = $(selector)[0]; if (!target || !$(target).hasClass(ClassName$2.CAROUSEL)) { return; } var config = _objectSpread({}, $(target).data(), $(this).data()); var slideIndex = this.getAttribute('data-slide-to'); if (slideIndex) { config.interval = false; } Carousel._jQueryInterface.call($(target), config); if (slideIndex) { $(target).data(DATA_KEY$2).to(slideIndex); } event.preventDefault(); }; _createClass(Carousel, null, [{ key: "VERSION", get: function get() { return VERSION$2; } }, { key: "Default", get: function get() { return Default; } }]); return Carousel; }(); /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ $(document).on(Event$2.CLICK_DATA_API, Selector$2.DATA_SLIDE, Carousel._dataApiClickHandler); $(window).on(Event$2.LOAD_DATA_API, function () { var carousels = [].slice.call(document.querySelectorAll(Selector$2.DATA_RIDE)); for (var i = 0, len = carousels.length; i < len; i++) { var $carousel = $(carousels[i]); Carousel._jQueryInterface.call($carousel, $carousel.data()); } }); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME$2] = Carousel._jQueryInterface; $.fn[NAME$2].Constructor = Carousel; $.fn[NAME$2].noConflict = function () { $.fn[NAME$2] = JQUERY_NO_CONFLICT$2; return Carousel._jQueryInterface; }; /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$3 = 'collapse'; var VERSION$3 = '4.3.1'; var DATA_KEY$3 = 'bs.collapse'; var EVENT_KEY$3 = "." + DATA_KEY$3; var DATA_API_KEY$3 = '.data-api'; var JQUERY_NO_CONFLICT$3 = $.fn[NAME$3]; var Default$1 = { toggle: true, parent: '' }; var DefaultType$1 = { toggle: 'boolean', parent: '(string|element)' }; var Event$3 = { SHOW: "show" + EVENT_KEY$3, SHOWN: "shown" + EVENT_KEY$3, HIDE: "hide" + EVENT_KEY$3, HIDDEN: "hidden" + EVENT_KEY$3, CLICK_DATA_API: "click" + EVENT_KEY$3 + DATA_API_KEY$3 }; var ClassName$3 = { SHOW: 'show', COLLAPSE: 'collapse', COLLAPSING: 'collapsing', COLLAPSED: 'collapsed' }; var Dimension = { WIDTH: 'width', HEIGHT: 'height' }; var Selector$3 = { ACTIVES: '.show, .collapsing', DATA_TOGGLE: '[data-toggle="collapse"]' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Collapse = /*#__PURE__*/ function () { function Collapse(element, config) { this._isTransitioning = false; this._element = element; this._config = this._getConfig(config); this._triggerArray = [].slice.call(document.querySelectorAll("[data-toggle=\"collapse\"][href=\"#" + element.id + "\"]," + ("[data-toggle=\"collapse\"][data-target=\"#" + element.id + "\"]"))); var toggleList = [].slice.call(document.querySelectorAll(Selector$3.DATA_TOGGLE)); for (var i = 0, len = toggleList.length; i < len; i++) { var elem = toggleList[i]; var selector = Util.getSelectorFromElement(elem); var filterElement = [].slice.call(document.querySelectorAll(selector)).filter(function (foundElem) { return foundElem === element; }); if (selector !== null && filterElement.length > 0) { this._selector = selector; this._triggerArray.push(elem); } } this._parent = this._config.parent ? this._getParent() : null; if (!this._config.parent) { this._addAriaAndCollapsedClass(this._element, this._triggerArray); } if (this._config.toggle) { this.toggle(); } } // Getters var _proto = Collapse.prototype; // Public _proto.toggle = function toggle() { if ($(this._element).hasClass(ClassName$3.SHOW)) { this.hide(); } else { this.show(); } }; _proto.show = function show() { var _this = this; if (this._isTransitioning || $(this._element).hasClass(ClassName$3.SHOW)) { return; } var actives; var activesData; if (this._parent) { actives = [].slice.call(this._parent.querySelectorAll(Selector$3.ACTIVES)).filter(function (elem) { if (typeof _this._config.parent === 'string') { return elem.getAttribute('data-parent') === _this._config.parent; } return elem.classList.contains(ClassName$3.COLLAPSE); }); if (actives.length === 0) { actives = null; } } if (actives) { activesData = $(actives).not(this._selector).data(DATA_KEY$3); if (activesData && activesData._isTransitioning) { return; } } var startEvent = $.Event(Event$3.SHOW); $(this._element).trigger(startEvent); if (startEvent.isDefaultPrevented()) { return; } if (actives) { Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide'); if (!activesData) { $(actives).data(DATA_KEY$3, null); } } var dimension = this._getDimension(); $(this._element).removeClass(ClassName$3.COLLAPSE).addClass(ClassName$3.COLLAPSING); this._element.style[dimension] = 0; if (this._triggerArray.length) { $(this._triggerArray).removeClass(ClassName$3.COLLAPSED).attr('aria-expanded', true); } this.setTransitioning(true); var complete = function complete() { $(_this._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).addClass(ClassName$3.SHOW); _this._element.style[dimension] = ''; _this.setTransitioning(false); $(_this._element).trigger(Event$3.SHOWN); }; var capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); var scrollSize = "scroll" + capitalizedDimension; var transitionDuration = Util.getTransitionDurationFromElement(this._element); $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); this._element.style[dimension] = this._element[scrollSize] + "px"; }; _proto.hide = function hide() { var _this2 = this; if (this._isTransitioning || !$(this._element).hasClass(ClassName$3.SHOW)) { return; } var startEvent = $.Event(Event$3.HIDE); $(this._element).trigger(startEvent); if (startEvent.isDefaultPrevented()) { return; } var dimension = this._getDimension(); this._element.style[dimension] = this._element.getBoundingClientRect()[dimension] + "px"; Util.reflow(this._element); $(this._element).addClass(ClassName$3.COLLAPSING).removeClass(ClassName$3.COLLAPSE).removeClass(ClassName$3.SHOW); var triggerArrayLength = this._triggerArray.length; if (triggerArrayLength > 0) { for (var i = 0; i < triggerArrayLength; i++) { var trigger = this._triggerArray[i]; var selector = Util.getSelectorFromElement(trigger); if (selector !== null) { var $elem = $([].slice.call(document.querySelectorAll(selector))); if (!$elem.hasClass(ClassName$3.SHOW)) { $(trigger).addClass(ClassName$3.COLLAPSED).attr('aria-expanded', false); } } } } this.setTransitioning(true); var complete = function complete() { _this2.setTransitioning(false); $(_this2._element).removeClass(ClassName$3.COLLAPSING).addClass(ClassName$3.COLLAPSE).trigger(Event$3.HIDDEN); }; this._element.style[dimension] = ''; var transitionDuration = Util.getTransitionDurationFromElement(this._element); $(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); }; _proto.setTransitioning = function setTransitioning(isTransitioning) { this._isTransitioning = isTransitioning; }; _proto.dispose = function dispose() { $.removeData(this._element, DATA_KEY$3); this._config = null; this._parent = null; this._element = null; this._triggerArray = null; this._isTransitioning = null; } // Private ; _proto._getConfig = function _getConfig(config) { config = _objectSpread({}, Default$1, config); config.toggle = Boolean(config.toggle); // Coerce string values Util.typeCheckConfig(NAME$3, config, DefaultType$1); return config; }; _proto._getDimension = function _getDimension() { var hasWidth = $(this._element).hasClass(Dimension.WIDTH); return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT; }; _proto._getParent = function _getParent() { var _this3 = this; var parent; if (Util.isElement(this._config.parent)) { parent = this._config.parent; // It's a jQuery object if (typeof this._config.parent.jquery !== 'undefined') { parent = this._config.parent[0]; } } else { parent = document.querySelector(this._config.parent); } var selector = "[data-toggle=\"collapse\"][data-parent=\"" + this._config.parent + "\"]"; var children = [].slice.call(parent.querySelectorAll(selector)); $(children).each(function (i, element) { _this3._addAriaAndCollapsedClass(Collapse._getTargetFromElement(element), [element]); }); return parent; }; _proto._addAriaAndCollapsedClass = function _addAriaAndCollapsedClass(element, triggerArray) { var isOpen = $(element).hasClass(ClassName$3.SHOW); if (triggerArray.length) { $(triggerArray).toggleClass(ClassName$3.COLLAPSED, !isOpen).attr('aria-expanded', isOpen); } } // Static ; Collapse._getTargetFromElement = function _getTargetFromElement(element) { var selector = Util.getSelectorFromElement(element); return selector ? document.querySelector(selector) : null; }; Collapse._jQueryInterface = function _jQueryInterface(config) { return this.each(function () { var $this = $(this); var data = $this.data(DATA_KEY$3); var _config = _objectSpread({}, Default$1, $this.data(), typeof config === 'object' && config ? config : {}); if (!data && _config.toggle && /show|hide/.test(config)) { _config.toggle = false; } if (!data) { data = new Collapse(this, _config); $this.data(DATA_KEY$3, data); } if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError("No method named \"" + config + "\""); } data[config](); } }); }; _createClass(Collapse, null, [{ key: "VERSION", get: function get() { return VERSION$3; } }, { key: "Default", get: function get() { return Default$1; } }]); return Collapse; }(); /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ $(document).on(Event$3.CLICK_DATA_API, Selector$3.DATA_TOGGLE, function (event) { // preventDefault only for elements (which change the URL) not inside the collapsible element if (event.currentTarget.tagName === 'A') { event.preventDefault(); } var $trigger = $(this); var selector = Util.getSelectorFromElement(this); var selectors = [].slice.call(document.querySelectorAll(selector)); $(selectors).each(function () { var $target = $(this); var data = $target.data(DATA_KEY$3); var config = data ? 'toggle' : $trigger.data(); Collapse._jQueryInterface.call($target, config); }); }); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME$3] = Collapse._jQueryInterface; $.fn[NAME$3].Constructor = Collapse; $.fn[NAME$3].noConflict = function () { $.fn[NAME$3] = JQUERY_NO_CONFLICT$3; return Collapse._jQueryInterface; }; /**! * @fileOverview Kickass library to create and place poppers near their reference elements. * @version 1.14.7 * @license * Copyright (c) 2016 Federico Zivolo and contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; var longerTimeoutBrowsers = ['Edge', 'Trident', 'Firefox']; var timeoutDuration = 0; for (var i = 0; i < longerTimeoutBrowsers.length; i += 1) { if (isBrowser && navigator.userAgent.indexOf(longerTimeoutBrowsers[i]) >= 0) { timeoutDuration = 1; break; } } function microtaskDebounce(fn) { var called = false; return function () { if (called) { return; } called = true; window.Promise.resolve().then(function () { called = false; fn(); }); }; } function taskDebounce(fn) { var scheduled = false; return function () { if (!scheduled) { scheduled = true; setTimeout(function () { scheduled = false; fn(); }, timeoutDuration); } }; } var supportsMicroTasks = isBrowser && window.Promise; /** * Create a debounced version of a method, that's asynchronously deferred * but called in the minimum time possible. * * @method * @memberof Popper.Utils * @argument {Function} fn * @returns {Function} */ var debounce = supportsMicroTasks ? microtaskDebounce : taskDebounce; /** * Check if the given variable is a function * @method * @memberof Popper.Utils * @argument {Any} functionToCheck - variable to check * @returns {Boolean} answer to: is a function? */ function isFunction(functionToCheck) { var getType = {}; return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; } /** * Get CSS computed property of the given element * @method * @memberof Popper.Utils * @argument {Eement} element * @argument {String} property */ function getStyleComputedProperty(element, property) { if (element.nodeType !== 1) { return []; } // NOTE: 1 DOM access here var window = element.ownerDocument.defaultView; var css = window.getComputedStyle(element, null); return property ? css[property] : css; } /** * Returns the parentNode or the host of the element * @method * @memberof Popper.Utils * @argument {Element} element * @returns {Element} parent */ function getParentNode(element) { if (element.nodeName === 'HTML') { return element; } return element.parentNode || element.host; } /** * Returns the scrolling parent of the given element * @method * @memberof Popper.Utils * @argument {Element} element * @returns {Element} scroll parent */ function getScrollParent(element) { // Return body, `getScroll` will take care to get the correct `scrollTop` from it if (!element) { return document.body; } switch (element.nodeName) { case 'HTML': case 'BODY': return element.ownerDocument.body; case '#document': return element.body; } // Firefox want us to check `-x` and `-y` variations as well var _getStyleComputedProp = getStyleComputedProperty(element), overflow = _getStyleComputedProp.overflow, overflowX = _getStyleComputedProp.overflowX, overflowY = _getStyleComputedProp.overflowY; if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) { return element; } return getScrollParent(getParentNode(element)); } var isIE11 = isBrowser && !!(window.MSInputMethodContext && document.documentMode); var isIE10 = isBrowser && /MSIE 10/.test(navigator.userAgent); /** * Determines if the browser is Internet Explorer * @method * @memberof Popper.Utils * @param {Number} version to check * @returns {Boolean} isIE */ function isIE(version) { if (version === 11) { return isIE11; } if (version === 10) { return isIE10; } return isIE11 || isIE10; } /** * Returns the offset parent of the given element * @method * @memberof Popper.Utils * @argument {Element} element * @returns {Element} offset parent */ function getOffsetParent(element) { if (!element) { return document.documentElement; } var noOffsetParent = isIE(10) ? document.body : null; // NOTE: 1 DOM access here var offsetParent = element.offsetParent || null; // Skip hidden elements which don't have an offsetParent while (offsetParent === noOffsetParent && element.nextElementSibling) { offsetParent = (element = element.nextElementSibling).offsetParent; } var nodeName = offsetParent && offsetParent.nodeName; if (!nodeName || nodeName === 'BODY' || nodeName === 'HTML') { return element ? element.ownerDocument.documentElement : document.documentElement; } // .offsetParent will return the closest TH, TD or TABLE in case // no offsetParent is present, I hate this job... if (['TH', 'TD', 'TABLE'].indexOf(offsetParent.nodeName) !== -1 && getStyleComputedProperty(offsetParent, 'position') === 'static') { return getOffsetParent(offsetParent); } return offsetParent; } function isOffsetContainer(element) { var nodeName = element.nodeName; if (nodeName === 'BODY') { return false; } return nodeName === 'HTML' || getOffsetParent(element.firstElementChild) === element; } /** * Finds the root node (document, shadowDOM root) of the given element * @method * @memberof Popper.Utils * @argument {Element} node * @returns {Element} root node */ function getRoot(node) { if (node.parentNode !== null) { return getRoot(node.parentNode); } return node; } /** * Finds the offset parent common to the two provided nodes * @method * @memberof Popper.Utils * @argument {Element} element1 * @argument {Element} element2 * @returns {Element} common offset parent */ function findCommonOffsetParent(element1, element2) { // This check is needed to avoid errors in case one of the elements isn't defined for any reason if (!element1 || !element1.nodeType || !element2 || !element2.nodeType) { return document.documentElement; } // Here we make sure to give as "start" the element that comes first in the DOM var order = element1.compareDocumentPosition(element2) & Node.DOCUMENT_POSITION_FOLLOWING; var start = order ? element1 : element2; var end = order ? element2 : element1; // Get common ancestor container var range = document.createRange(); range.setStart(start, 0); range.setEnd(end, 0); var commonAncestorContainer = range.commonAncestorContainer; // Both nodes are inside #document if (element1 !== commonAncestorContainer && element2 !== commonAncestorContainer || start.contains(end)) { if (isOffsetContainer(commonAncestorContainer)) { return commonAncestorContainer; } return getOffsetParent(commonAncestorContainer); } // one of the nodes is inside shadowDOM, find which one var element1root = getRoot(element1); if (element1root.host) { return findCommonOffsetParent(element1root.host, element2); } else { return findCommonOffsetParent(element1, getRoot(element2).host); } } /** * Gets the scroll value of the given element in the given side (top and left) * @method * @memberof Popper.Utils * @argument {Element} element * @argument {String} side `top` or `left` * @returns {number} amount of scrolled pixels */ function getScroll(element) { var side = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'top'; var upperSide = side === 'top' ? 'scrollTop' : 'scrollLeft'; var nodeName = element.nodeName; if (nodeName === 'BODY' || nodeName === 'HTML') { var html = element.ownerDocument.documentElement; var scrollingElement = element.ownerDocument.scrollingElement || html; return scrollingElement[upperSide]; } return element[upperSide]; } /* * Sum or subtract the element scroll values (left and top) from a given rect object * @method * @memberof Popper.Utils * @param {Object} rect - Rect object you want to change * @param {HTMLElement} element - The element from the function reads the scroll values * @param {Boolean} subtract - set to true if you want to subtract the scroll values * @return {Object} rect - The modifier rect object */ function includeScroll(rect, element) { var subtract = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var scrollTop = getScroll(element, 'top'); var scrollLeft = getScroll(element, 'left'); var modifier = subtract ? -1 : 1; rect.top += scrollTop * modifier; rect.bottom += scrollTop * modifier; rect.left += scrollLeft * modifier; rect.right += scrollLeft * modifier; return rect; } /* * Helper to detect borders of a given element * @method * @memberof Popper.Utils * @param {CSSStyleDeclaration} styles * Result of `getStyleComputedProperty` on the given element * @param {String} axis - `x` or `y` * @return {number} borders - The borders size of the given axis */ function getBordersSize(styles, axis) { var sideA = axis === 'x' ? 'Left' : 'Top'; var sideB = sideA === 'Left' ? 'Right' : 'Bottom'; return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10); } function getSize(axis, body, html, computedStyle) { return Math.max(body['offset' + axis], body['scroll' + axis], html['client' + axis], html['offset' + axis], html['scroll' + axis], isIE(10) ? parseInt(html['offset' + axis]) + parseInt(computedStyle['margin' + (axis === 'Height' ? 'Top' : 'Left')]) + parseInt(computedStyle['margin' + (axis === 'Height' ? 'Bottom' : 'Right')]) : 0); } function getWindowSizes(document) { var body = document.body; var html = document.documentElement; var computedStyle = isIE(10) && getComputedStyle(html); return { height: getSize('Height', body, html, computedStyle), width: getSize('Width', body, html, computedStyle) }; } var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var defineProperty = function (obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /** * Given element offsets, generate an output similar to getBoundingClientRect * @method * @memberof Popper.Utils * @argument {Object} offsets * @returns {Object} ClientRect like output */ function getClientRect(offsets) { return _extends({}, offsets, { right: offsets.left + offsets.width, bottom: offsets.top + offsets.height }); } /** * Get bounding client rect of given element * @method * @memberof Popper.Utils * @param {HTMLElement} element * @return {Object} client rect */ function getBoundingClientRect(element) { var rect = {}; // IE10 10 FIX: Please, don't ask, the element isn't // considered in DOM in some circumstances... // This isn't reproducible in IE10 compatibility mode of IE11 try { if (isIE(10)) { rect = element.getBoundingClientRect(); var scrollTop = getScroll(element, 'top'); var scrollLeft = getScroll(element, 'left'); rect.top += scrollTop; rect.left += scrollLeft; rect.bottom += scrollTop; rect.right += scrollLeft; } else { rect = element.getBoundingClientRect(); } } catch (e) {} var result = { left: rect.left, top: rect.top, width: rect.right - rect.left, height: rect.bottom - rect.top }; // subtract scrollbar size from sizes var sizes = element.nodeName === 'HTML' ? getWindowSizes(element.ownerDocument) : {}; var width = sizes.width || element.clientWidth || result.right - result.left; var height = sizes.height || element.clientHeight || result.bottom - result.top; var horizScrollbar = element.offsetWidth - width; var vertScrollbar = element.offsetHeight - height; // if an hypothetical scrollbar is detected, we must be sure it's not a `border` // we make this check conditional for performance reasons if (horizScrollbar || vertScrollbar) { var styles = getStyleComputedProperty(element); horizScrollbar -= getBordersSize(styles, 'x'); vertScrollbar -= getBordersSize(styles, 'y'); result.width -= horizScrollbar; result.height -= vertScrollbar; } return getClientRect(result); } function getOffsetRectRelativeToArbitraryNode(children, parent) { var fixedPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var isIE10 = isIE(10); var isHTML = parent.nodeName === 'HTML'; var childrenRect = getBoundingClientRect(children); var parentRect = getBoundingClientRect(parent); var scrollParent = getScrollParent(children); var styles = getStyleComputedProperty(parent); var borderTopWidth = parseFloat(styles.borderTopWidth, 10); var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10); // In cases where the parent is fixed, we must ignore negative scroll in offset calc if (fixedPosition && isHTML) { parentRect.top = Math.max(parentRect.top, 0); parentRect.left = Math.max(parentRect.left, 0); } var offsets = getClientRect({ top: childrenRect.top - parentRect.top - borderTopWidth, left: childrenRect.left - parentRect.left - borderLeftWidth, width: childrenRect.width, height: childrenRect.height }); offsets.marginTop = 0; offsets.marginLeft = 0; // Subtract margins of documentElement in case it's being used as parent // we do this only on HTML because it's the only element that behaves // differently when margins are applied to it. The margins are included in // the box of the documentElement, in the other cases not. if (!isIE10 && isHTML) { var marginTop = parseFloat(styles.marginTop, 10); var marginLeft = parseFloat(styles.marginLeft, 10); offsets.top -= borderTopWidth - marginTop; offsets.bottom -= borderTopWidth - marginTop; offsets.left -= borderLeftWidth - marginLeft; offsets.right -= borderLeftWidth - marginLeft; // Attach marginTop and marginLeft because in some circumstances we may need them offsets.marginTop = marginTop; offsets.marginLeft = marginLeft; } if (isIE10 && !fixedPosition ? parent.contains(scrollParent) : parent === scrollParent && scrollParent.nodeName !== 'BODY') { offsets = includeScroll(offsets, parent); } return offsets; } function getViewportOffsetRectRelativeToArtbitraryNode(element) { var excludeScroll = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var html = element.ownerDocument.documentElement; var relativeOffset = getOffsetRectRelativeToArbitraryNode(element, html); var width = Math.max(html.clientWidth, window.innerWidth || 0); var height = Math.max(html.clientHeight, window.innerHeight || 0); var scrollTop = !excludeScroll ? getScroll(html) : 0; var scrollLeft = !excludeScroll ? getScroll(html, 'left') : 0; var offset = { top: scrollTop - relativeOffset.top + relativeOffset.marginTop, left: scrollLeft - relativeOffset.left + relativeOffset.marginLeft, width: width, height: height }; return getClientRect(offset); } /** * Check if the given element is fixed or is inside a fixed parent * @method * @memberof Popper.Utils * @argument {Element} element * @argument {Element} customContainer * @returns {Boolean} answer to "isFixed?" */ function isFixed(element) { var nodeName = element.nodeName; if (nodeName === 'BODY' || nodeName === 'HTML') { return false; } if (getStyleComputedProperty(element, 'position') === 'fixed') { return true; } var parentNode = getParentNode(element); if (!parentNode) { return false; } return isFixed(parentNode); } /** * Finds the first parent of an element that has a transformed property defined * @method * @memberof Popper.Utils * @argument {Element} element * @returns {Element} first transformed parent or documentElement */ function getFixedPositionOffsetParent(element) { // This check is needed to avoid errors in case one of the elements isn't defined for any reason if (!element || !element.parentElement || isIE()) { return document.documentElement; } var el = element.parentElement; while (el && getStyleComputedProperty(el, 'transform') === 'none') { el = el.parentElement; } return el || document.documentElement; } /** * Computed the boundaries limits and return them * @method * @memberof Popper.Utils * @param {HTMLElement} popper * @param {HTMLElement} reference * @param {number} padding * @param {HTMLElement} boundariesElement - Element used to define the boundaries * @param {Boolean} fixedPosition - Is in fixed position mode * @returns {Object} Coordinates of the boundaries */ function getBoundaries(popper, reference, padding, boundariesElement) { var fixedPosition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; // NOTE: 1 DOM access here var boundaries = { top: 0, left: 0 }; var offsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference); // Handle viewport case if (boundariesElement === 'viewport') { boundaries = getViewportOffsetRectRelativeToArtbitraryNode(offsetParent, fixedPosition); } else { // Handle other cases based on DOM element used as boundaries var boundariesNode = void 0; if (boundariesElement === 'scrollParent') { boundariesNode = getScrollParent(getParentNode(reference)); if (boundariesNode.nodeName === 'BODY') { boundariesNode = popper.ownerDocument.documentElement; } } else if (boundariesElement === 'window') { boundariesNode = popper.ownerDocument.documentElement; } else { boundariesNode = boundariesElement; } var offsets = getOffsetRectRelativeToArbitraryNode(boundariesNode, offsetParent, fixedPosition); // In case of HTML, we need a different computation if (boundariesNode.nodeName === 'HTML' && !isFixed(offsetParent)) { var _getWindowSizes = getWindowSizes(popper.ownerDocument), height = _getWindowSizes.height, width = _getWindowSizes.width; boundaries.top += offsets.top - offsets.marginTop; boundaries.bottom = height + offsets.top; boundaries.left += offsets.left - offsets.marginLeft; boundaries.right = width + offsets.left; } else { // for all the other DOM elements, this one is good boundaries = offsets; } } // Add paddings padding = padding || 0; var isPaddingNumber = typeof padding === 'number'; boundaries.left += isPaddingNumber ? padding : padding.left || 0; boundaries.top += isPaddingNumber ? padding : padding.top || 0; boundaries.right -= isPaddingNumber ? padding : padding.right || 0; boundaries.bottom -= isPaddingNumber ? padding : padding.bottom || 0; return boundaries; } function getArea(_ref) { var width = _ref.width, height = _ref.height; return width * height; } /** * Utility used to transform the `auto` placement to the placement with more * available space. * @method * @memberof Popper.Utils * @argument {Object} data - The data object generated by update method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function computeAutoPlacement(placement, refRect, popper, reference, boundariesElement) { var padding = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0; if (placement.indexOf('auto') === -1) { return placement; } var boundaries = getBoundaries(popper, reference, padding, boundariesElement); var rects = { top: { width: boundaries.width, height: refRect.top - boundaries.top }, right: { width: boundaries.right - refRect.right, height: boundaries.height }, bottom: { width: boundaries.width, height: boundaries.bottom - refRect.bottom }, left: { width: refRect.left - boundaries.left, height: boundaries.height } }; var sortedAreas = Object.keys(rects).map(function (key) { return _extends({ key: key }, rects[key], { area: getArea(rects[key]) }); }).sort(function (a, b) { return b.area - a.area; }); var filteredAreas = sortedAreas.filter(function (_ref2) { var width = _ref2.width, height = _ref2.height; return width >= popper.clientWidth && height >= popper.clientHeight; }); var computedPlacement = filteredAreas.length > 0 ? filteredAreas[0].key : sortedAreas[0].key; var variation = placement.split('-')[1]; return computedPlacement + (variation ? '-' + variation : ''); } /** * Get offsets to the reference element * @method * @memberof Popper.Utils * @param {Object} state * @param {Element} popper - the popper element * @param {Element} reference - the reference element (the popper will be relative to this) * @param {Element} fixedPosition - is in fixed position mode * @returns {Object} An object containing the offsets which will be applied to the popper */ function getReferenceOffsets(state, popper, reference) { var fixedPosition = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; var commonOffsetParent = fixedPosition ? getFixedPositionOffsetParent(popper) : findCommonOffsetParent(popper, reference); return getOffsetRectRelativeToArbitraryNode(reference, commonOffsetParent, fixedPosition); } /** * Get the outer sizes of the given element (offset size + margins) * @method * @memberof Popper.Utils * @argument {Element} element * @returns {Object} object containing width and height properties */ function getOuterSizes(element) { var window = element.ownerDocument.defaultView; var styles = window.getComputedStyle(element); var x = parseFloat(styles.marginTop || 0) + parseFloat(styles.marginBottom || 0); var y = parseFloat(styles.marginLeft || 0) + parseFloat(styles.marginRight || 0); var result = { width: element.offsetWidth + y, height: element.offsetHeight + x }; return result; } /** * Get the opposite placement of the given one * @method * @memberof Popper.Utils * @argument {String} placement * @returns {String} flipped placement */ function getOppositePlacement(placement) { var hash = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; return placement.replace(/left|right|bottom|top/g, function (matched) { return hash[matched]; }); } /** * Get offsets to the popper * @method * @memberof Popper.Utils * @param {Object} position - CSS position the Popper will get applied * @param {HTMLElement} popper - the popper element * @param {Object} referenceOffsets - the reference offsets (the popper will be relative to this) * @param {String} placement - one of the valid placement options * @returns {Object} popperOffsets - An object containing the offsets which will be applied to the popper */ function getPopperOffsets(popper, referenceOffsets, placement) { placement = placement.split('-')[0]; // Get popper node sizes var popperRect = getOuterSizes(popper); // Add position, width and height to our offsets object var popperOffsets = { width: popperRect.width, height: popperRect.height }; // depending by the popper placement we have to compute its offsets slightly differently var isHoriz = ['right', 'left'].indexOf(placement) !== -1; var mainSide = isHoriz ? 'top' : 'left'; var secondarySide = isHoriz ? 'left' : 'top'; var measurement = isHoriz ? 'height' : 'width'; var secondaryMeasurement = !isHoriz ? 'height' : 'width'; popperOffsets[mainSide] = referenceOffsets[mainSide] + referenceOffsets[measurement] / 2 - popperRect[measurement] / 2; if (placement === secondarySide) { popperOffsets[secondarySide] = referenceOffsets[secondarySide] - popperRect[secondaryMeasurement]; } else { popperOffsets[secondarySide] = referenceOffsets[getOppositePlacement(secondarySide)]; } return popperOffsets; } /** * Mimics the `find` method of Array * @method * @memberof Popper.Utils * @argument {Array} arr * @argument prop * @argument value * @returns index or -1 */ function find(arr, check) { // use native find if supported if (Array.prototype.find) { return arr.find(check); } // use `filter` to obtain the same behavior of `find` return arr.filter(check)[0]; } /** * Return the index of the matching object * @method * @memberof Popper.Utils * @argument {Array} arr * @argument prop * @argument value * @returns index or -1 */ function findIndex(arr, prop, value) { // use native findIndex if supported if (Array.prototype.findIndex) { return arr.findIndex(function (cur) { return cur[prop] === value; }); } // use `find` + `indexOf` if `findIndex` isn't supported var match = find(arr, function (obj) { return obj[prop] === value; }); return arr.indexOf(match); } /** * Loop trough the list of modifiers and run them in order, * each of them will then edit the data object. * @method * @memberof Popper.Utils * @param {dataObject} data * @param {Array} modifiers * @param {String} ends - Optional modifier name used as stopper * @returns {dataObject} */ function runModifiers(modifiers, data, ends) { var modifiersToRun = ends === undefined ? modifiers : modifiers.slice(0, findIndex(modifiers, 'name', ends)); modifiersToRun.forEach(function (modifier) { if (modifier['function']) { // eslint-disable-line dot-notation console.warn('`modifier.function` is deprecated, use `modifier.fn`!'); } var fn = modifier['function'] || modifier.fn; // eslint-disable-line dot-notation if (modifier.enabled && isFunction(fn)) { // Add properties to offsets to make them a complete clientRect object // we do this before each modifier to make sure the previous one doesn't // mess with these values data.offsets.popper = getClientRect(data.offsets.popper); data.offsets.reference = getClientRect(data.offsets.reference); data = fn(data, modifier); } }); return data; } /** * Updates the position of the popper, computing the new offsets and applying * the new style.
* Prefer `scheduleUpdate` over `update` because of performance reasons. * @method * @memberof Popper */ function update() { // if popper is destroyed, don't perform any further update if (this.state.isDestroyed) { return; } var data = { instance: this, styles: {}, arrowStyles: {}, attributes: {}, flipped: false, offsets: {} }; // compute reference element offsets data.offsets.reference = getReferenceOffsets(this.state, this.popper, this.reference, this.options.positionFixed); // compute auto placement, store placement inside the data object, // modifiers will be able to edit `placement` if needed // and refer to originalPlacement to know the original value data.placement = computeAutoPlacement(this.options.placement, data.offsets.reference, this.popper, this.reference, this.options.modifiers.flip.boundariesElement, this.options.modifiers.flip.padding); // store the computed placement inside `originalPlacement` data.originalPlacement = data.placement; data.positionFixed = this.options.positionFixed; // compute the popper offsets data.offsets.popper = getPopperOffsets(this.popper, data.offsets.reference, data.placement); data.offsets.popper.position = this.options.positionFixed ? 'fixed' : 'absolute'; // run the modifiers data = runModifiers(this.modifiers, data); // the first `update` will call `onCreate` callback // the other ones will call `onUpdate` callback if (!this.state.isCreated) { this.state.isCreated = true; this.options.onCreate(data); } else { this.options.onUpdate(data); } } /** * Helper used to know if the given modifier is enabled. * @method * @memberof Popper.Utils * @returns {Boolean} */ function isModifierEnabled(modifiers, modifierName) { return modifiers.some(function (_ref) { var name = _ref.name, enabled = _ref.enabled; return enabled && name === modifierName; }); } /** * Get the prefixed supported property name * @method * @memberof Popper.Utils * @argument {String} property (camelCase) * @returns {String} prefixed property (camelCase or PascalCase, depending on the vendor prefix) */ function getSupportedPropertyName(property) { var prefixes = [false, 'ms', 'Webkit', 'Moz', 'O']; var upperProp = property.charAt(0).toUpperCase() + property.slice(1); for (var i = 0; i < prefixes.length; i++) { var prefix = prefixes[i]; var toCheck = prefix ? '' + prefix + upperProp : property; if (typeof document.body.style[toCheck] !== 'undefined') { return toCheck; } } return null; } /** * Destroys the popper. * @method * @memberof Popper */ function destroy() { this.state.isDestroyed = true; // touch DOM only if `applyStyle` modifier is enabled if (isModifierEnabled(this.modifiers, 'applyStyle')) { this.popper.removeAttribute('x-placement'); this.popper.style.position = ''; this.popper.style.top = ''; this.popper.style.left = ''; this.popper.style.right = ''; this.popper.style.bottom = ''; this.popper.style.willChange = ''; this.popper.style[getSupportedPropertyName('transform')] = ''; } this.disableEventListeners(); // remove the popper if user explicity asked for the deletion on destroy // do not use `remove` because IE11 doesn't support it if (this.options.removeOnDestroy) { this.popper.parentNode.removeChild(this.popper); } return this; } /** * Get the window associated with the element * @argument {Element} element * @returns {Window} */ function getWindow(element) { var ownerDocument = element.ownerDocument; return ownerDocument ? ownerDocument.defaultView : window; } function attachToScrollParents(scrollParent, event, callback, scrollParents) { var isBody = scrollParent.nodeName === 'BODY'; var target = isBody ? scrollParent.ownerDocument.defaultView : scrollParent; target.addEventListener(event, callback, { passive: true }); if (!isBody) { attachToScrollParents(getScrollParent(target.parentNode), event, callback, scrollParents); } scrollParents.push(target); } /** * Setup needed event listeners used to update the popper position * @method * @memberof Popper.Utils * @private */ function setupEventListeners(reference, options, state, updateBound) { // Resize event listener on window state.updateBound = updateBound; getWindow(reference).addEventListener('resize', state.updateBound, { passive: true }); // Scroll event listener on scroll parents var scrollElement = getScrollParent(reference); attachToScrollParents(scrollElement, 'scroll', state.updateBound, state.scrollParents); state.scrollElement = scrollElement; state.eventsEnabled = true; return state; } /** * It will add resize/scroll events and start recalculating * position of the popper element when they are triggered. * @method * @memberof Popper */ function enableEventListeners() { if (!this.state.eventsEnabled) { this.state = setupEventListeners(this.reference, this.options, this.state, this.scheduleUpdate); } } /** * Remove event listeners used to update the popper position * @method * @memberof Popper.Utils * @private */ function removeEventListeners(reference, state) { // Remove resize event listener on window getWindow(reference).removeEventListener('resize', state.updateBound); // Remove scroll event listener on scroll parents state.scrollParents.forEach(function (target) { target.removeEventListener('scroll', state.updateBound); }); // Reset state state.updateBound = null; state.scrollParents = []; state.scrollElement = null; state.eventsEnabled = false; return state; } /** * It will remove resize/scroll events and won't recalculate popper position * when they are triggered. It also won't trigger `onUpdate` callback anymore, * unless you call `update` method manually. * @method * @memberof Popper */ function disableEventListeners() { if (this.state.eventsEnabled) { cancelAnimationFrame(this.scheduleUpdate); this.state = removeEventListeners(this.reference, this.state); } } /** * Tells if a given input is a number * @method * @memberof Popper.Utils * @param {*} input to check * @return {Boolean} */ function isNumeric(n) { return n !== '' && !isNaN(parseFloat(n)) && isFinite(n); } /** * Set the style to the given popper * @method * @memberof Popper.Utils * @argument {Element} element - Element to apply the style to * @argument {Object} styles * Object with a list of properties and values which will be applied to the element */ function setStyles(element, styles) { Object.keys(styles).forEach(function (prop) { var unit = ''; // add unit if the value is numeric and is one of the following if (['width', 'height', 'top', 'right', 'bottom', 'left'].indexOf(prop) !== -1 && isNumeric(styles[prop])) { unit = 'px'; } element.style[prop] = styles[prop] + unit; }); } /** * Set the attributes to the given popper * @method * @memberof Popper.Utils * @argument {Element} element - Element to apply the attributes to * @argument {Object} styles * Object with a list of properties and values which will be applied to the element */ function setAttributes(element, attributes) { Object.keys(attributes).forEach(function (prop) { var value = attributes[prop]; if (value !== false) { element.setAttribute(prop, attributes[prop]); } else { element.removeAttribute(prop); } }); } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by `update` method * @argument {Object} data.styles - List of style properties - values to apply to popper element * @argument {Object} data.attributes - List of attribute properties - values to apply to popper element * @argument {Object} options - Modifiers configuration and options * @returns {Object} The same data object */ function applyStyle(data) { // any property present in `data.styles` will be applied to the popper, // in this way we can make the 3rd party modifiers add custom styles to it // Be aware, modifiers could override the properties defined in the previous // lines of this modifier! setStyles(data.instance.popper, data.styles); // any property present in `data.attributes` will be applied to the popper, // they will be set as HTML attributes of the element setAttributes(data.instance.popper, data.attributes); // if arrowElement is defined and arrowStyles has some properties if (data.arrowElement && Object.keys(data.arrowStyles).length) { setStyles(data.arrowElement, data.arrowStyles); } return data; } /** * Set the x-placement attribute before everything else because it could be used * to add margins to the popper margins needs to be calculated to get the * correct popper offsets. * @method * @memberof Popper.modifiers * @param {HTMLElement} reference - The reference element used to position the popper * @param {HTMLElement} popper - The HTML element used as popper * @param {Object} options - Popper.js options */ function applyStyleOnLoad(reference, popper, options, modifierOptions, state) { // compute reference element offsets var referenceOffsets = getReferenceOffsets(state, popper, reference, options.positionFixed); // compute auto placement, store placement inside the data object, // modifiers will be able to edit `placement` if needed // and refer to originalPlacement to know the original value var placement = computeAutoPlacement(options.placement, referenceOffsets, popper, reference, options.modifiers.flip.boundariesElement, options.modifiers.flip.padding); popper.setAttribute('x-placement', placement); // Apply `position` to popper before anything else because // without the position applied we can't guarantee correct computations setStyles(popper, { position: options.positionFixed ? 'fixed' : 'absolute' }); return options; } /** * @function * @memberof Popper.Utils * @argument {Object} data - The data object generated by `update` method * @argument {Boolean} shouldRound - If the offsets should be rounded at all * @returns {Object} The popper's position offsets rounded * * The tale of pixel-perfect positioning. It's still not 100% perfect, but as * good as it can be within reason. * Discussion here: https://github.com/FezVrasta/popper.js/pull/715 * * Low DPI screens cause a popper to be blurry if not using full pixels (Safari * as well on High DPI screens). * * Firefox prefers no rounding for positioning and does not have blurriness on * high DPI screens. * * Only horizontal placement and left/right values need to be considered. */ function getRoundedOffsets(data, shouldRound) { var _data$offsets = data.offsets, popper = _data$offsets.popper, reference = _data$offsets.reference; var round = Math.round, floor = Math.floor; var noRound = function noRound(v) { return v; }; var referenceWidth = round(reference.width); var popperWidth = round(popper.width); var isVertical = ['left', 'right'].indexOf(data.placement) !== -1; var isVariation = data.placement.indexOf('-') !== -1; var sameWidthParity = referenceWidth % 2 === popperWidth % 2; var bothOddWidth = referenceWidth % 2 === 1 && popperWidth % 2 === 1; var horizontalToInteger = !shouldRound ? noRound : isVertical || isVariation || sameWidthParity ? round : floor; var verticalToInteger = !shouldRound ? noRound : round; return { left: horizontalToInteger(bothOddWidth && !isVariation && shouldRound ? popper.left - 1 : popper.left), top: verticalToInteger(popper.top), bottom: verticalToInteger(popper.bottom), right: horizontalToInteger(popper.right) }; } var isFirefox = isBrowser && /Firefox/i.test(navigator.userAgent); /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by `update` method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function computeStyle(data, options) { var x = options.x, y = options.y; var popper = data.offsets.popper; // Remove this legacy support in Popper.js v2 var legacyGpuAccelerationOption = find(data.instance.modifiers, function (modifier) { return modifier.name === 'applyStyle'; }).gpuAcceleration; if (legacyGpuAccelerationOption !== undefined) { console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!'); } var gpuAcceleration = legacyGpuAccelerationOption !== undefined ? legacyGpuAccelerationOption : options.gpuAcceleration; var offsetParent = getOffsetParent(data.instance.popper); var offsetParentRect = getBoundingClientRect(offsetParent); // Styles var styles = { position: popper.position }; var offsets = getRoundedOffsets(data, window.devicePixelRatio < 2 || !isFirefox); var sideA = x === 'bottom' ? 'top' : 'bottom'; var sideB = y === 'right' ? 'left' : 'right'; // if gpuAcceleration is set to `true` and transform is supported, // we use `translate3d` to apply the position to the popper we // automatically use the supported prefixed version if needed var prefixedProperty = getSupportedPropertyName('transform'); // now, let's make a step back and look at this code closely (wtf?) // If the content of the popper grows once it's been positioned, it // may happen that the popper gets misplaced because of the new content // overflowing its reference element // To avoid this problem, we provide two options (x and y), which allow // the consumer to define the offset origin. // If we position a popper on top of a reference element, we can set // `x` to `top` to make the popper grow towards its top instead of // its bottom. var left = void 0, top = void 0; if (sideA === 'bottom') { // when offsetParent is the positioning is relative to the bottom of the screen (excluding the scrollbar) // and not the bottom of the html element if (offsetParent.nodeName === 'HTML') { top = -offsetParent.clientHeight + offsets.bottom; } else { top = -offsetParentRect.height + offsets.bottom; } } else { top = offsets.top; } if (sideB === 'right') { if (offsetParent.nodeName === 'HTML') { left = -offsetParent.clientWidth + offsets.right; } else { left = -offsetParentRect.width + offsets.right; } } else { left = offsets.left; } if (gpuAcceleration && prefixedProperty) { styles[prefixedProperty] = 'translate3d(' + left + 'px, ' + top + 'px, 0)'; styles[sideA] = 0; styles[sideB] = 0; styles.willChange = 'transform'; } else { // othwerise, we use the standard `top`, `left`, `bottom` and `right` properties var invertTop = sideA === 'bottom' ? -1 : 1; var invertLeft = sideB === 'right' ? -1 : 1; styles[sideA] = top * invertTop; styles[sideB] = left * invertLeft; styles.willChange = sideA + ', ' + sideB; } // Attributes var attributes = { 'x-placement': data.placement }; // Update `data` attributes, styles and arrowStyles data.attributes = _extends({}, attributes, data.attributes); data.styles = _extends({}, styles, data.styles); data.arrowStyles = _extends({}, data.offsets.arrow, data.arrowStyles); return data; } /** * Helper used to know if the given modifier depends from another one.
* It checks if the needed modifier is listed and enabled. * @method * @memberof Popper.Utils * @param {Array} modifiers - list of modifiers * @param {String} requestingName - name of requesting modifier * @param {String} requestedName - name of requested modifier * @returns {Boolean} */ function isModifierRequired(modifiers, requestingName, requestedName) { var requesting = find(modifiers, function (_ref) { var name = _ref.name; return name === requestingName; }); var isRequired = !!requesting && modifiers.some(function (modifier) { return modifier.name === requestedName && modifier.enabled && modifier.order < requesting.order; }); if (!isRequired) { var _requesting = '`' + requestingName + '`'; var requested = '`' + requestedName + '`'; console.warn(requested + ' modifier is required by ' + _requesting + ' modifier in order to work, be sure to include it before ' + _requesting + '!'); } return isRequired; } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by update method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function arrow(data, options) { var _data$offsets$arrow; // arrow depends on keepTogether in order to work if (!isModifierRequired(data.instance.modifiers, 'arrow', 'keepTogether')) { return data; } var arrowElement = options.element; // if arrowElement is a string, suppose it's a CSS selector if (typeof arrowElement === 'string') { arrowElement = data.instance.popper.querySelector(arrowElement); // if arrowElement is not found, don't run the modifier if (!arrowElement) { return data; } } else { // if the arrowElement isn't a query selector we must check that the // provided DOM node is child of its popper node if (!data.instance.popper.contains(arrowElement)) { console.warn('WARNING: `arrow.element` must be child of its popper element!'); return data; } } var placement = data.placement.split('-')[0]; var _data$offsets = data.offsets, popper = _data$offsets.popper, reference = _data$offsets.reference; var isVertical = ['left', 'right'].indexOf(placement) !== -1; var len = isVertical ? 'height' : 'width'; var sideCapitalized = isVertical ? 'Top' : 'Left'; var side = sideCapitalized.toLowerCase(); var altSide = isVertical ? 'left' : 'top'; var opSide = isVertical ? 'bottom' : 'right'; var arrowElementSize = getOuterSizes(arrowElement)[len]; // // extends keepTogether behavior making sure the popper and its // reference have enough pixels in conjunction // // top/left side if (reference[opSide] - arrowElementSize < popper[side]) { data.offsets.popper[side] -= popper[side] - (reference[opSide] - arrowElementSize); } // bottom/right side if (reference[side] + arrowElementSize > popper[opSide]) { data.offsets.popper[side] += reference[side] + arrowElementSize - popper[opSide]; } data.offsets.popper = getClientRect(data.offsets.popper); // compute center of the popper var center = reference[side] + reference[len] / 2 - arrowElementSize / 2; // Compute the sideValue using the updated popper offsets // take popper margin in account because we don't have this info available var css = getStyleComputedProperty(data.instance.popper); var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10); var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10); var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide; // prevent arrowElement from being placed not contiguously to its popper sideValue = Math.max(Math.min(popper[len] - arrowElementSize, sideValue), 0); data.arrowElement = arrowElement; data.offsets.arrow = (_data$offsets$arrow = {}, defineProperty(_data$offsets$arrow, side, Math.round(sideValue)), defineProperty(_data$offsets$arrow, altSide, ''), _data$offsets$arrow); return data; } /** * Get the opposite placement variation of the given one * @method * @memberof Popper.Utils * @argument {String} placement variation * @returns {String} flipped placement variation */ function getOppositeVariation(variation) { if (variation === 'end') { return 'start'; } else if (variation === 'start') { return 'end'; } return variation; } /** * List of accepted placements to use as values of the `placement` option.
* Valid placements are: * - `auto` * - `top` * - `right` * - `bottom` * - `left` * * Each placement can have a variation from this list: * - `-start` * - `-end` * * Variations are interpreted easily if you think of them as the left to right * written languages. Horizontally (`top` and `bottom`), `start` is left and `end` * is right.
* Vertically (`left` and `right`), `start` is top and `end` is bottom. * * Some valid examples are: * - `top-end` (on top of reference, right aligned) * - `right-start` (on right of reference, top aligned) * - `bottom` (on bottom, centered) * - `auto-end` (on the side with more space available, alignment depends by placement) * * @static * @type {Array} * @enum {String} * @readonly * @method placements * @memberof Popper */ var placements = ['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start']; // Get rid of `auto` `auto-start` and `auto-end` var validPlacements = placements.slice(3); /** * Given an initial placement, returns all the subsequent placements * clockwise (or counter-clockwise). * * @method * @memberof Popper.Utils * @argument {String} placement - A valid placement (it accepts variations) * @argument {Boolean} counter - Set to true to walk the placements counterclockwise * @returns {Array} placements including their variations */ function clockwise(placement) { var counter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var index = validPlacements.indexOf(placement); var arr = validPlacements.slice(index + 1).concat(validPlacements.slice(0, index)); return counter ? arr.reverse() : arr; } var BEHAVIORS = { FLIP: 'flip', CLOCKWISE: 'clockwise', COUNTERCLOCKWISE: 'counterclockwise' }; /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by update method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function flip(data, options) { // if `inner` modifier is enabled, we can't use the `flip` modifier if (isModifierEnabled(data.instance.modifiers, 'inner')) { return data; } if (data.flipped && data.placement === data.originalPlacement) { // seems like flip is trying to loop, probably there's not enough space on any of the flippable sides return data; } var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, options.boundariesElement, data.positionFixed); var placement = data.placement.split('-')[0]; var placementOpposite = getOppositePlacement(placement); var variation = data.placement.split('-')[1] || ''; var flipOrder = []; switch (options.behavior) { case BEHAVIORS.FLIP: flipOrder = [placement, placementOpposite]; break; case BEHAVIORS.CLOCKWISE: flipOrder = clockwise(placement); break; case BEHAVIORS.COUNTERCLOCKWISE: flipOrder = clockwise(placement, true); break; default: flipOrder = options.behavior; } flipOrder.forEach(function (step, index) { if (placement !== step || flipOrder.length === index + 1) { return data; } placement = data.placement.split('-')[0]; placementOpposite = getOppositePlacement(placement); var popperOffsets = data.offsets.popper; var refOffsets = data.offsets.reference; // using floor because the reference offsets may contain decimals we are not going to consider here var floor = Math.floor; var overlapsRef = placement === 'left' && floor(popperOffsets.right) > floor(refOffsets.left) || placement === 'right' && floor(popperOffsets.left) < floor(refOffsets.right) || placement === 'top' && floor(popperOffsets.bottom) > floor(refOffsets.top) || placement === 'bottom' && floor(popperOffsets.top) < floor(refOffsets.bottom); var overflowsLeft = floor(popperOffsets.left) < floor(boundaries.left); var overflowsRight = floor(popperOffsets.right) > floor(boundaries.right); var overflowsTop = floor(popperOffsets.top) < floor(boundaries.top); var overflowsBottom = floor(popperOffsets.bottom) > floor(boundaries.bottom); var overflowsBoundaries = placement === 'left' && overflowsLeft || placement === 'right' && overflowsRight || placement === 'top' && overflowsTop || placement === 'bottom' && overflowsBottom; // flip the variation if required var isVertical = ['top', 'bottom'].indexOf(placement) !== -1; var flippedVariation = !!options.flipVariations && (isVertical && variation === 'start' && overflowsLeft || isVertical && variation === 'end' && overflowsRight || !isVertical && variation === 'start' && overflowsTop || !isVertical && variation === 'end' && overflowsBottom); if (overlapsRef || overflowsBoundaries || flippedVariation) { // this boolean to detect any flip loop data.flipped = true; if (overlapsRef || overflowsBoundaries) { placement = flipOrder[index + 1]; } if (flippedVariation) { variation = getOppositeVariation(variation); } data.placement = placement + (variation ? '-' + variation : ''); // this object contains `position`, we want to preserve it along with // any additional property we may add in the future data.offsets.popper = _extends({}, data.offsets.popper, getPopperOffsets(data.instance.popper, data.offsets.reference, data.placement)); data = runModifiers(data.instance.modifiers, data, 'flip'); } }); return data; } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by update method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function keepTogether(data) { var _data$offsets = data.offsets, popper = _data$offsets.popper, reference = _data$offsets.reference; var placement = data.placement.split('-')[0]; var floor = Math.floor; var isVertical = ['top', 'bottom'].indexOf(placement) !== -1; var side = isVertical ? 'right' : 'bottom'; var opSide = isVertical ? 'left' : 'top'; var measurement = isVertical ? 'width' : 'height'; if (popper[side] < floor(reference[opSide])) { data.offsets.popper[opSide] = floor(reference[opSide]) - popper[measurement]; } if (popper[opSide] > floor(reference[side])) { data.offsets.popper[opSide] = floor(reference[side]); } return data; } /** * Converts a string containing value + unit into a px value number * @function * @memberof {modifiers~offset} * @private * @argument {String} str - Value + unit string * @argument {String} measurement - `height` or `width` * @argument {Object} popperOffsets * @argument {Object} referenceOffsets * @returns {Number|String} * Value in pixels, or original string if no values were extracted */ function toValue(str, measurement, popperOffsets, referenceOffsets) { // separate value from unit var split = str.match(/((?:\-|\+)?\d*\.?\d*)(.*)/); var value = +split[1]; var unit = split[2]; // If it's not a number it's an operator, I guess if (!value) { return str; } if (unit.indexOf('%') === 0) { var element = void 0; switch (unit) { case '%p': element = popperOffsets; break; case '%': case '%r': default: element = referenceOffsets; } var rect = getClientRect(element); return rect[measurement] / 100 * value; } else if (unit === 'vh' || unit === 'vw') { // if is a vh or vw, we calculate the size based on the viewport var size = void 0; if (unit === 'vh') { size = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); } else { size = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); } return size / 100 * value; } else { // if is an explicit pixel unit, we get rid of the unit and keep the value // if is an implicit unit, it's px, and we return just the value return value; } } /** * Parse an `offset` string to extrapolate `x` and `y` numeric offsets. * @function * @memberof {modifiers~offset} * @private * @argument {String} offset * @argument {Object} popperOffsets * @argument {Object} referenceOffsets * @argument {String} basePlacement * @returns {Array} a two cells array with x and y offsets in numbers */ function parseOffset(offset, popperOffsets, referenceOffsets, basePlacement) { var offsets = [0, 0]; // Use height if placement is left or right and index is 0 otherwise use width // in this way the first offset will use an axis and the second one // will use the other one var useHeight = ['right', 'left'].indexOf(basePlacement) !== -1; // Split the offset string to obtain a list of values and operands // The regex addresses values with the plus or minus sign in front (+10, -20, etc) var fragments = offset.split(/(\+|\-)/).map(function (frag) { return frag.trim(); }); // Detect if the offset string contains a pair of values or a single one // they could be separated by comma or space var divider = fragments.indexOf(find(fragments, function (frag) { return frag.search(/,|\s/) !== -1; })); if (fragments[divider] && fragments[divider].indexOf(',') === -1) { console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.'); } // If divider is found, we divide the list of values and operands to divide // them by ofset X and Y. var splitRegex = /\s*,\s*|\s+/; var ops = divider !== -1 ? [fragments.slice(0, divider).concat([fragments[divider].split(splitRegex)[0]]), [fragments[divider].split(splitRegex)[1]].concat(fragments.slice(divider + 1))] : [fragments]; // Convert the values with units to absolute pixels to allow our computations ops = ops.map(function (op, index) { // Most of the units rely on the orientation of the popper var measurement = (index === 1 ? !useHeight : useHeight) ? 'height' : 'width'; var mergeWithPrevious = false; return op // This aggregates any `+` or `-` sign that aren't considered operators // e.g.: 10 + +5 => [10, +, +5] .reduce(function (a, b) { if (a[a.length - 1] === '' && ['+', '-'].indexOf(b) !== -1) { a[a.length - 1] = b; mergeWithPrevious = true; return a; } else if (mergeWithPrevious) { a[a.length - 1] += b; mergeWithPrevious = false; return a; } else { return a.concat(b); } }, []) // Here we convert the string values into number values (in px) .map(function (str) { return toValue(str, measurement, popperOffsets, referenceOffsets); }); }); // Loop trough the offsets arrays and execute the operations ops.forEach(function (op, index) { op.forEach(function (frag, index2) { if (isNumeric(frag)) { offsets[index] += frag * (op[index2 - 1] === '-' ? -1 : 1); } }); }); return offsets; } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by update method * @argument {Object} options - Modifiers configuration and options * @argument {Number|String} options.offset=0 * The offset value as described in the modifier description * @returns {Object} The data object, properly modified */ function offset(data, _ref) { var offset = _ref.offset; var placement = data.placement, _data$offsets = data.offsets, popper = _data$offsets.popper, reference = _data$offsets.reference; var basePlacement = placement.split('-')[0]; var offsets = void 0; if (isNumeric(+offset)) { offsets = [+offset, 0]; } else { offsets = parseOffset(offset, popper, reference, basePlacement); } if (basePlacement === 'left') { popper.top += offsets[0]; popper.left -= offsets[1]; } else if (basePlacement === 'right') { popper.top += offsets[0]; popper.left += offsets[1]; } else if (basePlacement === 'top') { popper.left += offsets[0]; popper.top -= offsets[1]; } else if (basePlacement === 'bottom') { popper.left += offsets[0]; popper.top += offsets[1]; } data.popper = popper; return data; } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by `update` method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function preventOverflow(data, options) { var boundariesElement = options.boundariesElement || getOffsetParent(data.instance.popper); // If offsetParent is the reference element, we really want to // go one step up and use the next offsetParent as reference to // avoid to make this modifier completely useless and look like broken if (data.instance.reference === boundariesElement) { boundariesElement = getOffsetParent(boundariesElement); } // NOTE: DOM access here // resets the popper's position so that the document size can be calculated excluding // the size of the popper element itself var transformProp = getSupportedPropertyName('transform'); var popperStyles = data.instance.popper.style; // assignment to help minification var top = popperStyles.top, left = popperStyles.left, transform = popperStyles[transformProp]; popperStyles.top = ''; popperStyles.left = ''; popperStyles[transformProp] = ''; var boundaries = getBoundaries(data.instance.popper, data.instance.reference, options.padding, boundariesElement, data.positionFixed); // NOTE: DOM access here // restores the original style properties after the offsets have been computed popperStyles.top = top; popperStyles.left = left; popperStyles[transformProp] = transform; options.boundaries = boundaries; var order = options.priority; var popper = data.offsets.popper; var check = { primary: function primary(placement) { var value = popper[placement]; if (popper[placement] < boundaries[placement] && !options.escapeWithReference) { value = Math.max(popper[placement], boundaries[placement]); } return defineProperty({}, placement, value); }, secondary: function secondary(placement) { var mainSide = placement === 'right' ? 'left' : 'top'; var value = popper[mainSide]; if (popper[placement] > boundaries[placement] && !options.escapeWithReference) { value = Math.min(popper[mainSide], boundaries[placement] - (placement === 'right' ? popper.width : popper.height)); } return defineProperty({}, mainSide, value); } }; order.forEach(function (placement) { var side = ['left', 'top'].indexOf(placement) !== -1 ? 'primary' : 'secondary'; popper = _extends({}, popper, check[side](placement)); }); data.offsets.popper = popper; return data; } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by `update` method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function shift(data) { var placement = data.placement; var basePlacement = placement.split('-')[0]; var shiftvariation = placement.split('-')[1]; // if shift shiftvariation is specified, run the modifier if (shiftvariation) { var _data$offsets = data.offsets, reference = _data$offsets.reference, popper = _data$offsets.popper; var isVertical = ['bottom', 'top'].indexOf(basePlacement) !== -1; var side = isVertical ? 'left' : 'top'; var measurement = isVertical ? 'width' : 'height'; var shiftOffsets = { start: defineProperty({}, side, reference[side]), end: defineProperty({}, side, reference[side] + reference[measurement] - popper[measurement]) }; data.offsets.popper = _extends({}, popper, shiftOffsets[shiftvariation]); } return data; } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by update method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function hide(data) { if (!isModifierRequired(data.instance.modifiers, 'hide', 'preventOverflow')) { return data; } var refRect = data.offsets.reference; var bound = find(data.instance.modifiers, function (modifier) { return modifier.name === 'preventOverflow'; }).boundaries; if (refRect.bottom < bound.top || refRect.left > bound.right || refRect.top > bound.bottom || refRect.right < bound.left) { // Avoid unnecessary DOM access if visibility hasn't changed if (data.hide === true) { return data; } data.hide = true; data.attributes['x-out-of-boundaries'] = ''; } else { // Avoid unnecessary DOM access if visibility hasn't changed if (data.hide === false) { return data; } data.hide = false; data.attributes['x-out-of-boundaries'] = false; } return data; } /** * @function * @memberof Modifiers * @argument {Object} data - The data object generated by `update` method * @argument {Object} options - Modifiers configuration and options * @returns {Object} The data object, properly modified */ function inner(data) { var placement = data.placement; var basePlacement = placement.split('-')[0]; var _data$offsets = data.offsets, popper = _data$offsets.popper, reference = _data$offsets.reference; var isHoriz = ['left', 'right'].indexOf(basePlacement) !== -1; var subtractLength = ['top', 'left'].indexOf(basePlacement) === -1; popper[isHoriz ? 'left' : 'top'] = reference[basePlacement] - (subtractLength ? popper[isHoriz ? 'width' : 'height'] : 0); data.placement = getOppositePlacement(placement); data.offsets.popper = getClientRect(popper); return data; } /** * Modifier function, each modifier can have a function of this type assigned * to its `fn` property.
* These functions will be called on each update, this means that you must * make sure they are performant enough to avoid performance bottlenecks. * * @function ModifierFn * @argument {dataObject} data - The data object generated by `update` method * @argument {Object} options - Modifiers configuration and options * @returns {dataObject} The data object, properly modified */ /** * Modifiers are plugins used to alter the behavior of your poppers.
* Popper.js uses a set of 9 modifiers to provide all the basic functionalities * needed by the library. * * Usually you don't want to override the `order`, `fn` and `onLoad` props. * All the other properties are configurations that could be tweaked. * @namespace modifiers */ var modifiers = { /** * Modifier used to shift the popper on the start or end of its reference * element.
* It will read the variation of the `placement` property.
* It can be one either `-end` or `-start`. * @memberof modifiers * @inner */ shift: { /** @prop {number} order=100 - Index used to define the order of execution */ order: 100, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: shift }, /** * The `offset` modifier can shift your popper on both its axis. * * It accepts the following units: * - `px` or unit-less, interpreted as pixels * - `%` or `%r`, percentage relative to the length of the reference element * - `%p`, percentage relative to the length of the popper element * - `vw`, CSS viewport width unit * - `vh`, CSS viewport height unit * * For length is intended the main axis relative to the placement of the popper.
* This means that if the placement is `top` or `bottom`, the length will be the * `width`. In case of `left` or `right`, it will be the `height`. * * You can provide a single value (as `Number` or `String`), or a pair of values * as `String` divided by a comma or one (or more) white spaces.
* The latter is a deprecated method because it leads to confusion and will be * removed in v2.
* Additionally, it accepts additions and subtractions between different units. * Note that multiplications and divisions aren't supported. * * Valid examples are: * ``` * 10 * '10%' * '10, 10' * '10%, 10' * '10 + 10%' * '10 - 5vh + 3%' * '-10px + 5vh, 5px - 6%' * ``` * > **NB**: If you desire to apply offsets to your poppers in a way that may make them overlap * > with their reference element, unfortunately, you will have to disable the `flip` modifier. * > You can read more on this at this [issue](https://github.com/FezVrasta/popper.js/issues/373). * * @memberof modifiers * @inner */ offset: { /** @prop {number} order=200 - Index used to define the order of execution */ order: 200, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: offset, /** @prop {Number|String} offset=0 * The offset value as described in the modifier description */ offset: 0 }, /** * Modifier used to prevent the popper from being positioned outside the boundary. * * A scenario exists where the reference itself is not within the boundaries.
* We can say it has "escaped the boundaries" — or just "escaped".
* In this case we need to decide whether the popper should either: * * - detach from the reference and remain "trapped" in the boundaries, or * - if it should ignore the boundary and "escape with its reference" * * When `escapeWithReference` is set to`true` and reference is completely * outside its boundaries, the popper will overflow (or completely leave) * the boundaries in order to remain attached to the edge of the reference. * * @memberof modifiers * @inner */ preventOverflow: { /** @prop {number} order=300 - Index used to define the order of execution */ order: 300, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: preventOverflow, /** * @prop {Array} [priority=['left','right','top','bottom']] * Popper will try to prevent overflow following these priorities by default, * then, it could overflow on the left and on top of the `boundariesElement` */ priority: ['left', 'right', 'top', 'bottom'], /** * @prop {number} padding=5 * Amount of pixel used to define a minimum distance between the boundaries * and the popper. This makes sure the popper always has a little padding * between the edges of its container */ padding: 5, /** * @prop {String|HTMLElement} boundariesElement='scrollParent' * Boundaries used by the modifier. Can be `scrollParent`, `window`, * `viewport` or any DOM element. */ boundariesElement: 'scrollParent' }, /** * Modifier used to make sure the reference and its popper stay near each other * without leaving any gap between the two. Especially useful when the arrow is * enabled and you want to ensure that it points to its reference element. * It cares only about the first axis. You can still have poppers with margin * between the popper and its reference element. * @memberof modifiers * @inner */ keepTogether: { /** @prop {number} order=400 - Index used to define the order of execution */ order: 400, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: keepTogether }, /** * This modifier is used to move the `arrowElement` of the popper to make * sure it is positioned between the reference element and its popper element. * It will read the outer size of the `arrowElement` node to detect how many * pixels of conjunction are needed. * * It has no effect if no `arrowElement` is provided. * @memberof modifiers * @inner */ arrow: { /** @prop {number} order=500 - Index used to define the order of execution */ order: 500, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: arrow, /** @prop {String|HTMLElement} element='[x-arrow]' - Selector or node used as arrow */ element: '[x-arrow]' }, /** * Modifier used to flip the popper's placement when it starts to overlap its * reference element. * * Requires the `preventOverflow` modifier before it in order to work. * * **NOTE:** this modifier will interrupt the current update cycle and will * restart it if it detects the need to flip the placement. * @memberof modifiers * @inner */ flip: { /** @prop {number} order=600 - Index used to define the order of execution */ order: 600, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: flip, /** * @prop {String|Array} behavior='flip' * The behavior used to change the popper's placement. It can be one of * `flip`, `clockwise`, `counterclockwise` or an array with a list of valid * placements (with optional variations) */ behavior: 'flip', /** * @prop {number} padding=5 * The popper will flip if it hits the edges of the `boundariesElement` */ padding: 5, /** * @prop {String|HTMLElement} boundariesElement='viewport' * The element which will define the boundaries of the popper position. * The popper will never be placed outside of the defined boundaries * (except if `keepTogether` is enabled) */ boundariesElement: 'viewport' }, /** * Modifier used to make the popper flow toward the inner of the reference element. * By default, when this modifier is disabled, the popper will be placed outside * the reference element. * @memberof modifiers * @inner */ inner: { /** @prop {number} order=700 - Index used to define the order of execution */ order: 700, /** @prop {Boolean} enabled=false - Whether the modifier is enabled or not */ enabled: false, /** @prop {ModifierFn} */ fn: inner }, /** * Modifier used to hide the popper when its reference element is outside of the * popper boundaries. It will set a `x-out-of-boundaries` attribute which can * be used to hide with a CSS selector the popper when its reference is * out of boundaries. * * Requires the `preventOverflow` modifier before it in order to work. * @memberof modifiers * @inner */ hide: { /** @prop {number} order=800 - Index used to define the order of execution */ order: 800, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: hide }, /** * Computes the style that will be applied to the popper element to gets * properly positioned. * * Note that this modifier will not touch the DOM, it just prepares the styles * so that `applyStyle` modifier can apply it. This separation is useful * in case you need to replace `applyStyle` with a custom implementation. * * This modifier has `850` as `order` value to maintain backward compatibility * with previous versions of Popper.js. Expect the modifiers ordering method * to change in future major versions of the library. * * @memberof modifiers * @inner */ computeStyle: { /** @prop {number} order=850 - Index used to define the order of execution */ order: 850, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: computeStyle, /** * @prop {Boolean} gpuAcceleration=true * If true, it uses the CSS 3D transformation to position the popper. * Otherwise, it will use the `top` and `left` properties */ gpuAcceleration: true, /** * @prop {string} [x='bottom'] * Where to anchor the X axis (`bottom` or `top`). AKA X offset origin. * Change this if your popper should grow in a direction different from `bottom` */ x: 'bottom', /** * @prop {string} [x='left'] * Where to anchor the Y axis (`left` or `right`). AKA Y offset origin. * Change this if your popper should grow in a direction different from `right` */ y: 'right' }, /** * Applies the computed styles to the popper element. * * All the DOM manipulations are limited to this modifier. This is useful in case * you want to integrate Popper.js inside a framework or view library and you * want to delegate all the DOM manipulations to it. * * Note that if you disable this modifier, you must make sure the popper element * has its position set to `absolute` before Popper.js can do its work! * * Just disable this modifier and define your own to achieve the desired effect. * * @memberof modifiers * @inner */ applyStyle: { /** @prop {number} order=900 - Index used to define the order of execution */ order: 900, /** @prop {Boolean} enabled=true - Whether the modifier is enabled or not */ enabled: true, /** @prop {ModifierFn} */ fn: applyStyle, /** @prop {Function} */ onLoad: applyStyleOnLoad, /** * @deprecated since version 1.10.0, the property moved to `computeStyle` modifier * @prop {Boolean} gpuAcceleration=true * If true, it uses the CSS 3D transformation to position the popper. * Otherwise, it will use the `top` and `left` properties */ gpuAcceleration: undefined } }; /** * The `dataObject` is an object containing all the information used by Popper.js. * This object is passed to modifiers and to the `onCreate` and `onUpdate` callbacks. * @name dataObject * @property {Object} data.instance The Popper.js instance * @property {String} data.placement Placement applied to popper * @property {String} data.originalPlacement Placement originally defined on init * @property {Boolean} data.flipped True if popper has been flipped by flip modifier * @property {Boolean} data.hide True if the reference element is out of boundaries, useful to know when to hide the popper * @property {HTMLElement} data.arrowElement Node used as arrow by arrow modifier * @property {Object} data.styles Any CSS property defined here will be applied to the popper. It expects the JavaScript nomenclature (eg. `marginBottom`) * @property {Object} data.arrowStyles Any CSS property defined here will be applied to the popper arrow. It expects the JavaScript nomenclature (eg. `marginBottom`) * @property {Object} data.boundaries Offsets of the popper boundaries * @property {Object} data.offsets The measurements of popper, reference and arrow elements * @property {Object} data.offsets.popper `top`, `left`, `width`, `height` values * @property {Object} data.offsets.reference `top`, `left`, `width`, `height` values * @property {Object} data.offsets.arrow] `top` and `left` offsets, only one of them will be different from 0 */ /** * Default options provided to Popper.js constructor.
* These can be overridden using the `options` argument of Popper.js.
* To override an option, simply pass an object with the same * structure of the `options` object, as the 3rd argument. For example: * ``` * new Popper(ref, pop, { * modifiers: { * preventOverflow: { enabled: false } * } * }) * ``` * @type {Object} * @static * @memberof Popper */ var Defaults = { /** * Popper's placement. * @prop {Popper.placements} placement='bottom' */ placement: 'bottom', /** * Set this to true if you want popper to position it self in 'fixed' mode * @prop {Boolean} positionFixed=false */ positionFixed: false, /** * Whether events (resize, scroll) are initially enabled. * @prop {Boolean} eventsEnabled=true */ eventsEnabled: true, /** * Set to true if you want to automatically remove the popper when * you call the `destroy` method. * @prop {Boolean} removeOnDestroy=false */ removeOnDestroy: false, /** * Callback called when the popper is created.
* By default, it is set to no-op.
* Access Popper.js instance with `data.instance`. * @prop {onCreate} */ onCreate: function onCreate() {}, /** * Callback called when the popper is updated. This callback is not called * on the initialization/creation of the popper, but only on subsequent * updates.
* By default, it is set to no-op.
* Access Popper.js instance with `data.instance`. * @prop {onUpdate} */ onUpdate: function onUpdate() {}, /** * List of modifiers used to modify the offsets before they are applied to the popper. * They provide most of the functionalities of Popper.js. * @prop {modifiers} */ modifiers: modifiers }; /** * @callback onCreate * @param {dataObject} data */ /** * @callback onUpdate * @param {dataObject} data */ // Utils // Methods var Popper = function () { /** * Creates a new Popper.js instance. * @class Popper * @param {HTMLElement|referenceObject} reference - The reference element used to position the popper * @param {HTMLElement} popper - The HTML element used as the popper * @param {Object} options - Your custom options to override the ones defined in [Defaults](#defaults) * @return {Object} instance - The generated Popper.js instance */ function Popper(reference, popper) { var _this = this; var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; classCallCheck(this, Popper); this.scheduleUpdate = function () { return requestAnimationFrame(_this.update); }; // make update() debounced, so that it only runs at most once-per-tick this.update = debounce(this.update.bind(this)); // with {} we create a new object with the options inside it this.options = _extends({}, Popper.Defaults, options); // init state this.state = { isDestroyed: false, isCreated: false, scrollParents: [] }; // get reference and popper elements (allow jQuery wrappers) this.reference = reference && reference.jquery ? reference[0] : reference; this.popper = popper && popper.jquery ? popper[0] : popper; // Deep merge modifiers options this.options.modifiers = {}; Object.keys(_extends({}, Popper.Defaults.modifiers, options.modifiers)).forEach(function (name) { _this.options.modifiers[name] = _extends({}, Popper.Defaults.modifiers[name] || {}, options.modifiers ? options.modifiers[name] : {}); }); // Refactoring modifiers' list (Object => Array) this.modifiers = Object.keys(this.options.modifiers).map(function (name) { return _extends({ name: name }, _this.options.modifiers[name]); }) // sort the modifiers by order .sort(function (a, b) { return a.order - b.order; }); // modifiers have the ability to execute arbitrary code when Popper.js get inited // such code is executed in the same order of its modifier // they could add new properties to their options configuration // BE AWARE: don't add options to `options.modifiers.name` but to `modifierOptions`! this.modifiers.forEach(function (modifierOptions) { if (modifierOptions.enabled && isFunction(modifierOptions.onLoad)) { modifierOptions.onLoad(_this.reference, _this.popper, _this.options, modifierOptions, _this.state); } }); // fire the first update to position the popper in the right place this.update(); var eventsEnabled = this.options.eventsEnabled; if (eventsEnabled) { // setup event listeners, they will take care of update the position in specific situations this.enableEventListeners(); } this.state.eventsEnabled = eventsEnabled; } // We can't use class properties because they don't get listed in the // class prototype and break stuff like Sinon stubs createClass(Popper, [{ key: 'update', value: function update$$1() { return update.call(this); } }, { key: 'destroy', value: function destroy$$1() { return destroy.call(this); } }, { key: 'enableEventListeners', value: function enableEventListeners$$1() { return enableEventListeners.call(this); } }, { key: 'disableEventListeners', value: function disableEventListeners$$1() { return disableEventListeners.call(this); } /** * Schedules an update. It will run on the next UI update available. * @method scheduleUpdate * @memberof Popper */ /** * Collection of utilities useful when writing custom modifiers. * Starting from version 1.7, this method is available only if you * include `popper-utils.js` before `popper.js`. * * **DEPRECATION**: This way to access PopperUtils is deprecated * and will be removed in v2! Use the PopperUtils module directly instead. * Due to the high instability of the methods contained in Utils, we can't * guarantee them to follow semver. Use them at your own risk! * @static * @private * @type {Object} * @deprecated since version 1.8 * @member Utils * @memberof Popper */ }]); return Popper; }(); /** * The `referenceObject` is an object that provides an interface compatible with Popper.js * and lets you use it as replacement of a real DOM node.
* You can use this method to position a popper relatively to a set of coordinates * in case you don't have a DOM node to use as reference. * * ``` * new Popper(referenceObject, popperNode); * ``` * * NB: This feature isn't supported in Internet Explorer 10. * @name referenceObject * @property {Function} data.getBoundingClientRect * A function that returns a set of coordinates compatible with the native `getBoundingClientRect` method. * @property {number} data.clientWidth * An ES6 getter that will return the width of the virtual reference element. * @property {number} data.clientHeight * An ES6 getter that will return the height of the virtual reference element. */ Popper.Utils = (typeof window !== 'undefined' ? window : global).PopperUtils; Popper.placements = placements; Popper.Defaults = Defaults; /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$4 = 'dropdown'; var VERSION$4 = '4.3.1'; var DATA_KEY$4 = 'bs.dropdown'; var EVENT_KEY$4 = "." + DATA_KEY$4; var DATA_API_KEY$4 = '.data-api'; var JQUERY_NO_CONFLICT$4 = $.fn[NAME$4]; var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key var SPACE_KEYCODE = 32; // KeyboardEvent.which value for space key var TAB_KEYCODE = 9; // KeyboardEvent.which value for tab key var ARROW_UP_KEYCODE = 38; // KeyboardEvent.which value for up arrow key var ARROW_DOWN_KEYCODE = 40; // KeyboardEvent.which value for down arrow key var RIGHT_MOUSE_BUTTON_WHICH = 3; // MouseEvent.which value for the right button (assuming a right-handed mouse) var REGEXP_KEYDOWN = new RegExp(ARROW_UP_KEYCODE + "|" + ARROW_DOWN_KEYCODE + "|" + ESCAPE_KEYCODE); var Event$4 = { HIDE: "hide" + EVENT_KEY$4, HIDDEN: "hidden" + EVENT_KEY$4, SHOW: "show" + EVENT_KEY$4, SHOWN: "shown" + EVENT_KEY$4, CLICK: "click" + EVENT_KEY$4, CLICK_DATA_API: "click" + EVENT_KEY$4 + DATA_API_KEY$4, KEYDOWN_DATA_API: "keydown" + EVENT_KEY$4 + DATA_API_KEY$4, KEYUP_DATA_API: "keyup" + EVENT_KEY$4 + DATA_API_KEY$4 }; var ClassName$4 = { DISABLED: 'disabled', SHOW: 'show', DROPUP: 'dropup', DROPRIGHT: 'dropright', DROPLEFT: 'dropleft', MENURIGHT: 'dropdown-menu-right', MENULEFT: 'dropdown-menu-left', POSITION_STATIC: 'position-static' }; var Selector$4 = { DATA_TOGGLE: '[data-toggle="dropdown"]', FORM_CHILD: '.dropdown form', MENU: '.dropdown-menu', NAVBAR_NAV: '.navbar-nav', VISIBLE_ITEMS: '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' }; var AttachmentMap = { TOP: 'top-start', TOPEND: 'top-end', BOTTOM: 'bottom-start', BOTTOMEND: 'bottom-end', RIGHT: 'right-start', RIGHTEND: 'right-end', LEFT: 'left-start', LEFTEND: 'left-end' }; var Default$2 = { offset: 0, flip: true, boundary: 'scrollParent', reference: 'toggle', display: 'dynamic' }; var DefaultType$2 = { offset: '(number|string|function)', flip: 'boolean', boundary: '(string|element)', reference: '(string|element)', display: 'string' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Dropdown = /*#__PURE__*/ function () { function Dropdown(element, config) { this._element = element; this._popper = null; this._config = this._getConfig(config); this._menu = this._getMenuElement(); this._inNavbar = this._detectNavbar(); this._addEventListeners(); } // Getters var _proto = Dropdown.prototype; // Public _proto.toggle = function toggle() { if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED)) { return; } var parent = Dropdown._getParentFromElement(this._element); var isActive = $(this._menu).hasClass(ClassName$4.SHOW); Dropdown._clearMenus(); if (isActive) { return; } var relatedTarget = { relatedTarget: this._element }; var showEvent = $.Event(Event$4.SHOW, relatedTarget); $(parent).trigger(showEvent); if (showEvent.isDefaultPrevented()) { return; } // Disable totally Popper.js for Dropdown in Navbar if (!this._inNavbar) { /** * Check for Popper dependency * Popper - https://popper.js.org */ if (typeof Popper === 'undefined') { throw new TypeError('Bootstrap\'s dropdowns require Popper.js (https://popper.js.org/)'); } var referenceElement = this._element; if (this._config.reference === 'parent') { referenceElement = parent; } else if (Util.isElement(this._config.reference)) { referenceElement = this._config.reference; // Check if it's jQuery element if (typeof this._config.reference.jquery !== 'undefined') { referenceElement = this._config.reference[0]; } } // If boundary is not `scrollParent`, then set position to `static` // to allow the menu to "escape" the scroll parent's boundaries // https://github.com/twbs/bootstrap/issues/24251 if (this._config.boundary !== 'scrollParent') { $(parent).addClass(ClassName$4.POSITION_STATIC); } this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig()); } // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; // only needed because of broken event delegation on iOS // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement && $(parent).closest(Selector$4.NAVBAR_NAV).length === 0) { $(document.body).children().on('mouseover', null, $.noop); } this._element.focus(); this._element.setAttribute('aria-expanded', true); $(this._menu).toggleClass(ClassName$4.SHOW); $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.SHOWN, relatedTarget)); }; _proto.show = function show() { if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || $(this._menu).hasClass(ClassName$4.SHOW)) { return; } var relatedTarget = { relatedTarget: this._element }; var showEvent = $.Event(Event$4.SHOW, relatedTarget); var parent = Dropdown._getParentFromElement(this._element); $(parent).trigger(showEvent); if (showEvent.isDefaultPrevented()) { return; } $(this._menu).toggleClass(ClassName$4.SHOW); $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.SHOWN, relatedTarget)); }; _proto.hide = function hide() { if (this._element.disabled || $(this._element).hasClass(ClassName$4.DISABLED) || !$(this._menu).hasClass(ClassName$4.SHOW)) { return; } var relatedTarget = { relatedTarget: this._element }; var hideEvent = $.Event(Event$4.HIDE, relatedTarget); var parent = Dropdown._getParentFromElement(this._element); $(parent).trigger(hideEvent); if (hideEvent.isDefaultPrevented()) { return; } $(this._menu).toggleClass(ClassName$4.SHOW); $(parent).toggleClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget)); }; _proto.dispose = function dispose() { $.removeData(this._element, DATA_KEY$4); $(this._element).off(EVENT_KEY$4); this._element = null; this._menu = null; if (this._popper !== null) { this._popper.destroy(); this._popper = null; } }; _proto.update = function update() { this._inNavbar = this._detectNavbar(); if (this._popper !== null) { this._popper.scheduleUpdate(); } } // Private ; _proto._addEventListeners = function _addEventListeners() { var _this = this; $(this._element).on(Event$4.CLICK, function (event) { event.preventDefault(); event.stopPropagation(); _this.toggle(); }); }; _proto._getConfig = function _getConfig(config) { config = _objectSpread({}, this.constructor.Default, $(this._element).data(), config); Util.typeCheckConfig(NAME$4, config, this.constructor.DefaultType); return config; }; _proto._getMenuElement = function _getMenuElement() { if (!this._menu) { var parent = Dropdown._getParentFromElement(this._element); if (parent) { this._menu = parent.querySelector(Selector$4.MENU); } } return this._menu; }; _proto._getPlacement = function _getPlacement() { var $parentDropdown = $(this._element.parentNode); var placement = AttachmentMap.BOTTOM; // Handle dropup if ($parentDropdown.hasClass(ClassName$4.DROPUP)) { placement = AttachmentMap.TOP; if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) { placement = AttachmentMap.TOPEND; } } else if ($parentDropdown.hasClass(ClassName$4.DROPRIGHT)) { placement = AttachmentMap.RIGHT; } else if ($parentDropdown.hasClass(ClassName$4.DROPLEFT)) { placement = AttachmentMap.LEFT; } else if ($(this._menu).hasClass(ClassName$4.MENURIGHT)) { placement = AttachmentMap.BOTTOMEND; } return placement; }; _proto._detectNavbar = function _detectNavbar() { return $(this._element).closest('.navbar').length > 0; }; _proto._getOffset = function _getOffset() { var _this2 = this; var offset = {}; if (typeof this._config.offset === 'function') { offset.fn = function (data) { data.offsets = _objectSpread({}, data.offsets, _this2._config.offset(data.offsets, _this2._element) || {}); return data; }; } else { offset.offset = this._config.offset; } return offset; }; _proto._getPopperConfig = function _getPopperConfig() { var popperConfig = { placement: this._getPlacement(), modifiers: { offset: this._getOffset(), flip: { enabled: this._config.flip }, preventOverflow: { boundariesElement: this._config.boundary } } // Disable Popper.js if we have a static display }; if (this._config.display === 'static') { popperConfig.modifiers.applyStyle = { enabled: false }; } return popperConfig; } // Static ; Dropdown._jQueryInterface = function _jQueryInterface(config) { return this.each(function () { var data = $(this).data(DATA_KEY$4); var _config = typeof config === 'object' ? config : null; if (!data) { data = new Dropdown(this, _config); $(this).data(DATA_KEY$4, data); } if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError("No method named \"" + config + "\""); } data[config](); } }); }; Dropdown._clearMenus = function _clearMenus(event) { if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || event.type === 'keyup' && event.which !== TAB_KEYCODE)) { return; } var toggles = [].slice.call(document.querySelectorAll(Selector$4.DATA_TOGGLE)); for (var i = 0, len = toggles.length; i < len; i++) { var parent = Dropdown._getParentFromElement(toggles[i]); var context = $(toggles[i]).data(DATA_KEY$4); var relatedTarget = { relatedTarget: toggles[i] }; if (event && event.type === 'click') { relatedTarget.clickEvent = event; } if (!context) { continue; } var dropdownMenu = context._menu; if (!$(parent).hasClass(ClassName$4.SHOW)) { continue; } if (event && (event.type === 'click' && /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && $.contains(parent, event.target)) { continue; } var hideEvent = $.Event(Event$4.HIDE, relatedTarget); $(parent).trigger(hideEvent); if (hideEvent.isDefaultPrevented()) { continue; } // If this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { $(document.body).children().off('mouseover', null, $.noop); } toggles[i].setAttribute('aria-expanded', 'false'); $(dropdownMenu).removeClass(ClassName$4.SHOW); $(parent).removeClass(ClassName$4.SHOW).trigger($.Event(Event$4.HIDDEN, relatedTarget)); } }; Dropdown._getParentFromElement = function _getParentFromElement(element) { var parent; var selector = Util.getSelectorFromElement(element); if (selector) { parent = document.querySelector(selector); } return parent || element.parentNode; } // eslint-disable-next-line complexity ; Dropdown._dataApiKeydownHandler = function _dataApiKeydownHandler(event) { // If not input/textarea: // - And not a key in REGEXP_KEYDOWN => not a dropdown command // If input/textarea: // - If space key => not a dropdown command // - If key is other than escape // - If key is not up or down => not a dropdown command // - If trigger inside the menu => not a dropdown command if (/input|textarea/i.test(event.target.tagName) ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || $(event.target).closest(Selector$4.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) { return; } event.preventDefault(); event.stopPropagation(); if (this.disabled || $(this).hasClass(ClassName$4.DISABLED)) { return; } var parent = Dropdown._getParentFromElement(this); var isActive = $(parent).hasClass(ClassName$4.SHOW); if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) { if (event.which === ESCAPE_KEYCODE) { var toggle = parent.querySelector(Selector$4.DATA_TOGGLE); $(toggle).trigger('focus'); } $(this).trigger('click'); return; } var items = [].slice.call(parent.querySelectorAll(Selector$4.VISIBLE_ITEMS)); if (items.length === 0) { return; } var index = items.indexOf(event.target); if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up index--; } if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // Down index++; } if (index < 0) { index = 0; } items[index].focus(); }; _createClass(Dropdown, null, [{ key: "VERSION", get: function get() { return VERSION$4; } }, { key: "Default", get: function get() { return Default$2; } }, { key: "DefaultType", get: function get() { return DefaultType$2; } }]); return Dropdown; }(); /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ $(document).on(Event$4.KEYDOWN_DATA_API, Selector$4.DATA_TOGGLE, Dropdown._dataApiKeydownHandler).on(Event$4.KEYDOWN_DATA_API, Selector$4.MENU, Dropdown._dataApiKeydownHandler).on(Event$4.CLICK_DATA_API + " " + Event$4.KEYUP_DATA_API, Dropdown._clearMenus).on(Event$4.CLICK_DATA_API, Selector$4.DATA_TOGGLE, function (event) { event.preventDefault(); event.stopPropagation(); Dropdown._jQueryInterface.call($(this), 'toggle'); }).on(Event$4.CLICK_DATA_API, Selector$4.FORM_CHILD, function (e) { e.stopPropagation(); }); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME$4] = Dropdown._jQueryInterface; $.fn[NAME$4].Constructor = Dropdown; $.fn[NAME$4].noConflict = function () { $.fn[NAME$4] = JQUERY_NO_CONFLICT$4; return Dropdown._jQueryInterface; }; /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$5 = 'modal'; var VERSION$5 = '4.3.1'; var DATA_KEY$5 = 'bs.modal'; var EVENT_KEY$5 = "." + DATA_KEY$5; var DATA_API_KEY$5 = '.data-api'; var JQUERY_NO_CONFLICT$5 = $.fn[NAME$5]; var ESCAPE_KEYCODE$1 = 27; // KeyboardEvent.which value for Escape (Esc) key var Default$3 = { backdrop: true, keyboard: true, focus: true, show: true }; var DefaultType$3 = { backdrop: '(boolean|string)', keyboard: 'boolean', focus: 'boolean', show: 'boolean' }; var Event$5 = { HIDE: "hide" + EVENT_KEY$5, HIDDEN: "hidden" + EVENT_KEY$5, SHOW: "show" + EVENT_KEY$5, SHOWN: "shown" + EVENT_KEY$5, FOCUSIN: "focusin" + EVENT_KEY$5, RESIZE: "resize" + EVENT_KEY$5, CLICK_DISMISS: "click.dismiss" + EVENT_KEY$5, KEYDOWN_DISMISS: "keydown.dismiss" + EVENT_KEY$5, MOUSEUP_DISMISS: "mouseup.dismiss" + EVENT_KEY$5, MOUSEDOWN_DISMISS: "mousedown.dismiss" + EVENT_KEY$5, CLICK_DATA_API: "click" + EVENT_KEY$5 + DATA_API_KEY$5 }; var ClassName$5 = { SCROLLABLE: 'modal-dialog-scrollable', SCROLLBAR_MEASURER: 'modal-scrollbar-measure', BACKDROP: 'modal-backdrop', OPEN: 'modal-open', FADE: 'fade', SHOW: 'show' }; var Selector$5 = { DIALOG: '.modal-dialog', MODAL_BODY: '.modal-body', DATA_TOGGLE: '[data-toggle="modal"]', DATA_DISMISS: '[data-dismiss="modal"]', FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', STICKY_CONTENT: '.sticky-top' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Modal = /*#__PURE__*/ function () { function Modal(element, config) { this._config = this._getConfig(config); this._element = element; this._dialog = element.querySelector(Selector$5.DIALOG); this._backdrop = null; this._isShown = false; this._isBodyOverflowing = false; this._ignoreBackdropClick = false; this._isTransitioning = false; this._scrollbarWidth = 0; } // Getters var _proto = Modal.prototype; // Public _proto.toggle = function toggle(relatedTarget) { return this._isShown ? this.hide() : this.show(relatedTarget); }; _proto.show = function show(relatedTarget) { var _this = this; if (this._isShown || this._isTransitioning) { return; } if ($(this._element).hasClass(ClassName$5.FADE)) { this._isTransitioning = true; } var showEvent = $.Event(Event$5.SHOW, { relatedTarget: relatedTarget }); $(this._element).trigger(showEvent); if (this._isShown || showEvent.isDefaultPrevented()) { return; } this._isShown = true; this._checkScrollbar(); this._setScrollbar(); this._adjustDialog(); this._setEscapeEvent(); this._setResizeEvent(); $(this._element).on(Event$5.CLICK_DISMISS, Selector$5.DATA_DISMISS, function (event) { return _this.hide(event); }); $(this._dialog).on(Event$5.MOUSEDOWN_DISMISS, function () { $(_this._element).one(Event$5.MOUSEUP_DISMISS, function (event) { if ($(event.target).is(_this._element)) { _this._ignoreBackdropClick = true; } }); }); this._showBackdrop(function () { return _this._showElement(relatedTarget); }); }; _proto.hide = function hide(event) { var _this2 = this; if (event) { event.preventDefault(); } if (!this._isShown || this._isTransitioning) { return; } var hideEvent = $.Event(Event$5.HIDE); $(this._element).trigger(hideEvent); if (!this._isShown || hideEvent.isDefaultPrevented()) { return; } this._isShown = false; var transition = $(this._element).hasClass(ClassName$5.FADE); if (transition) { this._isTransitioning = true; } this._setEscapeEvent(); this._setResizeEvent(); $(document).off(Event$5.FOCUSIN); $(this._element).removeClass(ClassName$5.SHOW); $(this._element).off(Event$5.CLICK_DISMISS); $(this._dialog).off(Event$5.MOUSEDOWN_DISMISS); if (transition) { var transitionDuration = Util.getTransitionDurationFromElement(this._element); $(this._element).one(Util.TRANSITION_END, function (event) { return _this2._hideModal(event); }).emulateTransitionEnd(transitionDuration); } else { this._hideModal(); } }; _proto.dispose = function dispose() { [window, this._element, this._dialog].forEach(function (htmlElement) { return $(htmlElement).off(EVENT_KEY$5); }); /** * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API` * Do not move `document` in `htmlElements` array * It will remove `Event.CLICK_DATA_API` event that should remain */ $(document).off(Event$5.FOCUSIN); $.removeData(this._element, DATA_KEY$5); this._config = null; this._element = null; this._dialog = null; this._backdrop = null; this._isShown = null; this._isBodyOverflowing = null; this._ignoreBackdropClick = null; this._isTransitioning = null; this._scrollbarWidth = null; }; _proto.handleUpdate = function handleUpdate() { this._adjustDialog(); } // Private ; _proto._getConfig = function _getConfig(config) { config = _objectSpread({}, Default$3, config); Util.typeCheckConfig(NAME$5, config, DefaultType$3); return config; }; _proto._showElement = function _showElement(relatedTarget) { var _this3 = this; var transition = $(this._element).hasClass(ClassName$5.FADE); if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { // Don't move modal's DOM position document.body.appendChild(this._element); } this._element.style.display = 'block'; this._element.removeAttribute('aria-hidden'); this._element.setAttribute('aria-modal', true); if ($(this._dialog).hasClass(ClassName$5.SCROLLABLE)) { this._dialog.querySelector(Selector$5.MODAL_BODY).scrollTop = 0; } else { this._element.scrollTop = 0; } if (transition) { Util.reflow(this._element); } $(this._element).addClass(ClassName$5.SHOW); if (this._config.focus) { this._enforceFocus(); } var shownEvent = $.Event(Event$5.SHOWN, { relatedTarget: relatedTarget }); var transitionComplete = function transitionComplete() { if (_this3._config.focus) { _this3._element.focus(); } _this3._isTransitioning = false; $(_this3._element).trigger(shownEvent); }; if (transition) { var transitionDuration = Util.getTransitionDurationFromElement(this._dialog); $(this._dialog).one(Util.TRANSITION_END, transitionComplete).emulateTransitionEnd(transitionDuration); } else { transitionComplete(); } }; _proto._enforceFocus = function _enforceFocus() { var _this4 = this; $(document).off(Event$5.FOCUSIN) // Guard against infinite focus loop .on(Event$5.FOCUSIN, function (event) { if (document !== event.target && _this4._element !== event.target && $(_this4._element).has(event.target).length === 0) { _this4._element.focus(); } }); }; _proto._setEscapeEvent = function _setEscapeEvent() { var _this5 = this; if (this._isShown && this._config.keyboard) { $(this._element).on(Event$5.KEYDOWN_DISMISS, function (event) { if (event.which === ESCAPE_KEYCODE$1) { event.preventDefault(); _this5.hide(); } }); } else if (!this._isShown) { $(this._element).off(Event$5.KEYDOWN_DISMISS); } }; _proto._setResizeEvent = function _setResizeEvent() { var _this6 = this; if (this._isShown) { $(window).on(Event$5.RESIZE, function (event) { return _this6.handleUpdate(event); }); } else { $(window).off(Event$5.RESIZE); } }; _proto._hideModal = function _hideModal() { var _this7 = this; this._element.style.display = 'none'; this._element.setAttribute('aria-hidden', true); this._element.removeAttribute('aria-modal'); this._isTransitioning = false; this._showBackdrop(function () { $(document.body).removeClass(ClassName$5.OPEN); _this7._resetAdjustments(); _this7._resetScrollbar(); $(_this7._element).trigger(Event$5.HIDDEN); }); }; _proto._removeBackdrop = function _removeBackdrop() { if (this._backdrop) { $(this._backdrop).remove(); this._backdrop = null; } }; _proto._showBackdrop = function _showBackdrop(callback) { var _this8 = this; var animate = $(this._element).hasClass(ClassName$5.FADE) ? ClassName$5.FADE : ''; if (this._isShown && this._config.backdrop) { this._backdrop = document.createElement('div'); this._backdrop.className = ClassName$5.BACKDROP; if (animate) { this._backdrop.classList.add(animate); } $(this._backdrop).appendTo(document.body); $(this._element).on(Event$5.CLICK_DISMISS, function (event) { if (_this8._ignoreBackdropClick) { _this8._ignoreBackdropClick = false; return; } if (event.target !== event.currentTarget) { return; } if (_this8._config.backdrop === 'static') { _this8._element.focus(); } else { _this8.hide(); } }); if (animate) { Util.reflow(this._backdrop); } $(this._backdrop).addClass(ClassName$5.SHOW); if (!callback) { return; } if (!animate) { callback(); return; } var backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); $(this._backdrop).one(Util.TRANSITION_END, callback).emulateTransitionEnd(backdropTransitionDuration); } else if (!this._isShown && this._backdrop) { $(this._backdrop).removeClass(ClassName$5.SHOW); var callbackRemove = function callbackRemove() { _this8._removeBackdrop(); if (callback) { callback(); } }; if ($(this._element).hasClass(ClassName$5.FADE)) { var _backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop); $(this._backdrop).one(Util.TRANSITION_END, callbackRemove).emulateTransitionEnd(_backdropTransitionDuration); } else { callbackRemove(); } } else if (callback) { callback(); } } // ---------------------------------------------------------------------- // the following methods are used to handle overflowing modals // todo (fat): these should probably be refactored out of modal.js // ---------------------------------------------------------------------- ; _proto._adjustDialog = function _adjustDialog() { var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; if (!this._isBodyOverflowing && isModalOverflowing) { this._element.style.paddingLeft = this._scrollbarWidth + "px"; } if (this._isBodyOverflowing && !isModalOverflowing) { this._element.style.paddingRight = this._scrollbarWidth + "px"; } }; _proto._resetAdjustments = function _resetAdjustments() { this._element.style.paddingLeft = ''; this._element.style.paddingRight = ''; }; _proto._checkScrollbar = function _checkScrollbar() { var rect = document.body.getBoundingClientRect(); this._isBodyOverflowing = rect.left + rect.right < window.innerWidth; this._scrollbarWidth = this._getScrollbarWidth(); }; _proto._setScrollbar = function _setScrollbar() { var _this9 = this; if (this._isBodyOverflowing) { // Note: DOMNode.style.paddingRight returns the actual value or '' if not set // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT)); var stickyContent = [].slice.call(document.querySelectorAll(Selector$5.STICKY_CONTENT)); // Adjust fixed content padding $(fixedContent).each(function (index, element) { var actualPadding = element.style.paddingRight; var calculatedPadding = $(element).css('padding-right'); $(element).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + _this9._scrollbarWidth + "px"); }); // Adjust sticky content margin $(stickyContent).each(function (index, element) { var actualMargin = element.style.marginRight; var calculatedMargin = $(element).css('margin-right'); $(element).data('margin-right', actualMargin).css('margin-right', parseFloat(calculatedMargin) - _this9._scrollbarWidth + "px"); }); // Adjust body padding var actualPadding = document.body.style.paddingRight; var calculatedPadding = $(document.body).css('padding-right'); $(document.body).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + this._scrollbarWidth + "px"); } $(document.body).addClass(ClassName$5.OPEN); }; _proto._resetScrollbar = function _resetScrollbar() { // Restore fixed content padding var fixedContent = [].slice.call(document.querySelectorAll(Selector$5.FIXED_CONTENT)); $(fixedContent).each(function (index, element) { var padding = $(element).data('padding-right'); $(element).removeData('padding-right'); element.style.paddingRight = padding ? padding : ''; }); // Restore sticky content var elements = [].slice.call(document.querySelectorAll("" + Selector$5.STICKY_CONTENT)); $(elements).each(function (index, element) { var margin = $(element).data('margin-right'); if (typeof margin !== 'undefined') { $(element).css('margin-right', margin).removeData('margin-right'); } }); // Restore body padding var padding = $(document.body).data('padding-right'); $(document.body).removeData('padding-right'); document.body.style.paddingRight = padding ? padding : ''; }; _proto._getScrollbarWidth = function _getScrollbarWidth() { // thx d.walsh var scrollDiv = document.createElement('div'); scrollDiv.className = ClassName$5.SCROLLBAR_MEASURER; document.body.appendChild(scrollDiv); var scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth; document.body.removeChild(scrollDiv); return scrollbarWidth; } // Static ; Modal._jQueryInterface = function _jQueryInterface(config, relatedTarget) { return this.each(function () { var data = $(this).data(DATA_KEY$5); var _config = _objectSpread({}, Default$3, $(this).data(), typeof config === 'object' && config ? config : {}); if (!data) { data = new Modal(this, _config); $(this).data(DATA_KEY$5, data); } if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError("No method named \"" + config + "\""); } data[config](relatedTarget); } else if (_config.show) { data.show(relatedTarget); } }); }; _createClass(Modal, null, [{ key: "VERSION", get: function get() { return VERSION$5; } }, { key: "Default", get: function get() { return Default$3; } }]); return Modal; }(); /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ $(document).on(Event$5.CLICK_DATA_API, Selector$5.DATA_TOGGLE, function (event) { var _this10 = this; var target; var selector = Util.getSelectorFromElement(this); if (selector) { target = document.querySelector(selector); } var config = $(target).data(DATA_KEY$5) ? 'toggle' : _objectSpread({}, $(target).data(), $(this).data()); if (this.tagName === 'A' || this.tagName === 'AREA') { event.preventDefault(); } var $target = $(target).one(Event$5.SHOW, function (showEvent) { if (showEvent.isDefaultPrevented()) { // Only register focus restorer if modal will actually get shown return; } $target.one(Event$5.HIDDEN, function () { if ($(_this10).is(':visible')) { _this10.focus(); } }); }); Modal._jQueryInterface.call($(target), config, this); }); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME$5] = Modal._jQueryInterface; $.fn[NAME$5].Constructor = Modal; $.fn[NAME$5].noConflict = function () { $.fn[NAME$5] = JQUERY_NO_CONFLICT$5; return Modal._jQueryInterface; }; /** * -------------------------------------------------------------------------- * Bootstrap (v4.3.1): tools/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ var uriAttrs = ['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']; var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; var DefaultWhitelist = { // Global attributes allowed on any supplied element below. '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], a: ['target', 'href', 'title', 'rel'], area: [], b: [], br: [], col: [], code: [], div: [], em: [], hr: [], h1: [], h2: [], h3: [], h4: [], h5: [], h6: [], i: [], img: ['src', 'alt', 'title', 'width', 'height'], li: [], ol: [], p: [], pre: [], s: [], small: [], span: [], sub: [], sup: [], strong: [], u: [], ul: [] /** * A pattern that recognizes a commonly useful subset of URLs that are safe. * * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts */ }; var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi; /** * A pattern that matches safe data URLs. Only matches image, video and audio types. * * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts */ var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i; function allowedAttribute(attr, allowedAttributeList) { var attrName = attr.nodeName.toLowerCase(); if (allowedAttributeList.indexOf(attrName) !== -1) { if (uriAttrs.indexOf(attrName) !== -1) { return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN)); } return true; } var regExp = allowedAttributeList.filter(function (attrRegex) { return attrRegex instanceof RegExp; }); // Check if a regular expression validates the attribute. for (var i = 0, l = regExp.length; i < l; i++) { if (attrName.match(regExp[i])) { return true; } } return false; } function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) { if (unsafeHtml.length === 0) { return unsafeHtml; } if (sanitizeFn && typeof sanitizeFn === 'function') { return sanitizeFn(unsafeHtml); } var domParser = new window.DOMParser(); var createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); var whitelistKeys = Object.keys(whiteList); var elements = [].slice.call(createdDocument.body.querySelectorAll('*')); var _loop = function _loop(i, len) { var el = elements[i]; var elName = el.nodeName.toLowerCase(); if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) { el.parentNode.removeChild(el); return "continue"; } var attributeList = [].slice.call(el.attributes); var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []); attributeList.forEach(function (attr) { if (!allowedAttribute(attr, whitelistedAttributes)) { el.removeAttribute(attr.nodeName); } }); }; for (var i = 0, len = elements.length; i < len; i++) { var _ret = _loop(i, len); if (_ret === "continue") continue; } return createdDocument.body.innerHTML; } /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$6 = 'tooltip'; var VERSION$6 = '4.3.1'; var DATA_KEY$6 = 'bs.tooltip'; var EVENT_KEY$6 = "." + DATA_KEY$6; var JQUERY_NO_CONFLICT$6 = $.fn[NAME$6]; var CLASS_PREFIX = 'bs-tooltip'; var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g'); var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']; var DefaultType$4 = { animation: 'boolean', template: 'string', title: '(string|element|function)', trigger: 'string', delay: '(number|object)', html: 'boolean', selector: '(string|boolean)', placement: '(string|function)', offset: '(number|string|function)', container: '(string|element|boolean)', fallbackPlacement: '(string|array)', boundary: '(string|element)', sanitize: 'boolean', sanitizeFn: '(null|function)', whiteList: 'object' }; var AttachmentMap$1 = { AUTO: 'auto', TOP: 'top', RIGHT: 'right', BOTTOM: 'bottom', LEFT: 'left' }; var Default$4 = { animation: true, template: '', trigger: 'hover focus', title: '', delay: 0, html: false, selector: false, placement: 'top', offset: 0, container: false, fallbackPlacement: 'flip', boundary: 'scrollParent', sanitize: true, sanitizeFn: null, whiteList: DefaultWhitelist }; var HoverState = { SHOW: 'show', OUT: 'out' }; var Event$6 = { HIDE: "hide" + EVENT_KEY$6, HIDDEN: "hidden" + EVENT_KEY$6, SHOW: "show" + EVENT_KEY$6, SHOWN: "shown" + EVENT_KEY$6, INSERTED: "inserted" + EVENT_KEY$6, CLICK: "click" + EVENT_KEY$6, FOCUSIN: "focusin" + EVENT_KEY$6, FOCUSOUT: "focusout" + EVENT_KEY$6, MOUSEENTER: "mouseenter" + EVENT_KEY$6, MOUSELEAVE: "mouseleave" + EVENT_KEY$6 }; var ClassName$6 = { FADE: 'fade', SHOW: 'show' }; var Selector$6 = { TOOLTIP: '.tooltip', TOOLTIP_INNER: '.tooltip-inner', ARROW: '.arrow' }; var Trigger = { HOVER: 'hover', FOCUS: 'focus', CLICK: 'click', MANUAL: 'manual' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Tooltip = /*#__PURE__*/ function () { function Tooltip(element, config) { /** * Check for Popper dependency * Popper - https://popper.js.org */ if (typeof Popper === 'undefined') { throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org/)'); } // private this._isEnabled = true; this._timeout = 0; this._hoverState = ''; this._activeTrigger = {}; this._popper = null; // Protected this.element = element; this.config = this._getConfig(config); this.tip = null; this._setListeners(); } // Getters var _proto = Tooltip.prototype; // Public _proto.enable = function enable() { this._isEnabled = true; }; _proto.disable = function disable() { this._isEnabled = false; }; _proto.toggleEnabled = function toggleEnabled() { this._isEnabled = !this._isEnabled; }; _proto.toggle = function toggle(event) { if (!this._isEnabled) { return; } if (event) { var dataKey = this.constructor.DATA_KEY; var context = $(event.currentTarget).data(dataKey); if (!context) { context = new this.constructor(event.currentTarget, this._getDelegateConfig()); $(event.currentTarget).data(dataKey, context); } context._activeTrigger.click = !context._activeTrigger.click; if (context._isWithActiveTrigger()) { context._enter(null, context); } else { context._leave(null, context); } } else { if ($(this.getTipElement()).hasClass(ClassName$6.SHOW)) { this._leave(null, this); return; } this._enter(null, this); } }; _proto.dispose = function dispose() { clearTimeout(this._timeout); $.removeData(this.element, this.constructor.DATA_KEY); $(this.element).off(this.constructor.EVENT_KEY); $(this.element).closest('.modal').off('hide.bs.modal'); if (this.tip) { $(this.tip).remove(); } this._isEnabled = null; this._timeout = null; this._hoverState = null; this._activeTrigger = null; if (this._popper !== null) { this._popper.destroy(); } this._popper = null; this.element = null; this.config = null; this.tip = null; }; _proto.show = function show() { var _this = this; if ($(this.element).css('display') === 'none') { throw new Error('Please use show on visible elements'); } var showEvent = $.Event(this.constructor.Event.SHOW); if (this.isWithContent() && this._isEnabled) { $(this.element).trigger(showEvent); var shadowRoot = Util.findShadowRoot(this.element); var isInTheDom = $.contains(shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement, this.element); if (showEvent.isDefaultPrevented() || !isInTheDom) { return; } var tip = this.getTipElement(); var tipId = Util.getUID(this.constructor.NAME); tip.setAttribute('id', tipId); this.element.setAttribute('aria-describedby', tipId); this.setContent(); if (this.config.animation) { $(tip).addClass(ClassName$6.FADE); } var placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement; var attachment = this._getAttachment(placement); this.addAttachmentClass(attachment); var container = this._getContainer(); $(tip).data(this.constructor.DATA_KEY, this); if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) { $(tip).appendTo(container); } $(this.element).trigger(this.constructor.Event.INSERTED); this._popper = new Popper(this.element, tip, { placement: attachment, modifiers: { offset: this._getOffset(), flip: { behavior: this.config.fallbackPlacement }, arrow: { element: Selector$6.ARROW }, preventOverflow: { boundariesElement: this.config.boundary } }, onCreate: function onCreate(data) { if (data.originalPlacement !== data.placement) { _this._handlePopperPlacementChange(data); } }, onUpdate: function onUpdate(data) { return _this._handlePopperPlacementChange(data); } }); $(tip).addClass(ClassName$6.SHOW); // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; // only needed because of broken event delegation on iOS // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html if ('ontouchstart' in document.documentElement) { $(document.body).children().on('mouseover', null, $.noop); } var complete = function complete() { if (_this.config.animation) { _this._fixTransition(); } var prevHoverState = _this._hoverState; _this._hoverState = null; $(_this.element).trigger(_this.constructor.Event.SHOWN); if (prevHoverState === HoverState.OUT) { _this._leave(null, _this); } }; if ($(this.tip).hasClass(ClassName$6.FADE)) { var transitionDuration = Util.getTransitionDurationFromElement(this.tip); $(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); } else { complete(); } } }; _proto.hide = function hide(callback) { var _this2 = this; var tip = this.getTipElement(); var hideEvent = $.Event(this.constructor.Event.HIDE); var complete = function complete() { if (_this2._hoverState !== HoverState.SHOW && tip.parentNode) { tip.parentNode.removeChild(tip); } _this2._cleanTipClass(); _this2.element.removeAttribute('aria-describedby'); $(_this2.element).trigger(_this2.constructor.Event.HIDDEN); if (_this2._popper !== null) { _this2._popper.destroy(); } if (callback) { callback(); } }; $(this.element).trigger(hideEvent); if (hideEvent.isDefaultPrevented()) { return; } $(tip).removeClass(ClassName$6.SHOW); // If this is a touch-enabled device we remove the extra // empty mouseover listeners we added for iOS support if ('ontouchstart' in document.documentElement) { $(document.body).children().off('mouseover', null, $.noop); } this._activeTrigger[Trigger.CLICK] = false; this._activeTrigger[Trigger.FOCUS] = false; this._activeTrigger[Trigger.HOVER] = false; if ($(this.tip).hasClass(ClassName$6.FADE)) { var transitionDuration = Util.getTransitionDurationFromElement(tip); $(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration); } else { complete(); } this._hoverState = ''; }; _proto.update = function update() { if (this._popper !== null) { this._popper.scheduleUpdate(); } } // Protected ; _proto.isWithContent = function isWithContent() { return Boolean(this.getTitle()); }; _proto.addAttachmentClass = function addAttachmentClass(attachment) { $(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment); }; _proto.getTipElement = function getTipElement() { this.tip = this.tip || $(this.config.template)[0]; return this.tip; }; _proto.setContent = function setContent() { var tip = this.getTipElement(); this.setElementContent($(tip.querySelectorAll(Selector$6.TOOLTIP_INNER)), this.getTitle()); $(tip).removeClass(ClassName$6.FADE + " " + ClassName$6.SHOW); }; _proto.setElementContent = function setElementContent($element, content) { if (typeof content === 'object' && (content.nodeType || content.jquery)) { // Content is a DOM node or a jQuery if (this.config.html) { if (!$(content).parent().is($element)) { $element.empty().append(content); } } else { $element.text($(content).text()); } return; } if (this.config.html) { if (this.config.sanitize) { content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn); } $element.html(content); } else { $element.text(content); } }; _proto.getTitle = function getTitle() { var title = this.element.getAttribute('data-original-title'); if (!title) { title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title; } return title; } // Private ; _proto._getOffset = function _getOffset() { var _this3 = this; var offset = {}; if (typeof this.config.offset === 'function') { offset.fn = function (data) { data.offsets = _objectSpread({}, data.offsets, _this3.config.offset(data.offsets, _this3.element) || {}); return data; }; } else { offset.offset = this.config.offset; } return offset; }; _proto._getContainer = function _getContainer() { if (this.config.container === false) { return document.body; } if (Util.isElement(this.config.container)) { return $(this.config.container); } return $(document).find(this.config.container); }; _proto._getAttachment = function _getAttachment(placement) { return AttachmentMap$1[placement.toUpperCase()]; }; _proto._setListeners = function _setListeners() { var _this4 = this; var triggers = this.config.trigger.split(' '); triggers.forEach(function (trigger) { if (trigger === 'click') { $(_this4.element).on(_this4.constructor.Event.CLICK, _this4.config.selector, function (event) { return _this4.toggle(event); }); } else if (trigger !== Trigger.MANUAL) { var eventIn = trigger === Trigger.HOVER ? _this4.constructor.Event.MOUSEENTER : _this4.constructor.Event.FOCUSIN; var eventOut = trigger === Trigger.HOVER ? _this4.constructor.Event.MOUSELEAVE : _this4.constructor.Event.FOCUSOUT; $(_this4.element).on(eventIn, _this4.config.selector, function (event) { return _this4._enter(event); }).on(eventOut, _this4.config.selector, function (event) { return _this4._leave(event); }); } }); $(this.element).closest('.modal').on('hide.bs.modal', function () { if (_this4.element) { _this4.hide(); } }); if (this.config.selector) { this.config = _objectSpread({}, this.config, { trigger: 'manual', selector: '' }); } else { this._fixTitle(); } }; _proto._fixTitle = function _fixTitle() { var titleType = typeof this.element.getAttribute('data-original-title'); if (this.element.getAttribute('title') || titleType !== 'string') { this.element.setAttribute('data-original-title', this.element.getAttribute('title') || ''); this.element.setAttribute('title', ''); } }; _proto._enter = function _enter(event, context) { var dataKey = this.constructor.DATA_KEY; context = context || $(event.currentTarget).data(dataKey); if (!context) { context = new this.constructor(event.currentTarget, this._getDelegateConfig()); $(event.currentTarget).data(dataKey, context); } if (event) { context._activeTrigger[event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER] = true; } if ($(context.getTipElement()).hasClass(ClassName$6.SHOW) || context._hoverState === HoverState.SHOW) { context._hoverState = HoverState.SHOW; return; } clearTimeout(context._timeout); context._hoverState = HoverState.SHOW; if (!context.config.delay || !context.config.delay.show) { context.show(); return; } context._timeout = setTimeout(function () { if (context._hoverState === HoverState.SHOW) { context.show(); } }, context.config.delay.show); }; _proto._leave = function _leave(event, context) { var dataKey = this.constructor.DATA_KEY; context = context || $(event.currentTarget).data(dataKey); if (!context) { context = new this.constructor(event.currentTarget, this._getDelegateConfig()); $(event.currentTarget).data(dataKey, context); } if (event) { context._activeTrigger[event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER] = false; } if (context._isWithActiveTrigger()) { return; } clearTimeout(context._timeout); context._hoverState = HoverState.OUT; if (!context.config.delay || !context.config.delay.hide) { context.hide(); return; } context._timeout = setTimeout(function () { if (context._hoverState === HoverState.OUT) { context.hide(); } }, context.config.delay.hide); }; _proto._isWithActiveTrigger = function _isWithActiveTrigger() { for (var trigger in this._activeTrigger) { if (this._activeTrigger[trigger]) { return true; } } return false; }; _proto._getConfig = function _getConfig(config) { var dataAttributes = $(this.element).data(); Object.keys(dataAttributes).forEach(function (dataAttr) { if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) { delete dataAttributes[dataAttr]; } }); config = _objectSpread({}, this.constructor.Default, dataAttributes, typeof config === 'object' && config ? config : {}); if (typeof config.delay === 'number') { config.delay = { show: config.delay, hide: config.delay }; } if (typeof config.title === 'number') { config.title = config.title.toString(); } if (typeof config.content === 'number') { config.content = config.content.toString(); } Util.typeCheckConfig(NAME$6, config, this.constructor.DefaultType); if (config.sanitize) { config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn); } return config; }; _proto._getDelegateConfig = function _getDelegateConfig() { var config = {}; if (this.config) { for (var key in this.config) { if (this.constructor.Default[key] !== this.config[key]) { config[key] = this.config[key]; } } } return config; }; _proto._cleanTipClass = function _cleanTipClass() { var $tip = $(this.getTipElement()); var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX); if (tabClass !== null && tabClass.length) { $tip.removeClass(tabClass.join('')); } }; _proto._handlePopperPlacementChange = function _handlePopperPlacementChange(popperData) { var popperInstance = popperData.instance; this.tip = popperInstance.popper; this._cleanTipClass(); this.addAttachmentClass(this._getAttachment(popperData.placement)); }; _proto._fixTransition = function _fixTransition() { var tip = this.getTipElement(); var initConfigAnimation = this.config.animation; if (tip.getAttribute('x-placement') !== null) { return; } $(tip).removeClass(ClassName$6.FADE); this.config.animation = false; this.hide(); this.show(); this.config.animation = initConfigAnimation; } // Static ; Tooltip._jQueryInterface = function _jQueryInterface(config) { return this.each(function () { var data = $(this).data(DATA_KEY$6); var _config = typeof config === 'object' && config; if (!data && /dispose|hide/.test(config)) { return; } if (!data) { data = new Tooltip(this, _config); $(this).data(DATA_KEY$6, data); } if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError("No method named \"" + config + "\""); } data[config](); } }); }; _createClass(Tooltip, null, [{ key: "VERSION", get: function get() { return VERSION$6; } }, { key: "Default", get: function get() { return Default$4; } }, { key: "NAME", get: function get() { return NAME$6; } }, { key: "DATA_KEY", get: function get() { return DATA_KEY$6; } }, { key: "Event", get: function get() { return Event$6; } }, { key: "EVENT_KEY", get: function get() { return EVENT_KEY$6; } }, { key: "DefaultType", get: function get() { return DefaultType$4; } }]); return Tooltip; }(); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME$6] = Tooltip._jQueryInterface; $.fn[NAME$6].Constructor = Tooltip; $.fn[NAME$6].noConflict = function () { $.fn[NAME$6] = JQUERY_NO_CONFLICT$6; return Tooltip._jQueryInterface; }; /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$7 = 'popover'; var VERSION$7 = '4.3.1'; var DATA_KEY$7 = 'bs.popover'; var EVENT_KEY$7 = "." + DATA_KEY$7; var JQUERY_NO_CONFLICT$7 = $.fn[NAME$7]; var CLASS_PREFIX$1 = 'bs-popover'; var BSCLS_PREFIX_REGEX$1 = new RegExp("(^|\\s)" + CLASS_PREFIX$1 + "\\S+", 'g'); var Default$5 = _objectSpread({}, Tooltip.Default, { placement: 'right', trigger: 'click', content: '', template: '' }); var DefaultType$5 = _objectSpread({}, Tooltip.DefaultType, { content: '(string|element|function)' }); var ClassName$7 = { FADE: 'fade', SHOW: 'show' }; var Selector$7 = { TITLE: '.popover-header', CONTENT: '.popover-body' }; var Event$7 = { HIDE: "hide" + EVENT_KEY$7, HIDDEN: "hidden" + EVENT_KEY$7, SHOW: "show" + EVENT_KEY$7, SHOWN: "shown" + EVENT_KEY$7, INSERTED: "inserted" + EVENT_KEY$7, CLICK: "click" + EVENT_KEY$7, FOCUSIN: "focusin" + EVENT_KEY$7, FOCUSOUT: "focusout" + EVENT_KEY$7, MOUSEENTER: "mouseenter" + EVENT_KEY$7, MOUSELEAVE: "mouseleave" + EVENT_KEY$7 /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var Popover = /*#__PURE__*/ function (_Tooltip) { _inheritsLoose(Popover, _Tooltip); function Popover() { return _Tooltip.apply(this, arguments) || this; } var _proto = Popover.prototype; // Overrides _proto.isWithContent = function isWithContent() { return this.getTitle() || this._getContent(); }; _proto.addAttachmentClass = function addAttachmentClass(attachment) { $(this.getTipElement()).addClass(CLASS_PREFIX$1 + "-" + attachment); }; _proto.getTipElement = function getTipElement() { this.tip = this.tip || $(this.config.template)[0]; return this.tip; }; _proto.setContent = function setContent() { var $tip = $(this.getTipElement()); // We use append for html objects to maintain js events this.setElementContent($tip.find(Selector$7.TITLE), this.getTitle()); var content = this._getContent(); if (typeof content === 'function') { content = content.call(this.element); } this.setElementContent($tip.find(Selector$7.CONTENT), content); $tip.removeClass(ClassName$7.FADE + " " + ClassName$7.SHOW); } // Private ; _proto._getContent = function _getContent() { return this.element.getAttribute('data-content') || this.config.content; }; _proto._cleanTipClass = function _cleanTipClass() { var $tip = $(this.getTipElement()); var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX$1); if (tabClass !== null && tabClass.length > 0) { $tip.removeClass(tabClass.join('')); } } // Static ; Popover._jQueryInterface = function _jQueryInterface(config) { return this.each(function () { var data = $(this).data(DATA_KEY$7); var _config = typeof config === 'object' ? config : null; if (!data && /dispose|hide/.test(config)) { return; } if (!data) { data = new Popover(this, _config); $(this).data(DATA_KEY$7, data); } if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError("No method named \"" + config + "\""); } data[config](); } }); }; _createClass(Popover, null, [{ key: "VERSION", // Getters get: function get() { return VERSION$7; } }, { key: "Default", get: function get() { return Default$5; } }, { key: "NAME", get: function get() { return NAME$7; } }, { key: "DATA_KEY", get: function get() { return DATA_KEY$7; } }, { key: "Event", get: function get() { return Event$7; } }, { key: "EVENT_KEY", get: function get() { return EVENT_KEY$7; } }, { key: "DefaultType", get: function get() { return DefaultType$5; } }]); return Popover; }(Tooltip); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME$7] = Popover._jQueryInterface; $.fn[NAME$7].Constructor = Popover; $.fn[NAME$7].noConflict = function () { $.fn[NAME$7] = JQUERY_NO_CONFLICT$7; return Popover._jQueryInterface; }; /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ var NAME$8 = 'scrollspy'; var VERSION$8 = '4.3.1'; var DATA_KEY$8 = 'bs.scrollspy'; var EVENT_KEY$8 = "." + DATA_KEY$8; var DATA_API_KEY$6 = '.data-api'; var JQUERY_NO_CONFLICT$8 = $.fn[NAME$8]; var Default$6 = { offset: 10, method: 'auto', target: '' }; var DefaultType$6 = { offset: 'number', method: 'string', target: '(string|element)' }; var Event$8 = { ACTIVATE: "activate" + EVENT_KEY$8, SCROLL: "scroll" + EVENT_KEY$8, LOAD_DATA_API: "load" + EVENT_KEY$8 + DATA_API_KEY$6 }; var ClassName$8 = { DROPDOWN_ITEM: 'dropdown-item', DROPDOWN_MENU: 'dropdown-menu', ACTIVE: 'active' }; var Selector$8 = { DATA_SPY: '[data-spy="scroll"]', ACTIVE: '.active', NAV_LIST_GROUP: '.nav, .list-group', NAV_LINKS: '.nav-link', NAV_ITEMS: '.nav-item', LIST_ITEMS: '.list-group-item', DROPDOWN: '.dropdown', DROPDOWN_ITEMS: '.dropdown-item', DROPDOWN_TOGGLE: '.dropdown-toggle' }; var OffsetMethod = { OFFSET: 'offset', POSITION: 'position' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ }; var ScrollSpy = /*#__PURE__*/ function () { function ScrollSpy(element, config) { var _this = this; this._element = element; this._scrollElement = element.tagName === 'BODY' ? window : element; this._config = this._getConfig(config); this._selector = this._config.target + " " + Selector$8.NAV_LINKS + "," + (this._config.target + " " + Selector$8.LIST_ITEMS + ",") + (this._config.target + " " + Selector$8.DROPDOWN_ITEMS); this._offsets = []; this._targets = []; this._activeTarget = null; this._scrollHeight = 0; $(this._scrollElement).on(Event$8.SCROLL, function (event) { return _this._process(event); }); this.refresh(); this._process(); } // Getters var _proto = ScrollSpy.prototype; // Public _proto.refresh = function refresh() { var _this2 = this; var autoMethod = this._scrollElement === this._scrollElement.window ? OffsetMethod.OFFSET : OffsetMethod.POSITION; var offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method; var offsetBase = offsetMethod === OffsetMethod.POSITION ? this._getScrollTop() : 0; this._offsets = []; this._targets = []; this._scrollHeight = this._getScrollHeight(); var targets = [].slice.call(document.querySelectorAll(this._selector)); targets.map(function (element) { var target; var targetSelector = Util.getSelectorFromElement(element); if (targetSelector) { target = document.querySelector(targetSelector); } if (target) { var targetBCR = target.getBoundingClientRect(); if (targetBCR.width || targetBCR.height) { // TODO (fat): remove sketch reliance on jQuery position/offset return [$(target)[offsetMethod]().top + offsetBase, targetSelector]; } } return null; }).filter(function (item) { return item; }).sort(function (a, b) { return a[0] - b[0]; }).forEach(function (item) { _this2._offsets.push(item[0]); _this2._targets.push(item[1]); }); }; _proto.dispose = function dispose() { $.removeData(this._element, DATA_KEY$8); $(this._scrollElement).off(EVENT_KEY$8); this._element = null; this._scrollElement = null; this._config = null; this._selector = null; this._offsets = null; this._targets = null; this._activeTarget = null; this._scrollHeight = null; } // Private ; _proto._getConfig = function _getConfig(config) { config = _objectSpread({}, Default$6, typeof config === 'object' && config ? config : {}); if (typeof config.target !== 'string') { var id = $(config.target).attr('id'); if (!id) { id = Util.getUID(NAME$8); $(config.target).attr('id', id); } config.target = "#" + id; } Util.typeCheckConfig(NAME$8, config, DefaultType$6); return config; }; _proto._getScrollTop = function _getScrollTop() { return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop; }; _proto._getScrollHeight = function _getScrollHeight() { return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); }; _proto._getOffsetHeight = function _getOffsetHeight() { return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height; }; _proto._process = function _process() { var scrollTop = this._getScrollTop() + this._config.offset; var scrollHeight = this._getScrollHeight(); var maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight(); if (this._scrollHeight !== scrollHeight) { this.refresh(); } if (scrollTop >= maxScroll) { var target = this._targets[this._targets.length - 1]; if (this._activeTarget !== target) { this._activate(target); } return; } if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { this._activeTarget = null; this._clear(); return; } var offsetLength = this._offsets.length; for (var i = offsetLength; i--;) { var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]); if (isActiveTarget) { this._activate(this._targets[i]); } } }; _proto._activate = function _activate(target) { this._activeTarget = target; this._clear(); var queries = this._selector.split(',').map(function (selector) { return selector + "[data-target=\"" + target + "\"]," + selector + "[href=\"" + target + "\"]"; }); var $link = $([].slice.call(document.querySelectorAll(queries.join(',')))); if ($link.hasClass(ClassName$8.DROPDOWN_ITEM)) { $link.closest(Selector$8.DROPDOWN).find(Selector$8.DROPDOWN_TOGGLE).addClass(ClassName$8.ACTIVE); $link.addClass(ClassName$8.ACTIVE); } else { // Set triggered link as active $link.addClass(ClassName$8.ACTIVE); // Set triggered links parents as active // With both
    and