From 70fb3743ee6864c05b7c3293797343dbfc195d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CFiboAI=E2=80=9D?= <“[git@fiboai.cn]”> Date: Thu, 2 Dec 2021 18:33:17 +0800 Subject: [PATCH] first commit --- jar-enginex-runner/.gitignore | 34 + jar-enginex-runner/README.md | 3 + jar-enginex-runner/lib/javax.ejb.jar | Bin 0 -> 47581 bytes jar-enginex-runner/lib/javax.jms.jar | Bin 0 -> 25957 bytes jar-enginex-runner/lib/javax.persistence.jar | Bin 0 -> 129793 bytes jar-enginex-runner/lib/javax.resource.jar | Bin 0 -> 44511 bytes jar-enginex-runner/lib/javax.servlet.jsp.jar | Bin 0 -> 78836 bytes .../lib/javax.servlet.jsp.jstl.jar | Bin 0 -> 28120 bytes jar-enginex-runner/lib/javax.transaction.jar | Bin 0 -> 9714 bytes jar-enginex-runner/mvnw | 310 +++ jar-enginex-runner/mvnw.cmd | 182 ++ jar-enginex-runner/pom.xml | 311 +++ .../executor/JarEnginexRunnerApplication.java | 19 + .../executor/canal/CacheController.java | 119 ++ .../enginex/executor/canal/CanalClient.java | 246 +++ .../enginex/executor/canal/TableEnum.java | 77 + .../common/basefactory/CustomBeanFactory.java | 17 + .../common/constants/CommonConst.java | 31 + .../executor/common/constants/Constants.java | 30 + .../common/constants/ParamTypeConst.java | 10 + .../common/ksession/KSessionFactory.java | 60 + .../common/ksession/KSessionPool.java | 67 + .../executor/common/mapper/BaseMapper.java | 55 + .../common/mapper/EmailTemplateMapper.java | 39 + .../common/mapper/EmailTemplateMapper.xml | 345 ++++ .../executor/common/model/BasePage.java | 81 + .../executor/common/model/EmailTemplate.java | 122 ++ .../common/model/EmailTemplateExample.java | 711 +++++++ .../common/model/ExpressionParam.java | 18 + .../executor/common/session/SessionData.java | 15 + .../common/session/SessionManager.java | 20 + .../enginex/executor/config/ConfigHolder.java | 72 + .../config/ConfigurationContainor.java | 46 + .../executor/config/DataSourceConfig.java | 61 + .../executor/config/RestTemplateConfig.java | 52 + .../datamanage/mapper/FieldMapper.java | 57 + .../datamanage/mapper/FieldMapper.xml | 147 ++ .../datamanage/mapper/FieldTypeMapper.java | 128 ++ .../datamanage/mapper/FieldTypeMapper.xml | 201 ++ .../mapper/FieldTypeUserMapper.java | 54 + .../datamanage/mapper/FieldTypeUserMapper.xml | 70 + .../datamanage/mapper/FieldUserMapper.java | 77 + .../datamanage/mapper/FieldUserMapper.xml | 117 ++ .../datamanage/mapper/SimpleMapper.java | 11 + .../datamanage/mapper/SimpleMapper.xml | 12 + .../executor/datamanage/model/CustList.java | 236 +++ .../executor/datamanage/model/Field.java | 203 ++ .../executor/datamanage/model/FieldCond.java | 142 ++ .../executor/datamanage/model/FieldInter.java | 104 + .../executor/datamanage/model/FieldType.java | 106 + .../datamanage/model/FieldTypeUser.java | 80 + .../executor/datamanage/model/FieldUser.java | 102 + .../datamanage/model/FormulaField.java | 45 + .../datamanage/service/FieldService.java | 19 + .../service/impl/FieldServiceImpl.java | 112 ++ .../executor/datamanage/vo/FieldEnumVo.java | 29 + .../executor/datamanage/vo/FieldExcelVo.java | 150 ++ .../datamanage/vo/FieldFormulaVo.java | 89 + .../datamanage/vo/FieldSubCondVo.java | 108 ++ .../executor/engine/consts/EngineConst.java | 20 + .../executor/engine/consts/EngineMsg.java | 29 + .../engine/consts/EngineOperator.java | 151 ++ .../executor/engine/consts/EnumConst.java | 47 + .../engine/controller/ApiController.java | 119 ++ .../engine/controller/DecisionController.java | 70 + .../engine/enums/CallBackTypeEnum.java | 23 + .../executor/engine/enums/NodeTypeEnum.java | 125 ++ .../executor/engine/mapper/EngineMapper.java | 9 + .../engine/mapper/EngineNodeMapper.java | 18 + .../engine/mapper/EngineNodeMapper.xml | 31 + .../engine/mapper/EngineResultSetMapper.java | 72 + .../engine/mapper/EngineResultSetMapper.xml | 223 +++ .../engine/mapper/EngineVersionMapper.java | 27 + .../engine/mapper/EngineVersionMapper.xml | 48 + .../executor/engine/model/ComplexRule.java | 42 + .../engine/model/DecisionOptions.java | 62 + .../engine/model/DecisionReqModel.java | 16 + .../enginex/executor/engine/model/Engine.java | 90 + .../executor/engine/model/EngineNode.java | 85 + .../engine/model/EngineResultSet.java | 252 +++ .../executor/engine/model/EngineRule.java | 74 + .../executor/engine/model/EngineVersion.java | 79 + .../engine/model/IndexEngineReportVo.java | 53 + .../executor/engine/model/InputParam.java | 36 + .../executor/engine/model/NodeKnowledge.java | 63 + .../enginex/executor/engine/model/Result.java | 63 + .../executor/engine/model/ResultSetList.java | 76 + .../executor/engine/model/Sandbox.java | 53 + .../engine/model/ScoreCardEngine.java | 49 + .../executor/engine/model/TestRule.java | 37 + .../model/request/DecisionApiBizData.java | 18 + .../model/request/DecisionApiRequest.java | 17 + .../engine/service/EngineApiService.java | 8 + .../engine/service/EngineNodeService.java | 16 + .../engine/service/EngineService.java | 14 + .../engine/service/EngineVersionService.java | 17 + .../service/impl/EngineApiServiceImpl.java | 400 ++++ .../service/impl/EngineNodeServiceImpl.java | 45 + .../service/impl/EngineServiceImpl.java | 37 + .../impl/EngineVersionServiceImpl.java | 56 + .../engine/thread/EngineCallable.java | 25 + .../knowledge/mapper/KnowledgeTreeMapper.java | 36 + .../knowledge/mapper/KnowledgeTreeMapper.xml | 244 +++ .../knowledge/mapper/RuleFieldMapper.java | 74 + .../knowledge/mapper/RuleFieldMapper.xml | 126 ++ .../executor/knowledge/mapper/RuleMapper.java | 90 + .../executor/knowledge/mapper/RuleMapper.xml | 468 +++++ .../knowledge/model/EngineRuleRel.java | 41 + .../knowledge/model/KnowledgeTree.java | 227 +++ .../knowledge/model/KnowledgeTreeRel.java | 48 + .../executor/knowledge/model/Rule.java | 411 ++++ .../executor/knowledge/model/RuleContent.java | 149 ++ .../executor/knowledge/model/RuleExcel.java | 74 + .../executor/knowledge/model/RuleField.java | 164 ++ .../knowledge/model/ScoreCardJson.java | 44 + .../knowledge/model/ScorecardExcel.java | 71 + .../knowledge/model/ScorecardField.java | 50 + .../knowledge/model/ScorecardRuleContent.java | 140 ++ .../message/email/service/EmailService.java | 21 + .../email/service/impl/EmailServiceImpl.java | 49 + .../executor/node/service/CommonService.java | 26 + .../node/service/EngineNodeService.java | 26 + .../node/service/impl/CommonServiceImpl.java | 789 ++++++++ .../service/impl/DecisionOptionsNode.java | 144 ++ .../executor/node/service/impl/GroupNode.java | 107 ++ .../node/service/impl/RuleSetNode.java | 866 +++++++++ .../service/impl/SandboxProportionNode.java | 114 ++ .../enginex/executor/redis/RedisManager.java | 683 +++++++ .../enginex/executor/redis/RedisPipeline.java | 46 + .../enginex/executor/redis/RedisUtils.java | 49 + .../rule/consts/RuleConditionConst.java | 12 + .../executor/rule/consts/RuleConst.java | 28 + .../executor/rule/consts/RuleRunnerConst.java | 86 + .../rule/mapper/RuleConditionInfoMapper.java | 14 + .../rule/mapper/RuleFieldInfoMapper.java | 14 + .../rule/mapper/RuleFieldInfoMapper.xml | 14 + .../executor/rule/mapper/RuleInfoMapper.java | 16 + .../executor/rule/mapper/RuleInfoMapper.xml | 45 + .../mapper/RuleLoopGroupActionMapper.java | 9 + .../rule/mapper/RuleScriptVersionMapper.java | 15 + .../rule/mapper/RuleVersionMapper.java | 9 + .../rule/model/RuleConditionInfo.java | 66 + .../executor/rule/model/RuleFieldInfo.java | 38 + .../enginex/executor/rule/model/RuleInfo.java | 86 + .../rule/model/RuleLoopGroupAction.java | 55 + .../rule/model/RuleScriptVersion.java | 74 + .../executor/rule/model/RuleVersion.java | 76 + .../rule/model/vo/RuleConditionVo.java | 56 + .../executor/rule/model/vo/RuleVersionVo.java | 19 + .../executor/rule/model/vo/RuleVo.java | 29 + .../rule/service/RuleConditionService.java | 15 + .../rule/service/RuleFieldInfoService.java | 13 + .../service/RuleLoopGroupActionService.java | 14 + .../service/RuleScriptVersionService.java | 19 + .../executor/rule/service/RuleService.java | 28 + .../rule/service/RuleVersionService.java | 14 + .../impl/RuleConditionServiceImpl.java | 158 ++ .../impl/RuleFieldInfoServiceImpl.java | 58 + .../impl/RuleLoopGroupActionServiceImpl.java | 50 + .../impl/RuleScriptVersionServiceImpl.java | 90 + .../rule/service/impl/RuleServiceImpl.java | 107 ++ .../service/impl/RuleVersionServiceImpl.java | 84 + .../system/mapper/DepartmentMapper.java | 26 + .../system/mapper/DepartmentMapper.xml | 129 ++ .../executor/system/mapper/MenuMapper.java | 57 + .../executor/system/mapper/MenuMapper.xml | 236 +++ .../executor/system/mapper/RoleMapper.java | 26 + .../executor/system/mapper/RoleMapper.xml | 129 ++ .../executor/system/mapper/SysMenuMapper.java | 111 ++ .../executor/system/mapper/SysMenuMapper.xml | 164 ++ .../system/mapper/SysOrganizationMapper.java | 57 + .../system/mapper/SysOrganizationMapper.xml | 91 + .../executor/system/mapper/SysRoleMapper.java | 90 + .../executor/system/mapper/SysRoleMapper.xml | 137 ++ .../executor/system/mapper/SysUserMapper.java | 120 ++ .../executor/system/mapper/SysUserMapper.xml | 255 +++ .../executor/system/mapper/UserMapper.java | 57 + .../executor/system/mapper/UserMapper.xml | 243 +++ .../executor/system/model/Department.java | 119 ++ .../enginex/executor/system/model/Menu.java | 247 +++ .../executor/system/model/MenuJson.java | 54 + .../enginex/executor/system/model/Pager.java | 78 + .../enginex/executor/system/model/Role.java | 130 ++ .../executor/system/model/SysMenu.java | 127 ++ .../system/model/SysOrganization.java | 89 + .../executor/system/model/SysRole.java | 83 + .../executor/system/model/SysSuccess.java | 40 + .../executor/system/model/SysUser.java | 141 ++ .../enginex/executor/system/model/User.java | 203 ++ .../executor/system/model/UserRole.java | 52 + .../executor/tactics/consts/TacticsType.java | 16 + .../tactics/mapper/TacticsOutputMapper.java | 10 + .../executor/tactics/model/OutCondition.java | 18 + .../executor/tactics/model/TacticsOutput.java | 78 + .../tactics/service/TacticsOutputService.java | 18 + .../impl/TacticsOutputServiceImpl.java | 141 ++ .../enginex/executor/util/CollectionUtil.java | 104 + .../executor/util/CustomValueUtils.java | 32 + .../enginex/executor/util/DataHelp.java | 40 + .../executor/util/DictVariableUtils.java | 29 + .../enginex/executor/util/ExecuteUtils.java | 674 +++++++ .../enginex/executor/util/JevalUtil.java | 168 ++ .../enginex/executor/util/ListOpUtils.java | 106 + .../baoying/enginex/executor/util/MD5.java | 94 + .../enginex/executor/util/NumUtils.java | 24 + .../enginex/executor/util/StrUtils.java | 32 + .../enginex/executor/util/StringUtil.java | 465 +++++ .../enginex/executor/util/SysConstant.java | 96 + .../executor/util/https/HttpClient.java | 320 ++++ .../executor/util/https/HttpsException.java | 49 + .../util/https/MySSLSocketFactory.java | 101 + .../executor/util/https/PostParameter.java | 208 ++ .../enginex/executor/util/https/Response.java | 225 +++ .../util/jeval/ArgumentTokenizer.java | 119 ++ .../util/jeval/EvaluationConstants.java | 63 + .../util/jeval/EvaluationException.java | 57 + .../executor/util/jeval/EvaluationHelper.java | 87 + .../executor/util/jeval/EvaluationResult.java | 177 ++ .../executor/util/jeval/Evaluator.java | 1703 +++++++++++++++++ .../util/jeval/ExpressionOperand.java | 62 + .../util/jeval/ExpressionOperator.java | 63 + .../executor/util/jeval/ExpressionTree.java | 368 ++++ .../executor/util/jeval/NextOperator.java | 62 + .../executor/util/jeval/ParsedFunction.java | 81 + .../executor/util/jeval/VariableResolver.java | 26 + .../util/jeval/function/Function.java | 51 + .../jeval/function/FunctionConstants.java | 33 + .../jeval/function/FunctionException.java | 57 + .../util/jeval/function/FunctionGroup.java | 57 + .../util/jeval/function/FunctionHelper.java | 284 +++ .../util/jeval/function/FunctionResult.java | 73 + .../util/jeval/function/math/Abs.java | 71 + .../util/jeval/function/math/Acos.java | 71 + .../util/jeval/function/math/Asin.java | 72 + .../util/jeval/function/math/Atan.java | 71 + .../util/jeval/function/math/Atan2.java | 77 + .../util/jeval/function/math/Average.java | 51 + .../util/jeval/function/math/Ceil.java | 71 + .../util/jeval/function/math/Cos.java | 71 + .../util/jeval/function/math/Exp.java | 73 + .../util/jeval/function/math/Floor.java | 71 + .../util/jeval/function/math/Groovy.java | 172 ++ .../jeval/function/math/IEEEremainder.java | 79 + .../executor/util/jeval/function/math/Ln.java | 41 + .../util/jeval/function/math/Log.java | 71 + .../jeval/function/math/MathFunctions.java | 118 ++ .../util/jeval/function/math/Max.java | 40 + .../util/jeval/function/math/Min.java | 40 + .../util/jeval/function/math/Pow.java | 79 + .../util/jeval/function/math/Python.java | 183 ++ .../util/jeval/function/math/Random.java | 63 + .../util/jeval/function/math/Rint.java | 73 + .../util/jeval/function/math/Round.java | 71 + .../util/jeval/function/math/Sin.java | 71 + .../util/jeval/function/math/Sqrt.java | 71 + .../util/jeval/function/math/Sum.java | 49 + .../util/jeval/function/math/Tan.java | 71 + .../util/jeval/function/math/ToDegrees.java | 72 + .../util/jeval/function/math/ToRadians.java | 72 + .../util/jeval/function/string/CharAt.java | 96 + .../util/jeval/function/string/CompareTo.java | 93 + .../function/string/CompareToIgnoreCase.java | 94 + .../util/jeval/function/string/Concat.java | 92 + .../util/jeval/function/string/Contains.java | 98 + .../util/jeval/function/string/EndsWith.java | 98 + .../util/jeval/function/string/Equals.java | 98 + .../function/string/EqualsIgnoreCase.java | 99 + .../util/jeval/function/string/Eval.java | 78 + .../util/jeval/function/string/IndexOf.java | 95 + .../jeval/function/string/LastIndexOf.java | 95 + .../util/jeval/function/string/Length.java | 80 + .../jeval/function/string/NotContains.java | 98 + .../util/jeval/function/string/NotEquals.java | 98 + .../util/jeval/function/string/Replace.java | 115 ++ .../jeval/function/string/StartsWith.java | 102 + .../function/string/StringFunctions.java | 112 ++ .../util/jeval/function/string/Substring.java | 93 + .../jeval/function/string/ToLowerCase.java | 80 + .../jeval/function/string/ToUpperCase.java | 80 + .../util/jeval/function/string/Trim.java | 81 + .../util/jeval/operator/AbstractOperator.java | 182 ++ .../util/jeval/operator/AdditionOperator.java | 70 + .../jeval/operator/BooleanAndOperator.java | 46 + .../jeval/operator/BooleanNotOperator.java | 44 + .../jeval/operator/BooleanOrOperator.java | 46 + .../operator/ClosedParenthesesOperator.java | 30 + .../util/jeval/operator/DivisionOperator.java | 44 + .../util/jeval/operator/EqualOperator.java | 64 + .../jeval/operator/GreaterThanOperator.java | 63 + .../operator/GreaterThanOrEqualOperator.java | 63 + .../util/jeval/operator/LessThanOperator.java | 64 + .../operator/LessThanOrEqualOperator.java | 64 + .../util/jeval/operator/ModulusOperator.java | 44 + .../operator/MultiplicationOperator.java | 44 + .../util/jeval/operator/NotEqualOperator.java | 64 + .../operator/OpenParenthesesOperator.java | 30 + .../util/jeval/operator/Operator.java | 88 + .../jeval/operator/SubtractionOperator.java | 54 + .../main/resources/application-dev.properties | 38 + .../resources/application-prod.properties | 45 + .../resources/application-test.properties | 45 + .../src/main/resources/application.properties | 1 + .../src/main/resources/logging-config.xml | 98 + .../JarEnginexExecutorApplicationTests.java | 13 + .../baoying/enginex/executor/JevalTest.java | 16 + .../baoying/enginex/executor/RegexTest.java | 36 + .../enginex/executor/grouping/Auth.java | 11 + .../enginex/executor/grouping/GroupTest.java | 250 +++ .../enginex/executor/grouping/Role.java | 30 + .../enginex/executor/grouping/User.java | 13 + .../enginex/executor/sql/RedisTest.java | 75 + .../enginex/executor/sql/SqlForTest.java | 51 + 312 files changed, 30833 insertions(+) create mode 100644 jar-enginex-runner/.gitignore create mode 100644 jar-enginex-runner/README.md create mode 100644 jar-enginex-runner/lib/javax.ejb.jar create mode 100644 jar-enginex-runner/lib/javax.jms.jar create mode 100644 jar-enginex-runner/lib/javax.persistence.jar create mode 100644 jar-enginex-runner/lib/javax.resource.jar create mode 100644 jar-enginex-runner/lib/javax.servlet.jsp.jar create mode 100644 jar-enginex-runner/lib/javax.servlet.jsp.jstl.jar create mode 100644 jar-enginex-runner/lib/javax.transaction.jar create mode 100644 jar-enginex-runner/mvnw create mode 100644 jar-enginex-runner/mvnw.cmd create mode 100644 jar-enginex-runner/pom.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/JarEnginexRunnerApplication.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CacheController.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CanalClient.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/TableEnum.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/basefactory/CustomBeanFactory.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/CommonConst.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/Constants.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/ParamTypeConst.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionFactory.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionPool.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/BaseMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/BasePage.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplate.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplateExample.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/ExpressionParam.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionData.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionManager.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigHolder.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigurationContainor.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/DataSourceConfig.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/RestTemplateConfig.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/CustList.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/Field.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldCond.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldInter.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldType.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldTypeUser.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldUser.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FormulaField.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/FieldService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/impl/FieldServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldEnumVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldExcelVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldFormulaVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldSubCondVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineConst.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineMsg.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EnumConst.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/ApiController.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/DecisionController.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/CallBackTypeEnum.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/NodeTypeEnum.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ComplexRule.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionOptions.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionReqModel.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Engine.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineNode.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineResultSet.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineRule.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineVersion.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/IndexEngineReportVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/InputParam.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/NodeKnowledge.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Result.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ResultSetList.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Sandbox.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ScoreCardEngine.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/TestRule.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiBizData.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiRequest.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineApiService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineNodeService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineVersionService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineApiServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineNodeServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineVersionServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/thread/EngineCallable.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/EngineRuleRel.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTree.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTreeRel.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/Rule.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleContent.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleExcel.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleField.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScoreCardJson.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardExcel.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardField.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardRuleContent.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/EmailService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/impl/EmailServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/CommonService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/EngineNodeService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/CommonServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/DecisionOptionsNode.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/GroupNode.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/RuleSetNode.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/SandboxProportionNode.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisManager.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisPipeline.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisUtils.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConditionConst.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConst.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleRunnerConst.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleConditionInfoMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleLoopGroupActionMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleScriptVersionMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleVersionMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleConditionInfo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleFieldInfo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleInfo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleLoopGroupAction.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleScriptVersion.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleVersion.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleConditionVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVersionVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleConditionService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleFieldInfoService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleLoopGroupActionService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleScriptVersionService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleVersionService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleConditionServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleFieldInfoServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleLoopGroupActionServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleScriptVersionServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleVersionServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.xml create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Department.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Menu.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/MenuJson.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Pager.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Role.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysMenu.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysOrganization.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysRole.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysSuccess.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysUser.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/User.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/UserRole.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/consts/TacticsType.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/mapper/TacticsOutputMapper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/OutCondition.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/TacticsOutput.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/TacticsOutputService.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/impl/TacticsOutputServiceImpl.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CollectionUtil.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CustomValueUtils.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DataHelp.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DictVariableUtils.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ExecuteUtils.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/JevalUtil.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ListOpUtils.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/MD5.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/NumUtils.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StrUtils.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StringUtil.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/SysConstant.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpClient.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpsException.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/MySSLSocketFactory.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/PostParameter.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/Response.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ArgumentTokenizer.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationConstants.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationException.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationHelper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationResult.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/Evaluator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperand.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionTree.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/NextOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ParsedFunction.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/VariableResolver.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/Function.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionConstants.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionException.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionGroup.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionHelper.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionResult.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Abs.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Acos.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Asin.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan2.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Average.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ceil.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Cos.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Exp.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Floor.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Groovy.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/IEEEremainder.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ln.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Log.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/MathFunctions.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Max.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Min.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Pow.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Python.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Random.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Rint.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Round.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sin.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sqrt.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sum.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Tan.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToDegrees.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToRadians.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CharAt.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareTo.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareToIgnoreCase.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Concat.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Contains.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EndsWith.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Equals.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EqualsIgnoreCase.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Eval.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/IndexOf.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/LastIndexOf.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Length.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotContains.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotEquals.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Replace.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StartsWith.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StringFunctions.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Substring.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToLowerCase.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToUpperCase.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Trim.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AbstractOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AdditionOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanAndOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanNotOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanOrOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ClosedParenthesesOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/DivisionOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/EqualOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOrEqualOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOrEqualOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ModulusOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/MultiplicationOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/NotEqualOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/OpenParenthesesOperator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/Operator.java create mode 100644 jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/SubtractionOperator.java create mode 100644 jar-enginex-runner/src/main/resources/application-dev.properties create mode 100644 jar-enginex-runner/src/main/resources/application-prod.properties create mode 100644 jar-enginex-runner/src/main/resources/application-test.properties create mode 100644 jar-enginex-runner/src/main/resources/application.properties create mode 100644 jar-enginex-runner/src/main/resources/logging-config.xml create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JarEnginexExecutorApplicationTests.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JevalTest.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/RegexTest.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/Auth.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/GroupTest.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/Role.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/User.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/RedisTest.java create mode 100644 jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/SqlForTest.java diff --git a/jar-enginex-runner/.gitignore b/jar-enginex-runner/.gitignore new file mode 100644 index 0000000..cf60db2 --- /dev/null +++ b/jar-enginex-runner/.gitignore @@ -0,0 +1,34 @@ +HELP.md +target/ +logs/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### 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/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/jar-enginex-runner/README.md b/jar-enginex-runner/README.md new file mode 100644 index 0000000..68556ce --- /dev/null +++ b/jar-enginex-runner/README.md @@ -0,0 +1,3 @@ +# jar-enginex-runner + +enginex后端执行器 \ No newline at end of file diff --git a/jar-enginex-runner/lib/javax.ejb.jar b/jar-enginex-runner/lib/javax.ejb.jar new file mode 100644 index 0000000000000000000000000000000000000000..4ebf5ecd4c9ab83540ef206f6c5a1906db008098 GIT binary patch literal 47581 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

x$jzCx>QKI9Dc;_z4e z0Yjof!jtYPhbWx5hKGB$%LhfCBm1`-iGRzk7{pT4|G=z*;ASA7z{7XMS%+kLwXk%W>j7B@a_J!C@WeN1+*~V>{Bl#-HQiNtX^sL^z#~Mk&VLmX>zsDS*--Z5!6_ zmyRl7s(i+;QDxn)?`n6UYg4?s_nq&1alMX7Ku2!YsIXC8I_2ltv;oQhymtI+SybF} z&5%yV8Fnis+qSDJR969o0ePgX?>;nd8ilT!8EO$W(t2lKb^a@_Kv!Z*Qr`py`u0g3 zZbg(P;3`80t34!4MxMUfAtf5-l2pfc`~4o`Nn>PHWMZ{b^}Tq!IwLnS=D7Q@nP_AJ z8sVco2#(CK5g9n>xsoML==2oKhcCtLFy{?{xX#>Z&lk>Rso+VQP2bI;*p#A39_3ua z3zya53Tr%pUuzV&vlWDW1X|$xfqs}%)d9;}8Om^7+L!id9t6GxKHtOdzTXeqV9Jx! z3tmZxybfD?S*MPdg8Yyzxeny(r4u#+Pi{N;939nD8jYt&PnrFIsp6>KC+OLmrLq&S^b}zlDjCytmd7*0xc^f^MC9@ z=&NP_ano*sGCn+17@8=dpTx`IX{m-AX2O1xKfI3v*rmYo7&K&ngiW$r9mr_rx)sM_ z#Oz=4Vv`zRZpXu&Mmb(!)N6q=g+V9i!6~U(QvqD8>wnK=8H%^MaQx|0(^ETQWB?(j z)>@rFuE&URSsM)!vFUiB*5>)ainsa+K~=f=`~V@-tbQ7awxjYeWi7nicAh733(G?(In)J1z$v! zL)gO*mdA8wN2rh$t0s*R_F)t71Fc-W$DmA4%YZ5OBX?nfnfoN)+l~Aka750IO#Bo~ zZe&XByG|qJYr-bX^f>*UgsC_FPBhVcu zE?M4|@Ruv)k2hBFW{aTLN~uCB#iXo{tcREvLbp5b!I5W6lPQIXmT&ztMU$l&mh67# zAVy>wOf`Z@GYN~<9~>2ZQ)I%iXWE?%z?cj|Q{04$##f^$+D6A`k9Np#ZT=(obE*6&ga+De#a*1}r|Tm%hJ3*HK%S{$iBTjr6E zcRAkU5^&yPn!YG)RYb1Mp?J?nfnrr6wM5#lYRlUNv%b0z1qno-+KO5py~U(0-o|J7 zZG>rh9+nU6g?vloX{oe!XiIW&JI|RXQKd3)S|&LDu#KENkxJ{^hzwD-$szetxtXs;FE fq85Hs^+j9}Vd)!)Er7{XjDPYXacZ#z&d&6IINz)K literal 0 HcmV?d00001 diff --git a/jar-enginex-runner/lib/javax.resource.jar b/jar-enginex-runner/lib/javax.resource.jar new file mode 100644 index 0000000000000000000000000000000000000000..696a2345878907025784d2e6e49c6e6b41d1cfac GIT binary patch literal 44511 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 Rg4PB8C6U3vu!Mnr`hR(p1f~E0 literal 0 HcmV?d00001 diff --git a/jar-enginex-runner/lib/javax.servlet.jsp.jar b/jar-enginex-runner/lib/javax.servlet.jsp.jar new file mode 100644 index 0000000000000000000000000000000000000000..9c0631cea0fd56031db19fc3edbb4db4bfbb923a GIT binary patch literal 78836 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 literal 0 HcmV?d00001 diff --git a/jar-enginex-runner/lib/javax.transaction.jar b/jar-enginex-runner/lib/javax.transaction.jar new file mode 100644 index 0000000000000000000000000000000000000000..729c6952389cd4769d5d840b3d1a7941b3c0f63e GIT binary patch literal 9714 zcmb7q1yq#X^FQ56Ei7Hq-5}CkODxje9a1icf^?&VbR!BXpaLQxEC>oHEuGTRjZ(i| zeZMb=$ou~f=Q-y&oO|cq=QDHX&U|JyRFF{s2pF(G%fK!XgfoJPfPw&$yD7z?q$baG z+=hUFjG&={jsYKm^v^JjzfK0gM*KS&B&DV#FQ;{r6C}R{`qZwf!ok^nMTLX4<5TBr zFn1r{}>BT2eGtvgDc{+bTaqwhq;aPo4j*x0N3 zx+_(!v-Yy=cHS~C0JpilVDEV-nZ1^h*3iHv|N12Qx=e(Ujs^fKX>#ndc5;tv=4g87 zN&_YuN{3q_UOCs>NeAgJ|AJ2bwxW-D&*a*&09>e2cCVrol~`HR(u_Qt99r3$d+qfB zMtAvc$R*)deY>wqMS;sH#dX}+kvKzFLn9n{qW)zUx-Mjuh@ywglRMocW7b$mCC;%% z3OzQhvXr4)XfY}qsZQbZQJBBZhs&Xr#&px&!!`c+`E%>G5}yvY057pdcCi7MKVOB#?wu9)RjY*aQiF;q)n+E&kx671@fgqq#N zCimViSsD6{0(UC5IvFq-A_Bq~>^XI+^SEeS^j$Ujl9ap1As%<6Er$=6iG#JmwB5=Vy8ueQ!C+a_ zZRgGoCOGQz2~OWhVTcc3iaPJ*sDoXdoGi>O9nS%u6#G)Gi5oA}KcW0SzlI$Pn}s8` zgJc8Bw?Ii8d}O(yOf%G=rJHJjKMYipNFWGQSmrekC27#uzaLJF`A>ZR>F)TxC~E$A zg#4XvXJ%7!4ivNPnV)`YZ1-uiVd!Ll-Y@EC0&D7b_4PG6>adt3iq?G&QMCK9{drY2 zD!PG#8Zrglm?ZGb<61AcX=G!IlD{BfB-XWb(t`69fsKZ-+z*1GsyDF2wRuOr>_EuP zN)KfvT*X@pL|C|LN0;*5DGQ9R)eJHL9x0g=B^uv?mW658y{D)fG6@7_QW~Q2hhjU` z4tgsnnAS^T0K*m>h}c1P->$OfXd|~6kh^+R)k_{QP04m<7&LbDXcI;|n0@A)sN^%u zh9ghj&&>e$_vuTK=erPjB^PgVCwr@#r=jXR^#6t$<7dWRBAJlB5y5WeJfJmD6E6~7 z$hWgvcp?DmxlqF*f}-{YNC?rD#uA_zyko&9-)_`}o&}7Y z_(XX8T$UCyR@GijyVa_hAR+&@dkZfQ>D|ZgzGv1dV;b!+vr@<6Cg)J<`?XjphZO^O z>*#a3>l>=`dyhUZHq+`zs@kEgyq6Z|bKPP(I(?oP zv)w`m%cC>_T9)z%v!00b1ES6j;Ptj$4W{7E$|u&GZd0@A=0_tUXg^TYCcl+V^s^eJ zO)g@w=jbD&8IYF_NEqepB)FB_;bb?zy;zJXFHmL^wl0a=DOD>W+RoYw~GdQckz7fyl`y{|T?_HEBbT8A2u+x8~daN(Ea- zPk#cJzK%h>Si$1ZoY%zO(cg227X6Xni1SkyrvYJT_g#uO%Y}&lCBnqSepYUh1cY>^ zly~zRI6LprNgG0QI01<+vbxZaq-CaC;rYE0^hMhhMnp#Y2;zwlTQZI1miuo#{5y_1 z{O;BHBM8gKK}m5eL+2j%y&~iD%-(0iB^uC&h^aTLFnD|}bFJPgW3A%!aqyioF1gv* zJ8#NrYhZ)pvcs%`8;hQtOL(--s7>qT4RymiLRP3AH}kqfg06?mmpj`8yG$A1n#|$` z=29wgxY+i}0`^2T8xLvJ-$we-f{cX@*t`{5BB0qbujz<{z?jSCnS*l=a_ewLZe|YS z*hLc$`o5l~j1;eHz%(N8WX66($8V2>ZTBUBXI6fMf|9fASVcHL(?ex}OIrYHb;U_< z%ebcZ2n~+34>^FF5Qg%{OOd9!5NRz>b5AdiUkQu$gE5ytNj!O5IjNVd{SGS=Y>E3A z0DBn#cffAE6Q(^s3ZZ_wxFOdMgu71*`J@we=Gf;Achjxz-K&3xXoiN1lhU38Y}Tob zC1{Twi)m_jI(AjGGse=k!wf?8_82=zDbq)%%56@GggsgPQJoog3D+S_giL$fQ=xTI zoNs7OH0k%F(KB+?_ZtOR(Xp^lEP-w80+>zTK1Y zI5O?&syt&hGYZCO}f1aI@nWkNFQKj<2c@&_3dGwKm$)&{##%<27=-15Du` zE0X%|bqefLnqbfW|HpcGT05V+vqBzxR%?VGW(+WkcA|v89==dOM9i)H?y?7p(%>Pp zL0qpu%Nzk2o_#$|oCs{{hA`goR6%LCsXRVgm(HV(%Fa=_2>r}p>m~EFFSh~7wUnEPR3J}<%sQgNqxNzZ4(F7x!nFl-xSbML zz`9y6r&PGqDX(4VlxGr*UlAcB-dG*xkf9T-B>`Cb5s{HCagr8BwR3IQG4x~vHqjd; ziD+Tge1<%e>RXM}lX1M>e$F5BY8HIQ_ZF`^tJlAi5Jxnc3^03lv*c6ef-Z!yktnhbl|F9dC1Rj<2&7U^|7TD(uJbshsa7?Bl>cB&xSl}mUt(s})M z*d;0e=~jZ;rJ-VKeT}Q5Sw*6A`_brWKG*nX*wQD4#JZ!)Z2il;blcFTx$qC|h~sjv z+Oj-bW9yFKzVrIiU16q=ck&+JrWJj*wr<<$wR~I9Gk*Ljom4;2!Ki-jqd4kgMQ{Og z4JE+d;)4q`LPUMTQ5uDIz7{=F^intZ{*v4;A^cW5)Vl<^!3#nA=*r|Ido9 zuD#rNE!3{YW5f%BEzI0zC%H*xM5TLVvJ5Tsyc^>n^@l=CMHaRW^<-STi2EAhTf-HY zsq*w6lIQQeyD`7Imu)J9AU>7Z;P6JM>h1~PsTcY^LvnBd;eFqfmY3d0pTmtN6Wd=% zrmsY)GU$Y;JyBgTDR%rK#2WDf*^(LDlE3Xk&;yZoVvpFGVhEwQWy^vJm`nBUZM>hg zr}o1oweTc~-G(!mtsu7blSjSyYbLrWrR`hm6u%_%VI@!h18tZEwSxZY1m zSl6G&@6Ak&hcRKzw)8F@1?TJx#VL}h%{2!SBg!k zmF9t7@OdRA{-LZ)9!qyTgnmQsYlN(!g|UTVl9^|Ly-mbC}qaw zN90fT2FP|Lx|#~9EMv2rxu7Mx9nX=-nuxqjoP5;{e->reJj=2AgIAVgfwsQEiNIoou;SX9ID2aLvF9mLyg*p3Xdu&53ZkC|`XCf5L< z0C!hv@A7TU{j7;eMNo9!0P%$fYA4+l-PU0>F^xM_w4RBwca7qp>gFpN;V=1eG^iY` zX$$pcn;{C;k6zlVCOmN^U@HY98H%sBh0HcG+J)K3$Vc&$s6eB(BhB!R2Mi-b#wK*? zK7Jqz=LF$$`WXvq2dBHYVHnVI4IYWcrKWTVTZ*WqcCqILF1_xRlP=LID#0wzbxaJovF7ALA7|tPwM3}K@4J7=ILAi}-OWSeD zc1NC78fhxiW%U+cu969qnLk=1`#k=WlECAkzC#UzfBRAsz}dK8q~2M=?#zXy^?O1E zwzh>abM8L>L_iHFJlDuTS4=7n_=*m}uNq8&OuI|VvYwN>1Q3c(|euSp&fCJb!COEZLv(^hQ{Yxgst|3dff!x0#|c%dh=U8te~ zSXoIVT*2=L_wmTyY7j(?b1fC>k5(3pkwFBw{1B+ys<5%-aMC?-C;a$&t;BzVo3Hf1 zwct%DbmV0t@pK?GGWXRmp6l9~6tkr%OqX zEcE@u%agB$X+8&HWpkyU^xZJ5ti6>dS2paUEIXQBkEy(&?i}bqx9$4GC<0o2FLnu#o|07t+QfESUQepo9MzK&WmtqFb)>$=c5pkE6!TkIJ4?#j zYv_TN2fT0YQjqCnyadVMmPLv2m+?h>v3W_b87jc&>6qKr=ZTa0D)2hTkRU0k#XCZe z&Z%v3xOsSZr%se%=1Ka;JQtJPL>Hb1i1{TPHsjx>=t-SV%@Yi@dL;oLI){6DG&lou z0)P#MEXs1VL1x2+&h#KRa*;`FLgipHP-dmD9|tvsjo&)*en@T9yc4)42v>Zje*Vp! zUH=Z7gwA@2dq@GO+$6H#*V<^^iE1)VLtAybSjdHZ8iLv}uFsM<_y+DDwp3@W1-R&F z^DAO-a~N7b0KBX)*WIq|Z#A5M==v z?fw)2nQ8EuxB3=apWGemusTvXuuiJly{uU{=6U!?EC21MhY3)j0gyRIZO!hxfyj)E z3_sfM%9}AWH(GJyv+3&I4lMm*s zS9@_nV_f!kK$$Y^$g(aJShbaFlEx;O5*g)`<9d7PcMJ2@0nWfwW~}?%F|`~9@tdS2YsEQ6-d2$ z&Y0>#h|$9hb`+tvtTf*5WR@h4#HqRV8w5-Ul_(qtuSh>m4p9`2IBukcr5Vm`t zTPkp^OB!w7JoaUE)2B#+dc1U9!G)irgrWQbDC`Usp#QQ^8 zwdH(}rBv$9a#rsm+1kGFm&MeYwJR>X#Xgmen_}dvwYDCeZgpF9`yp!;BaHL&;uCV{ z^0Ua(uU{iyc~Y~oT}|EnA@1Oarpbs`1npi#0wJbR;0^))?O%91OSEeTKcSLNW0q1XID)M00Idno$(+xkrB#^w$uNa=?0FA3 z$%V(j)tf8TMWt?-m1?aw@zW$Es=KXRB)oXw#MRGKAcg7;r;6}ROzaimM)5o+(_k5= zSI6_HUB&!Ot%zeVXcHEU8Bs2CqKm0DSTNSJ zxBBHh+Obk^byTRU zaBjw#H}}_)8xy2~ca6Wdil^gu1f%tXhGON6+*f1R8fET4O`o#^J$8hR7e)y?WGcYN z>NnYR2>GIB{0A_v67XY$36<#iavoxjC=-%h(@lE2O3tPMJmAeJiU@zSyfTG!5H2Y^ z4Av4p{-CL<7)-05%xJ|?;Q7Ju$q_dE#0d`i@lL_c6Fm$t4HZNr0K)HWX4ql;DeyM) z@A7Zo7~XU~KlNddcKd=96F9zQ5I+Z|Jto~E~f40BQFL1W?0B`A>3L^SN3tZCRIj{OG*ADMp zoQenRcM^Z@{r?e?vs5xXnw-iQ=0$4H()Q=Kc$TS!XYr?^jeVi6-z%qI0qeZdv$W#x zN~LiwR(b*DdtT{TvJ1{gp31i?7b*Rt@;I;XESbW3stxv5g!>-~e`Czf>pe>oz^Ry1 zNyGmSy?-HQ&aZH`3Wn1Sr$R#bUn~5Ld^o?x*~0I)U$-En!{X|nzIz7lORMYi>zu7% ze)km|;)~X~u&9J9{aXx&7eA-+40w^!pQX_09Rv@_XEQ>0F?1^9e^c>CX>|Tu&n5`) yT=G=5$o{e|J*nR_%kwJFUZcOq0%6L(sr+A_p`n8M^MV5+01 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/jar-enginex-runner/mvnw.cmd b/jar-enginex-runner/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/jar-enginex-runner/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/jar-enginex-runner/pom.xml b/jar-enginex-runner/pom.xml new file mode 100644 index 0000000..0e7e184 --- /dev/null +++ b/jar-enginex-runner/pom.xml @@ -0,0 +1,311 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.2 + + + com.baoying + enginex-runner + 0.0.1-SNAPSHOT + jar-enginex-runner + Demo project for Spring Boot + + 1.8 + 1.2.12 + 6.4.0.Final + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + junit + junit + test + + + + + + com.baomidou + mybatis-plus-boot-starter + 3.3.2 + + + + mysql + mysql-connector-java + + + + com.alibaba + druid-spring-boot-starter + 1.1.21 + + + + org.codehaus.jackson + jackson-mapper-lgpl + 1.7.4 + + + + org.apache.commons + commons-lang3 + 3.1 + + + + com.alibaba + fastjson + 1.2.58 + + + + org.projectlombok + lombok + 1.18.0 + + + + + org.drools + drools-core + ${drools.version} + + + org.kie + kie-spring + ${drools.version} + + + org.drools + drools-compiler + ${drools.version} + + + org.kie + kie-api + ${drools.version} + + + org.kie + kie-ci + ${drools.version} + + + org.drools + knowledge-api + 6.4.0.Final + + + com.itextpdf + itextpdf + 5.4.3 + + + com.itextpdf + itext-asian + 5.2.0 + + + + + + com.github.ben-manes.caffeine + caffeine + 2.8.4 + + + + + org.apache.commons + commons-pool2 + 2.0 + + + + + redis.clients + jedis + 2.4.2 + + + + + + commons-httpclient + commons-httpclient + 3.1 + + + + + org.codehaus.groovy + groovy-all + 2.4.15 + + + + org.python + jython-standalone + 2.7.0 + + + + org.jpmml + pmml-evaluator + 1.4.1 + + + org.jpmml + pmml-evaluator-extension + 1.4.1 + + + + + org.springframework.boot + spring-boot-starter-mail + + + + com.alibaba + transmittable-thread-local + 2.2.0 + + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.3 + compile + + + + + + com.spring4all + spring-boot-starter-hbase + 1.0.0.RELEASE + + + + org.apache.hbase + hbase-client + + + + + + + + org.apache.hbase + hbase-shaded-client + 1.3.1 + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + 1.2.3 + + + org.mybatis + mybatis + + + org.mybatis + mybatis-spring + + + + + + + com.alibaba.otter + canal.client + 1.1.4 + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + + + com.google.protobuf + protobuf-java + 3.5.1 + + + + cn.hutool + hutool-all + 5.5.2 + + + + + + + src/main/java + + + **/*.xml + + + + + src/main/resources + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/JarEnginexRunnerApplication.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/JarEnginexRunnerApplication.java new file mode 100644 index 0000000..75a7d47 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/JarEnginexRunnerApplication.java @@ -0,0 +1,19 @@ +package com.baoying.enginex.executor; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication +@EnableTransactionManagement +@MapperScan("com.baoying.enginex.executor.*.mapper") +@ComponentScan(basePackages = "com.baoying.enginex.executor.**") +public class JarEnginexRunnerApplication { + + public static void main(String[] args) { + SpringApplication.run(JarEnginexRunnerApplication.class, args); + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CacheController.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CacheController.java new file mode 100644 index 0000000..4da7c78 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CacheController.java @@ -0,0 +1,119 @@ +package com.baoying.enginex.executor.canal; + +import com.baoying.enginex.executor.datamanage.mapper.SimpleMapper; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/cache") +public class CacheController { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Resource + private SimpleMapper simpleMapper; + @Autowired + private RedisManager redisManager; + + @RequestMapping(value = "initCache", method = RequestMethod.GET) + public void initCache() { + logger.info("===================== 缓存初始化开始 ====================="); + long start = System.currentTimeMillis(); + // 遍历表 + for (TableEnum tableEnum : TableEnum.values()) { + String tableName = tableEnum.getTableName(); + logger.info("===================== 开始初始化缓存表[{}] =====================", tableName); + + String sqlStr = "select * from " + tableName; + Map parameterMap = new HashMap<>(); + parameterMap.put("sqlStr", sqlStr); + List> result = simpleMapper.customSelect(parameterMap); + // 遍历行 + for (LinkedHashMap map : result) { + row(tableEnum, map); + } + logger.info("===================== 结束初始化缓存表[{}],共[{}]条数据 =====================", tableName, result.size()); + } + long end = System.currentTimeMillis(); + logger.info("===================== 缓存初始化成功!!耗时:{}ms =====================", (end - start)); + } + + private void row(TableEnum tableEnum, LinkedHashMap map) { + String tableName = tableEnum.getTableName(); + String primaryKey = null; + String foreignKey = null; + + if (StringUtils.isNotBlank(tableEnum.getPrimaryId())) { + String primaryId = map.get(tableEnum.getPrimaryId()).toString(); + primaryKey = RedisUtils.getPrimaryKey(tableName, primaryId); + } + + if (StringUtils.isNotBlank(tableEnum.getForeignId())) { + Object obj = map.get(tableEnum.getForeignId()); + if (obj != null && !"".equals(obj.toString())) { + String foreignId = obj.toString(); + foreignKey = RedisUtils.getForeignKey(tableName, foreignId); + } + } + + if (StringUtils.isNotBlank(primaryKey)) { + // 遍历列 + for (String field : map.keySet()) { + String value = map.get(field) == null ? null : map.get(field).toString(); + setColumnCache(primaryKey, field, value); + } + } + + if (StringUtils.isNotBlank(foreignKey)) { + setForeignKeyCache(primaryKey, foreignKey); + } + + // 指标表特殊处理 + dealSpecialTable(tableName, map); + } + + private void setColumnCache(String primaryKey, String field, String value) { + logger.info("开始主键缓存设置, primaryKey:{}, field:{}, value:{}", primaryKey, field, value); + + redisManager.hset(primaryKey, field, value); + + logger.info("结束主键缓存设置, primaryKey:{}, field:{}, value:{}", primaryKey, field, value); + } + + private void setForeignKeyCache(String primaryKey, String foreignKey) { + logger.info("开始外键缓存设置, primaryKey:{}, foreignKey:{}", primaryKey, foreignKey); + + redisManager.sadd(foreignKey, primaryKey); + + logger.info("结束外键缓存设置, primaryKey:{}, foreignKey:{}", primaryKey, foreignKey); + } + + private void dealSpecialTable(String tableName, LinkedHashMap map) { + if(tableName.equals(TableEnum.T_FIELD.getTableName())){ + String fieldEn = "field_en:" + map.get("organ_id") + ":" + map.get("field_en"); + String fieldEnKey = RedisUtils.getPrimaryKey(tableName, fieldEn); + + String fieldCn = "field_cn:" + map.get("organ_id") + ":" + map.get("field_cn"); + String fieldCnKey = RedisUtils.getPrimaryKey(tableName, fieldCn); + + for (String field : map.keySet()) { + String value = map.get(field) == null ? null : map.get(field).toString(); + setColumnCache(fieldEnKey, field, value); + setColumnCache(fieldCnKey, field, value); + } + } + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CanalClient.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CanalClient.java new file mode 100644 index 0000000..cdbb23d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/CanalClient.java @@ -0,0 +1,246 @@ +package com.baoying.enginex.executor.canal; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.Message; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Optional; + +/** + * Canal数据同步 + * 实现ApplicationRunner接口,springboot启动成功后会执行run方法 + */ +@Component +public class CanalClient implements ApplicationRunner { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final static int BATCH_SIZE = 1000; + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + + @Override + public void run(ApplicationArguments args) throws Exception { + if(Constants.switchFlag.OFF.equals(configHolder.getCanalCacheSwitch())){ + return; + } + + // 创建链接 + CanalConnector connector = CanalConnectors.newSingleConnector( + new InetSocketAddress(configHolder.getCanalHostName(), configHolder.getCanalPort()), + "example", "", ""); + try { + //打开连接 + connector.connect(); + //订阅数据库表,全部表 + connector.subscribe(".*\\..*"); + //回滚到未进行ack的地方,下次fetch的时候,可以从最后一个没有ack的地方开始拿 + connector.rollback(); + while (true) { + logger.info("canal数据同步监听中..."); + // 获取指定数量的数据 + Message message = connector.getWithoutAck(BATCH_SIZE); + //获取批量ID + long batchId = message.getId(); + //获取批量的数量 + int size = message.getEntries().size(); + //如果没有数据 + if (batchId == -1 || size == 0) { + try { + //线程休眠2秒 + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + //如果有数据,处理数据 + printEntry(message.getEntries()); + } + //进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。 + connector.ack(batchId); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + connector.disconnect(); + } + } + + /** + * 解析binlog获得的实体类信息 + */ + private void printEntry(List entrys) { + for (CanalEntry.Entry entry : entrys) { + if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { + //开启/关闭事务的实体类型,跳过 + continue; + } + + String tableName = entry.getHeader().getTableName(); + TableEnum tableEnum = TableEnum.getByTableName(tableName); + if(tableEnum == null){ + // 没有在枚举中定义的表,跳过 + continue; + } + + //RowChange对象,包含了一行数据变化的所有特征 + //比如isDdl 是否是ddl变更操作 sql 具体的ddl sql beforeColumns afterColumns 变更前后的数据字段等等 + CanalEntry.RowChange rowChage; + try { + rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e); + } + + //获取操作类型:insert/update/delete类型 + CanalEntry.EventType eventType = rowChage.getEventType(); + //打印Header信息 + logger.info(String.format("============= binlog[%s:%s] , name[%s,%s] , eventType : %s =============", + entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(), + entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), + eventType)); + + //判断是否是DDL语句 + if (rowChage.getIsDdl()) { + logger.info("============= isDdl: true,sql:" + rowChage.getSql()); + } + + //获取RowChange对象里的每一行数据 + for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) { + //如果是删除语句 + if (eventType == CanalEntry.EventType.DELETE) { + row(rowData.getBeforeColumnsList(), tableName); + //如果是新增语句 + } else if (eventType == CanalEntry.EventType.INSERT) { + row(rowData.getAfterColumnsList(), tableName); + //如果是更新的语句 + } else { + //变更前的数据 +// printColumn(rowData.getBeforeColumnsList(), tableName); + //变更后的数据 + row(rowData.getAfterColumnsList(), tableName); + } + } + } + } + + private void row(List columns, String tableName) { + Optional keyColumn = columns.stream().filter(item -> item.getIsKey()).findFirst(); + if(keyColumn.isPresent()){ + // 获取主键id + String id = keyColumn.get().getValue(); + // 拼接主键key + String key = RedisUtils.getPrimaryKey(tableName, id); + // 拼接外键key + String foreignKey = null; + // 子表的redis key需要拼接上主表的id + TableEnum tableEnum = TableEnum.getByTableName(tableName); + if(tableEnum != null){ + Optional foreignKeyColumn = columns.stream().filter(item -> item.getName().equals(tableEnum.getForeignId())).findFirst(); + if(foreignKeyColumn.isPresent()){ + String foreignKeyValue = foreignKeyColumn.get().getValue(); + foreignKey = RedisUtils.getForeignKey(tableName, foreignKeyValue); + } + } + + for (CanalEntry.Column column : columns) { + // 更新发生改变的字段缓存 + setUpdatedColumnCache(column, key, foreignKey); + } + + // 指标表特殊处理 + dealSpecialTable(columns, tableName); + } + } + + private void setUpdatedColumnCache(CanalEntry.Column column, String key, String foreignKey){ + if(column.getUpdated()) { + logger.info("开始主键缓存更新, {}, {}, {}", key, column.getName(), column.getValue()); + + redisManager.hset(key, column.getName(), column.getValue()); + + logger.info("结束主键缓存更新, {}, {}, {}", key, column.getName(), column.getValue()); + + if(foreignKey != null){ + logger.info("开始外键缓存更新, {}, {}", key, foreignKey); + + redisManager.sadd(foreignKey, key); + + logger.info("结束外键缓存更新, {}, {}", key, foreignKey); + } + } + } + + private void setAllColumnCache(CanalEntry.Column column, String key){ + logger.info("开始主键缓存更新, {}, {}, {}", key, column.getName(), column.getValue()); + + redisManager.hset(key, column.getName(), column.getValue()); + + logger.info("结束主键缓存更新, {}, {}, {}", key, column.getName(), column.getValue()); + } + + private void dealSpecialTable(List columns, String tableName){ + if(tableName.equals(TableEnum.T_FIELD.getTableName())){ + String organ_id = null; + String field_en = null; + String field_cn = null; + for (CanalEntry.Column column : columns) { + String name = column.getName(); + switch (name) { + case "organ_id": + organ_id = column.getValue(); + break; + case "field_en": + field_en = column.getValue(); + break; + case "field_cn": + field_cn = column.getValue(); + break; + default: + break; + } + } + + String fieldEn = "field_en:" + organ_id + ":" + field_en; + String fieldEnKey = RedisUtils.getPrimaryKey(tableName, fieldEn); + + String fieldCn = "field_cn:" + organ_id + ":" + field_cn; + String fieldCnKey = RedisUtils.getPrimaryKey(tableName, fieldCn); + + // 如果field_en或field_cn发生变化,则对应的key为新生成的,需要保存所有字段缓存 + Optional fieldEnOptional = columns.stream().filter(item -> item.getName().equals("field_en") && item.getUpdated()).findFirst(); + Optional fieldCnOptional = columns.stream().filter(item -> item.getName().equals("field_cn") && item.getUpdated()).findFirst(); + for (CanalEntry.Column column : columns) { + if(fieldEnOptional.isPresent()){ + // 更新所有字段缓存 + setAllColumnCache(column, fieldEnKey); + } else { + // 更新发生改变的字段缓存 + setUpdatedColumnCache(column, fieldEnKey, null); + } + + if(fieldCnOptional.isPresent()){ + setAllColumnCache(column, fieldCnKey); + } else { + setUpdatedColumnCache(column, fieldCnKey, null); + } + } + } + } + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/TableEnum.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/TableEnum.java new file mode 100644 index 0000000..ba4b0be --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/canal/TableEnum.java @@ -0,0 +1,77 @@ +package com.baoying.enginex.executor.canal; + +/** + * 缓存数据同步表 + */ +public enum TableEnum { + + /** + * 引擎 + */ + T_ENGINE("t_engine", "id", ""), + T_ENGINE_VERSION("t_engine_version", "version_id", "engine_id"), + T_ENGINE_NODE("t_engine_node", "node_id", "version_id"), + + /** + * 指标 + */ + T_FIELD("t_field", "id", ""), + T_FIELD_INTERFACE("t_field_interface", "id", ""), + T_FIELD_DATA_SOURCE("t_field_data_source", "id", ""), + + /** + * 规则 + */ + T_RULE("t_rule", "id", ""), + T_RULE_VERSION("t_rule_version", "id", "rule_id"), + T_RULE_CONDITION("t_rule_condition", "id", "version_id"), + T_RULE_LOOP_GROUP_ACTION("t_rule_loop_group_action", "id", "condition_for_id"), + T_RULE_FIELD("t_rule_field", "id", "rule_id"), + /** + * 策略输出 + */ + T_TACTICS_OUTPUT("t_tactics_output", "id", "tactics_id"); + + private String tableName; + private String primaryId; + private String foreignId; + + TableEnum(String tableName, String primaryId, String foreignId) { + this.tableName = tableName; + this.primaryId = primaryId; + this.foreignId = foreignId; + } + + public static TableEnum getByTableName(String tableName) { + for (TableEnum tableEnum : TableEnum.values()) { + if (tableName.equals(tableEnum.getTableName())) { + return tableEnum; + } + } + return null; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getPrimaryId() { + return primaryId; + } + + public void setPrimaryId(String primaryId) { + this.primaryId = primaryId; + } + + public String getForeignId() { + return foreignId; + } + + public void setForeignId(String foreignId) { + this.foreignId = foreignId; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/basefactory/CustomBeanFactory.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/basefactory/CustomBeanFactory.java new file mode 100644 index 0000000..b2caba1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/basefactory/CustomBeanFactory.java @@ -0,0 +1,17 @@ +package com.baoying.enginex.executor.common.basefactory; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public final class CustomBeanFactory { + public static ApplicationContext getContext() { + final String[] applicationXML = { "applicationContext.xml"}; + ApplicationContext context = getSpringContext(applicationXML); + return context; + } + + public static ApplicationContext getSpringContext(String[] paths) { + return new ClassPathXmlApplicationContext(paths); + + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/CommonConst.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/CommonConst.java new file mode 100644 index 0000000..f34a03d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/CommonConst.java @@ -0,0 +1,31 @@ +package com.baoying.enginex.executor.common.constants; + +public class CommonConst { + + /** + * 逗号 + */ + public static final String SYMBOL_COMMA = ","; + + /** + * 单引号 + */ + public static final String SYMBOL_SINGLE_QUOTA = "\'"; + + /** + * 空格 + */ + public static final String SYMBOL_BLANK = " "; + + /** + * 空字符串 + */ + public static final String STRING_EMPTY = ""; + + /** + * 30分钟(s) + * */ + public static final long MINUTE_30 = 1800000; + + public static String DROOLS_KSESSION_KEY_PREFIX = "DROOLS_KSESSION#"; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/Constants.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/Constants.java new file mode 100644 index 0000000..9fdc18c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/Constants.java @@ -0,0 +1,30 @@ +package com.baoying.enginex.executor.common.constants; + +/** + * 公共变量约定 + */ +public class Constants { + + // 规则集节点相关常量 + public interface ruleNode { + // 互斥组(串行) + int MUTEXGROUP = 1; + // 执行组(并行) + int EXECUTEGROUP = 2; + } + + public interface switchFlag { + // 开关打开 + String ON = "on"; + // 开关关闭 + String OFF = "off"; + } + + public interface fieldName { + // 字段英文名 + String fieldEn = "field_en"; + //字段中文名 + String fieldCn = "field_cn"; + } + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/ParamTypeConst.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/ParamTypeConst.java new file mode 100644 index 0000000..9ce45b6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/constants/ParamTypeConst.java @@ -0,0 +1,10 @@ +package com.baoying.enginex.executor.common.constants; + +public class ParamTypeConst { + public static final int CONSTANT = 1; + public static final int VARIABLE = 2; + public static final int CUSTOM = 3; + public static final int REGEX = 4; + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionFactory.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionFactory.java new file mode 100644 index 0000000..6b4e3f5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionFactory.java @@ -0,0 +1,60 @@ +package com.baoying.enginex.executor.common.ksession; + +import com.baoying.enginex.executor.redis.RedisManager; +import org.apache.commons.pool2.BaseKeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.drools.KnowledgeBase; +import org.drools.KnowledgeBaseFactory; +import org.drools.builder.*; +import org.drools.io.ResourceFactory; +import org.drools.runtime.StatefulKnowledgeSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * kSession工厂类 + */ +@Component +public class KSessionFactory extends BaseKeyedPooledObjectFactory { + + @Autowired + private RedisManager redisManager; + + @Override + public StatefulKnowledgeSession create(String key) throws Exception { + StatefulKnowledgeSession kSession = null; + try { + String ruleString = redisManager.get(key); + if(ruleString == null){ + throw new Exception("create kSession fail, key is "+ key + ", ruleString is null!"); + } + + long start = System.currentTimeMillis(); + KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder(); + kb.add(ResourceFactory.newByteArrayResource(ruleString.getBytes("utf-8")), ResourceType.DRL); + KnowledgeBuilderErrors errors = kb.getErrors(); + for (KnowledgeBuilderError error : errors) { + System.out.println(error); + } + KnowledgeBase kBase = KnowledgeBaseFactory.newKnowledgeBase(); + kBase.addKnowledgePackages(kb.getKnowledgePackages()); + kSession = kBase.newStatefulKnowledgeSession(); + long end = System.currentTimeMillis(); + System.out.println("------------------drools kSession创建耗时:" + (end - start) + " ----------------------"); + } catch (Exception e) { + throw e; + } + + return kSession; + } + + @Override + public PooledObject wrap(StatefulKnowledgeSession kSession) { + return new DefaultPooledObject(kSession); + } + + public void setRedisManager(RedisManager redisManager) { + this.redisManager = redisManager; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionPool.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionPool.java new file mode 100644 index 0000000..6951d13 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/ksession/KSessionPool.java @@ -0,0 +1,67 @@ +package com.baoying.enginex.executor.common.ksession; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import org.drools.runtime.StatefulKnowledgeSession; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * kSession连接池 + */ +@Component +public class KSessionPool implements InitializingBean { + + private GenericKeyedObjectPool pool; + + @Autowired + private KSessionFactory kSessionFactory; + + /** + * 初始化方法 + * @throws Exception + */ + @Override + public void afterPropertiesSet() throws Exception { + initPool(); + } + + /** + * 初始化连接池 + * @return + * @throws Exception + */ + public void initPool() throws Exception { + GenericKeyedObjectPoolConfig poolConfig = new GenericKeyedObjectPoolConfig(); + poolConfig.setMaxTotalPerKey(200); + poolConfig.setMaxIdlePerKey(50); + poolConfig.setMinIdlePerKey(5); + poolConfig.setMaxTotal(2000); + this.pool = new GenericKeyedObjectPool(kSessionFactory, poolConfig); + } + + /** + * 获取一个连接对象 + * @return + * @throws Exception + */ + public StatefulKnowledgeSession borrowObject(String key) throws Exception { + return pool.borrowObject(key); + } + + /** + * 归还一个连接对象 + * @param ftpClient + */ + public void returnObject(String key, StatefulKnowledgeSession kSession) { + if(kSession != null){ + pool.returnObject(key, kSession); + } + } + + public void setkSessionFactory(KSessionFactory kSessionFactory) { + this.kSessionFactory = kSessionFactory; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/BaseMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/BaseMapper.java new file mode 100644 index 0000000..f70a40b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/BaseMapper.java @@ -0,0 +1,55 @@ +package com.baoying.enginex.executor.common.mapper; + +import java.util.List; + +public abstract interface BaseMapper { + + /** + * @Description: 根据对象删除数据 + * @param entity 对象 + * @return 是否删除成功 + */ + int deleteByExample(IdEntity entity); + + /** + * @Description: 根据对象主键ID删除数据 + * @param id 对象id编号 + * @return 是否删除成功 + */ + int deleteByPrimaryKey(Long id); + + /** + * @Description: 插入一条新的数据 + * @param entity 对象 + * @return 是否插入成功 + */ + int insertSelective(IdEntity entity); + + /** + * @Description: 根据对象主键更新对象信息 + * @param entity 对象 + * @return 是否修改成功标志 + */ + int updateByPrimaryKeySelective(IdEntity entity); + + /** + * @Description: 根据对象获取数据条数 + * @param entity 对象 + * @return 返回行数 + */ + int countByExample(IdEntity entity); + + /** + * @Description: 根据对象主键ID获取指定数据(多个) + * @param entity 对象 + * @return 对象列表 + */ + List selectByExample(IdEntity entity); + + /** + * @Description: 根据对象主键ID获取指定数据(单个) + * @param id id编号 + * @return 返回单个对象 + */ + IdEntity selectByPrimaryKey(Long id); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.java new file mode 100644 index 0000000..e990cb6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.java @@ -0,0 +1,39 @@ +package com.baoying.enginex.executor.common.mapper; + +import com.baoying.enginex.executor.common.model.EmailTemplate; +import com.baoying.enginex.executor.common.model.EmailTemplateExample; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface EmailTemplateMapper { + long countByExample(EmailTemplateExample example); + + int deleteByExample(EmailTemplateExample example); + + int deleteByPrimaryKey(Integer templateId); + + int insert(EmailTemplate record); + + int insertSelective(EmailTemplate record); + + List selectByExampleWithBLOBs(EmailTemplateExample example); + + List selectByExample(EmailTemplateExample example); + + EmailTemplate selectByPrimaryKey(Integer templateId); + + int updateByExampleSelective(@Param("record") EmailTemplate record, @Param("example") EmailTemplateExample example); + + int updateByExampleWithBLOBs(@Param("record") EmailTemplate record, @Param("example") EmailTemplateExample example); + + int updateByExample(@Param("record") EmailTemplate record, @Param("example") EmailTemplateExample example); + + int updateByPrimaryKeySelective(EmailTemplate record); + + int updateByPrimaryKeyWithBLOBs(EmailTemplate record); + + int updateByPrimaryKey(EmailTemplate record); + + EmailTemplate selectTemplateByNid(String nid); +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.xml new file mode 100644 index 0000000..5f092f0 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/mapper/EmailTemplateMapper.xml @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + template_id, subject, nid, status, address, use_type, create_time, update_time + + + content + + + + + + delete from t_email_template + where template_id = #{templateId,jdbcType=INTEGER} + + + delete from t_email_template + + + + + + insert into t_email_template (template_id, subject, nid, + status, address, use_type, + create_time, update_time, content + ) + values (#{templateId,jdbcType=INTEGER}, #{subject,jdbcType=VARCHAR}, #{nid,jdbcType=VARCHAR}, + #{status,jdbcType=TINYINT}, #{address,jdbcType=VARCHAR}, #{useType,jdbcType=TINYINT}, + #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, #{content,jdbcType=LONGVARCHAR} + ) + + + insert into t_email_template + + + template_id, + + + subject, + + + nid, + + + status, + + + address, + + + use_type, + + + create_time, + + + update_time, + + + content, + + + + + #{templateId,jdbcType=INTEGER}, + + + #{subject,jdbcType=VARCHAR}, + + + #{nid,jdbcType=VARCHAR}, + + + #{status,jdbcType=TINYINT}, + + + #{address,jdbcType=VARCHAR}, + + + #{useType,jdbcType=TINYINT}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + #{content,jdbcType=LONGVARCHAR}, + + + + + + update t_email_template + + + template_id = #{record.templateId,jdbcType=INTEGER}, + + + subject = #{record.subject,jdbcType=VARCHAR}, + + + nid = #{record.nid,jdbcType=VARCHAR}, + + + status = #{record.status,jdbcType=TINYINT}, + + + address = #{record.address,jdbcType=VARCHAR}, + + + use_type = #{record.useType,jdbcType=TINYINT}, + + + create_time = #{record.createTime,jdbcType=TIMESTAMP}, + + + update_time = #{record.updateTime,jdbcType=TIMESTAMP}, + + + content = #{record.content,jdbcType=LONGVARCHAR}, + + + + + + + + update t_email_template + set template_id = #{record.templateId,jdbcType=INTEGER}, + subject = #{record.subject,jdbcType=VARCHAR}, + nid = #{record.nid,jdbcType=VARCHAR}, + status = #{record.status,jdbcType=TINYINT}, + address = #{record.address,jdbcType=VARCHAR}, + use_type = #{record.useType,jdbcType=TINYINT}, + create_time = #{record.createTime,jdbcType=TIMESTAMP}, + update_time = #{record.updateTime,jdbcType=TIMESTAMP}, + content = #{record.content,jdbcType=LONGVARCHAR} + + + + + + update t_email_template + set template_id = #{record.templateId,jdbcType=INTEGER}, + subject = #{record.subject,jdbcType=VARCHAR}, + nid = #{record.nid,jdbcType=VARCHAR}, + status = #{record.status,jdbcType=TINYINT}, + address = #{record.address,jdbcType=VARCHAR}, + use_type = #{record.useType,jdbcType=TINYINT}, + create_time = #{record.createTime,jdbcType=TIMESTAMP}, + update_time = #{record.updateTime,jdbcType=TIMESTAMP} + + + + + + update t_email_template + + + subject = #{subject,jdbcType=VARCHAR}, + + + nid = #{nid,jdbcType=VARCHAR}, + + + status = #{status,jdbcType=TINYINT}, + + + address = #{address,jdbcType=VARCHAR}, + + + use_type = #{useType,jdbcType=TINYINT}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + content = #{content,jdbcType=LONGVARCHAR}, + + + where template_id = #{templateId,jdbcType=INTEGER} + + + update t_email_template + set subject = #{subject,jdbcType=VARCHAR}, + nid = #{nid,jdbcType=VARCHAR}, + status = #{status,jdbcType=TINYINT}, + address = #{address,jdbcType=VARCHAR}, + use_type = #{useType,jdbcType=TINYINT}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = #{updateTime,jdbcType=TIMESTAMP}, + content = #{content,jdbcType=LONGVARCHAR} + where template_id = #{templateId,jdbcType=INTEGER} + + + update t_email_template + set subject = #{subject,jdbcType=VARCHAR}, + nid = #{nid,jdbcType=VARCHAR}, + status = #{status,jdbcType=TINYINT}, + address = #{address,jdbcType=VARCHAR}, + use_type = #{useType,jdbcType=TINYINT}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + where template_id = #{templateId,jdbcType=INTEGER} + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/BasePage.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/BasePage.java new file mode 100644 index 0000000..d2a2e1c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/BasePage.java @@ -0,0 +1,81 @@ +package com.baoying.enginex.executor.common.model; + + +public class BasePage { + + /** + * 当前页数 + */ + private int page; + + /** + * 每页显示的行数 + */ + private int rows; + + /** + * 开始行数 + */ + private Integer curRow; + + /** + * 结束行数 + */ + private Integer endRow; + + /** + * 总行数 + */ + private Integer total; + + public BasePage() { + + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + } + + /** + * setPagination:(设置当前页面和每页显示行数).
+ * @author wz + * @param page 当前页数 + * @param rows 每页显示的行数 + */ + public void setPagination(int page,int rows){ + this.page = page; + this.rows = rows; + this.curRow = (page-1)*rows; + this.endRow = (page)*rows; + } + + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = rows; + } + + public void setCurRow(Integer curRow) { + this.curRow = curRow; + } + + public void setEndRow(Integer endRow) { + this.endRow = endRow; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplate.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplate.java new file mode 100644 index 0000000..c7cefad --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplate.java @@ -0,0 +1,122 @@ +package com.baoying.enginex.executor.common.model; + +import java.util.Date; + +public class EmailTemplate { + private Integer templateId; + + private String subject; + + private String nid; + + private Byte status; + + private String address; + + private Byte useType; + + private Date createTime; + + private Date updateTime; + + private String content; + + public EmailTemplate(Integer templateId, String subject, String nid, Byte status, String address, Byte useType, Date createTime, Date updateTime) { + this.templateId = templateId; + this.subject = subject; + this.nid = nid; + this.status = status; + this.address = address; + this.useType = useType; + this.createTime = createTime; + this.updateTime = updateTime; + } + + public EmailTemplate(Integer templateId, String subject, String nid, Byte status, String address, Byte useType, Date createTime, Date updateTime, String content) { + this.templateId = templateId; + this.subject = subject; + this.nid = nid; + this.status = status; + this.address = address; + this.useType = useType; + this.createTime = createTime; + this.updateTime = updateTime; + this.content = content; + } + + public EmailTemplate() { + super(); + } + + public Integer getTemplateId() { + return templateId; + } + + public void setTemplateId(Integer templateId) { + this.templateId = templateId; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject == null ? null : subject.trim(); + } + + public String getNid() { + return nid; + } + + public void setNid(String nid) { + this.nid = nid == null ? null : nid.trim(); + } + + public Byte getStatus() { + return status; + } + + public void setStatus(Byte status) { + this.status = status; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address == null ? null : address.trim(); + } + + public Byte getUseType() { + return useType; + } + + public void setUseType(Byte useType) { + this.useType = useType; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content == null ? null : content.trim(); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplateExample.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplateExample.java new file mode 100644 index 0000000..6ae7b1d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/EmailTemplateExample.java @@ -0,0 +1,711 @@ +package com.baoying.enginex.executor.common.model; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class EmailTemplateExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public EmailTemplateExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andTemplateIdIsNull() { + addCriterion("template_id is null"); + return (Criteria) this; + } + + public Criteria andTemplateIdIsNotNull() { + addCriterion("template_id is not null"); + return (Criteria) this; + } + + public Criteria andTemplateIdEqualTo(Integer value) { + addCriterion("template_id =", value, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdNotEqualTo(Integer value) { + addCriterion("template_id <>", value, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdGreaterThan(Integer value) { + addCriterion("template_id >", value, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdGreaterThanOrEqualTo(Integer value) { + addCriterion("template_id >=", value, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdLessThan(Integer value) { + addCriterion("template_id <", value, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdLessThanOrEqualTo(Integer value) { + addCriterion("template_id <=", value, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdIn(List values) { + addCriterion("template_id in", values, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdNotIn(List values) { + addCriterion("template_id not in", values, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdBetween(Integer value1, Integer value2) { + addCriterion("template_id between", value1, value2, "templateId"); + return (Criteria) this; + } + + public Criteria andTemplateIdNotBetween(Integer value1, Integer value2) { + addCriterion("template_id not between", value1, value2, "templateId"); + return (Criteria) this; + } + + public Criteria andSubjectIsNull() { + addCriterion("subject is null"); + return (Criteria) this; + } + + public Criteria andSubjectIsNotNull() { + addCriterion("subject is not null"); + return (Criteria) this; + } + + public Criteria andSubjectEqualTo(String value) { + addCriterion("subject =", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectNotEqualTo(String value) { + addCriterion("subject <>", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectGreaterThan(String value) { + addCriterion("subject >", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectGreaterThanOrEqualTo(String value) { + addCriterion("subject >=", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectLessThan(String value) { + addCriterion("subject <", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectLessThanOrEqualTo(String value) { + addCriterion("subject <=", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectLike(String value) { + addCriterion("subject like", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectNotLike(String value) { + addCriterion("subject not like", value, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectIn(List values) { + addCriterion("subject in", values, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectNotIn(List values) { + addCriterion("subject not in", values, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectBetween(String value1, String value2) { + addCriterion("subject between", value1, value2, "subject"); + return (Criteria) this; + } + + public Criteria andSubjectNotBetween(String value1, String value2) { + addCriterion("subject not between", value1, value2, "subject"); + return (Criteria) this; + } + + public Criteria andNidIsNull() { + addCriterion("nid is null"); + return (Criteria) this; + } + + public Criteria andNidIsNotNull() { + addCriterion("nid is not null"); + return (Criteria) this; + } + + public Criteria andNidEqualTo(String value) { + addCriterion("nid =", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidNotEqualTo(String value) { + addCriterion("nid <>", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidGreaterThan(String value) { + addCriterion("nid >", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidGreaterThanOrEqualTo(String value) { + addCriterion("nid >=", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidLessThan(String value) { + addCriterion("nid <", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidLessThanOrEqualTo(String value) { + addCriterion("nid <=", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidLike(String value) { + addCriterion("nid like", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidNotLike(String value) { + addCriterion("nid not like", value, "nid"); + return (Criteria) this; + } + + public Criteria andNidIn(List values) { + addCriterion("nid in", values, "nid"); + return (Criteria) this; + } + + public Criteria andNidNotIn(List values) { + addCriterion("nid not in", values, "nid"); + return (Criteria) this; + } + + public Criteria andNidBetween(String value1, String value2) { + addCriterion("nid between", value1, value2, "nid"); + return (Criteria) this; + } + + public Criteria andNidNotBetween(String value1, String value2) { + addCriterion("nid not between", value1, value2, "nid"); + return (Criteria) this; + } + + public Criteria andStatusIsNull() { + addCriterion("status is null"); + return (Criteria) this; + } + + public Criteria andStatusIsNotNull() { + addCriterion("status is not null"); + return (Criteria) this; + } + + public Criteria andStatusEqualTo(Byte value) { + addCriterion("status =", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusNotEqualTo(Byte value) { + addCriterion("status <>", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusGreaterThan(Byte value) { + addCriterion("status >", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusGreaterThanOrEqualTo(Byte value) { + addCriterion("status >=", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusLessThan(Byte value) { + addCriterion("status <", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusLessThanOrEqualTo(Byte value) { + addCriterion("status <=", value, "status"); + return (Criteria) this; + } + + public Criteria andStatusIn(List values) { + addCriterion("status in", values, "status"); + return (Criteria) this; + } + + public Criteria andStatusNotIn(List values) { + addCriterion("status not in", values, "status"); + return (Criteria) this; + } + + public Criteria andStatusBetween(Byte value1, Byte value2) { + addCriterion("status between", value1, value2, "status"); + return (Criteria) this; + } + + public Criteria andStatusNotBetween(Byte value1, Byte value2) { + addCriterion("status not between", value1, value2, "status"); + return (Criteria) this; + } + + public Criteria andAddressIsNull() { + addCriterion("address is null"); + return (Criteria) this; + } + + public Criteria andAddressIsNotNull() { + addCriterion("address is not null"); + return (Criteria) this; + } + + public Criteria andAddressEqualTo(String value) { + addCriterion("address =", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressNotEqualTo(String value) { + addCriterion("address <>", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressGreaterThan(String value) { + addCriterion("address >", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressGreaterThanOrEqualTo(String value) { + addCriterion("address >=", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressLessThan(String value) { + addCriterion("address <", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressLessThanOrEqualTo(String value) { + addCriterion("address <=", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressLike(String value) { + addCriterion("address like", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressNotLike(String value) { + addCriterion("address not like", value, "address"); + return (Criteria) this; + } + + public Criteria andAddressIn(List values) { + addCriterion("address in", values, "address"); + return (Criteria) this; + } + + public Criteria andAddressNotIn(List values) { + addCriterion("address not in", values, "address"); + return (Criteria) this; + } + + public Criteria andAddressBetween(String value1, String value2) { + addCriterion("address between", value1, value2, "address"); + return (Criteria) this; + } + + public Criteria andAddressNotBetween(String value1, String value2) { + addCriterion("address not between", value1, value2, "address"); + return (Criteria) this; + } + + public Criteria andUseTypeIsNull() { + addCriterion("use_type is null"); + return (Criteria) this; + } + + public Criteria andUseTypeIsNotNull() { + addCriterion("use_type is not null"); + return (Criteria) this; + } + + public Criteria andUseTypeEqualTo(Byte value) { + addCriterion("use_type =", value, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeNotEqualTo(Byte value) { + addCriterion("use_type <>", value, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeGreaterThan(Byte value) { + addCriterion("use_type >", value, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeGreaterThanOrEqualTo(Byte value) { + addCriterion("use_type >=", value, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeLessThan(Byte value) { + addCriterion("use_type <", value, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeLessThanOrEqualTo(Byte value) { + addCriterion("use_type <=", value, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeIn(List values) { + addCriterion("use_type in", values, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeNotIn(List values) { + addCriterion("use_type not in", values, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeBetween(Byte value1, Byte value2) { + addCriterion("use_type between", value1, value2, "useType"); + return (Criteria) this; + } + + public Criteria andUseTypeNotBetween(Byte value1, Byte value2) { + addCriterion("use_type not between", value1, value2, "useType"); + return (Criteria) this; + } + + public Criteria andCreateTimeIsNull() { + addCriterion("create_time is null"); + return (Criteria) this; + } + + public Criteria andCreateTimeIsNotNull() { + addCriterion("create_time is not null"); + return (Criteria) this; + } + + public Criteria andCreateTimeEqualTo(Date value) { + addCriterion("create_time =", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotEqualTo(Date value) { + addCriterion("create_time <>", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThan(Date value) { + addCriterion("create_time >", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThanOrEqualTo(Date value) { + addCriterion("create_time >=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThan(Date value) { + addCriterion("create_time <", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThanOrEqualTo(Date value) { + addCriterion("create_time <=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeIn(List values) { + addCriterion("create_time in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotIn(List values) { + addCriterion("create_time not in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeBetween(Date value1, Date value2) { + addCriterion("create_time between", value1, value2, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotBetween(Date value1, Date value2) { + addCriterion("create_time not between", value1, value2, "createTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIsNull() { + addCriterion("update_time is null"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIsNotNull() { + addCriterion("update_time is not null"); + return (Criteria) this; + } + + public Criteria andUpdateTimeEqualTo(Date value) { + addCriterion("update_time =", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotEqualTo(Date value) { + addCriterion("update_time <>", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeGreaterThan(Date value) { + addCriterion("update_time >", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeGreaterThanOrEqualTo(Date value) { + addCriterion("update_time >=", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeLessThan(Date value) { + addCriterion("update_time <", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeLessThanOrEqualTo(Date value) { + addCriterion("update_time <=", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIn(List values) { + addCriterion("update_time in", values, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotIn(List values) { + addCriterion("update_time not in", values, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeBetween(Date value1, Date value2) { + addCriterion("update_time between", value1, value2, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotBetween(Date value1, Date value2) { + addCriterion("update_time not between", value1, value2, "updateTime"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/ExpressionParam.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/ExpressionParam.java new file mode 100644 index 0000000..3c20f2a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/model/ExpressionParam.java @@ -0,0 +1,18 @@ +package com.baoying.enginex.executor.common.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +//表达式的参数实体类 +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ExpressionParam { + private String fieldEn;//表达式中key字段en + private String operator;//表达式的操作符 + private Integer variableType;//表达式中value类型,1常量 2变量,3自定义 + private String fieldValue;//表达式中对应常量value值或者变量key + private String executionLogic;//执行逻辑 + private Integer conditionType;//规则节点的类型:1-关系节点,2-表达式节点 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionData.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionData.java new file mode 100644 index 0000000..89163d5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionData.java @@ -0,0 +1,15 @@ +package com.baoying.enginex.executor.common.session; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = false) +public class SessionData { + + private Long organId; // 组织id + private Long engineId; // 引擎id + private Integer reqType;//请求类型 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionManager.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionManager.java new file mode 100644 index 0000000..5040ea1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/common/session/SessionManager.java @@ -0,0 +1,20 @@ +package com.baoying.enginex.executor.common.session; + +import com.alibaba.ttl.TransmittableThreadLocal; + +/** + * session管理类 + */ +public class SessionManager { + private static TransmittableThreadLocal session = new TransmittableThreadLocal() { + + }; + + public static SessionData getSession() { + return session.get(); + } + + public static void setSession(SessionData conn) { + session.set(conn); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigHolder.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigHolder.java new file mode 100644 index 0000000..1e482d2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigHolder.java @@ -0,0 +1,72 @@ +package com.baoying.enginex.executor.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Data +public class ConfigHolder { + + //redisConfig + @Value("${redis.host}") + private String redisHost; + @Value("${redis.port}") + private int redisPort; + @Value("${redis.db}") + private int redisDb; + @Value("${redis.password}") + private String redisPwd; + @Value("${redis.pool.maxTotal}") + private int redisMaxTotal; + @Value("${redis.pool.maxIdle}") + private int redisMaxIdle; + @Value("${redis.pool.maxWait}") + private int redisMaxWait; + @Value("${redis.pool.timeout}") + private int redisTimeout; + + // 业务逻辑是否使用缓存 + @Value("${switch.use.cache}") + private String cacheSwitch; + // canal缓存同步是否开启 + @Value("${switch.canal.cache}") + private String canalCacheSwitch; + // canal主机地址 + @Value("${canal.hostname}") + private String canalHostName; + // canal端口 + @Value("${canal.port}") + private int canalPort; + + //jdbcConfig + /*@Value("${jdbc.url}") + private String jdbcUrl; + @Value("${jdbc.driver}") + private String DriverName; + @Value("${pool.maxPoolSize}") + private int maxPoolSize; + @Value("${jdbc.username}") + private String jdbcUserName; + @Value("${jdbc.password}") + private String jdbcPwd; + @Value("${pool.maxWait}") + private int jdbcMaxWait; + @Value("${pool.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + @Value("${pool.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + @Value("${pool.validationQuery}") + private String validationQuery; + + //rabbitconfig + @Value("${rabbitMQ.host}") + private String rabbitHost; + @Value("${rabbitMQ.port}") + private int rabbitPort; + @Value("${rabbitMQ.username}") + private String rabbitUsername; + @Value("${rabbitMQ.password}") + private String rabbitPassword;*/ + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigurationContainor.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigurationContainor.java new file mode 100644 index 0000000..54c1e29 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/ConfigurationContainor.java @@ -0,0 +1,46 @@ +package com.baoying.enginex.executor.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +import javax.annotation.Resource; +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +public class ConfigurationContainor { + + @Resource + private ConfigHolder configHolder; + + @Bean(name = "threadPoolTaskExecutor") + ThreadPoolTaskExecutor threadPoolTaskExecutor(){ + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2000); + executor.setMaxPoolSize(10000); + executor.setQueueCapacity(100000); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); + return executor; + } + + @Bean(name = "jedisPool") + public JedisPool jedisPool(){ + JedisPoolConfig config = new JedisPoolConfig(); + config.setMaxTotal(configHolder.getRedisMaxTotal()); + config.setMaxIdle(configHolder.getRedisMaxIdle()); + config.setMaxWaitMillis(configHolder.getRedisMaxWait()); + config.setTestOnBorrow(true); +// config.setTestOnReturn(true); + + JedisPool pool = new JedisPool(config, + configHolder.getRedisHost(), + configHolder.getRedisPort(), + configHolder.getRedisTimeout(), + configHolder.getRedisPwd(), + configHolder.getRedisDb()); + return pool; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/DataSourceConfig.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/DataSourceConfig.java new file mode 100644 index 0000000..af53c49 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/DataSourceConfig.java @@ -0,0 +1,61 @@ +package com.baoying.enginex.executor.config; + +import com.alibaba.druid.pool.DruidDataSource; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +import javax.sql.DataSource; + + +@Configuration +public class DataSourceConfig { + @Value("${spring.datasource.default.url}") + private String defaultDBUrl; + @Value("${spring.datasource.default.username}") + private String defaultDBUser; + @Value("${spring.datasource.default.password}") + private String defaultDBPassword; + @Value("${spring.datasource.default.driver-class-name}") + private String defaultDBDreiverName; + + @Bean + public DruidDataSource druidDataSource(){ + DruidDataSource defaultDataSource = new DruidDataSource(); + defaultDataSource.setUrl(defaultDBUrl); + defaultDataSource.setUsername(defaultDBUser); + defaultDataSource.setPassword(defaultDBPassword); + defaultDataSource.setDriverClassName(defaultDBDreiverName); + + return defaultDataSource; + } + + @Bean + public SqlSessionFactory sqlSessionFactory( + @Qualifier("druidDataSource") DataSource druidDataSource) + throws Exception { + MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] mapperXmlResource = resolver.getResources("classpath*:com/baoying/enginex/executor/*/mapper/*Mapper.xml"); + bean.setDataSource(druidDataSource); + bean.setMapperLocations(mapperXmlResource); + bean.setTypeAliasesPackage("com.baoying.enginex.executor.**.model"); + bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); + bean.getObject().getConfiguration().setCacheEnabled(false); + return bean.getObject(); + } + + @Bean(name = "sqlSessionTemplate") + public SqlSessionTemplate sqlSessionTemplate( + @Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) + throws Exception { + return new SqlSessionTemplate(sqlSessionFactory); + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/RestTemplateConfig.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/RestTemplateConfig.java new file mode 100644 index 0000000..00dfa2a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/config/RestTemplateConfig.java @@ -0,0 +1,52 @@ +package com.baoying.enginex.executor.config; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.http.client.AsyncClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.AsyncRestTemplate; +import org.springframework.web.client.RestTemplate; + +/** + * RestTemplate配置 + */ +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate(@Qualifier("clientHttpRequestFactory") ClientHttpRequestFactory factory){ + return new RestTemplate(factory); + } + + @Bean + public AsyncRestTemplate asyncRestTemplate(@Qualifier("asyncClientHttpRequestFactory") AsyncClientHttpRequestFactory factory){ + return new AsyncRestTemplate(factory); + } + + @Bean + public ClientHttpRequestFactory clientHttpRequestFactory(){ + // 创建一个 httpCilent 简单工厂 + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + // 设置连接超时 + factory.setConnectTimeout(15000); + // 设置读取超时 + factory.setReadTimeout(5000); + return factory; + } + + @Bean + public AsyncClientHttpRequestFactory asyncClientHttpRequestFactory(){ + // 创建一个 httpCilent 简单工厂 + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + // 设置连接超时 +// factory.setConnectTimeout(15000); + // 设置读取超时 +// factory.setReadTimeout(5000); + //设置异步任务(线程不会重用,每次调用时都会重新启动一个新的线程) + factory.setTaskExecutor(new SimpleAsyncTaskExecutor()); + return factory; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.java new file mode 100644 index 0000000..26afa10 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.java @@ -0,0 +1,57 @@ +package com.baoying.enginex.executor.datamanage.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.datamanage.model.Field; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface FieldMapper extends BaseMapper { + + /** + * findFieldByIds:(找出一批字段id对应的字段列表).
+ * @author caowenyu + * @param paramMap 参数集合 + * @return 字段列表 + */ + public List findFieldByIdsbyorganId(Map paramMap); + + /** + * findByFieldEn:(根据引擎和字段英文名找出引擎所用字段对象).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 字段对象 + */ + public Field findByFieldEnbyorganId(Map paramMap); + + /** + * findByFieldCn:(根据字段中文名找出字段对象).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 字段对象 + */ + public Field findByFieldCnbyorganId(Map paramMap); + + /** + * findByFieldCn:(按中文名查找通用字段).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 字段对象 + */ + public Field findByFieldCnNoEngineIdbyorganId(Map paramMap); + + /** + * findByFieldId:(根据字段Id查找字段对象).
+ * @author caowenyu + * @param paramMap 参数集合 + * @return 字段对象 + */ + public Field findByFieldIdbyorganId(Map paramMap); + + List selectFieldListByIds(@Param("ids") List ids); + + List selectFieldListByEns(@Param("ens")List ens); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.xml new file mode 100644 index 0000000..c3007df --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldMapper.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.java new file mode 100644 index 0000000..c944126 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.java @@ -0,0 +1,128 @@ + + +package com.baoying.enginex.executor.datamanage.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.datamanage.model.FieldType; + +import java.util.List; +import java.util.Map; + +public interface FieldTypeMapper extends BaseMapper { + + /** + * getFieldTypeList:(查找用户的字段类型列表).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 字段类型列表 + */ + public List getFieldTypeList(Map paramMap); + + /** + * getSubFieldTypeList:(根据传入的字段父类型查找子类型列表).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 字段类型列表 + */ + public List getSubFieldTypeList(Map paramMap); + + /** + * findFieldTypeById:(根据传入的字段类型ID查找字段类型名).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 字段类型列表 + */ + public FieldType findFieldTypeById(Map paramMap); + + /** + * findTypeIdByParentId:(根据传入的字段类型父ID查找子类型ID).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 子字段类型ID + */ + public String findTypeIdByParentId(Map paramMap); + + /** + * findTypeIdByParentId:(根据传入的字段类型类型ID查找父ID).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 子字段类型ID + */ + public String findParentIdByTypeId(Map paramMap); + + /** + * findFieldType:(查找用户可用的字段类型列表,通用组织所有,引擎只有自定义).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 字段类型列表 + */ + public List findFieldType(Map paramMap); + + /** + * createFieldType:(新增字段类型).
+ * @author yuanlinfeng + * @param fieldTypeVo 字段类型实体类 + * @return 插入成功 + */ + public boolean createFieldType(FieldType fieldTypeVo); + + /** + * findIdByFieldType:(新增字段类型).
+ * @author yuanlinfeng + * @param paramMap paramMap + * @return 字段类型编号 + */ + public long findIdByFieldType(Map paramMap); + + /** + * updateFieldType:(更新字段类型名).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新成功 + */ + public boolean updateFieldType(Map paramMap); + + /** + * updateFieldTypeByTypeIds:(更新字段类型为删除状态(-1)).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新成功 + */ + public boolean updateFieldTypeByTypeIds(Map paramMap); + + /** + * deleteFieldTypeByTypeIds:(删除字段类型下没有字段的空节点)).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新成功 + */ + public boolean deleteFieldTypeByTypeIds(Map paramMap); + + /** + * backFieldTypeByTypeIds:(更新字段类型状态为启用状态(1)).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新成功 + */ + public boolean backFieldTypeByTypeIds(Map paramMap); + + /** + * isExists:(查找字段名是否存在).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 存在的记录条数 + */ + public int isExists(Map paramMap); + + + /** + * isExistsDefaultTreeName:(查找默认节点名是否存在).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 存在的记录条数 + */ + public int isExistsDefaultTreeName(Map paramMap); + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.xml new file mode 100644 index 0000000..ca38dc0 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeMapper.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + insert into t_field_type ( field_type, parent_id, is_common ) + values ( #{fieldType}, #{parentId}, #{isCommon} ) + + + + update t_field_type + set field_type = #{fieldType} + where id = (select field_typeid + from t_field_type_user_rel + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + + and engine_id = #{engineId} + + + and engine_id is null + + and field_typeid = #{id} + ) + + + + update t_field_type_user_rel + set status = -1 + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + and engine_id = #{engineId} + and field_typeid in + + #{item} + + and status = 1 + + + + delete from t_field_type_user_rel + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + and engine_id = #{engineId} + and field_typeid in + + #{item} + + and field_typeid not in + ( select field_typeid + from t_field f,t_field_user_rel r + where f.id = r.field_id + and f.field_typeid in + + #{item} + + ) + + + + update t_field_type_user_rel + set status = 1 + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + and engine_id = #{engineId} + and field_typeid in + + #{item} + + and status = -1 + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.java new file mode 100644 index 0000000..3487ced --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.java @@ -0,0 +1,54 @@ + + +package com.baoying.enginex.executor.datamanage.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.datamanage.model.FieldTypeUser; + +import java.util.Map; + +public interface FieldTypeUserMapper extends BaseMapper { + + /** + * createFieldTypeUserRel:(新增字段类型).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 插入成功 + */ + public boolean createFieldTypeUserRel(Map paramMap); + + /** + * batchBindEngineFieldTypeUserRel:(把一批通用字段类型id中不存在的类型id批量绑定到引擎).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 插入成功 + */ + public boolean batchBindEngineFieldTypeUserRel(Map paramMap); + + /** + * deleteFieldTypeUserRel:(取消字段类型).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 删除成功 + */ + public boolean deleteFieldTypeUserRel(Map paramMap); + + /** + * updateFieldTypeUserRel:(更新字段类型名).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新成功 + */ + public boolean updateFieldTypeUserRel(Map paramMap); + + /** + * findNodeIds:(查找引擎在用的节点集合).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return + */ + public String findNodeIds(Map paramMap); + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.xml new file mode 100644 index 0000000..d37e5d7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldTypeUserMapper.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + insert into t_field_type_user_rel ( field_typeid, organ_id, engine_id, user_id, created ) + values ( #{fieldTypeId}, #{organId}, #{engineId}, #{userId}, now() ) + + + + insert into t_field_type_user_rel ( field_typeid, organ_id, engine_id, user_id, created ) + select field_typeid, organ_id, #{engineId}, #{userId}, now() + from t_field_type_user_rel r + where r.field_typeid in + + #{item} + + and field_typeid not in ( select field_typeid from t_field_type_user_rel where engine_id = #{engineId}) + and engine_id is null + + + + + + delete from t_field_type_user_rel + where + organ_id = ( select organ_id from t_user where user_id = #{userId} ) + + and engine_id = #{engineId} + + + and engine_id is null + + and field_typeid = #{fieldTypeId} + + + + update t_field_type_user_rel + set user_id = #{userId}, created = now() + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + + and engine_id = #{engineId} + + + and engine_id is null + + and field_typeid = #{id} + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.java new file mode 100644 index 0000000..b95260b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.java @@ -0,0 +1,77 @@ + + +package com.baoying.enginex.executor.datamanage.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.datamanage.model.FieldUser; + +import java.util.Map; + +public interface FieldUserMapper extends BaseMapper { + + /** + * createFieldUserRel:(绑定字段和用户关系).
+ * @author yuanlinfeng + * @param fieldUser 用户字段实体类 + * @return 插入成功 + * */ + public boolean createFieldUserRel(FieldUser fieldUserVo); + + /** + * batchCreateFieldUserRel:(批量导入字段信息后批量绑定字段和用户关系).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 插入成功 + * */ + public boolean batchCreateFieldUserRel(Map paramMap); + + /** + * batchBindEngineFieldUserRel:(把一批通用字段id中未绑定的字段id批量绑定到引擎).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 插入成功 + * */ + public boolean batchBindEngineFieldUserRel(Map paramMap); + + /** + * batchCreateEngineFieldUserRel:(把id、英文名、中文名不重复的组织字段批量绑定到引擎).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 插入成功 + * */ + public boolean batchCreateEngineFieldUserRel(Map paramMap); + + /** + * updateFieldUserRel:(更新字段).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新成功 + * */ + public boolean updateFieldUserRel(Map paramMap); + + /** + * updateStatus:(单个或批量更新用户字段关系).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新成功 + * */ + public boolean updateStatus(Map paramMap); + + /** + * deleteFieldByIds:(批量修改字段启用状态为删除状态(-1)).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新是否成功 + */ + public boolean deleteFieldByIds(Map paramMap); + + /** + * deleteFieldByIds:(批量修改字段删除状态为启用状态(1)).
+ * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 更新是否成功 + */ + public boolean backFieldByIds(Map paramMap); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.xml new file mode 100644 index 0000000..0a982d9 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/FieldUserMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + insert into t_field_user_rel (field_id, organ_id, engine_id, user_id, status, created, updated) + values (#{fieldId}, #{organId}, #{engineId}, #{userId}, #{status}, now(), now()) + + + + insert into t_field_user_rel (field_id, organ_id, engine_id, user_id, status, created, updated) + select id, #{organId}, #{engineId}, #{userId}, #{status}, now(), now() + from t_field t + where t.author = #{author} + and not exists ( select r.field_id from t_field_user_rel r where t.id = r.field_id ) + + + + insert into t_field_user_rel (field_id, organ_id, engine_id, user_id, status, created, updated) + select id, #{organId}, #{engineId}, #{userId}, #{status}, now(), now() + from t_field f + where f.field_typeid in + + #{item} + + and not exists ( select 1 + from ( select f.id,f.field_en,f.field_cn + from t_field f,t_field_user_rel fu + where f.id = fu.field_id + and fu.organ_id = ( select organ_id from t_user where user_id = #{userId} ) + and fu.engine_id = ${engineId} )y + where f.field_en = y.field_en or f.field_cn = y.field_cn or f.id = y.id + ) + + + + insert into t_field_user_rel (field_id, organ_id, engine_id, user_id, status, created, updated) + select id, #{organId}, #{engineId}, #{userId}, 1, now(), now() + from t_field f + where f.id in + + #{item} + + and not exists ( select 1 + from ( select f.id,f.field_en,f.field_cn + from t_field f,t_field_user_rel fu + where f.id = fu.field_id + and fu.organ_id = ( select organ_id from t_user where user_id = #{userId} ) + and fu.engine_id = ${engineId} )y + where f.field_en = y.field_en or f.field_cn = y.field_cn or f.id = y.id + ) + + + + update t_field_user_rel + set user_id = #{userId} , updated = now() + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + + and engine_id = #{engineId} + + + and engine_id is null + + and field_id = #{Id} + + + + update t_field_user_rel + set status=#{status} + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + + and engine_id = #{engineId} + + + and engine_id is null + + and field_id in + + #{item} + + + + + update t_field_user_rel + set status = -1 + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + and engine_id = #{engineId} + and field_id in + + #{item} + + and status = 1 + + + + update t_field_user_rel + set status = 1 + where organ_id = ( select organ_id from t_user where user_id = #{userId} ) + and engine_id = #{engineId} + and field_id in + + #{item} + + and status = -1 + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.java new file mode 100644 index 0000000..a168cfe --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.java @@ -0,0 +1,11 @@ +package com.baoying.enginex.executor.datamanage.mapper; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public interface SimpleMapper { + + List> customSelect(Map paramsMap); + List> test(Map paramsMap); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.xml new file mode 100644 index 0000000..9882a70 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/mapper/SimpleMapper.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/CustList.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/CustList.java new file mode 100644 index 0000000..d70b691 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/CustList.java @@ -0,0 +1,236 @@ +package com.baoying.enginex.executor.datamanage.model; + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; +import java.util.Date; + +public class CustList extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 以下20个t开头为匿名字段 + * */ + private String t0; + private String t1; + private String t2; + private String t3; + private String t4; + private String t5; + private String t6; + private String t7; + private String t8; + private String t9; + private String t10; + private String t11; + private String t12; + private String t13; + private String t14; + private String t15; + private String t16; + private String t17; + private String t18; + private String t19; + + /** + * 创建人编号 + * */ + private Long userId; + + /** + * 创建人昵称 + * */ + private String nickName; + + /** + * 创建时间 + * */ + private Date created; + + /** + * 检索客户信息是否存在的定制条件 + */ + private String checkCol; + + /** + * 检索名单库的表名称 + */ + private String tableName; + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public String getT0() { + return t0; + } + public void setT0(String t0) { + this.t0 = t0; + } + public String getT1() { + return t1; + } + public void setT1(String t1) { + this.t1 = t1; + } + public String getT2() { + return t2; + } + public void setT2(String t2) { + this.t2 = t2; + } + public String getT3() { + return t3; + } + public void setT3(String t3) { + this.t3 = t3; + } + public String getT4() { + return t4; + } + public void setT4(String t4) { + this.t4 = t4; + } + public String getT5() { + return t5; + } + public void setT5(String t5) { + this.t5 = t5; + } + public String getT6() { + return t6; + } + public void setT6(String t6) { + this.t6 = t6; + } + public String getT7() { + return t7; + } + public void setT7(String t7) { + this.t7 = t7; + } + public String getT8() { + return t8; + } + public void setT8(String t8) { + this.t8 = t8; + } + public String getT9() { + return t9; + } + public void setT9(String t9) { + this.t9 = t9; + } + public String getT10() { + return t10; + } + public void setT10(String t10) { + this.t10 = t10; + } + public String getT11() { + return t11; + } + public void setT11(String t11) { + this.t11 = t11; + } + public String getT12() { + return t12; + } + public void setT12(String t12) { + this.t12 = t12; + } + public String getT13() { + return t13; + } + public void setT13(String t13) { + this.t13 = t13; + } + public String getT14() { + return t14; + } + public void setT14(String t14) { + this.t14 = t14; + } + public String getT15() { + return t15; + } + public void setT15(String t15) { + this.t15 = t15; + } + public String getT16() { + return t16; + } + public void setT16(String t16) { + this.t16 = t16; + } + public String getT17() { + return t17; + } + public void setT17(String t17) { + this.t17 = t17; + } + public String getT18() { + return t18; + } + public void setT18(String t18) { + this.t18 = t18; + } + public String getT19() { + return t19; + } + public void setT19(String t19) { + this.t19 = t19; + } + public Long getUserId() { + return userId; + } + public void setUserId(Long userId) { + this.userId = userId; + } + public Date getCreated() { + return created; + } + public void setCreated(Date created) { + this.created = created; + } + public String getNickName() { + return nickName; + } + public void setNickName(String nickName) { + this.nickName = nickName; + } + public String getCheckCol() { + return checkCol; + } + public void setCheckCol(String checkCol) { + this.checkCol = checkCol; + } + public String getTableName() { + return tableName; + } + public void setTableName(String tableName) { + this.tableName = tableName; + } + @Override + public String toString() { + return "CustList [id=" + id + ", t0=" + t0 + ", t1=" + t1 + ", t2=" + t2 + + ", t3=" + t3 + ", t4=" + t4 + ", t5=" + t5 + ", t6=" + t6 + + ", t7=" + t7 + ", t8=" + t8 + ", t9=" + t9 + ", t10=" + t10 + + ", t11=" + t11 + ", t12=" + t12 + ", t13=" + t13 + ", t14=" + + t14 + ", t15=" + t15 + ", t16=" + t16 + ", t17=" + t17 + + ", t18=" + t18 + ", t19=" + t19 + ", userId=" + userId + + ", nickName=" + nickName + ", created=" + created + + ", checkCol=" + checkCol + ", tableName=" + tableName + "]"; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/Field.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/Field.java new file mode 100644 index 0000000..bf68b24 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/Field.java @@ -0,0 +1,203 @@ +package com.baoying.enginex.executor.datamanage.model; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +@TableName("t_field") +public class Field implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 字段英文名 + * */ + private String fieldEn; + + /** + * 字段中文名 + * */ + private String fieldCn; + + /** + * 字段类型编号 + * */ + @TableField("field_typeid") + private Long fieldTypeId; + + /** + * 字段类型名 + * */ + @TableField(exist = false) + private String fieldType; + + /** + * 字段存值类型 + * */ + private Integer valueType; + + /** + * 字段存值类型中文 + * */ + @TableField(exist = false) + private String valueTypeName; + + /** + * 字段约束范围 + * */ + private String valueScope; + + /** + * 是否衍生字段 + * */ + private Integer isDerivative; + + /** + * 是否衍生字段 + * */ + @TableField(exist = false) + private String isDerivativeName; + + /** + * 是否输出字段 + * */ + private Integer isOutput; + + /** + * 是否输出字段 + * */ + @TableField(exist = false) + private String isOutputName; + + /** + * 是否组织定义的通用字段 + * */ + private Integer isCommon; + + /** + * 衍生字段公式 + * */ + private String formula; + + /** + * 衍生字段公式回显信息 + * */ + private String formulaShow; + + /** + * 衍生字段引用的字段id + * */ + @TableField("used_fieldid") + private String usedFieldId; + + /** + * 衍生字段引用的原生字段id + * */ + @TableField("orig_fieldid") + private String origFieldId; + + /** + * 创建人 + * */ + private Long author; + + /** + * 创建人昵称 + * */ + @TableField(exist = false) + private String nickName; + + /** + * 创建时间 + * */ + private Date created; + + /** + * 归属的引擎ID + * */ + @TableField(exist = false) + private Long engineId; + + /** + * 归属的引擎名称 + * */ + @TableField(exist = false) + private String engineName; + + /** + * 字段状态(启用、停用、删除、未知) + * */ + @TableField(exist = false) + private String status; + + /** + * 字段条件设置集合 + * */ + @TableField(exist = false) + private List fieldCondList; + + /** + * 字段用户关系编号 + * */ + @TableField(exist = false) + private Long fieldRelId; + + /** + * 是否使用sql获取指标 + */ + private Boolean isUseSql; + + /** + * 使用sql获取指标时对应的数据源 + */ + private Integer dataSourceId; + + /** + * 使用sql获取指标时对应的sql语句 + */ + private String sqlStatement; + + /** + * sql变量配置 + */ + private String sqlVariable; + + /** + * 是否使用接口 + */ + private Boolean isInterface; + + /** + * 接口id + */ + private Integer interfaceId; + + /** + * 接口解析指标 + */ + private String interfaceParseField; + + /** + * json类型对应的json值 + */ + private String jsonValue; + /** + * 字典变量例如 日期:date + */ + private String dictVariable; + + /** + * 该字段归属的组织编号 + * */ + private Long organId; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldCond.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldCond.java new file mode 100644 index 0000000..ba07b14 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldCond.java @@ -0,0 +1,142 @@ +package com.baoying.enginex.executor.datamanage.model; + + +import com.baoying.enginex.executor.common.model.BasePage; +import com.baoying.enginex.executor.datamanage.vo.FieldSubCondVo; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +public class FieldCond extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 条件编号 + * */ + private Long id; + + /** + * 字段编号 + * */ + private Long fieldId; + + /** + * 字段条件值 + * */ + private String conditionValue; + + /** + * 字段条件区域设置json格式 + * */ + private String content; + + /** + * 条件字段编号 + * */ + private Long condFieldId; + + /** + * 条件字段的运算符 + * */ + private String condFieldOperator; + + /** + * 条件字段的条件设置值 + * */ + private String condFieldValue; + + /** + * 条件字段间的逻辑符 + * */ + private String condFieldLogical; + + /** + * 创建时间 + * */ + private Date created; + + private List fieldSubCond; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getFieldId() { + return fieldId; + } + + public void setFieldId(Long fieldId) { + this.fieldId = fieldId; + } + + public String getConditionValue() { + return conditionValue; + } + + public void setConditionValue(String conditionValue) { + this.conditionValue = conditionValue; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Long getCondFieldId() { + return condFieldId; + } + + public void setCondFieldId(Long condFieldId) { + this.condFieldId = condFieldId; + } + + public String getCondFieldOperator() { + return condFieldOperator; + } + + public void setCondFieldOperator(String condFieldOperator) { + this.condFieldOperator = condFieldOperator; + } + + public String getCondFieldValue() { + return condFieldValue; + } + + public void setCondFieldValue(String condFieldValue) { + this.condFieldValue = condFieldValue; + } + + public String getCondFieldLogical() { + return condFieldLogical; + } + + public void setCondFieldLogical(String condFieldLogical) { + this.condFieldLogical = condFieldLogical; + } + + public List getFieldSubCond() { + return fieldSubCond; + } + + public void setFieldSubCond(List fieldSubCond) { + this.fieldSubCond = fieldSubCond; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldInter.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldInter.java new file mode 100644 index 0000000..2ce413c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldInter.java @@ -0,0 +1,104 @@ +package com.baoying.enginex.executor.datamanage.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; + +public class FieldInter extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Integer id; + + /** + * 衍生字段编号 + * */ + private Integer fieldRelId; + + /** + * 公式引用的字段编号 + * */ + private Integer interFieldId; + + /** + * 公式引用的字段用户关系编号 + * */ + private Integer interFieldRelId; + + /** + * 同名字段的顺序 + * */ + private Integer seq; + + /** + * 字段值区间划分 + * */ + private String interval; + + /** + * 对应区间的值定义 + * */ + private String value; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getFieldRelId() { + return fieldRelId; + } + + public void setFieldRelId(Integer fieldRelId) { + this.fieldRelId = fieldRelId; + } + + public Integer getInterFieldId() { + return interFieldId; + } + + public void setInterFieldId(Integer interFieldId) { + this.interFieldId = interFieldId; + } + + public Integer getInterFieldRelId() { + return interFieldRelId; + } + + public void setInterFieldRelId(Integer interFieldRelId) { + this.interFieldRelId = interFieldRelId; + } + + public Integer getSeq() { + return seq; + } + + public void setSeq(Integer seq) { + this.seq = seq; + } + + public String getInterval() { + return interval; + } + + public void setInterval(String interval) { + this.interval = interval; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldType.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldType.java new file mode 100644 index 0000000..1b62fd5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldType.java @@ -0,0 +1,106 @@ +package com.baoying.enginex.executor.datamanage.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; + +public class FieldType extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Integer id; + + /** + * 字段类型名 + * */ + private String fieldType; + + /** + * 父节点编号 + * */ + private Integer parentId; + + /** + * 是否组织定义的通用字段类型 + * */ + private Integer isCommon; + + /** + * 字段类型的子类集合 + * */ + private FieldType[] children; + + /** + * 是否为父类 + * */ + private String isParent = "true"; + + /** + * 引擎编号 + * */ + private Integer engineId; + + /** + *文件夹图片路径 + * */ + private String icon; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + public String getFieldType() { + return fieldType; + } + public void setFieldType(String fieldType) { + this.fieldType = fieldType; + } + public Integer getParentId() { + return parentId; + } + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + public Integer getIsCommon() { + return isCommon; + } + public void setIsCommon(Integer isCommon) { + this.isCommon = isCommon; + } + public FieldType[] getChildren() { + return children; + } + public void setChildren(FieldType[] children) { + this.children = children; + } + public String getIsParent() { + return isParent; + } + public void setIsParent(String isParent) { + this.isParent = isParent; + } + public Integer getEngineId() { + return engineId; + } + public void setEngineId(Integer engineId) { + this.engineId = engineId; + } + public String getIcon() { +// if(engineId!=null) +// icon = "../../resource/images/authority/folder.png"; +// else + icon = "../resource/images/authority/folder.png"; + return icon; + } + public void setIcon(String icon) { + this.icon = icon; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldTypeUser.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldTypeUser.java new file mode 100644 index 0000000..db8b115 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldTypeUser.java @@ -0,0 +1,80 @@ +package com.baoying.enginex.executor.datamanage.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; +import java.util.Date; + +public class FieldTypeUser extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Integer id; + + /** + * 字段类型编号(表主键) + * */ + private Integer fieldTypeId; + + /** + * 该字段类型归属的组织编号 + * */ + private Long organId; + + /** + * 该字段类型归属的引擎id(表主键) + * */ + private Integer engineId; + + /** + * 创建或修改该字段的用户编号 + * */ + private Long userId; + + /** + * 创建时间 + * */ + private Date created; + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + public Integer getFieldTypeId() { + return fieldTypeId; + } + public void setFieldTypeId(Integer fieldTypeId) { + this.fieldTypeId = fieldTypeId; + } + public Long getOrganId() { + return organId; + } + public void setOrganId(Long organId) { + this.organId = organId; + } + public Integer getEngineId() { + return engineId; + } + public void setEngineId(Integer engineId) { + this.engineId = engineId; + } + public Long getUserId() { + return userId; + } + public void setUserId(Long userId) { + this.userId = userId; + } + public Date getCreated() { + return created; + } + public void setCreated(Date created) { + this.created = created; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldUser.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldUser.java new file mode 100644 index 0000000..c887839 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FieldUser.java @@ -0,0 +1,102 @@ +package com.baoying.enginex.executor.datamanage.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; +import java.util.Date; + +public class FieldUser extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 字段编号(表主键) + * */ + private Long fieldId; + + /** + * 该字段归属的组织编号 + * */ + private Long organId; + + /** + * 该字段归属的引擎id(表主键) + * */ + private Long engineId; + + /** + * 创建或修改该字段的用户编号 + * */ + private Long userId; + + /** + * 启用停用删除标志 + * */ + private int status; + + /** + * 创建时间 + * */ + private Date created; + + /** + * 更新时间 + * */ + private Date updated; + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public Long getFieldId() { + return fieldId; + } + public void setFieldId(Long fieldId) { + this.fieldId = fieldId; + } + public Long getOrganId() { + return organId; + } + public void setOrganId(Long organId) { + this.organId = organId; + } + public Long getEngineId() { + return engineId; + } + public void setEngineId(Long engineId) { + this.engineId = engineId; + } + public Long getUserId() { + return userId; + } + public void setUserId(Long userId) { + this.userId = userId; + } + public int getStatus() { + return status; + } + public void setStatus(int status) { + this.status = status; + } + public Date getCreated() { + return created; + } + public void setCreated(Date created) { + this.created = created; + } + public Date getUpdated() { + return updated; + } + public void setUpdated(Date updated) { + this.updated = updated; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FormulaField.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FormulaField.java new file mode 100644 index 0000000..b655d49 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/model/FormulaField.java @@ -0,0 +1,45 @@ +package com.baoying.enginex.executor.datamanage.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; + +public class FormulaField extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 字段编号(表主键) + * */ + private Long fieldId; + /** + * 公式用到的字段编号(表主键) + * */ + private Long formulaFieldId; + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public Long getFieldId() { + return fieldId; + } + public void setFieldId(Long fieldId) { + this.fieldId = fieldId; + } + public Long getFormulaFieldId() { + return formulaFieldId; + } + public void setFormulaFieldId(Long formulaFieldId) { + this.formulaFieldId = formulaFieldId; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/FieldService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/FieldService.java new file mode 100644 index 0000000..f7e08e6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/FieldService.java @@ -0,0 +1,19 @@ +package com.baoying.enginex.executor.datamanage.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.datamanage.model.Field; + +import java.util.List; + +public interface FieldService extends IService { + + Field queryById(Long id); + + List findFieldByIdsbyorganId(Long organId, List ids); + + List selectFieldListByEns(List fieldEnList); + + Field findByFieldEnbyorganId(Long organId, String fieldEn); + + Field findByFieldCnbyorganId(Long organId, String fieldCn); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/impl/FieldServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/impl/FieldServiceImpl.java new file mode 100644 index 0000000..cfeb580 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/service/impl/FieldServiceImpl.java @@ -0,0 +1,112 @@ +package com.baoying.enginex.executor.datamanage.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.common.session.SessionManager; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.datamanage.mapper.FieldMapper; +import com.baoying.enginex.executor.datamanage.model.Field; +import com.baoying.enginex.executor.datamanage.service.FieldService; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class FieldServiceImpl extends ServiceImpl implements FieldService { + + @Autowired + public FieldMapper fieldMapper; + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + + @Override + public Field queryById(Long id) { + Field field = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getPrimaryKey(TableEnum.T_FIELD, id); + field = redisManager.getByPrimaryKey(key, Field.class); + } else { + field = fieldMapper.selectById(id); + } + + return field; + } + + @Override + public List findFieldByIdsbyorganId(Long organId, List ids) { + List fieldList = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + List keys = RedisUtils.getPrimaryKey(TableEnum.T_FIELD, ids); + fieldList = redisManager.hgetAllBatchByPrimaryKeys(keys, Field.class); + } else { + Map paramMap = new HashMap<>(); + paramMap.put("organId", organId); + paramMap.put("Ids", ids); + fieldList = fieldMapper.findFieldByIdsbyorganId(paramMap); + } + return fieldList; + } + + @Override + public List selectFieldListByEns(List fieldEnList) { + List fieldList = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + Long organId = SessionManager.getSession().getOrganId(); + List keys = fieldEnList.stream().map(item -> { + String fieldEnStr = Constants.fieldName.fieldEn + ":" + organId + ":" + item; + String fieldEnKey = RedisUtils.getPrimaryKey(TableEnum.T_FIELD, fieldEnStr); + return fieldEnKey; + }).collect(Collectors.toList()); + + fieldList = redisManager.hgetAllBatchByPrimaryKeys(keys, Field.class); + + } else { + fieldList = fieldMapper.selectFieldListByEns(fieldEnList); + } + return fieldList; + } + + @Override + public Field findByFieldEnbyorganId(Long organId, String fieldEn) { + Field field = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String fieldEnStr = Constants.fieldName.fieldEn + ":" + organId + ":" + fieldEn; + String fieldEnKey = RedisUtils.getPrimaryKey(TableEnum.T_FIELD, fieldEnStr); + field = redisManager.getByPrimaryKey(fieldEnKey, Field.class); + // todo 是否需要status = 1判断 + } else { + Map paramMap = new HashMap(); + paramMap.put("organId", organId); + paramMap.put("fieldEn", fieldEn); + field = fieldMapper.findByFieldEnbyorganId(paramMap); + } + return field; + } + + @Override + public Field findByFieldCnbyorganId(Long organId, String fieldCn) { + Field field = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String fieldCnStr = Constants.fieldName.fieldCn + ":" + organId + ":" + fieldCn; + String fieldCnKey = RedisUtils.getPrimaryKey(TableEnum.T_FIELD, fieldCnStr); + field = redisManager.getByPrimaryKey(fieldCnKey, Field.class); + // todo 是否需要status = 1判断 + } else { + Map paramMap = new HashMap(); + paramMap.put("organId", organId); + paramMap.put("fieldCn", fieldCn); + field = fieldMapper.findByFieldCnbyorganId(paramMap); + } + return field; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldEnumVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldEnumVo.java new file mode 100644 index 0000000..dcab312 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldEnumVo.java @@ -0,0 +1,29 @@ +package com.baoying.enginex.executor.datamanage.vo; + +import com.baoying.enginex.executor.datamanage.model.Field; + +import java.util.List; + + +public class FieldEnumVo { + + private Field field; + + private List enums; + + public Field getField() { + return field; + } + + public void setField(Field field) { + this.field = field; + } + + public List getEnums() { + return enums; + } + + public void setEnums(List enums) { + this.enums = enums; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldExcelVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldExcelVo.java new file mode 100644 index 0000000..be106f7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldExcelVo.java @@ -0,0 +1,150 @@ +package com.baoying.enginex.executor.datamanage.vo; + +import java.util.Date; + +public class FieldExcelVo { + + /** + * 主键 + * */ + private Integer id; + + /** + * 字段英文名 + * */ + private String fieldEn; + + /** + * 字段中文名 + * */ + private String fieldCn; + + /** + * 字段类型名称 + * */ + private String fieldType; + + /** + * 字段存值类型 + * */ + private String valueType; + + /** + * 字段约束范围 + * */ + private String valueScope; + + /** + * 是否衍生字段 + * */ + private String isDerivative; + + /** + * 是否输出字段 + * */ + private String isOutput; + + /** + * 衍生字段公式 + * */ + private String formula; + + /** + * 创建人 + * */ + private String author; + + /** + * 创建时间 + * */ + private Date created; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getFieldEn() { + return fieldEn; + } + + public void setFieldEn(String fieldEn) { + this.fieldEn = fieldEn; + } + + public String getFieldCn() { + return fieldCn; + } + + public void setFieldCn(String fieldCn) { + this.fieldCn = fieldCn; + } + + public String getFieldType() { + return fieldType; + } + + public void setFieldType(String fieldType) { + this.fieldType = fieldType; + } + + public String getValueType() { + return valueType; + } + + public void setValueType(String valueType) { + this.valueType = valueType; + } + + public String getValueScope() { + return valueScope; + } + + public void setValueScope(String valueScope) { + this.valueScope = valueScope; + } + + public String getIsDerivative() { + return isDerivative; + } + + public void setIsDerivative(String isDerivative) { + this.isDerivative = isDerivative; + } + + public String getIsOutput() { + return isOutput; + } + + public void setIsOutput(String isOutput) { + this.isOutput = isOutput; + } + + public String getFormula() { + return formula; + } + + public void setFormula(String formula) { + this.formula = formula; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldFormulaVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldFormulaVo.java new file mode 100644 index 0000000..1a58aff --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldFormulaVo.java @@ -0,0 +1,89 @@ +package com.baoying.enginex.executor.datamanage.vo; + +import java.io.Serializable; + +public class FieldFormulaVo implements Serializable { + + private static final long serialVersionUID = 1L; + + // [{fvalue: "0",formula: "a",farr: [{fieldCN:"引擎字段1-1",fieldCond:[{"inputOne":"c","inputThree":"5"},{"inputOne":"b","inputThree":"12"}]},{fieldCN:"通用字段2贷前",fieldCond:[{"inputOne":"(30,40]","inputThree":"5"},{"inputOne":"[45,51)","inputThree":"12"}]}]}]; + + /** + * 衍生字段公式设置对应的值 + * */ + private String fvalue; + + /** + * 衍生字段公式 + * */ + private String formula; + + /** + * 衍生字段公式里字段的条件区域设置 + * */ + private Integer idx; + + /** + * 衍生字段公式里字段的条件区域设置 + * */ + private String farr; + + /** + * 衍生字段公式里条件区域设置的某个字段中文名 + * */ + private String fieldCN; + + /** + * 衍生字段公式里条件区域设置的某个字段的具体设置 + * */ + private String fieldCond; + + public String getFvalue() { + return fvalue; + } + + public void setFvalue(String fvalue) { + this.fvalue = fvalue; + } + + public String getFormula() { + return formula; + } + + public void setFormula(String formula) { + this.formula = formula; + } + + public Integer getIdx() { + return idx; + } + + public void setIdx(Integer idx) { + this.idx = idx; + } + + public String getFarr() { + return farr; + } + + public void setFarr(String farr) { + this.farr = farr; + } + + public String getFieldCN() { + return fieldCN; + } + + public void setFieldCN(String fieldCN) { + this.fieldCN = fieldCN; + } + + public String getFieldCond() { + return fieldCond; + } + + public void setFieldCond(String fieldCond) { + this.fieldCond = fieldCond; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldSubCondVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldSubCondVo.java new file mode 100644 index 0000000..12955c3 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/datamanage/vo/FieldSubCondVo.java @@ -0,0 +1,108 @@ +package com.baoying.enginex.executor.datamanage.vo; + +import java.io.Serializable; + +public class FieldSubCondVo implements Serializable{ + + private static final long serialVersionUID = 1L; + + //[{"fieldId":"43","operator":"in","fieldValue":"b","logical":"and"}] + + /** + * 条件字段编号 + * */ + private Integer fieldId; + + /** + * 条件字段的运算符 + * */ + private String operator; + + /** + * 条件字段的条件设置值 + * */ + private String fieldValue; + + /** + * 条件字段间的逻辑符 + * */ + private String logical; + + /** + * 条件字段的值类型 + * */ + private Integer valueType; + + /** + * 条件字段的取值范围 + * */ + private String valueScope; + + /** + * 条件字段的取值范围拆解后的数组 + * */ + private String[] values; + + /** + * 条件字段的字段名 + */ + private String fieldCn; + + + public Integer getFieldId() { + return fieldId; + } + public void setFieldId(Integer fieldId) { + this.fieldId = fieldId; + } + public String getOperator() { + return operator; + } + public void setOperator(String operator) { + this.operator = operator; + } + public String getFieldValue() { + return fieldValue; + } + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + public String getLogical() { + return logical; + } + public void setLogical(String logical) { + this.logical = logical; + } + public Integer getValueType() { + return valueType; + } + public void setValueType(Integer valueType) { + this.valueType = valueType; + } + public String getValueScope() { + return valueScope; + } + public void setValueScope(String valueScope) { + this.valueScope = valueScope; + } + public String[] getValues() { + if(valueType == 3){ + values = valueScope.split(","); + }else{ + values = new String[]{valueScope}; + } + return values; + } + public void setValues(String[] values) { + this.values = values; + } + public String getFieldCn() { + return fieldCn; + } + public void setFieldCn(String fieldCn) { + this.fieldCn = fieldCn; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineConst.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineConst.java new file mode 100644 index 0000000..d94a29f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineConst.java @@ -0,0 +1,20 @@ +package com.baoying.enginex.executor.engine.consts; + + +public class EngineConst { + + /** + * 版本部署状态 + */ + public static final int BOOT_STATE_DEPLOY = 1; + + /** + * 版本未部署状态 + */ + public static final int BOOT_STATE_UNDEPLOY = 0; + + /** + * 决策选项结果集key + */ + public static final String DECISION_COLLECTION_KEY = "formulaList"; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineMsg.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineMsg.java new file mode 100644 index 0000000..564e1cb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineMsg.java @@ -0,0 +1,29 @@ +package com.baoying.enginex.executor.engine.consts; + +public class EngineMsg { + + /** + * 部署成功 + */ + public static final int STATUS_SUCCESS = 1; + + public static final String DEPLOY_SUCCESS = "部署成功!"; + + public static final String UNDEPLOY_SUCCESS = "当前版本已停用!"; + + /** + * 部署失败 + */ + public static final int STATUS_FAILED = 0; + + public static final String DEPLOY_FAILED = "部署失败!"; + + public static final String UNDEPLOY_FAILED = "停用当前版本失败!"; + + public static final String DELETE_RUNNING_FAILED = "当前版本正在运行,不能删除!"; + + public static final String DELETE_VERSION_SUCCESS = "当前版本删除成功!"; + + public static final String DELETE_VERSION_FAILED = "未知异常,当前版本删除失败!"; + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineOperator.java new file mode 100644 index 0000000..7f9bbc3 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EngineOperator.java @@ -0,0 +1,151 @@ +package com.baoying.enginex.executor.engine.consts; + + +public class EngineOperator { + + /*---------------------------- 关系运算符 ----------------------------*/ + + public static final String OPERATOR_AND_RELATION = "&&"; + + public static final String OPERATOR_OR_RELATION = "||"; + + public static final String OPERATOR_NOT_RELATION = "!"; + + public static final String OPERATOR_EQUALS_RELATION = "=="; + + public static final String OPERATOR_GREATER_RELATION = ">"; + + public static final String OPERATOR_GREATER_EQUALS_RELATION = ">="; + + public static final String OPERATOR_LESS_RELATION = "<"; + + public static final String OPERATOR_LESS_EQUALS_RELATION = "<="; + + public static final String OPERATOR_NOT_EQUALS_RELATION = "!="; + + public static final String OPERATOR_AND_STRING_RELATION = "AND"; + + public static final String OPERATOR_OR_STRING_RELATION = "OR"; + + /*---------------------------- 数学运算符 ----------------------------*/ + + public static final String OPERATOR_ADD_MATH = "+"; + + public static final String OPERATOR_MINUS_MATH = "-"; + + public static final String OPERATOR_MULITI_MATH = "*"; + + public static final String OPERATOR_DIVIDE_MATH = "/"; + + public static final String OPERATOR_MODULU_MATH = "%"; + + public static final String OPERATOR_ABS_MATH = "abs"; + + public static final String OPERATOR_ACOS_MATH = "acos"; + + public static final String OPERATOR_ASIN_MATH = "asin"; + + public static final String OPERATOR_ATAN_MATH = "atan"; + + public static final String OPERATOR_ATAN2_MATH = "atan2"; + + public static final String OPERATOR_AVERAGE_MATH = "avg"; + + public static final String OPERATOR_CEIL_MATH = "ceil"; + + public static final String OPERATOR_COS_MATH = "cos"; + + public static final String OPERATOR_EXP_MATH = "exp"; + + public static final String OPERATOR_FLOOR_MATH = "floor"; + + public static final String OPERATOR_IEEE_MATH = "IEEEremainder"; + + public static final String OPERATOR_LN_MATH = "ln"; + + public static final String OPERATOR_LOG_MATH = "log"; + + public static final String OPERATOR_MAX_MATH = "max"; + + public static final String OPERATOR_MIN_MATH = "min"; + + public static final String OPERATOR_POW_MATH = "pow"; + + public static final String OPERATOR_RANDOM_MATH = "random"; + + public static final String OPERATOR_RINT_MATH = "rint"; + + public static final String OPERATOR_ROUND_MATH = "round"; + + public static final String OPERATOR_SIN_MATH = "sin"; + + public static final String OPERATOR_SQRT_MATH = "sqrt"; + + public static final String OPERATOR_SUM_MATH = "sum"; + + public static final String OPERATOR_TAN_MATH = "tan"; + + public static final String OPERATOR_TODEGREES_MATH = "toDegrees"; + + public static final String OPERATOR_TORADIANS_MATH = "toRadians"; + + /*---------------------------- 字符串运算符 ----------------------------*/ + + public static final String OPERATOR_CHARAT_STRING = "charAt"; + + public static final String OPERATOR_COMPARE_STRING = "compareTo"; + + public static final String OPERATOR_CTIC_STRING = "compareToIgnoreCase"; + + public static final String OPERATOR_CONCAT_STRING = "concat"; + + public static final String OPERATOR_ENDSWITH_STRING = "endsWith"; + + public static final String OPERATOR_EIC_STRING = "equalsIgnoreCase"; + + public static final String OPERATOR_EVAL_STRING = "eval"; + + public static final String OPERATOR_INDEXOF_STRING = "indexOf"; + + public static final String OPERATOR_LASTINDEXOF_STRING = "lastIndexOf"; + + public static final String OPERATOR_LENGTH_STRING = "length"; + + public static final String OPERATOR_REPLACE_STRING = "replace"; + + public static final String OPERATOR_STARTSWITH_STRING = "startsWith"; + + public static final String OPERATOR_SUB_STRING = "substring"; + + public static final String OPERATOR_TLC_STRING = "toLowerCase"; + + public static final String OPERATOR_TUC_STRING = "toUpperCase"; + + public static final String OPERATOR_TRIM_STRING = "trim"; + + public static final String OPERATOR_CONTAINS_STRING = "contains"; + + public static final String OPERATOR_UNCONTAINS_STRING = "notContains"; + + public static final String OPERATOR_EQUALS_STRING = "equals"; + + public static final String OPERATOR_UNEQUALS_STRING = "notEquals"; + + /*---------------------------- 符号 ----------------------------*/ + + public static final String OPERATOR_LEFT_BRACE = "{"; + + public static final String OPERATOR_RIGHT_BRACE = "}"; + + public static final String OPERATOR_VARIABLE_LEFT = "#"+OPERATOR_LEFT_BRACE; + + public static final String OPERATOR_VARIABLE_RIGHT = "}"; + + public static final String OPERATOR_LEFT_PARENTHESES = "("; + + public static final String OPERATOR_RIGHT_PARENTHESES = ")"; + + public static final String OPERATOR_LEFT_BRACKET = "["; + + public static final String OPERATOR_RIGHT_BRACKET = "]"; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EnumConst.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EnumConst.java new file mode 100644 index 0000000..d57c954 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/consts/EnumConst.java @@ -0,0 +1,47 @@ +package com.baoying.enginex.executor.engine.consts; + + +public class EnumConst { + + public static final String NODE_START = "开始"; + + public static final String NODE_POLICY = "政策规则"; + + public static final String NODE_CLASSIFY = "客户分群"; + + public static final String NODE_SCORECARD = "评分卡"; + + public static final String NODE_BLACK = "黑名单"; + + public static final String NODE_WHITE = "白名单"; + + public static final String NODE_SANDBOX = "沙盒比例"; + + public static final String NODE_CREDIT_LEVEL = "信用评级"; + + public static final String NODE_DECISION = "决策选项"; + + public static final String NODE_QUOTA_CALC = "额度计算"; + + public static final String NODE_REPORT = "报表分析"; + + public static final String NODE_CUSTOMIZE = "自定义按钮"; + + public static final String NODE_COMPLEXRULE = "复杂规则"; + + public static final String NODE_CHILD_ENGINE = "子引擎"; + + public static final String NODE_MODEL = "模型"; + + public static final String DECISION_TABLES = "决策表"; + + public static final String DECISION_TREE = "决策树"; + + public static final String NODE_RPC = "远程调用"; + + public static final String NODE_PARALLEL = "并行"; + + public static final String NODE_AGGREGATION = "聚合"; + + public static final String NODE_CHAMPION_CHALLENGE= "冠军挑战"; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/ApiController.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/ApiController.java new file mode 100644 index 0000000..fb2a01b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/ApiController.java @@ -0,0 +1,119 @@ +package com.baoying.enginex.executor.engine.controller; + +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.engine.model.DecisionReqModel; +import com.baoying.enginex.executor.engine.service.EngineApiService; +import com.baoying.enginex.executor.engine.thread.EngineCallable; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +@Controller +@RequestMapping("/QueryString") +public class ApiController { + + private static final Logger logger = LoggerFactory.getLogger(ApiController.class); + + @Autowired + public EngineApiService engineApiService; + + @RequestMapping(value = "/decision", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json;charset=UTF-8") + @ResponseBody + public String decision(String ts, String nonce, String act, String pid, String uid, String sign, String token, String paramJson, String fields) { + logger.info("请求参数--" + "ts:" + ts + ",nonce:" + nonce + ",act:" + act + ",pid:" + pid + ",uid:" + uid + ", sign:" + sign + ",token:" + token + ",paramJson" + paramJson); + Map map = new HashMap<>(); + map.put("ts", ts); + map.put("nonce", nonce); + map.put("act", act); + map.put("pid", pid); + map.put("uid", uid); + map.put("token", token); + JSONObject jsonObject = JSONObject.parseObject(paramJson); + if (jsonObject.getInteger("reqType") == 2) { + map.put("version", jsonObject.getInteger("version")); + map.put("subversion", jsonObject.getInteger("subversion")); + } + map.put("reqType", jsonObject.getInteger("reqType")); + map.put("engineId", jsonObject.getLong("engineId")); + map.put("organId", jsonObject.getLong("organId")); + map.put("sign", jsonObject.getString("sign")); + + Map requestFields = new HashMap<>(); + if(StringUtils.isNotBlank(fields)){ + requestFields = JSONObject.parseObject(fields, Map.class); + } + map.put("fields", requestFields); + String result = engineApiService.engineApi(map); + logger.info("uid:" + uid + " 响应参数--" + "result:" + result); + return result; + } + + @RequestMapping(value = "/batchDecision", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json;charset=UTF-8") + @ResponseBody + public String batchDecision(HttpServletResponse response, String ts, String nonce, String act, String sign, String token, int reqType, Long engineId, Long organId, String paramJson) { + List resultList = new ArrayList<>(); + Map resultMap = new HashMap<>(); + + List> list = new ArrayList<>(); + List reqModelList = JSONObject.parseArray(paramJson, DecisionReqModel.class); + for (DecisionReqModel reqModel : reqModelList) { + Map map = new HashMap<>(); + map.put("ts", ts); + map.put("nonce", nonce); + map.put("act", act); + map.put("token", token); + map.put("reqType", reqType); + map.put("engineId", engineId); + map.put("organId", organId); + map.put("sign", sign); + map.put("pid", reqModel.getPid()); + map.put("uid", reqModel.getUid()); + + Map requestFields = new HashMap<>(); + if(reqModel.getFields() != null){ + requestFields = JSONObject.parseObject(JSONObject.toJSONString(reqModel.getFields()), Map.class); + } + map.put("fields", requestFields); + list.add(map); + } + + List> futureList = new ArrayList<>(); + ExecutorService executorService = Executors.newFixedThreadPool(10); + for(Map paramMap : list){ + futureList.add(executorService.submit(new EngineCallable(paramMap))); + } + + // 获取线程执行结果 + for (final Future future : futureList) { + try { + final String str = future.get(5, TimeUnit.MINUTES); + resultList.add(JSONObject.parseObject(str)); + } catch (Exception e) { + boolean cancelResult = future.cancel(true); + logger.error("取消结果(" + cancelResult + ")" + e.getMessage(), e); + } + } + + String result = JSONObject.toJSONString(resultList); + resultMap.put("result", resultList); + logger.info(" 响应参数--" + "result:" + result); + + return result; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/DecisionController.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/DecisionController.java new file mode 100644 index 0000000..7a6d70a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/controller/DecisionController.java @@ -0,0 +1,70 @@ +package com.baoying.enginex.executor.engine.controller; + +import com.baoying.enginex.executor.common.session.SessionData; +import com.baoying.enginex.executor.common.session.SessionManager; +import com.baoying.enginex.executor.engine.model.request.DecisionApiBizData; +import com.baoying.enginex.executor.engine.model.request.DecisionApiRequest; +import com.baoying.enginex.executor.engine.service.EngineApiService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Controller +@RequestMapping("/runner") +public class DecisionController { + + private static final Logger logger = LoggerFactory.getLogger(DecisionController.class); + + @Autowired + public EngineApiService engineApiService; + + @RequestMapping(value = "/decision", method = RequestMethod.POST, produces = "application/json;charset=UTF-8") + @ResponseBody + public String decision(@RequestBody DecisionApiRequest apiRequest) { + long start = System.currentTimeMillis(); + DecisionApiBizData bizData = apiRequest.getBiz_data(); + Map map = new HashMap<>(); + map.put("pid", bizData.getBusinessId()); + map.put("uid", ""); + map.put("reqType", 1); + map.put("engineId", bizData.getEngineId()); + map.put("organId", bizData.getOrganId()); + + SessionData sessionData = new SessionData(); + sessionData.setOrganId(bizData.getOrganId()); + sessionData.setEngineId(bizData.getEngineId()); + sessionData.setReqType(1); + SessionManager.setSession(sessionData); + + if(bizData.getFields() != null){ + map.put("fields", bizData.getFields()); + } else { + map.put("fields", new HashMap<>()); + } + String result = engineApiService.engineApi(map); + long end = System.currentTimeMillis(); + logger.info("============ 接口调用耗时:{}ms ============", (end -start)); + return result; + } + + @RequestMapping(value = "/batchExecute", method = RequestMethod.POST, produces = "application/json;charset=UTF-8") + @ResponseBody + public List batchExecute(@RequestBody List requestList){ + List list = new ArrayList<>(); + for (DecisionApiRequest apiRequest : requestList) { + String decision = this.decision(apiRequest); + list.add(decision); + } + return list; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/CallBackTypeEnum.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/CallBackTypeEnum.java new file mode 100644 index 0000000..8478cc4 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/CallBackTypeEnum.java @@ -0,0 +1,23 @@ +package com.baoying.enginex.executor.engine.enums; + +public enum CallBackTypeEnum { + + SYNC(1,"同步"), + ASYNC(2,"异步"); + + private int code; + private String message; + + CallBackTypeEnum(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/NodeTypeEnum.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/NodeTypeEnum.java new file mode 100644 index 0000000..545e33b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/enums/NodeTypeEnum.java @@ -0,0 +1,125 @@ +package com.baoying.enginex.executor.engine.enums; + +import com.baoying.enginex.executor.engine.consts.EnumConst; + +public enum NodeTypeEnum { + /** + * 开始节点 + */ + START(1, EnumConst.NODE_START), + /** + * 规则节点 + */ + POLICY(2,EnumConst.NODE_POLICY), + /** + * 分组节点 + */ + CLASSIFY(3, EnumConst.NODE_CLASSIFY), + /** + * 评分卡节点 + */ + SCORECARD(4,EnumConst.NODE_SCORECARD), + /** + * 黑名单节点 + */ + BLACKLIST(5,EnumConst.NODE_BLACK), + /** + * 白名单节点 + */ + WHITELIST(6,EnumConst.NODE_WHITE), + /** + * 沙盒节点 + */ + SANDBOX(7,EnumConst.NODE_SANDBOX), + /** + * 信用评级节点 + */ + CREDITLEVEL(8,EnumConst.NODE_CREDIT_LEVEL), + /** + * 决策选项节点 + */ + DECISION(9,EnumConst.NODE_DECISION), + /** + * 额度计算节点 + */ + QUOTACALC(10,EnumConst.NODE_QUOTA_CALC), + /** + * 报表分析节点 + */ + REPORT(11,EnumConst.NODE_REPORT), + /** + * 自定义节点 + */ + CUSTOMIZE(12,EnumConst.NODE_CUSTOMIZE), + /** + * 复杂规则 + */ + NODE_COMPLEXRULE(13,EnumConst.NODE_COMPLEXRULE), + /** + * 子引擎 + */ + CHILD_ENGINE(14,EnumConst.NODE_CHILD_ENGINE), + /** + * 模型 + */ + MODEL(15,EnumConst.NODE_MODEL), + /** + * 决策表 + */ + DECISION_TABLES(16,EnumConst.DECISION_TABLES), + /** + * 决策树 + */ + DECISION_TREE(17,EnumConst.DECISION_TREE), + /** + * 远程调用 + */ + RPC(18, EnumConst.NODE_RPC), + /** + * 并行节点 + */ + PARALLEL(19, EnumConst.NODE_PARALLEL), + /** + * 聚合节点 + */ + AGGREGATION(20, EnumConst.NODE_AGGREGATION), + /** + * 冠军挑战节点 + */ + CHAMPION_CHALLENGE(21, EnumConst.NODE_CHAMPION_CHALLENGE); + + private int value; + + private String type; + + private NodeTypeEnum(int value, String type) + { + this.value = value; + this.type = type; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public static NodeTypeEnum adapad(int value) { + for (NodeTypeEnum nodeTypeEnum : NodeTypeEnum.values()) { + if (nodeTypeEnum.getValue() == value) { + return nodeTypeEnum; + } + } + return null; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineMapper.java new file mode 100644 index 0000000..08717b6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineMapper.java @@ -0,0 +1,9 @@ +package com.baoying.enginex.executor.engine.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.engine.model.Engine; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface EngineMapper extends BaseMapper { +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.java new file mode 100644 index 0000000..2a3012e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.java @@ -0,0 +1,18 @@ +package com.baoying.enginex.executor.engine.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.engine.model.EngineNode; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface EngineNodeMapper extends BaseMapper { + + /** + * 根据版本id获取版本下的所有节点 + * @param engineVersionId + * @return + */ + List getEngineNodeListByVersionId(@Param("engineVersionId") Long engineVersionId); + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.xml new file mode 100644 index 0000000..ce6e7f1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineNodeMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + node_id, parent_id, version_id, node_name, node_code, node_order, node_type, node_x, node_y,node_json,node_script,next_nodes,params,snapshot + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.java new file mode 100644 index 0000000..d75f9c6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.java @@ -0,0 +1,72 @@ + + +package com.baoying.enginex.executor.engine.mapper; + + +import com.baoying.enginex.executor.engine.model.EngineResultSet; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + + +public interface EngineResultSetMapper { + /** + * + * 增加结果集 + * @param resultSet 结果集对象 + * @return 返回结果 + * @see + */ + int insertResultSet(EngineResultSet resultSet); + /** + * + * 查询结果集列表 + * @param resultSet 查询对象 + * @return 返回结果集 + * @see + */ + List getResultSetByList(EngineResultSet resultSet); + + /** + * 根据引擎编号和时间段获取结果集数据 + * @param map + * @return + */ + List getEngineResultSetBySegment(Map map); + + /** + * + * 通过主键编号得到 + * @param resultSet 对象 + * @return 返回对象 + * @see + */ + EngineResultSet getResultSetById(EngineResultSet resultSet); + + List getResultSetDetailsById(long resultSetId); + + /** + * 查找引擎id的批量测试结果 + * yuanlinfeng + * @param resultSetId + * @return + */ + List getBatchTestResultSetByEngineId(Map paramMap); + + /** + * 查找引擎批量测试批次号的所有测试结果 + * yuanlinfeng + * @param resultSetId + * @return + */ + List getBatchTestResultSetByBatchNo(Map paramMap); + + /** + * 更新结果出参 + * @param resultSet + */ + void updateResultOutput(EngineResultSet resultSet); + void updateResultById(@Param("resultId") Integer resultId, @Param("rowKeyStr") String rowKeyStr); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.xml new file mode 100644 index 0000000..758e1a4 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineResultSetMapper.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO + t_resultset + + + uid, + + + pid, + + + input, + + + output, + + + result, + + + engine_id, + + + uuid, + + + engine_version, + + + engine_name, + + + engine_code, + + + type, + + + sub_version, + + + scorecardscore, + + + batch_no, + + + datilResult, + + + values + + + #{uid}, + + + #{pid}, + + + #{input}, + + + #{output}, + + + #{result}, + + + #{engine_id}, + + + #{uuid}, + + + #{engine_version}, + + + #{engine_name}, + + + #{engine_code}, + + + #{type}, + + + #{subVersion}, + + + #{scorecardscore}, + + + #{batchNo}, + + + #{datilResult}, + + + + + + + + + + + + + + + update t_resultset t set t.`output` = #{output} where t.`id` = #{id} + + + + UPDATE t_resultset + set hbase_row_key = #{rowKeyStr} + where id = #{resultId} + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.java new file mode 100644 index 0000000..b8acde4 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.java @@ -0,0 +1,27 @@ +package com.baoying.enginex.executor.engine.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.engine.model.EngineVersion; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Map; + +@Mapper +public interface EngineVersionMapper extends BaseMapper { + + /** + * 获取引擎正在运行中的版本 + * @param engineId + * @return + */ + EngineVersion getRunningVersion(@Param("engineId") Long engineId); + + /** + * 获取指定版本信息 + * @param paramMap + * @return + */ + EngineVersion getTargetEngineVersion(Map paramMap); + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.xml new file mode 100644 index 0000000..b207c87 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/mapper/EngineVersionMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + version_id, engine_id, version, boot_state, status, layout, user_id, create_time, + latest_user, latest_time, sub_version + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ComplexRule.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ComplexRule.java new file mode 100644 index 0000000..89791a2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ComplexRule.java @@ -0,0 +1,42 @@ +package com.baoying.enginex.executor.engine.model; + +import java.util.Map; + +public class ComplexRule { + + private Map result; + + private String out; + + private Map returnResult; + + + + public Map getReturnResult() { + return returnResult; + } + + public void setReturnResult(Map returnResult) { + this.returnResult = returnResult; + } + + public Map getResult() { + return result; + } + + public void setResult(Map result) { + this.result = result; + } + + public String getOut() { + return out; + } + + public void setOut(String out) { + this.out = out; + } + + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionOptions.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionOptions.java new file mode 100644 index 0000000..9a36d3b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionOptions.java @@ -0,0 +1,62 @@ + +package com.baoying.enginex.executor.engine.model; + +import java.util.Map; + +public class DecisionOptions { + private String code;//决策选项code + private String name;//决策选项名称 + private Map inFields;//输入字段 + private Map outFields;//输出字段 + private Integer fType;//输出字段类型 + private Long nodId;//节点id + private String fieldScope; + + + + public String getFieldScope() { + return fieldScope; + } + public void setFieldScope(String fieldScope) { + this.fieldScope = fieldScope; + } + public Long getNodId() { + return nodId; + } + public void setNodId(Long nodId) { + this.nodId = nodId; + } + public Integer getfType() { + return fType; + } + public void setfType(Integer fType) { + this.fType = fType; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Map getInFields() { + return inFields; + } + public void setInFields(Map inFields) { + this.inFields = inFields; + } + public Map getOutFields() { + return outFields; + } + public void setOutFields(Map outFields) { + this.outFields = outFields; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionReqModel.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionReqModel.java new file mode 100644 index 0000000..6666277 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/DecisionReqModel.java @@ -0,0 +1,16 @@ +package com.baoying.enginex.executor.engine.model; + +import com.alibaba.fastjson.JSONObject; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class DecisionReqModel implements Serializable { + private static final long serialVersionUID = 1743177499998353115L; + + private String pid; + private String uid; + private JSONObject fields; + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Engine.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Engine.java new file mode 100644 index 0000000..a3d0cd8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Engine.java @@ -0,0 +1,90 @@ +package com.baoying.enginex.executor.engine.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +@TableName("t_engine") +public class Engine implements Serializable { + private static final long serialVersionUID = -6611916471057697499L; + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 引擎编号 + */ + private String code; + + /** + * 引擎名称 + */ + private String name; + + /** + * 引擎描述 + */ + private String description; + + /** + * 引擎状态 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createDatetime; + + /** + * 修改时间 + */ + private Date updateDatetime; + + /** + * 创建人 + */ + private Long creator; + + /** + * 修改人 + */ + private Long userId; + + /** + * 公司编号 + */ + private Long organId; + + /** + * 调用方式 1:同步,2:异步 + */ + private Integer callbackType; + + /** + * 回调地址 + */ + private String callbackUrl; + + /** + * 异常回调地址 + */ + private String exceptionCallbackUrl; + + /** + * 引擎版本集合 + */ + @TableField(exist = false) + private List engineVersionList; + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineNode.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineNode.java new file mode 100644 index 0000000..ae8026a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineNode.java @@ -0,0 +1,85 @@ +package com.baoying.enginex.executor.engine.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +@Data +@TableName("t_engine_node") +public class EngineNode implements Serializable{ + private static final long serialVersionUID = -1867357850853531748L; + + /** + * 节点编号 + */ + @TableId(type = IdType.AUTO) + private Long nodeId; + + /** + * 版本编号 + */ + private Long versionId; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 节点code + */ + private String nodeCode; + + /** + * 节点顺序 + */ + private Integer nodeOrder; + + /** + * 节点类型 + */ + private Integer nodeType; + + /** + * 节点json + */ + private String nodeJson; + + /** + * 节点X轴 + */ + private double nodeX; + + /** + * 节点Y轴 + */ + private double nodeY; + + /** + * 节点脚本 + */ + private String nodeScript; + + /** + * 下一节点 + */ + private String nextNodes; + + /** + * 节点类型,图标等信息 + */ + private String params; + + /** + * 父节点编号 + */ + private String parentId; + + /** + * 节点配置快照 + */ + private String snapshot; +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineResultSet.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineResultSet.java new file mode 100644 index 0000000..cd21487 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineResultSet.java @@ -0,0 +1,252 @@ + + +package com.baoying.enginex.executor.engine.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.Date; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Accessors +public class EngineResultSet { + private Integer id; + private String uid; + private String pid; + private String input; + private String output; + private Date create_datetime; + + private String result; + + private Long engine_id; + + private Integer engine_version; + + private String uuid; + + private String engine_name; + + private String engine_code; + + private Date startDate; + + private Date endDate; + + private Integer type; + + private Integer subVersion; + + private String scorecardscore; + + private String datilResult; + /** + *决策表结果 + */ + private String decisionTablesResult; + + /** + *决策树结果 + */ + private String decisionTreeResult; + + /** + * 批量测试批次号 + */ + private String batchNo; + + /** + * 批量测试每批测试开始时间 + */ + private Date startTime; + + /** + * 批量测试每批次花费时间 + */ + private String costTime; + + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getPid() { + return pid; + } + + public void setPid(String pid) { + this.pid = pid; + } + + public String getScorecardscore() { + return scorecardscore; + } + + public void setScorecardscore(String scorecardscore) { + this.scorecardscore = scorecardscore; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getSubVersion() { + return subVersion; + } + + public void setSubVersion(Integer subVersion) { + this.subVersion = subVersion; + } + + private List resultSetList; + + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public String getEngine_name() { + return engine_name; + } + + public void setEngine_name(String engine_name) { + this.engine_name = engine_name; + } + + public String getEngine_code() { + return engine_code; + } + + public void setEngine_code(String engine_code) { + this.engine_code = engine_code; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public Date getCreate_datetime() { + return create_datetime; + } + + public void setCreate_datetime(Date create_datetime) { + this.create_datetime = create_datetime; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public Long getEngine_id() { + return engine_id; + } + + public void setEngine_id(Long engine_id) { + this.engine_id = engine_id; + } + + public Integer getEngine_version() { + return engine_version; + } + + public void setEngine_version(Integer engine_version) { + this.engine_version = engine_version; + } + + public List getResultSetList() { + return resultSetList; + } + + public void setResultSetList(List resultSetList) { + this.resultSetList = resultSetList; + } + + public String getBatchNo() { + return batchNo; + } + + public void setBatchNo(String batchNo) { + this.batchNo = batchNo; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public String getCostTime() { + return costTime; + } + + public void setCostTime(String costTime) { + this.costTime = costTime; + } + + public String getDatilResult() { + return datilResult; + } + + public void setDatilResult(String datilResult) { + this.datilResult = datilResult; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineRule.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineRule.java new file mode 100644 index 0000000..9c840fe --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineRule.java @@ -0,0 +1,74 @@ + +package com.baoying.enginex.executor.engine.model; + +import java.util.Map; + +public class EngineRule { + + private String refused; + + private String code ; + + private String policyName; + + private String desc; + + private String Strtus; + + + private Mapfields; + + + public String getStrtus() { + return Strtus; + } + + public void setStrtus(String strtus) { + Strtus = strtus; + } + + public String getRefused() { + return refused; + } + + public void setRefused(String refused) { + this.refused = refused; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getPolicyName() { + return policyName; + } + + public void setPolicyName(String policyName) { + this.policyName = policyName; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Map getFields() { + return fields; + } + + public void setFields(Map fields) { + this.fields = fields; + } + + + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineVersion.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineVersion.java new file mode 100644 index 0000000..8820695 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/EngineVersion.java @@ -0,0 +1,79 @@ +package com.baoying.enginex.executor.engine.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +@TableName("t_engine_version") +public class EngineVersion implements Serializable { + private static final long serialVersionUID = 2923432053414979455L; + + /** + * 版本编号 + */ + @TableId(type = IdType.AUTO) + private Long versionId; + + /** + * 引擎编号 + */ + private Long engineId; + + /** + * 版本号 + */ + private Integer version; + + /** + * 子版本 + */ + private Integer subVersion; + + /** + * 部署状态 + */ + private Integer bootState; + + /** + * 版本状态 + */ + private Integer status; + + /** + * 布局方式 + */ + private Integer layout; + + /** + * 创建者 + */ + private Long userId; + + /** + * 创建时间 + */ + private String createTime; + + /** + * 修改人 + */ + private Long latestUser; + + /** + * 最后修改时间 + */ + private String latestTime; + + /** + * 节点集合 + * */ + @TableField(exist = false) + private List engineNodeList; + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/IndexEngineReportVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/IndexEngineReportVo.java new file mode 100644 index 0000000..db1cad3 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/IndexEngineReportVo.java @@ -0,0 +1,53 @@ +package com.baoying.enginex.executor.engine.model; + +import java.io.Serializable; + +public class IndexEngineReportVo implements Serializable { + + private static final long serialVersionUID = -1274492726714567316L; + private String dayTime; + private String monthTime; + private Integer engineId; + private String engineName; + private Integer useNum; + + public String getDayTime() { + return dayTime; + } + + public void setDayTime(String dayTime) { + this.dayTime = dayTime; + } + + public String getMonthTime() { + return monthTime; + } + + public void setMonthTime(String monthTime) { + this.monthTime = monthTime; + } + + public Integer getEngineId() { + return engineId; + } + + public void setEngineId(Integer engineId) { + this.engineId = engineId; + } + + public String getEngineName() { + return engineName; + } + + public void setEngineName(String engineName) { + this.engineName = engineName; + } + + public Integer getUseNum() { + return useNum; + } + + public void setUseNum(Integer useNum) { + this.useNum = useNum; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/InputParam.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/InputParam.java new file mode 100644 index 0000000..eabdde6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/InputParam.java @@ -0,0 +1,36 @@ +package com.baoying.enginex.executor.engine.model; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class InputParam { + private Map inputParam; + private List result; + // 数组中 符合条件的对象属性 + private Map> outputParam; + + public Map getInputParam() { + return inputParam; + } + + public void setInputParam(Map inputParam) { + this.inputParam = inputParam; + } + + public List getResult() { + return result; + } + + public void setResult(List result) { + this.result = result; + } + + public Map> getOutputParam() { + return outputParam; + } + + public void setOutputParam(Map> outputParam) { + this.outputParam = outputParam; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/NodeKnowledge.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/NodeKnowledge.java new file mode 100644 index 0000000..3ea8595 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/NodeKnowledge.java @@ -0,0 +1,63 @@ +package com.baoying.enginex.executor.engine.model; + +import java.io.Serializable; + +/** + * 节点与知识库映射关系模型 + * @author sunyk + * + */ +public class NodeKnowledge implements Serializable { + private static final long serialVersionUID = -55965399064577379L; + /** + * 主键编号 + */ + private Long relId; + + /** + * 节点编号 + */ + private Long nodeId; + + /** + * 知识库信息编号 + */ + private Long knowledgeId; + + /** + * 知识库类型1规则2评分卡 + */ + private Integer knowledgeType; + + public Long getRelId() { + return relId; + } + + public void setRelId(Long relId) { + this.relId = relId; + } + + public Long getNodeId() { + return nodeId; + } + + public void setNodeId(Long nodeId) { + this.nodeId = nodeId; + } + + public Long getKnowledgeId() { + return knowledgeId; + } + + public void setKnowledgeId(Long knowledgeId) { + this.knowledgeId = knowledgeId; + } + + public Integer getKnowledgeType() { + return knowledgeType; + } + + public void setKnowledgeType(Integer knowledgeType) { + this.knowledgeType = knowledgeType; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Result.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Result.java new file mode 100644 index 0000000..21f19a5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Result.java @@ -0,0 +1,63 @@ + + +package com.baoying.enginex.executor.engine.model; + +import java.util.List; +import java.util.Map; + +public class Result { + private String resultType;//规则1代表加减法,2拒绝规则 + private Integer id;//规则编号 + private String code;//规则code + private String name; + private String value; + private Map map;//评分 + private List list; + + + public Map getMap() { + return map; + } + public void setMap(Map map) { + this.map = map; + } + public String getValue() { + return value; + } + public void setValue(String value) { + this.value = value; + } + public String getResultType() { + return resultType; + } + public void setResultType(String resultType) { + this.resultType = resultType; + } + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public List getList() { + return list; + } + public void setList(List list) { + this.list = list; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ResultSetList.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ResultSetList.java new file mode 100644 index 0000000..af50abb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ResultSetList.java @@ -0,0 +1,76 @@ + + +package com.baoying.enginex.executor.engine.model; + +import java.util.Date; + +public class ResultSetList { + private Long id; + private Integer type;//1.黑名单。2.白名单。3.拒绝规则。4.加减分规则 + private String code; + private String name; + private String description; + private String resultsetId; + private String expression; + private Date startDate; + private Date endDate; + + +public Date getStartDate() { + return startDate; +} +public void setStartDate(Date startDate) { + this.startDate = startDate; +} +public Date getEndDate() { + return endDate; +} +public void setEndDate(Date endDate) { + this.endDate = endDate; +} +public Long getId() { + return id; +} +public void setId(Long id) { + this.id = id; +} +public Integer getType() { + return type; +} +public void setType(Integer type) { + this.type = type; +} +public String getCode() { + return code; +} +public void setCode(String code) { + this.code = code; +} +public String getName() { + return name; +} +public void setName(String name) { + this.name = name; +} +public String getDescription() { + return description; +} +public void setDescription(String description) { + this.description = description; +} +public String getResultsetId() { + return resultsetId; +} +public void setResultsetId(String resultsetId) { + this.resultsetId = resultsetId; +} +public String getExpression() { + return expression; +} +public void setExpression(String expression) { + this.expression = expression; +} + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Sandbox.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Sandbox.java new file mode 100644 index 0000000..cc06839 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/Sandbox.java @@ -0,0 +1,53 @@ + + +package com.baoying.enginex.executor.engine.model; + +public class Sandbox { + private Integer sandbox;//沙盒组编号 + private Integer proportion;//沙盒占用比例 + private String nextNode;//下个节点序号 + private Integer sum;//分母 + private Integer startNumber;//起始值 + private Integer endNumberl;//终止值 + + + + public Integer getSum() { + return sum; + } + public void setSum(Integer sum) { + this.sum = sum; + } + public Integer getStartNumber() { + return startNumber; + } + public void setStartNumber(Integer startNumber) { + this.startNumber = startNumber; + } + public Integer getEndNumberl() { + return endNumberl; + } + public void setEndNumberl(Integer endNumberl) { + this.endNumberl = endNumberl; + } + public Integer getSandbox() { + return sandbox; + } + public void setSandbox(Integer sandbox) { + this.sandbox = sandbox; + } + public Integer getProportion() { + return proportion; + } + public void setProportion(Integer proportion) { + this.proportion = proportion; + } + public String getNextNode() { + return nextNode; + } + public void setNextNode(String nextNode) { + this.nextNode = nextNode; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ScoreCardEngine.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ScoreCardEngine.java new file mode 100644 index 0000000..1890952 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/ScoreCardEngine.java @@ -0,0 +1,49 @@ + + +package com.baoying.enginex.executor.engine.model; + +import java.util.Map; + +public class ScoreCardEngine { + private String code;//评分卡编号 + private String name;//评分卡名称 + private String scoreCardName;//评分卡名称 + private Map inFields;//评分可用到的字段 + private Map outFields;//评分卡 + + + public String getScoreCardName() { + return scoreCardName; + } + public void setScoreCardName(String scoreCardName) { + this.scoreCardName = scoreCardName; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Map getInFields() { + return inFields; + } + public void setInFields(Map inFields) { + this.inFields = inFields; + } + public Map getOutFields() { + return outFields; + } + public void setOutFields(Map outFields) { + this.outFields = outFields; + } + + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/TestRule.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/TestRule.java new file mode 100644 index 0000000..c1478ba --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/TestRule.java @@ -0,0 +1,37 @@ + + +package com.baoying.enginex.executor.engine.model; + +public class TestRule { + private String id; + + private String ruleid; + + private String param; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getRuleid() { + return ruleid; + } + + public void setRuleid(String ruleid) { + this.ruleid = ruleid; + } + + public String getParam() { + return param; + } + + public void setParam(String param) { + this.param = param; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiBizData.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiBizData.java new file mode 100644 index 0000000..28a0abb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiBizData.java @@ -0,0 +1,18 @@ +package com.baoying.enginex.executor.engine.model.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Map; + +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = false) +public class DecisionApiBizData { + + private String businessId; // 业务id + private Long organId; // 组织id + private Long engineId; // 引擎id + private Map fields; // 指标字段键值对 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiRequest.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiRequest.java new file mode 100644 index 0000000..df0e1f8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/model/request/DecisionApiRequest.java @@ -0,0 +1,17 @@ +package com.baoying.enginex.executor.engine.model.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = false) +public class DecisionApiRequest { + + private String tp_code; // 调用方编码 + private String timestamp; // 精确到毫秒 + private String sign; // 签名 + private String biz_enc; // biz_data加密方式(0不加密,1加密) + private DecisionApiBizData biz_data; // 请求的业务数据,json格式的字符串 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineApiService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineApiService.java new file mode 100644 index 0000000..80b2034 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineApiService.java @@ -0,0 +1,8 @@ +package com.baoying.enginex.executor.engine.service; + +import java.util.Map; + +public interface EngineApiService { + + String engineApi(Map paramJson); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineNodeService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineNodeService.java new file mode 100644 index 0000000..0dfd038 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineNodeService.java @@ -0,0 +1,16 @@ +package com.baoying.enginex.executor.engine.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.engine.model.EngineNode; + +import java.util.List; + +public interface EngineNodeService extends IService { + + /** + * 根据版本id获取版本下的所有节点 + * @param versionId + * @return + */ + List getEngineNodeListByVersionId(Long versionId); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineService.java new file mode 100644 index 0000000..163aecf --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineService.java @@ -0,0 +1,14 @@ +package com.baoying.enginex.executor.engine.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.engine.model.Engine; + +public interface EngineService extends IService { + + /** + * 根据id查询引擎 + * @param id + * @return + */ + Engine getEngineById(Long id); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineVersionService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineVersionService.java new file mode 100644 index 0000000..3a16c19 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/EngineVersionService.java @@ -0,0 +1,17 @@ +package com.baoying.enginex.executor.engine.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.engine.model.EngineVersion; + +public interface EngineVersionService extends IService { + + EngineVersion getEngineVersionById(Long versionId); + + /** + * 获取引擎正在运行中的版本 + * @param engineId + * @return + */ + EngineVersion getRunningVersion(Long engineId); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineApiServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineApiServiceImpl.java new file mode 100644 index 0000000..215cd62 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineApiServiceImpl.java @@ -0,0 +1,400 @@ +package com.baoying.enginex.executor.engine.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.engine.enums.NodeTypeEnum; +import com.baoying.enginex.executor.engine.mapper.EngineResultSetMapper; +import com.baoying.enginex.executor.engine.model.Engine; +import com.baoying.enginex.executor.engine.model.EngineNode; +import com.baoying.enginex.executor.engine.model.EngineResultSet; +import com.baoying.enginex.executor.engine.model.EngineVersion; +import com.baoying.enginex.executor.engine.service.EngineApiService; +import com.baoying.enginex.executor.engine.service.EngineNodeService; +import com.baoying.enginex.executor.engine.service.EngineService; +import com.baoying.enginex.executor.engine.service.EngineVersionService; +import com.baoying.enginex.executor.node.service.impl.*; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; +import org.springframework.web.client.AsyncRestTemplate; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class EngineApiServiceImpl implements EngineApiService { + + private static final Logger logger = LoggerFactory.getLogger(EngineApiServiceImpl.class); + + @Autowired + public EngineService engineService; + + @Resource + public EngineVersionService engineVersionService; + + @Resource + public EngineNodeService engineNodeService; + + @Resource + public EngineResultSetMapper resultSetMapper; + + @Autowired + private DecisionOptionsNode decisionOptionsNode; + + @Autowired + private RuleSetNode ruleSetNode; + + @Autowired + private GroupNode groupNode; + + @Autowired + private SandboxProportionNode sandboxProportionNode; + + @Autowired + private AsyncRestTemplate asyncRestTemplate; + + @Override + public String engineApi(Map paramJson) { + logger.info("请求参数,paramJson: {}", JSONObject.toJSONString(paramJson)); + JSONObject jsonObject = new JSONObject(); + JSONArray resultJson = new JSONArray(); + Map> featureMaps = new ConcurrentHashMap<>(); + //时间差小于等于30分钟并且鉴权成功 + if (true){ + Long organId = Long.valueOf(paramJson.get("organId").toString()); + Long engineId = Long.valueOf(paramJson.get("engineId").toString()); + //获取引擎信息 + Engine engine = engineService.getEngineById(engineId); + if(engine != null && !engine.getOrganId().equals(organId)){ + // todo 校验引擎是否为该组织所属 + } + //获取引擎正在运行中的版本 + EngineVersion engineVersion = engineVersionService.getRunningVersion(engineId); + if (engineVersion != null) { + //返回引擎下的所有节点集合 + List engineNodeList = engineNodeService.getEngineNodeListByVersionId(engineVersion.getVersionId()); + Map engineNodeMap = getEngineNodeListByMap(engineNodeList); + try { + //变量池 + Map inputParam = new ConcurrentHashMap<>(); + inputParam.putAll(JSONObject.parseObject(JSONObject.toJSONString(paramJson.get("fields")), Map.class)); + EngineNode engineNode = engineNodeMap.get("ND_START"); + if (null != engineNode && null != engineNode.getNextNodes()) { + //返回输出结果 + Map outMap = new ConcurrentHashMap<>(); + // 记录执行前全量指标 + featureMaps.put("before",inputParam); + //节点执行方法 + recursionEngineNode(inputParam, engineNodeMap.get(engineNode.getNextNodes()), engineNodeMap, outMap); + jsonObject.put("status", "0x0000"); + jsonObject.put("msg", "执行成功"); + if (outMap.containsKey("centens") && outMap.get("centens").equals("true")) { + jsonObject.put("status", "0x0006"); + jsonObject.put("msg", "获取数据失败"); + jsonObject.put("data", ""); + return jsonObject.toString(); + } + //记录执行后的全量指标 + featureMaps.put("after",inputParam); + paramJson.put("versionId",engineNode.getVersionId()); + String json = JSONObject.toJSONString(inputParam); + jsonObject.put("input", JSONObject.parseObject(json)); + + EngineResultSet resultSet = new EngineResultSet(); + resultSet.setEngine_code(engine.getCode()); + resultSet.setInput(json); + resultSet.setEngine_id(engine.getId()); + resultSet.setEngine_name(engine.getName()); + resultSet.setType(2); + resultSet.setSubVersion(engineVersion.getSubVersion()); + resultSet.setUid(String.valueOf(paramJson.get("uid"))); + resultSet.setPid(String.valueOf(paramJson.get("pid"))); + + //决策表最终结果 + if (outMap.containsKey("decisionTables")){ + jsonObject.put("decisionTablesResult", outMap.get("decisionTables").toString()); + resultSet.setDecisionTablesResult(outMap.get("decisionTables").toString()); + } + //决策树最终结果 + if (outMap.containsKey("decisionTree")){ + jsonObject.put("decisionTreeResult", outMap.get("decisionTree").toString()); + resultSet.setDecisionTreeResult(outMap.get("decisionTree").toString()); + } + // 节点终止输出 + if (outMap.containsKey("result")) { + resultSet.setResult(outMap.get("result").toString()); + //决策选项最终结果 + jsonObject.put("result", outMap.get("result").toString()); + } + + if (outMap.containsKey("blackJson")) { + resultJson.add(new JSONObject().parse(outMap.get("blackJson").toString())); + } + + if (outMap.containsKey("whiteJson")) { + resultJson.add(new JSONObject().parse(outMap.get("whiteJson").toString())); + } + + if (outMap.containsKey("ruleJson")) { + //规则集节点输出 + JSONObject ruleJson = new JSONObject(); + ruleJson.put("resultType", 2); + ruleJson.put("resultJson", outMap.get("ruleJson")); + resultJson.add(ruleJson); + } + + if (outMap.containsKey("scoreJson")) { + //评分卡输出 + JSONObject ruleJson = new JSONObject(); + ruleJson.put("resultType", 4); + ruleJson.put("resultJson", outMap.get("scoreJson")); + resultJson.add(ruleJson); + } + + if (outMap.containsKey("decisionJson")) { + //决策选项输出 + JSONObject ruleJson = new JSONObject(); + ruleJson.put("resultType", 9); + ruleJson.put("resultJson", outMap.get("decisionJson")); + resultJson.add(ruleJson); + } + + if (outMap.containsKey("childEngineJson")) { + //子引擎节点输出 + JSONObject ruleJson = new JSONObject(); + ruleJson.put("resultType", 14); + ruleJson.put("resultJson", outMap.get("childEngineJson")); + resultJson.add(ruleJson); + } + + if (outMap.containsKey("modelJson")) { + //模型节点输出 + JSONObject ruleJson = new JSONObject(); + ruleJson.put("resultType", 15); + ruleJson.put("resultJson", outMap.get("modelJson")); + resultJson.add(ruleJson); + } + + if (outMap.containsKey("decisionTablesJson")) { + //决策表输出 + JSONObject ruleJson = new JSONObject(); + ruleJson.put("resultType", 16); + ruleJson.put("resultJson", outMap.get("decisionTablesJson")); + resultJson.add(ruleJson); + } + + if (outMap.containsKey("decisionTreeJson")) { + //决策树输出 + JSONObject ruleJson = new JSONObject(); + ruleJson.put("resultType", 17); + ruleJson.put("resultJson", outMap.get("decisionTreeJson")); + resultJson.add(ruleJson); + } + + jsonObject.put("data", resultJson); + String result = JSONObject.toJSONString(jsonObject); + + JSONObject tmpJsonObject = JSONObject.parseObject(result); + tmpJsonObject.remove("input"); + resultSet.setOutput(JSONObject.toJSONString(tmpJsonObject)); + resultSetMapper.insertResultSet(resultSet); + Integer resultId = resultSet.getId(); + // 正常返回结果回调 + decisionCallback(engine.getCallbackUrl(), paramJson, result); + } + } catch (Exception e) { + logger.error("接口请求异常", e); + jsonObject.put("status", "0x0005"); + jsonObject.put("msg", "执行失败"); + jsonObject.put("data", ""); + // 异常回调 + decisionCallback(engine.getCallbackUrl(), paramJson, "执行失败"); + } + } else { + jsonObject.put("status", "0x0004"); + jsonObject.put("msg", "请求引擎不存在或尚未部署运行"); + jsonObject.put("data", ""); + } + } else { + jsonObject.put("status", "0x0001"); + jsonObject.put("msg", "鉴权失败,非法调用"); + jsonObject.put("data", ""); + } + + return jsonObject.toString(); + } + + + /** + * 递归执行节点 + * @param inputParam + * @param engineNode + * @param engineNodeMap + * @param outMap + */ + private EngineNode recursionEngineNode(Map inputParam, EngineNode engineNode, Map engineNodeMap, Map outMap) { + logger.info("请求参数--" + "inputParam:" + JSONObject.toJSONString(inputParam)); + + EngineNode resultNode = null; // 结束时返回节点: 串行流程返回null、并行流程返回聚合节点 + + if(engineNode == null){ + return null; + } + + // 获取节点所需的指标 + getNodeField(engineNode, inputParam); + // 执行节点逻辑 + runNode(engineNode, inputParam, outMap); + + //用于存储执行过的节点 + List executedNodeList = new ArrayList<>(); + if(outMap.containsKey("executedNodes")){ + executedNodeList =(List) outMap.get("executedNodes"); + } + executedNodeList.add(engineNode.getNodeId()+""); + // 更新执行过节点数组 + outMap.put("executedNodes",executedNodeList); + // 递归执行下一个节点 + if (StringUtils.isNotBlank(engineNode.getNextNodes())) { + // 串行节点执行 + EngineNode nextEngineNode = engineNodeMap.get(engineNode.getNextNodes()); + //如果输出的map里面有nextNode,则说明有分组,需要走分组下面的节点 + if (outMap.containsKey("nextNode")) { + nextEngineNode = engineNodeMap.get(outMap.get("nextNode")); + outMap.remove("nextNode"); + } + + if(nextEngineNode!=null&&nextEngineNode.getNodeType() == NodeTypeEnum.AGGREGATION.getValue()){ + // 并行节点后面的分支为多线程执行,执行到聚合节点则结束 + resultNode = nextEngineNode; + } else { + resultNode = recursionEngineNode(inputParam, nextEngineNode, engineNodeMap, outMap); + } + } + + return resultNode; + } + + + + /** + * 获取节点所需的指标 + * @param engineNode + * @param inputParam + */ + private void getNodeField(EngineNode engineNode, Map inputParam) { + switch (engineNode.getNodeType()) { + case 2: + //规则 + ruleSetNode.getNodeField(engineNode, inputParam); + break; + case 3: + //分组 + groupNode.getNodeField(engineNode, inputParam); + break; + case 9: + //决策选项 + decisionOptionsNode.getNodeField(engineNode, inputParam); + break; + default: + break; + } + } + + /** + * 执行节点逻辑 + * @param engineNode + * @param inputParam + * @param outMap + */ + private void runNode(EngineNode engineNode, Map inputParam, Map outMap) { + switch (engineNode.getNodeType()) { + case 2: + //规则 + ruleSetNode.runNode(engineNode, inputParam, outMap); + break; + case 3: + //分组 + groupNode.runNode(engineNode, inputParam, outMap); + break; + case 7: + //沙盒比例 + sandboxProportionNode.runNode(engineNode, inputParam, outMap); + break; + case 9: + //决策选项 + decisionOptionsNode.runNode(engineNode, inputParam, outMap); + break; + default: + break; + } + } + + /** + * 把引擎节点,以序号为key放入map + * + * @param nodelist 引擎节点 + * @return map + * @see + */ + private Map getEngineNodeListByMap(List nodelist) { + Map map = new HashMap<>(); + for (int i = 0; i < nodelist.size(); i++) { + map.put(nodelist.get(i).getNodeCode(), nodelist.get(i)); + } + return map; + } + + /** + * 决策流执行完回调(包括决策流正常返回结果回调、以及异常回调) + * @param url + * @param paramJson + * @param result + */ + private void decisionCallback(String url, Map paramJson, String result){ + if(StringUtils.isBlank(url)){ + return; + } + Map paramMap = new HashMap<>(); + paramMap.put("paramJson", JSONObject.toJSONString(paramJson)); + paramMap.put("result", result); + // 设置请求头 + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + // 封装请求体 + JSONObject body = JSONObject.parseObject(JSONObject.toJSONString(paramMap)); + // 封装参数和头信息 + HttpEntity httpEntity = new HttpEntity(body, httpHeaders); + ListenableFuture> future = asyncRestTemplate.postForEntity(url, httpEntity, String.class); + if(future != null){ + future.addCallback(new ListenableFutureCallback>() { + @Override + public void onFailure(Throwable throwable) { + logger.info("引擎回调异步调用失败", throwable); + } + + @Override + public void onSuccess(ResponseEntity stringResponseEntity) { + String result = stringResponseEntity.getBody(); + logger.info("引擎回调异步调用成功,result:{}", result); + } + }); + } + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineNodeServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineNodeServiceImpl.java new file mode 100644 index 0000000..9c83b83 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineNodeServiceImpl.java @@ -0,0 +1,45 @@ +package com.baoying.enginex.executor.engine.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.engine.mapper.EngineNodeMapper; +import com.baoying.enginex.executor.engine.model.EngineNode; +import com.baoying.enginex.executor.engine.service.EngineNodeService; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class EngineNodeServiceImpl extends ServiceImpl implements EngineNodeService { + + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + @Autowired + private EngineNodeMapper engineNodeMapper; + + @Override + public List getEngineNodeListByVersionId(Long versionId) { + List engineNodeList = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getForeignKey(TableEnum.T_ENGINE_NODE, versionId); + engineNodeList = redisManager.getByForeignKey(key, EngineNode.class); + if(engineNodeList != null){ + // 按node_order升序排序 + engineNodeList = engineNodeList.stream().sorted(Comparator.comparing(EngineNode::getNodeOrder)).collect(Collectors.toList()); + } + } else { + engineNodeList = engineNodeMapper.getEngineNodeListByVersionId(versionId); + } + + return engineNodeList; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineServiceImpl.java new file mode 100644 index 0000000..c895603 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineServiceImpl.java @@ -0,0 +1,37 @@ +package com.baoying.enginex.executor.engine.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.engine.mapper.EngineMapper; +import com.baoying.enginex.executor.engine.model.Engine; +import com.baoying.enginex.executor.engine.service.EngineService; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EngineServiceImpl extends ServiceImpl implements EngineService { + + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + @Autowired + private EngineMapper engineMapper; + + @Override + public Engine getEngineById(Long id) { + Engine engine = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getPrimaryKey(TableEnum.T_ENGINE, id); + engine = redisManager.getByPrimaryKey(key, Engine.class); + } else { + engine = engineMapper.selectById(id); + } + + return engine; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineVersionServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineVersionServiceImpl.java new file mode 100644 index 0000000..f5397c8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/service/impl/EngineVersionServiceImpl.java @@ -0,0 +1,56 @@ +package com.baoying.enginex.executor.engine.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.engine.mapper.EngineVersionMapper; +import com.baoying.enginex.executor.engine.model.EngineVersion; +import com.baoying.enginex.executor.engine.service.EngineVersionService; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class EngineVersionServiceImpl extends ServiceImpl implements EngineVersionService { + + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + @Autowired + private EngineVersionMapper engineVersionMapper; + + @Override + public EngineVersion getEngineVersionById(Long versionId) { + EngineVersion engineVersion = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getPrimaryKey(TableEnum.T_ENGINE_VERSION, versionId); + engineVersion = redisManager.getByPrimaryKey(key, EngineVersion.class); + } else { + engineVersion = engineVersionMapper.selectById(versionId); + } + return engineVersion; + } + + @Override + public EngineVersion getRunningVersion(Long engineId) { + EngineVersion engineVersion = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getForeignKey(TableEnum.T_ENGINE_VERSION, engineId); + List list = redisManager.getByForeignKey(key, EngineVersion.class); + Optional optional = list.stream().filter(item -> item.getBootState() == 1).findFirst(); + if(optional.isPresent()){ + engineVersion = optional.get(); + } + } else { + engineVersion = engineVersionMapper.getRunningVersion(engineId); + } + + return engineVersion; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/thread/EngineCallable.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/thread/EngineCallable.java new file mode 100644 index 0000000..57b75ba --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/engine/thread/EngineCallable.java @@ -0,0 +1,25 @@ +package com.baoying.enginex.executor.engine.thread; + +import com.baoying.enginex.executor.common.basefactory.CustomBeanFactory; +import com.baoying.enginex.executor.engine.service.EngineApiService; +import org.springframework.context.ApplicationContext; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class EngineCallable implements Callable { + + private Map paramJson; + + public EngineCallable(Map paramJson){ + this.paramJson = paramJson; + } + + @Override + public String call() { + ApplicationContext context = CustomBeanFactory.getContext(); + EngineApiService engineApiService = (EngineApiService) context.getBean("engineApiServiceImpl"); + String result = engineApiService.engineApi(paramJson); + return result; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.java new file mode 100644 index 0000000..96ba08b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.java @@ -0,0 +1,36 @@ +package com.baoying.enginex.executor.knowledge.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.knowledge.model.KnowledgeTree; + +import java.util.List; +import java.util.Map; + + +public interface KnowledgeTreeMapper extends BaseMapper { + + /** + * getTreeList:(根据父节点id和组织id,查询其下的所有子节点) + * @author keke + * @param paramMap 参数集合 + * @return 父节点下的所有子节点 + * */ + public List getTreeList(Map paramMap); + + /** + * batchInsert:(批量新增节点) + * @author keke + * @param k 节点信息集合 + * @return + * */ + public int batchInsert(List k); + + /** + * getTreeList:(根据父节点id和组织id,查询其下的所有子节点,若节点下规则,则过滤掉) + * @author keke + * @param paramMap 参数集合 + * @return 父节点下的所有子节点 + * */ + public List getTreeDataForEngine(Map paramMap); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.xml new file mode 100644 index 0000000..6ce10bb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/KnowledgeTreeMapper.xml @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + id, + name, + parent_id, + user_id, + organ_id, + engine_id, + status, + type, + tree_type, + created, + updated + + + + k.id, + k.name, + k.parent_id, + k.user_id, + k.organ_id, + k.engine_id, + k.status, + k.type, + k.tree_type, + k.created, + k.updated + + + + + + + + + insert into t_knowledge_tree (name,parent_id,user_id, + + organ_id, + + + engine_id, + + status,type,tree_type,created,updated) + values(#{name},#{parentId},#{userId}, + + #{organId}, + + + #{engineId}, + + #{status},#{type},#{treeType},now(), now()) + + + + + insert into t_knowledge_tree (name,parent_id,user_id, + + organ_id, + + + engine_id, + + status,type,tree_type,created,updated) + values(#{item.name},#{item.parentId},#{item.userId}, + + #{item.organId}, + + + #{item.engineId}, + + #{item.status},#{item.type},#{item.treeType},now(), now()) + + + + + + update t_knowledge_tree set + + name = #{name}, + + + status = #{status}, + + + type = #{type}, + + + parent_id = #{parentId}, + + updated = now() where id = #{id} + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.java new file mode 100644 index 0000000..8e2921a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.java @@ -0,0 +1,74 @@ +package com.baoying.enginex.executor.knowledge.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.engine.model.NodeKnowledge; +import com.baoying.enginex.executor.knowledge.model.Rule; +import com.baoying.enginex.executor.knowledge.model.RuleField; + +import java.util.List; +import java.util.Map; + + +public interface RuleFieldMapper extends BaseMapper { + + /** + * getFieldList : (根据规则id,,获取规则下的所有字段) + * @author keke + * @param ruleId 规则id + * @return 规则下的所有字段 + * */ + public List getFieldList(Long ruleId); + + /** + * insertField : (批量新增字段记录) + * @author keke + * @param rlist 字段信息集合 + * @return + * */ + public int insertField(List ruleFieldlist); + + /** + * updateField : (批量修改字段记录) + * @author keke + * @param rlist 字段信息集合 + * @return + * */ + public boolean updateField(List rlist); + + /** + * deleteField : (批量删除字段记录) + * @author keke + * @param rlist 字段信息集合 + * @return + * */ + public boolean deleteField(List rlist); + + + /** + * getNodeByList : (根据引擎节点得到所用字段) + * @author wenyu.cao + * @param nodeid 节点编号 + * @return 返回字段list + * */ + public List getNodeByList(NodeKnowledge knowledge); + public List getNodeByListNew(NodeKnowledge knowledge); + /** + * + * 根据规则得到规则引用字段 + * @param nodeKnowledge + * @return + * @see + */ + public List selectNodeByRuleList(NodeKnowledge nodeKnowledge); + public List selectNodeByRuleListNew(NodeKnowledge nodeKnowledge); + /** + * + * 根据规则id得到规则引用字段 + * @param paramMap 规则id集合 + * @return + * @see + */ + public List selectByRuleList(Map paramMap); + public List selectByRuleListNew(Map paramMap); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.xml new file mode 100644 index 0000000..c54c5bb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleFieldMapper.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + r.id , + r.logical, + r.operator, + t.field_cn as field, + r.field_value as fieldValue, + r.rule_id as ruleId, + r.field_id as fieldId, + t.field_en as fieldEn, + t.value_type as valueType, + t.value_scope as valueScope + + + + + + insert into t_rule_field + (logical,operator,field_value,rule_id,field_id) + values + + ( + #{item.logical}, + #{item.operator}, + #{item.fieldValue}, + #{item.ruleId}, + TRIM(#{item.fieldId}) + ) + + + + + + + + update t_rule_field set + + logical = #{item.logical} + + + ,operator = #{item.operator} + + + ,field_value = #{item.fieldValue} + + + ,field_id = TRIM(#{item.fieldId}) + + where id = #{item.id} + + + + + + delete from t_rule_field where id = #{item.id} + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.java new file mode 100644 index 0000000..4dafceb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.java @@ -0,0 +1,90 @@ +package com.baoying.enginex.executor.knowledge.mapper; + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.engine.model.NodeKnowledge; +import com.baoying.enginex.executor.knowledge.model.Rule; + +import java.util.List; +import java.util.Map; + + +public interface RuleMapper extends BaseMapper { + + /** + * getRuleList:(获取规则集合) + * @author keke + * @param paramMap 参数集合 + * @return 规则集合 + * */ + public List getRuleList(Map paramMap); + + /** + * updateRuleStatus:(批量修改规则状态记录) + * @author keke + * @param paramMap 参数集合 + * @return + * */ + public int updateRuleStatus(Map paramMap); + /** + * getNodeByRuleList : (根据引擎节点得到所用规则) + * @author wenyu.cao + * @param nodeid 节点编号 + * @return 返回字段list + * */ + public List getNodeByRuleList(NodeKnowledge knowledge); + + /** + * 根据规则类型查询规则 + * @param list 规则编号 + * @return + * @see + */ + public List selectnodeByInRoleid(List list); + + /** + * 根据父节点id查找,节点下所有规则id的集合 + * @param list 规则编号 + * @return + * @see + */ + public List getRuleIdsByParentId(Map param); + + /** + * getRuleList:(查找引用了某些字段的规则集合) + * @author yuanlinfeng + * @param paramMap 参数集合 + * @return 规则集合 + * */ + public List checkByField(Map paramMap); + + /** + * 效验规则名称唯一性 + * @param param 参数集合 + * @return + * @see + */ + public int countOnlyRuleName(Map param); + + /** + * 效验规则代码唯一性 + * @param param 参数集合 + * @return + * @see + */ + public int countOnlyRuleCode(Map param); + + /** + * getFieldIdsByRuleId:(根据规则id,获取规则所用字段id和Key) + * @author keke + * @param idList 规则id集合 + * @return + * */ + public List getFieldIdsByRuleId(List idList); + + public List getRuleListByType(Map paramMap); + + public List getNodeAddOrSubRulesByNodeId(Long nodeId); + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.xml new file mode 100644 index 0000000..240dda7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/mapper/RuleMapper.xml @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r.id, + r.name, + r.code, + r.description, + r.priority, + r.parent_id as parentId, + r.user_id as userId, + r.author, + u.nick_name as authorName, + r.organ_id as organId, + r.engine_id as engineId, + r.status, + r.rule_audit as ruleAudit, + r.type, + r.score, + r.last_logical as lastLogical, + r.is_non as isNon, + r.created, + r.updated, + r.rule_type, + r.result_field_en, + r.hit_field_en + + + + r.id, + r.name, + r.code, + r.description, + r.priority, + r.parent_id as parentId, + r.user_id as userId, + r.author, + u.nick_name as authorName, + r.organ_id as organId, + r.engine_id as engineId, + (CASE r. STATUS + WHEN r.id IN ( + SELECT + rule_id + FROM + t_engine_rule_rel td + WHERE + engine_id =#{engineId} + ) THEN + 1 + ELSE + 0 + END) AS status, + r.rule_audit as ruleAudit, + r.type, + r.score, + r.last_logical as lastLogical, + r.is_non as isNon, + r.created, + r.updated, + r.rule_type + + + + r.id, + r.name, + r.code, + r.description, + r.engine_id, + r.priority, + r.parent_id, + r.user_id, + r.author, + r.organ_id, + r.engine_id, + r.status, + r.type, + r.is_non, + r.rule_audit, + r.score, + r.last_logical, + r.created, + r.updated, + r.rule_type + + + + + + + + + insert into t_rule ( + name, + code, + description, + priority, + parent_id, + user_id, + author, + + content, + + + organ_id, + + + engine_id, + + + rule_audit, + + + score, + + + last_logical, + + status,type,is_non,created,updated,rule_type) + values( + #{name}, + #{code}, + #{description}, + #{priority}, + #{parentId}, + #{userId}, + #{author}, + + #{content}, + + + #{organId}, + + + #{engineId}, + + + #{ruleAudit}, + + + #{score}, + + + #{lastLogical}, + + #{status},#{type},#{isNon},now(), now(),#{ruleType} + ) + + + + update t_rule set + + name = #{name}, + + + code = #{code}, + + + content = #{content}, + + + description = #{description}, + + + priority = #{priority}, + + + status = #{status}, + + + type = #{type}, + + + is_non = #{isNon}, + + + rule_type = #{ruleType}, + + + rule_audit = #{ruleAudit}, + + + last_logical = #{lastLogical}, + + score = #{score}, + updated = now() where id = #{id} + + + + update t_rule set status = #{status} where id in + + #{item} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/EngineRuleRel.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/EngineRuleRel.java new file mode 100644 index 0000000..d269eeb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/EngineRuleRel.java @@ -0,0 +1,41 @@ +package com.baoying.enginex.executor.knowledge.model; + +import java.io.Serializable; + + +public class EngineRuleRel implements Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 引擎id + * */ + private Long engineId; + + /** + * 树形目录id + * */ + private Long ruleId; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getEngineId() { + return engineId; + } + + public void setEngineId(Long engineId) { + this.engineId = engineId; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTree.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTree.java new file mode 100644 index 0000000..1c33108 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTree.java @@ -0,0 +1,227 @@ +package com.baoying.enginex.executor.knowledge.model; + +import org.codehaus.jackson.annotate.JsonIgnore; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; + + +public class KnowledgeTree implements Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 目录名称 + * */ + private String name; + + /** + * 父节点id + * */ + private Long parentId; + + /** + * 创建人id + * */ + private Long userId; + + /** + * 组织id + * */ + private Long organId; + + + /** + * 引擎id + * */ + private Long engineId; + + /** + * 创建日期 + * */ + private Date created; + + /** + * 目录类型 0 : 系统的目录 1:组织的目录 2: 引擎的目录 + * */ + private Integer type; + + /** + * 树形分类:0:规则树 1:评分卡的树 2:回收站的树 + * */ + private Integer treeType; + + /** + * 状态 0 :停用 ,1 : 启用,-1:删除 + * */ + private Integer status; + + /** + * 修改日期 + * */ + private Date updated; + + /** + * 子类集合 + * */ + private KnowledgeTree[] children; + + /** + * 是否为父类 + * */ + private String isParent = "true"; + + /** + *文件夹图片路径 + * */ + private String icon=""; + + private String isLastNode=""; + + private Integer directoryType; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getOrganId() { + return organId; + } + + public void setOrganId(Long organId) { + this.organId = organId; + } + + public Long getEngineId() { + return engineId; + } + + public void setEngineId(Long engineId) { + this.engineId = engineId; + } + + @JsonIgnore + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + @JsonIgnore + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public KnowledgeTree[] getChildren() { + return children; + } + + public void setChildren(KnowledgeTree[] children) { + this.children = children; + } + + public String getIsParent() { + return isParent; + } + + public void setIsParent(String isParent) { + this.isParent = isParent; + } + + public Integer getTreeType() { + return treeType; + } + + public void setTreeType(Integer treeType) { + this.treeType = treeType; + } + + public String getIcon() { + if((int)treeType == 2 || (int)treeType == 3){ + icon = "../resource/images/datamanage/cabage.png"; + isLastNode ="true"; + }else{ + icon = "../resource/images/authority/folder.png"; + } + return icon; + } + + public Integer getDirectoryType() { + return directoryType = type ; + } + + public String getIsLastNode() { + if((int)treeType == 2 || (int)treeType == 3){ + isLastNode ="true"; + } + return isLastNode; + } + + @Override + public String toString() { + return "KnowledgeTree [id=" + id + ", name=" + name + ", parentId=" + + parentId + ", userId=" + userId + ", organId=" + organId + + ", engineId=" + engineId + ", created=" + created + ", type=" + + type + ", treeType=" + treeType + ", status=" + status + + ", updated=" + updated + ", children=" + + Arrays.toString(children) + ", isParent=" + isParent + + ", icon=" + icon + ", isLastNode=" + isLastNode + + ", directoryType=" + directoryType + "]"; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTreeRel.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTreeRel.java new file mode 100644 index 0000000..272dd88 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/KnowledgeTreeRel.java @@ -0,0 +1,48 @@ +package com.baoying.enginex.executor.knowledge.model; + +import java.io.Serializable; + + +public class KnowledgeTreeRel implements Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 引擎id + * */ + private Long engineId; + + /** + * 树形目录id + * */ + private Long treeId; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getEngineId() { + return engineId; + } + + public void setEngineId(Long engineId) { + this.engineId = engineId; + } + + public Long getTreeId() { + return treeId; + } + + public void setTreeId(Long treeId) { + this.treeId = treeId; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/Rule.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/Rule.java new file mode 100644 index 0000000..594772c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/Rule.java @@ -0,0 +1,411 @@ +package com.baoying.enginex.executor.knowledge.model; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + + +public class Rule implements Serializable,Cloneable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 名称 + * */ + private String name; + + /** + * 代码 + * */ + private String code; + + /** + * 描述 + * */ + private String description; + + /** + * 优先级 + * */ + private Integer priority; + + /** + * 父节点id + * */ + private Long parentId; + + /** + *修改人id + * */ + private Long userId; + + /** + *创建人id + * */ + private Long author; + + /** + *创建人名称 + * */ + private String authorName; + + /** + * 组织id + * */ + private Long organId; + + /** + * 引擎id + * */ + private Long engineId; + + /** + * 规则类型 0 : 系统的规则 1:组织的规则 2: 引擎的规则 + * */ + private Integer type; + + /** + * 逻辑关系"非" 0:不是非 1:是非 + * */ + private Integer isNon; + + /** + * 状态 0 :停用 ,1 : 启用,-1:删除 + * */ + private Integer status; + /** + * 审批规则 5 :通过 ,2 : 拒绝,3:人工审批 4:简化流程 + */ + public int ruleAudit; + /** + * 规则字段集合 + * */ + private List ruleFieldList; + + /** + * 规则内容集合 + * */ + private List ruleContentList; + + /** + * 创建日期 + * */ + private Date created; + + /** + * 修改日期 + * */ + private Date updated; + + /** + * 规则具体内容 + * */ + public String content; + + /** + * 0硬性拒绝规则1加减分规则 + */ + private Integer ruleType; + + /** + *得分 + */ + private Integer score; + + /** + *逻辑关系符,存储条件区域最后一个逻辑符号,值有')'、'))'、'-1' + */ + private String lastLogical; + + /** + * 引擎名 + * */ + private String engineName; + + /** + * 规则节点名称 + * */ + private String engineNodeName; + + /** + * 规则节点名称 + * */ + private Long engineNodeId; + + /** + * 区分规则集和规则 + * */ + private int showType = 0; + + public String getScoreFieldEn() { + return scoreFieldEn; + } + + public void setScoreFieldEn(String scoreFieldEn) { + this.scoreFieldEn = scoreFieldEn; + } + + private String resultFieldEn;//存放结果的字段en + + private String scoreFieldEn;//存放是否命中的字段 + + public String getResultFieldEn() { + return resultFieldEn; + } + + public void setResultFieldEn(String resultFieldEn) { + this.resultFieldEn = resultFieldEn; + } + + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public int getRuleAudit() { + return ruleAudit; + } + + public void setRuleAudit(int ruleAudit) { + this.ruleAudit = ruleAudit; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getAuthor() { + return author; + } + + public void setAuthor(Long author) { + this.author = author; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public Long getOrganId() { + return organId; + } + + public void setOrganId(Long organId) { + this.organId = organId; + } + + public Long getEngineId() { + return engineId; + } + + public void setEngineId(Long engineId) { + this.engineId = engineId; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public List getRuleFieldList() { + return ruleFieldList; + } + + public void setRuleFieldList(List ruleFieldList) { + this.ruleFieldList = ruleFieldList; + } + + public List getRuleContentList() { + return ruleContentList; + } + + public void setRuleContentList(List ruleContentList) { + this.ruleContentList = ruleContentList; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Integer getIsNon() { + return isNon; + } + + public void setIsNon(Integer isNon) { + this.isNon = isNon; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getRuleType() { + if(ruleAudit == 2) { + ruleType = 0; + }else{ + ruleType = 1; + } + return ruleType; + } + + public void setRuleType(Integer ruleType) { + this.ruleType = ruleType; + } + + public Integer getScore() { + return score; + } + + public void setScore(Integer score) { + this.score = score; + } + + public String getLastLogical() { + return lastLogical; + } + + public void setLastLogical(String lastLogical) { + this.lastLogical = lastLogical; + } + + public String getEngineName() { + return engineName; + } + + public void setEngineName(String engineName) { + this.engineName = engineName; + } + + + + public String getEngineNodeName() { + return engineNodeName; + } + + public void setEngineNodeName(String engineNodeName) { + this.engineNodeName = engineNodeName; + } + + public Long getEngineNodeId() { + return engineNodeId; + } + + public void setEngineNodeId(Long engineNodeId) { + this.engineNodeId = engineNodeId; + } + + public int getShowType() { + return showType; + } + + public void setShowType(int showType) { + this.showType = showType; + } + + @Override + public Object clone() throws CloneNotSupportedException { + // TODO Auto-generated method stub + return super.clone(); + } + + @Override + public String toString() { + return "Rule [id=" + id + ", name=" + name + ", versionCode=" + code + ", description=" + description + ", priority=" + + priority + ", parentId=" + parentId + ", userId=" + userId + ", author=" + author + ", authorName=" + + authorName + ", organId=" + organId + ", engineId=" + engineId + ", type=" + type + ", isNon=" + isNon + + ", status=" + status + ", ruleAudit=" + ruleAudit + ", ruleFieldList=" + ruleFieldList + + ", ruleContentList=" + ruleContentList + ", created=" + created + ", updated=" + updated + + ", content=" + content + ", ruleType=" + ruleType + ", score=" + score + ", lastLogical=" + + lastLogical + ", engineName=" + engineName + ", engineNodeName=" + engineNodeName + ", engineNodeId=" + + engineNodeId + ", showType=" + showType + "]"; + } + + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleContent.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleContent.java new file mode 100644 index 0000000..5b9ebd2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleContent.java @@ -0,0 +1,149 @@ +package com.baoying.enginex.executor.knowledge.model; + + +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; + + +public class RuleContent implements Serializable{ + + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 字段名 + * */ + private String field; + + /** + * 字段值 + * */ + private String fieldValue; + + /** + * 字段id + * */ + private String fieldId; + + /** + * 规则Id + * */ + private Long ruleId; + + + /** + * 关联的字段的英文名称 + * */ + private String fieldEn; + + /** + * 关联的字段的值类型 + * */ + private Integer valueType; + + /** + * 关联的字段的取值范围 + * */ + private String valueScope; + + /** + * 关联的字段的值拆解后的数组 + * */ + private String[] values; + + /** + * 类型:1 常量、2 变量 + */ + private Integer variableType; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public String getFieldId() { + return fieldId; + } + + public void setFieldId(String fieldId) { + this.fieldId = fieldId; + } + + public Long getRuleId() { + return ruleId; + } + + public void setRuleId(Long ruleId) { + this.ruleId = ruleId; + } + + public Integer getValueType() { + return valueType; + } + + public void setValueType(Integer valueType) { + this.valueType = valueType; + } + + public String getValueScope() { + return valueScope; + } + + public void setValueScope(String valueScope) { + this.valueScope = valueScope; + } + + public String[] getValues() { + if(!StringUtils.isBlank(valueScope)){ + if(valueType == 3){ + values = valueScope.split(","); + }else{ + values = new String[]{valueScope}; + } + }else{ + values = null; + } + return values; + } + + public String getFieldEn() { + return fieldEn; + } + + public void setFieldEn(String fieldEn) { + this.fieldEn = fieldEn; + } + + public Integer getVariableType() { + return variableType; + } + + public void setVariableType(Integer variableType) { + this.variableType = variableType; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleExcel.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleExcel.java new file mode 100644 index 0000000..db444b2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleExcel.java @@ -0,0 +1,74 @@ +package com.baoying.enginex.executor.knowledge.model; + +public class RuleExcel { + + /** + * 规则名称 + * */ + private String name; + + /** + * 规则代码 + * */ + private String code; + + /** + * 规则描述 + * */ + private String description; + + /** + * 优先级 + * */ + private Integer priority; + + /** + * 规则字段内容 + * */ + private String fieldContent; + + /** + * 规则内容 + * */ + private String content; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public Integer getPriority() { + return priority; + } + public void setPriority(Integer priority) { + this.priority = priority; + } + public String getFieldContent() { + return fieldContent; + } + public void setFieldContent(String fieldContent) { + this.fieldContent = fieldContent; + } + public String getContent() { + return content; + } + public void setContent(String content) { + this.content = content; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleField.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleField.java new file mode 100644 index 0000000..600a143 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/RuleField.java @@ -0,0 +1,164 @@ +package com.baoying.enginex.executor.knowledge.model; + + +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; + + +public class RuleField implements Serializable{ + + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 逻辑运算符 + * */ + private String logical; + + /** + * 字段内容 + * */ + private String field; + + /** + * 运算符 + * */ + private String operator; + + /** + * 字段值 + * */ + private String fieldValue; + + /** + * 关联的规则的id + * */ + private Long ruleId; + + /** + * 关联的字段的id + * */ + private String fieldId; + + /** + * 关联的字段的英文名称 + * */ + private String fieldEn; + + /** + * 关联的字段的值类型 + * */ + private Integer valueType; + + /** + * 关联的字段的取值范围 + * */ + private String valueScope; + + /** + * 关联的字段的值拆解后的数组 + * */ + private String[] values; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogical() { + return logical; + } + + public void setLogical(String logical) { + this.logical = logical; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Long getRuleId() { + return ruleId; + } + + public void setRuleId(Long ruleId) { + this.ruleId = ruleId; + } + + public String getFieldId() { + return fieldId; + } + + public void setFieldId(String fieldId) { + this.fieldId = fieldId; + } + + public Integer getValueType() { + return valueType; + } + + public void setValueType(Integer valueType) { + this.valueType = valueType; + } + + public String getValueScope() { + return valueScope; + } + + public void setValueScope(String valueScope) { + this.valueScope = valueScope; + } + + public String[] getValues() { + if(!StringUtils.isBlank(valueScope)){ + if(valueType == 3){ + values = valueScope.split(","); + }else{ + values = new String[]{valueScope}; + } + }else{ + values = null; + } + return values; + } + + public String getFieldEn() { + return fieldEn; + } + + public void setFieldEn(String fieldEn) { + this.fieldEn = fieldEn; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScoreCardJson.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScoreCardJson.java new file mode 100644 index 0000000..072e1a1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScoreCardJson.java @@ -0,0 +1,44 @@ +package com.baoying.enginex.executor.knowledge.model; + +public class ScoreCardJson { + + private String ouput; + private Integer index; + private String formula; + private String formula_show; + private String fields; + + public String getOuput() { + return ouput; + } + public void setOuput(String ouput) { + this.ouput = ouput; + } + public Integer getIndex() { + return index; + } + public void setIndex(Integer index) { + this.index = index; + } + public String getFormula() { + return formula; + } + public void setFormula(String formula) { + this.formula = formula; + } + public String getFormula_show() { + return formula_show; + } + public void setFormula_show(String formula_show) { + this.formula_show = formula_show; + } + public String getFields() { + if(fields!=null){ + fields = fields.substring(1,fields.length()-1).trim(); + } + return fields; + } + public void setFields(String fields) { + this.fields = fields; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardExcel.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardExcel.java new file mode 100644 index 0000000..27cca47 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardExcel.java @@ -0,0 +1,71 @@ +package com.baoying.enginex.executor.knowledge.model; + +public class ScorecardExcel { + + /** + * 评分卡名称 + * */ + private String name; + + /** + * 评分卡代码 + * */ + private String code; + + /** + * 评分卡描述 + * */ + private String description; + + /** + * 版本号 + * */ + private String version; + + /** + * 评分卡字段内容 + * */ + private String fieldContent; + + /** + * 评分卡内容 + * */ + private String content; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getVersion() { + return version; + } + public void setVersion(String version) { + this.version = version; + } + public String getFieldContent() { + return fieldContent; + } + public void setFieldContent(String fieldContent) { + this.fieldContent = fieldContent; + } + public String getContent() { + return content; + } + public void setContent(String content) { + this.content = content; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardField.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardField.java new file mode 100644 index 0000000..9c331e2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardField.java @@ -0,0 +1,50 @@ +package com.baoying.enginex.executor.knowledge.model; + +import java.io.Serializable; + + +public class ScorecardField implements Serializable{ + + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 关联的评分卡的id + * */ + private Long scorecardId; + + /** + * 关联的字段id + * */ + private Long fieldId; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getScorecardId() { + return scorecardId; + } + + public void setScorecardId(Long scorecardId) { + this.scorecardId = scorecardId; + } + + public Long getFieldId() { + return fieldId; + } + + public void setFieldId(Long fieldId) { + this.fieldId = fieldId; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardRuleContent.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardRuleContent.java new file mode 100644 index 0000000..257f76e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/knowledge/model/ScorecardRuleContent.java @@ -0,0 +1,140 @@ +package com.baoying.enginex.executor.knowledge.model; + + +import com.alibaba.fastjson.JSONObject; + +import java.io.Serializable; + + +public class ScorecardRuleContent implements Serializable{ + + + private static final long serialVersionUID = 1L; + + /** + * 主键 + * */ + private Long id; + + /** + * 评分卡Id + * */ + private Long scorecardId; + + /** + * 字段 + * */ + private String field; + + /** + * 字段英文名称 + * */ + private String fieldEn; + + /** + * 字段值 + * */ + private String fieldValue; + + /** + * 字段id + * */ + private Long fieldId; + + /** + * 关联的字段的值类型 + * */ + private Integer valueType; + + /** + * 关联的字段的取值范围 + * */ + private String valueScope; + + /** + * 关联的字段的值拆解后的数组 + * */ + private String[] values; + + private ScoreCardJson sCardJson; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getScorecardId() { + return scorecardId; + } + + public void setScorecardId(Long scorecardId) { + this.scorecardId = scorecardId; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Long getFieldId() { + return fieldId; + } + + public void setFieldId(Long fieldId) { + this.fieldId = fieldId; + } + + public Integer getValueType() { + return valueType; + } + + public void setValueType(Integer valueType) { + this.valueType = valueType; + } + + public String getValueScope() { + return valueScope; + } + + public void setValueScope(String valueScope) { + this.valueScope = valueScope; + } + + public String[] getValues() { + if(valueType == 3){ + values = valueScope.split(","); + }else{ + values = new String[]{valueScope}; + } + return values; + } + + public ScoreCardJson getsCardJson() { + sCardJson = JSONObject.parseObject(fieldValue, ScoreCardJson.class); + return sCardJson; + } + + public String getFieldEn() { + return fieldEn; + } + + public void setFieldEn(String fieldEn) { + this.fieldEn = fieldEn; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/EmailService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/EmailService.java new file mode 100644 index 0000000..a26e901 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/EmailService.java @@ -0,0 +1,21 @@ +package com.baoying.enginex.executor.message.email.service; + +public interface EmailService { + /** + * 发送邮件方法 + * @param to 收件人邮件地址 + * @param subject 主题 + * @param content 邮件内容 + */ + public void sendHtmlMail(String to, String subject, String content); + + /** + * 发送模板邮件方法 + * @param to 收件人邮件地址 + * @param subject 主题 + * @param templateName 模板名称 + * @param context 追加参数集合 + */ +// public void sendTemplateMail(String to, String subject, String templateName, Context context); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/impl/EmailServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/impl/EmailServiceImpl.java new file mode 100644 index 0000000..06f3ba1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/message/email/service/impl/EmailServiceImpl.java @@ -0,0 +1,49 @@ +package com.baoying.enginex.executor.message.email.service.impl; + +import com.baoying.enginex.executor.message.email.service.EmailService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.mail.internet.MimeMessage; + +@Service("emailService") +@Slf4j +public class EmailServiceImpl implements EmailService { + + @Resource + private JavaMailSender mailSender; + + @Value("${spring.mail.username}") + private String fromAddr; + + @Override + public void sendHtmlMail(String to, String subject, String content) { + log.info("开始发送邮件,to:{}, subject:{}, content:{}", to, subject, content); + MimeMessage message = mailSender.createMimeMessage(); + try { + //true表示需要创建一个multipart message + MimeMessageHelper helper = new MimeMessageHelper(message,true, "utf-8"); + helper.setFrom(fromAddr); + String[] split = to.split(","); + helper.setTo(split); + helper.setSubject(subject); + helper.setText(content,true); + mailSender.send(message); + log.info("邮件发送成功"); + }catch (Exception e){ + log.error("邮件发送失败",e); + } + } + + public String getFromAddr() { + return fromAddr; + } + + public void setFromAddr(String fromAddr) { + this.fromAddr = fromAddr; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/CommonService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/CommonService.java new file mode 100644 index 0000000..086d78c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/CommonService.java @@ -0,0 +1,26 @@ +package com.baoying.enginex.executor.node.service; + +import com.baoying.enginex.executor.datamanage.model.Field; + +import java.util.List; +import java.util.Map; + +public interface CommonService { + + boolean getFieldByIds(List ids, Map inputParam); + + /** + * 获取引擎节点所需的指标 + * @param fields + * @param engineNode + * @param inputParam + * @return + */ + boolean getEngineField(List fields, Map inputParam); + + /** + * 获取衍生指标 + * @param inputParam + */ + void getFieldResult (Map inputParam); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/EngineNodeService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/EngineNodeService.java new file mode 100644 index 0000000..4dba3b1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/EngineNodeService.java @@ -0,0 +1,26 @@ +package com.baoying.enginex.executor.node.service; + +import com.baoying.enginex.executor.engine.model.EngineNode; + +import java.util.Map; + +/** + * 引擎节点执行 + */ +public interface EngineNodeService { + + /** + * 获取节点所需的指标 + * @param engineNode + * @param inputParam + */ + void getNodeField(EngineNode engineNode, Map inputParam); + + /** + * 执行节点逻辑 + * @param engineNode + * @param inputParam + * @param outMap + */ + void runNode(EngineNode engineNode, Map inputParam, Map outMap); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/CommonServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/CommonServiceImpl.java new file mode 100644 index 0000000..083a9e7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/CommonServiceImpl.java @@ -0,0 +1,789 @@ +package com.baoying.enginex.executor.node.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.common.session.SessionData; +import com.baoying.enginex.executor.common.session.SessionManager; +import com.baoying.enginex.executor.datamanage.mapper.SimpleMapper; +import com.baoying.enginex.executor.datamanage.model.Field; +import com.baoying.enginex.executor.datamanage.model.FieldCond; +import com.baoying.enginex.executor.datamanage.service.FieldService; +import com.baoying.enginex.executor.engine.model.ComplexRule; +import com.baoying.enginex.executor.node.service.CommonService; +import com.baoying.enginex.executor.util.DictVariableUtils; +import com.baoying.enginex.executor.util.ExecuteUtils; +import com.baoying.enginex.executor.util.StringUtil; +import com.baoying.enginex.executor.util.https.HttpClient; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.math.Groovy; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class CommonServiceImpl implements CommonService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Resource + private SimpleMapper simpleMapper; + + @Autowired + public FieldService fieldService; + + @Autowired + private Groovy groovy; + + @Override + public boolean getFieldByIds(List ids, Map inputParam) { + if (ids == null || ids.size() == 0) { + return true; + } + SessionData sessionData = SessionManager.getSession(); +// Long organId = sessionData.getOrganId(); + Long organId = 46L; + List fieldList = fieldService.findFieldByIdsbyorganId(organId, ids); + List list = new ArrayList<>(); + ids = new ArrayList<>(); + for (int i = 0; i < fieldList.size(); i++) { + if (fieldList.get(i).getIsDerivative() == 1) { + ids.addAll(StringUtil.toLongList(fieldList.get(i).getOrigFieldId())); + } else + list.add(fieldList.get(i)); + } + if (ids.size() > 0) { + List lists = fieldService.findFieldByIdsbyorganId(organId, ids); + list.addAll(lists); + } + + List fields = new ArrayList<>(); + fields.addAll(list); + + this.getEngineField(fields, inputParam); + + for (Field field : fieldList) { + if (field.getIsDerivative() == 1) { + inputParam.put(field.getFieldEn(), ""); + this.getFieldResult(inputParam); + } + } + return false; + } + + /** + * 调用http请求得到引擎节点所需要的字段 + * + * @return 引擎所需字段 + * @see + */ + @Override + public boolean getEngineField(List fields, Map inputParam) { + logger.info("start getEngineField, fields:{},inputParam:{}", JSONObject.toJSONString(fields), JSONObject.toJSONString(inputParam)); + + int type = 1; + + if (null != fields && fields.size() < 1) { + return true; + } + + // 循环规则特殊处理 + List tempFields = new ArrayList<>(fields); + for (Field field : fields) { + if (field.getFieldEn().contains("[") && field.getFieldEn().contains("]")) { + String fieldEn = field.getFieldEn().substring(0, field.getFieldEn().indexOf("[")); + Field nField = new Field(); + nField.setFieldEn(fieldEn); + tempFields.add(nField); + tempFields.remove(field); + } + } + fields = new ArrayList<>(tempFields); + + // 需要调用指标系统的字段集合 + List remainFields = new ArrayList<>(fields); + + for (Field field : fields) { + if (inputParam.containsKey(field.getFieldEn())) { + // 优先从入参中获取指标 + remainFields.remove(field); + } + } + + if (null != remainFields && remainFields.size() < 1) { + return true; + } + + Properties p = new Properties(); + try { + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("datacenter.properties"); + p.load(inputStream); + } catch (Exception e1) { + e1.printStackTrace(); + logger.error("remainFields:{},请求异常", JSONObject.toJSONString(remainFields), e1); + } + String act = p.getProperty("act"); + //随机数 + String nonce = UUID.randomUUID().toString(); + String token = p.getProperty("token"); + Date date = new Date(); + long ts = date.getTime(); +// String sign = MD5.GetMD5Code(act.trim() + "," + date.getTime() + "," + nonce.trim() + "," + pid.trim() + "," + uid.trim() + "," + token.trim()); + HttpClient httpClient = new HttpClient(); + String url = p.getProperty("url") + "?token=" + token.trim() + "&ts=" + ts + "&act=" + act.trim() + "&nonce=" + nonce.trim(); + Map pam = new HashMap<>(); + pam.put("fields", getListFieldByString(remainFields)); +// pam.put("sign", sign); + pam.put("type", String.valueOf(type)); + try { + String result = httpClient.post(url, pam); + JSONObject jsonObject = JSONObject.parseObject(result); + //返回成功状态下解析返回的字段 + if (jsonObject.getString("status").equals("0x0000")) { + JSONArray array = jsonObject.getJSONArray("data"); + + if (type == 1) { //普通规则 + for (int i = 0; i < array.size(); i++) { + JSONObject object = array.getJSONObject(i); + for (Map.Entry entry : object.entrySet()) { + inputParam.put(entry.getKey(), entry.getValue()); + } + } + return true; + } else { //复杂规则 + List list = new ArrayList(); + return true; + } + } else { + //返回状态不成功,直接返回null + return false; + } + } catch (Exception e) { + e.printStackTrace(); + logger.error("remainFields:{},请求异常", JSONObject.toJSONString(remainFields), e); + } + return false; + } + + /** + * 把list对象转换为以,号隔开的string字符串 + * + * @param list + * @return + * @see + */ + private String getListFieldByString(List list) { + String fields = ""; + for (int i = 0; i < list.size(); i++) { + fields = fields + list.get(i).getFieldEn() + ","; + } + return fields; + + } + + /** + * 根据输入字段参数返回衍生字段的结果 + * 输入:引擎id及参数列表(原生字段英文字段名和值、待计算的衍生字段只有英文字段名) + * 输出:待计算衍生字段的计算结果值 + */ + @Override + public void getFieldResult(Map paramMap) { + //参数传递中间临时Map + Map paramMap2 = new HashMap(); + for (Map.Entry entry : paramMap.entrySet()) { + if (null != entry.getValue()) + paramMap2.put(entry.getKey(), entry.getValue()); + else + paramMap2.put(entry.getKey(), ""); + } + + SessionData sessionData = SessionManager.getSession(); + Long organId = sessionData.getOrganId(); + paramMap2.put("organId", organId); + + for (Map.Entry entry : paramMap.entrySet()) { + String fieldEn = entry.getKey(); + String fieldValue = ""; + if (null != entry.getValue()) + fieldValue = String.valueOf(entry.getValue()); + + if (null == fieldValue || fieldValue.equals("")) { + + paramMap2.put("fieldEn", fieldEn); + Field field = fieldService.findByFieldEnbyorganId(organId, fieldEn); + + if (field != null) { + if (field.getIsDerivative() == 1) { + String result = ""; + paramMap2.put("fieldCn", field.getFieldCn()); + result = getExpAll(field.getFieldCn(), "", paramMap); + //单独返回数值结果会带上(),做处理去掉 + result = result.replace("(", ""); + result = result.replace(")", ""); + paramMap.put(fieldEn, result); + } + } + } + } + } + + /** + * 递归生成衍生字段嵌套的逻辑表达式或公式计算的结果 + */ + private String getExpAll(String fieldCn, String exp, Map param) { + + String result = ""; + Map param2 = new HashMap(); + for (Map.Entry entry : param.entrySet()) { + if (null != entry.getValue()) + param2.put(entry.getKey(), entry.getValue()); + else + param2.put(entry.getKey(), ""); + } + + SessionData sessionData = SessionManager.getSession(); + Long organId = sessionData.getOrganId(); + + Map paramMap = new HashMap(); + + paramMap.put("organId", organId); + paramMap.put("engineId", param.get("engineId")); + paramMap.put("fieldCn", fieldCn); + + //放大从组织通用字段拷贝生成的引擎字段里字段的获取范围,优先取引擎,没有再取组织里的(因为中文名在组织通用字段和引擎字段里有重复) + String arrFormula = ""; + Field engField = fieldService.findByFieldCnbyorganId(organId, fieldCn); + String engFormula = engField.getFormula(); + if (!engFormula.equals("") && engFormula != null) { + arrFormula = engFormula; + } + + if (arrFormula.equals("") || arrFormula == null) { //衍生字段只有条件区域 + + List fieldCondList = new ArrayList(); + List engfieldCondList = fieldService.findByFieldCnbyorganId(organId, fieldCn).getFieldCondList(); + if (engfieldCondList.size() > 0) { + fieldCondList = engfieldCondList; + } + + if (fieldCondList.size() > 0) {//需要进行条件区域逻辑运算 + for (FieldCond fieldCond : fieldCondList) {//这里的fieldCond是字段设置的逻辑运算符 + String condValue = fieldCond.getConditionValue(); + List condList = new ArrayList<>(); + condList = JSONObject.parseArray(fieldCond.getContent()); + exp = ""; + for (int j = 0; j < condList.size(); j++) { + JSONObject cond = ((JSONArray) condList).getJSONObject(j); + //[{\"fieldId\":\"31\",\"operator\":\">\",\"fieldValue\":\"1000\",\"logical\":\"&&\"},{\"fieldId\":\"31\",\"operator\":\">=\",\"fieldValue\":\"7000\"}] + paramMap.put("id", cond.getString("fieldId")); + + Field condfield = fieldService.queryById(Long.valueOf(cond.getString("fieldId"))); + if (condfield == null) { + condfield = fieldService.findByFieldCnbyorganId(organId, fieldCn); + } + + String condFieldEn = condfield.getFieldEn();//yqshouru 月收入 + String condFieldCn = condfield.getFieldCn(); + Integer condValueType = condfield.getValueType(); //1数值型 + String condFieldValue = cond.getString("fieldValue"); //1000 + String operator = cond.getString("operator"); //>大于号 + String fieldValue = param2.get(condFieldEn).toString(); //取输入变量的值 + + String logical = ""; + + if (condfield.getIsDerivative() == 0) { + if (cond.containsKey("logical")) + logical = " " + cond.getString("logical") + " "; + if (operator.equals("in")) { + //exp += "(indexOf(#{"+fieldValue+"},'"+condFieldValue+"')>0"+logical; + exp += "(indexOf('" + condFieldValue + "','" + fieldValue + "',0) >= 0)" + logical; + } else if (operator.equals("not in")) { + //exp += "(indexOf(#{"+fieldValue+"},'"+condFieldValue+"')=0"+logical; + exp += "(indexOf('" + condFieldValue + "','" + fieldValue + "',0) = -1)" + logical; + } else if (operator.equals("like")) { //交换位置 (indexOf('abc','c',0) >= 0) + exp += "(indexOf('" + fieldValue + "','" + condFieldValue + "',0) >= 0)" + logical; + } else if (operator.equals("not like")) { //(indexOf('abc','x',0) = -1) + exp += "(indexOf('" + fieldValue + "','" + condFieldValue + "',0) = -1)" + logical; + } else { + if (condValueType == 1 || condValueType == 4) { + exp += " (" + fieldValue + "" + operator + "" + condFieldValue + ") " + logical; + } else + exp += " ('" + fieldValue + "'" + operator + "'" + condFieldValue + "') " + logical; + } + } else {//衍生字段 + if (cond.containsKey("logical")) + logical = " " + cond.getString("logical") + " "; + if (operator.equals("in")) { + //exp += "(indexOf(#{"+getExpAll(condFieldCn,"",param2)+"},'"+condFieldValue+"')>0"+logical; + //(indexOf('abc','c',0) >= 0) && (indexOf('abc','x',0) = -1) + exp += "(indexOf('" + condFieldValue + "','" + getExpAll(condFieldCn, "", param2) + "',0) >= 0)" + logical; + } else if (operator.equals("not in")) { + exp += "(indexOf('" + condFieldValue + "','" + getExpAll(condFieldCn, "", param2) + "',0) = -1)" + logical; + } else if (operator.equals("like")) { //交换位置 (indexOf('abc','c',0) >= 0) + exp += "(indexOf('" + getExpAll(condFieldCn, "", param2) + "','" + condFieldValue + "',0) >= 0)" + logical; + } else if (operator.equals("not like")) { //(indexOf('abc','x',0) = -1) + exp += "(indexOf('" + getExpAll(condFieldCn, "", param2) + "','" + condFieldValue + "',0) = -1)" + logical; + } else { + if (condValueType == 1 || condValueType == 4) { + exp += " (" + getExpAll(condFieldCn, "", param2) + "" + operator + "" + condFieldValue + ") " + logical; + } else + exp += " ('" + getExpAll(condFieldCn, "", param2) + "'" + operator + "'" + condFieldValue + "') " + logical; + } + } + } + Evaluator evaluator = new Evaluator(); + String b = ""; + try { + System.out.println("========字段区域设置的的表达式输出:" + exp); + b = evaluator.evaluate(exp); + } catch (EvaluationException e) { + e.printStackTrace(); + logger.error("请求异常", e); + } + if (b.equals("1.0")) { + result = condValue; + break; //遇到一个满足条件的则跳出循环 + } + } + } + } else { //衍生字段只有公式 + List formulaList = new ArrayList<>(); + formulaList = JSONObject.parseArray(arrFormula); + for (int i = 0; i < formulaList.size(); i++) { + JSONObject formulaJson = ((JSONArray) formulaList).getJSONObject(i); + + String formula = (String) formulaJson.get("formula"); + formula = formula.replace(">", ">"); //3>=6 && 3< 12 + formula = formula.replace("<", "<"); + Pattern pattern = Pattern.compile("@[a-zA-Z0-9_\u4e00-\u9fa5()()-]+@"); + Matcher matcher = pattern.matcher(formula); + String subexp = formula; + int j = 0; + exp = ""; + // 存放groovy脚本入参 + Map data = new HashMap<>(); + //System.out.println("待替换的字段串:"+formula); + while (matcher.find()) { + String fieldCN = matcher.group(0).replace("@", ""); + Map fieldMap = new HashMap(); + paramMap.put("organId", organId); + fieldMap.put("engineId", paramMap.get("engineId")); + fieldMap.put("fieldCn", fieldCN); + fieldMap.put("organId", organId); + Field subField = fieldService.findByFieldCnbyorganId(organId, fieldCN); + + //构造字段条件区间计算输入参数 + Map paramCond = new HashMap(); + paramCond.put("fieldValue", param2.get(subField.getFieldEn())); + paramCond.put("fieldEn", subField.getFieldEn()); + paramCond.put("fieldValueType", subField.getValueType()); + + //字段条件转换 + JSONArray fieldCond = new JSONArray(); + if (formulaJson.get("farr") != null && !"".equals(formulaJson.get("farr"))) { + JSONArray jsonArr = (JSONArray) formulaJson.get("farr"); + for (Iterator iterator = jsonArr.iterator(); iterator.hasNext(); ) { + JSONObject job = (JSONObject) iterator.next(); + if (job.get("fieldCN").equals(fieldCN) && !job.get("fieldCond").equals("")) { + fieldCond = (JSONArray) job.get("fieldCond"); + break; + } + } + } + + paramCond.put("fieldCond", fieldCond); + String v = ""; + if (fieldCond.size() > 0) { + v = calcFieldCond(paramCond); + } else { + v = "" + param2.get(subField.getFieldEn()); + } + data.put(subField.getFieldEn(), param2.get(subField.getFieldEn())); + + if (subField.getIsDerivative() == 0) { +// if(subexp.indexOf("substring")>=0||subexp.indexOf("equals")>=0){ //substring(@字段A@,3,6) +// exp += subexp.substring(j, matcher.end()).replace("@"+fieldCN+"@", "'"+v+"'"); +// }else{ +// exp += subexp.substring(j, matcher.end()).replace("@"+fieldCN+"@", v); +// } + + if (subexp.contains("def main")) { + // groovy脚本替换为动态参数 + v = "_['" + subField.getFieldEn() + "']"; + exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", v); + } else { + if (subField.getValueType() == 1 || subField.getValueType() == 4) { + exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", v); + } else { + exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", "'" + v + "'"); + } + } + + } else { + + v = getExpAll(fieldCN, exp, param2); + // 存衍生字段 + if (subField.getValueType() == 1 || subField.getValueType() == 4) { + data.put(subField.getFieldEn(), Integer.valueOf(v)); + } else { + data.put(subField.getFieldEn(), v); + } + + if (subexp.contains("def main")) { + // groovy脚本替换为动态参数 + v = "_['" + subField.getFieldEn() + "']"; + } + exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", v); + } + j = matcher.end(); + } + exp += formula.substring(j, formula.length()); + Evaluator evaluator = new Evaluator(); + String b = ""; + try { +// System.out.println("========字段公式编辑设置的表达式输出:" + exp); + + if (exp.contains("def main")) { + // 执行groovy脚本 + b = groovy.execute(exp, data); +// b = String.valueOf(groovy.execute(exp, data)); + } else { + b = evaluator.evaluate(exp); + } + + } catch (EvaluationException e) { + e.printStackTrace(); + logger.error("请求异常", e); + } + + // + if (engField.getValueType().intValue() == 1 || engField.getValueType().intValue() == 2) { //数值型或者字符型字段的衍生表达式b的结果返回值0.0代表计算结果 + if (!b.equals("")) { + result = b; + if (StringUtil.isValidStr(result) && result.startsWith("'") && result.endsWith("'")) { + result = result.substring(1, result.length() - 1); + } + } + } else if (engField.getValueType().intValue() == 3) { //枚举型字段的衍生表达式b的结果返回值0.0代表false即逻辑表达式无效 + if (!b.equals("1.0") && !b.equals("0.0") && !b.equals("")) { + result = b; + if (StringUtil.isValidStr(result) && result.startsWith("'") && result.endsWith("'")) { + result = result.substring(1, result.length() - 1); + } + } + if (b.equals("1.0")) { + result = (String) formulaJson.get("fvalue"); + //result = result.substring(result.indexOf(":")+1,result.length());// a:2 取2返回 + if (isNumeric(result)) { + result = "(" + result + ")"; + } else { + result = "'" + result + "'"; + } + break; //遇到一个满足条件的则跳出循环 + } + } + + } + } + + return result; + + } + + /** + * 公式编辑里设置字段条件区域转换的通用方法 + * 输入:待转换字段的输入参数、值类型 + * 输出:输入参数所在区间对应的结果值 + */ + private String calcFieldCond(Map paramMap) { + + String fieldValue = (String) paramMap.get("fieldValue"); + Integer fieldValueType = (Integer) paramMap.get("fieldValueType"); + + String result = ""; + //[{"fieldCN":"引擎字段1-1","fieldCond":[{"inputOne":"a","inputThree":"33"},{"inputOne":"b","inputThree":"490"},{"inputOne":"c","inputThree":"50"}]}] + //[{"fieldCN":"引擎字段1-1","fieldCond":[{"inputOne":"(3,19]","inputThree":"1"},{"inputOne":"(19,200]","inputThree":"2"}]},{"fieldCN":"通用字段2贷前","fieldCond":""}] + JSONArray jsonArr = (JSONArray) paramMap.get("fieldCond"); + for (Iterator iterator = jsonArr.iterator(); iterator.hasNext(); ) { + JSONObject job = (JSONObject) iterator.next(); + String inputOne = (String) job.get("inputOne"); + String inputThree = (String) job.get("inputThree"); + + if (fieldValueType == 3) { + if (fieldValue.equals(inputOne)) { + result = inputThree; + break; + } + } else if (fieldValueType == 1 || fieldValueType == 4) { + //(40,50] + Double lv = Double.parseDouble(inputOne.substring(1, inputOne.indexOf(","))); + Double rv = Double.parseDouble(inputOne.substring(inputOne.indexOf(",") + 1, inputOne.length() - 1)); + + String exp = ""; + if (inputOne.startsWith("(") && !lv.equals("")) { + exp = fieldValue + ">" + lv; + } + if (inputOne.startsWith("[") && !lv.equals("")) { + exp = fieldValue + ">=" + lv; + } + if (inputOne.endsWith(")") && !rv.equals("")) { + if (exp.equals("")) + exp += fieldValue + "<" + rv; + else + exp += "&&" + fieldValue + "<" + rv; + } + if (inputOne.endsWith("]") && !rv.equals("")) { + if (exp.equals("")) + exp += fieldValue + "<=" + rv; + else + exp += "&&" + fieldValue + "<=" + rv; + } + + Evaluator evaluator = new Evaluator(); + String b = ""; + try { + b = evaluator.evaluate(exp); + } catch (EvaluationException e) { + e.printStackTrace(); + logger.error("请求异常", e); + } + if (b.equals("1.0")) { + result = inputThree; + break; //遇到一个满足条件的则跳出循环 + } + } + } + return result; + } + + /** + * 判断表达式的运算结果是否数值型的公共方法 + */ + public boolean isNumeric(String str) { + Pattern pattern = Pattern.compile("^(-|\\+)?\\d+(\\.\\d+)?$"); + Matcher isNum = pattern.matcher(str); + if (!isNum.matches()) { + return false; + } + return true; + } + + private Map getSqlFieldParam(Field field, Map inputParam, Map parameterMap) { + String sqlStr = field.getSqlStatement(); + // 添加动态参数 + //处理in方法中的遍历 + Pattern sqlInPattern = Pattern.compile("[\\s]*in[\\s]*\\([\\s]*#\\{([a-zA-Z0-9_\u4e00-\u9fa5()()-]+)\\}[\\s]*\\)"); + Matcher sqlInMatcher = sqlInPattern.matcher(sqlStr); + while (sqlInMatcher.find()) { + String replaceOld = sqlInMatcher.group(0); + String sqlField = sqlInMatcher.group(1); + String sqlVariable = field.getSqlVariable(); + String fieldEn = sqlField.split("\\.")[0]; + String convertStr = ""; + Object value = null; + if (StringUtils.isNotBlank(sqlVariable)) { + JSONArray sqlVariableArr = JSONArray.parseArray(sqlVariable); + for (int i = 0; i < sqlVariableArr.size(); i++) { + JSONObject sqlVariableObj = sqlVariableArr.getJSONObject(i); + if (sqlField.equals(sqlVariableObj.getString("key"))) { + value = sqlVariableObj.getJSONArray("value"); + } + } + } + if (value == null) { + if (!inputParam.containsKey(fieldEn)) { + //不存再需要重新获取 + List fieldEns = new ArrayList<>(); + fieldEns.add(fieldEn); + //查询未入参的en是否是指标库中的指标,如果是则u需要继续查找 + List fieldList = fieldService.selectFieldListByEns(fieldEns); + if (fieldList != null && !fieldList.isEmpty()) { + //查询引用到的指标 + getEngineField(fieldList, inputParam); + } + + } +// value = inputParam.get(sqlField); + value = ExecuteUtils.getObjFromMap(inputParam, sqlField); + } + if (StringUtils.isBlank(convertStr) && value != null) { + if (value instanceof String) { + convertStr = value.toString(); + } else if (value instanceof List) { + List collection = (List) value; + int size = collection.size(); + + for (int i = 0; i < size; i++) { + convertStr += ("'" + String.valueOf(collection.get(i)) + "'"); + if (i < size - 1) { + convertStr += ","; + } + } + } + } + sqlStr = sqlStr.replace(replaceOld, " in (" + convertStr + ") "); + } + Pattern pattern = Pattern.compile("#\\{[a-zA-Z0-9_\u4e00-\u9fa5()()-]+\\}"); + Matcher matcher = pattern.matcher(sqlStr); + while (matcher.find()) { + String sqlField = matcher.group(0).replace("#{", "").replace("}", ""); + String fieldEn = sqlField.split("\\.")[0]; + // sql动态参数从页面配置获取 + String sqlVariable = field.getSqlVariable(); + if (StringUtils.isNotBlank(sqlVariable)) { + JSONArray sqlVariableArr = JSONArray.parseArray(sqlVariable); + for (int i = 0; i < sqlVariableArr.size(); i++) { + JSONObject sqlVariableObj = sqlVariableArr.getJSONObject(i); + if (sqlField.equals(sqlVariableObj.getString("key"))) { + parameterMap.put(sqlField, sqlVariableObj.get("value")); + } + } + } + + // sql动态参数从变量池获取 + if (!parameterMap.containsKey(fieldEn)) { + if (!inputParam.containsKey(fieldEn)) { + //不存再需要重新获取 + List fieldEns = new ArrayList<>(); + fieldEns.add(fieldEn); + //查询未入参的en是否是指标库中的指标,如果是则u需要继续查找 + List fieldList = fieldService.selectFieldListByEns(fieldEns); + if (fieldList != null && !fieldList.isEmpty()) { + //查询引用到的指标 + getEngineField(fieldList, inputParam); + } + + } + parameterMap.put(sqlField, ExecuteUtils.getObjFromMap(inputParam, sqlField)); + } + } + Pattern pattern$ = Pattern.compile("\\$\\{[a-zA-Z0-9_\u4e00-\u9fa5()()-]+\\}"); + Matcher matcher$ = pattern$.matcher(sqlStr); + while (matcher$.find()) { + String sqlField = matcher$.group(0).replace("${", "").replace("}", ""); + String fieldEn = sqlField.split("\\.")[0]; + // sql绑定参数从页面配置获取 + String sqlVariable = field.getSqlVariable(); + String dictVariable = field.getDictVariable(); + String replaceStr = ""; + if (StringUtils.isNotBlank(sqlVariable)) { + JSONArray sqlVariableArr = JSONArray.parseArray(sqlVariable); + for (int i = 0; i < sqlVariableArr.size(); i++) { + JSONObject sqlVariableObj = sqlVariableArr.getJSONObject(i); + + if (!sqlField.equals(sqlVariableObj.getString("key"))) { + continue; + } + if (inputParam.containsKey(fieldEn)) { + replaceStr = ExecuteUtils.getObjFromMap(inputParam, sqlField).toString(); + } else if (sqlVariableObj.get("value") != null) { + replaceStr = String.valueOf(sqlVariableObj.get("value")); + } + if (StringUtils.isNotBlank(replaceStr)) { + break; + } + + } + } + if (StringUtils.isNotBlank(dictVariable)){ + JSONArray jsonArray = JSONArray.parseArray(dictVariable); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject jsonObject = jsonArray.getJSONObject(i); + if (!sqlField.equals(jsonObject.getString("key"))) { + continue; + } +// if (inputParam.containsKey(fieldEn)) { +// replaceStr = ExecuteUtils.getObjFromMap(inputParam, sqlField).toString(); +// } else +// if (jsonObject.get("value") != null) { +// switch (jsonObject.getString("type")){ +// case "date": +// try { +// replaceStr = DateUtil.format(new Date(),jsonObject.getString("value")); +// }catch (Exception e){ +// e.printStackTrace(); +// replaceStr = DateUtil.format(new Date(),"yyyyMMdd"); +// } +// break; +// default: +// replaceStr = String.valueOf(jsonObject.get("value")); +// } +// } + replaceStr = DictVariableUtils.getValueFromJsonObject(jsonObject).toString(); + if (StringUtils.isNotBlank(replaceStr)) { + break; + } + } + } + if (StringUtils.isNotBlank(replaceStr)) { + //已经取出则直接跳过 + sqlStr = sqlStr.replace("${" + sqlField + "}", replaceStr); + continue; + } + // sql动态参数从变量池获取 + if (!parameterMap.containsKey(fieldEn)) { + if (!inputParam.containsKey(fieldEn)) { + //不存再需要重新获取 + //查询未入参的en是否是指标库中的指标,如果是则u需要继续查找 + List fieldList = fieldService.selectFieldListByEns(Arrays.asList(fieldEn)); + if (fieldList != null && !fieldList.isEmpty()) { + //查询引用到的指标 + getEngineField(fieldList, inputParam); + } + } + replaceStr = ExecuteUtils.getObjFromMap(inputParam, sqlField).toString(); + } + if (StringUtils.isNotBlank(replaceStr)) { + sqlStr = sqlStr.replace("${" + sqlField + "}", replaceStr); + break; + } + } + parameterMap.put("sqlStr", sqlStr); + return parameterMap; + } + + private Object handlerSqlFieldResult(Field field, Map parameterMap) { + List> result = simpleMapper.customSelect(parameterMap); + Object resultValue = null; + if (result == null || result.size() == 0) { + resultValue = null; + } else { + //json类型的数据 + if (field.getValueType() == 6) { + String json = field.getJsonValue(); + //示例数据为数组 + if (StringUtils.isNotBlank(json) && json.startsWith("[") && json.endsWith("]")) { + resultValue = result; + } else { + resultValue = result.get(0); + } + } else if (result.size() == 1) {// 基本指标只能是一个值。所以只能取sql查出来的第一个值。否则抛出异常 + LinkedHashMap resultMap = result.get(0); + if (resultMap.size() == 1) { + for (Map.Entry entry : resultMap.entrySet()) { + Object value = entry.getValue(); + + // 防止double表示为科学计数法 + if (value instanceof Double) { + value = BigDecimal.valueOf((Double) value); + } + resultValue = value.toString(); + } + } else { + throw new RuntimeException("sql库指标计算异常,sql字段个数超出预期。查询结果resultMap:" + resultMap.toString()); + } + } else { + throw new RuntimeException("sql库指标计算异常,sql结果个数超出预期。查询结果result:" + result.toString()); + } + } + return resultValue; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/DecisionOptionsNode.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/DecisionOptionsNode.java new file mode 100644 index 0000000..6958bd6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/DecisionOptionsNode.java @@ -0,0 +1,144 @@ +package com.baoying.enginex.executor.node.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.engine.model.EngineNode; +import com.baoying.enginex.executor.node.service.CommonService; +import com.baoying.enginex.executor.node.service.EngineNodeService; +import com.baoying.enginex.executor.util.JevalUtil; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 决策选项节点 + */ +@Service +public class DecisionOptionsNode implements EngineNodeService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private CommonService commonService; + + @Override + public void getNodeField(EngineNode engineNode, Map inputParam) { + logger.info("start【获取决策选项节点指标】DecisionOptionsNode.getNodeField engineNode:{},inputParam:{}", JSONObject.toJSONString(engineNode), JSONObject.toJSONString(inputParam)); + JSONObject jsonObject = JSONObject.parseObject(engineNode.getNodeScript()); + JSONArray array = jsonObject.getJSONArray("input"); + List ids = new ArrayList<>(); + for (int i = 0; i < array.size(); i++) { + JSONObject input = array.getJSONObject(i); + Object fieldId = input.get("field_id"); + if(fieldId != null && !"".equals(fieldId.toString())){ + ids.add(Long.valueOf(fieldId.toString())); + } + } + commonService.getFieldByIds(ids, inputParam); + } + + @Override + public void runNode(EngineNode engineNode, Map inputParam, Map outMap) { + //监控信息--节点信息记录(不需要策略层面的监控) + outMap.put("nodeSnapshot",JSON.parseObject(engineNode.getNodeJson())); + JSONObject nodeInfo = new JSONObject(); + nodeInfo.put("engineNode",engineNode); + nodeInfo.put("nodeId",engineNode.getNodeId()); + nodeInfo.put("nodeName",engineNode.getNodeName()); + nodeInfo.put("nodeType",engineNode.getNodeType()); + outMap.put("nodeInfo",nodeInfo); + JSONObject jsonObject = JSONObject.parseObject(engineNode.getNodeScript()); + JSONArray inputArray = jsonObject.getJSONArray("input"); + List inputList = JSONObject.parseArray(JSONObject.toJSONString(jsonObject.getJSONArray("input")), JSONObject.class); + JSONArray conditionArray = jsonObject.getJSONArray("conditions"); + JSONObject outputJson = jsonObject.getJSONObject("output"); + + // 变量值转义 + Map variablesMap = new HashMap<>(); + for (int i = 0; i < inputArray.size(); i++) { + String input = inputArray.get(i).toString(); + JSONObject inputField = JSONObject.parseObject(input); + String field_code = inputField.getString("field_code"); + Map fieldsMap = new HashMap<>(); + fieldsMap.put(field_code, inputField.getInteger("valueType")); + variablesMap.put(field_code, inputParam.get(field_code)); + variablesMap = JevalUtil.convertVariables(fieldsMap, variablesMap); + } + + // 默认值处理 + String dicisionResult =""; + String defaultValue = outputJson.getString("defaultValue"); + if (StringUtils.isNotBlank(defaultValue)){ + dicisionResult = defaultValue; + } + // 决策条件判断 + if(conditionArray != null && conditionArray.size() > 0){ + for (int i = 0; i < conditionArray.size(); i++) { + JSONObject formulaJson = JSONObject.parseObject(conditionArray.getString(i)); + try { + boolean outfieldvalue = JevalUtil.evaluateBoolean(formulaJson.getString("formula"), variablesMap); + if (outfieldvalue) { + dicisionResult = formulaJson.getString("result"); + // 输出结果计算 + String result = formulaJson.getString("result"); + if(result.contains("{") && result.contains("}")){ + String expression = result; + Pattern pattern = Pattern.compile("\\{[a-zA-Z0-9_\u4e00-\u9fa5()()-]+\\}"); + Matcher matcher = pattern.matcher(expression); + while (matcher.find()) { + String asName = matcher.group(0).replace("{", "").replace("}", ""); + Optional inputObj = inputList.stream().filter(item -> asName.equals(item.getString("asName"))).findFirst(); + if(inputObj.isPresent()){ + String field_code = inputObj.get().getString("field_code"); + expression = expression.replaceAll(asName, field_code); + } + } + expression = expression.replaceAll("\\{", "#{"); + Double calResult = JevalUtil.evaluateNumric(expression, variablesMap); + dicisionResult = calResult.toString(); + } + + break; + } + } catch (EvaluationException e) { + e.printStackTrace(); + logger.error("请求异常", e); + } + } + } + + Map outFields = new HashMap<>(); + String outputFieldCode = outputJson.getString("field_code"); + outFields.put("fieldId", outputJson.getIntValue("field_id")); + outFields.put("fieldName", outputJson.getString("field_name")); + outFields.put("fieldCode", outputFieldCode); + outFields.put("outValue", dicisionResult); + outMap.put("result", dicisionResult); + String key = engineNode.getNodeType() + "_" + engineNode.getNodeId() + "_result"; + inputParam.put(key, dicisionResult); + inputParam.put(outputFieldCode, dicisionResult); + + JSONObject json = new JSONObject(); + json.put("nodeId", engineNode.getNodeId()); + json.put("nodeName", engineNode.getNodeName()); + json.put("outFields", JSONObject.parseObject(JSON.toJSONString(outFields))); + //监控中心===》hbase中写入结果信息 + outMap.put("nodeResult",json); + if (outMap.containsKey("decisionJson")) { + JSONArray resultJson = (JSONArray) outMap.get("decisionJson"); + resultJson.add(json); + } else { + JSONArray resultJson = new JSONArray(); + resultJson.add(json); + outMap.put("decisionJson", resultJson); + } + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/GroupNode.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/GroupNode.java new file mode 100644 index 0000000..ff0abd6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/GroupNode.java @@ -0,0 +1,107 @@ +package com.baoying.enginex.executor.node.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.common.constants.CommonConst; +import com.baoying.enginex.executor.engine.model.EngineNode; +import com.baoying.enginex.executor.node.service.CommonService; +import com.baoying.enginex.executor.node.service.EngineNodeService; +import com.baoying.enginex.executor.util.JevalUtil; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class GroupNode implements EngineNodeService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private CommonService commonService; + + @Override + public void getNodeField(EngineNode engineNode, Map inputParam) { + logger.info("start【获取分组节点指标】GroupNode.getNodeField engineNode:{},inputParam:{}", JSONObject.toJSONString(engineNode), JSONObject.toJSONString(inputParam)); + JSONObject jsonObject = JSONObject.parseObject(engineNode.getNodeScript()); + JSONArray array = jsonObject.getJSONArray("fields"); + List ids = new ArrayList<>(); + for (int i = 0; i < array.size(); i++) { + JSONObject input = array.getJSONObject(i); + Object fieldId = input.get("fieldId"); + if(fieldId != null && !"".equals(fieldId.toString())){ + ids.add(Long.valueOf(fieldId.toString())); + } + } + commonService.getFieldByIds(ids, inputParam); + } + + @Override + public void runNode(EngineNode engineNode, Map inputParam, Map outMap) { + JSONObject jsonScript = JSONObject.parseObject(engineNode.getNodeScript()); + //监控中心--节点信息记录(不需要策略层面的监控) + outMap.put("nodeSnapshot",JSONObject.parse(engineNode.getNodeJson())); + JSONObject nodeInfo = new JSONObject(); + nodeInfo.put("engineNode",engineNode); + nodeInfo.put("nodeId",engineNode.getNodeId()); + nodeInfo.put("nodeName",engineNode.getNodeName()); + nodeInfo.put("nodeType",engineNode.getNodeType()); + outMap.put("nodeInfo",nodeInfo); + try { + String nextNode = handleClassify(jsonScript, inputParam); + outMap.put("nextNode", nextNode); + JSONObject result = new JSONObject(); + result.put("nodeResult",nextNode); + outMap.put("nodeResult",result); + } catch (EvaluationException e) { + e.printStackTrace(); + logger.error("请求异常", e); + } + } + + private static String handleClassify(JSONObject jsonScript, Map inputParam) throws EvaluationException { + JSONArray conditions = jsonScript.getJSONArray("conditions"); + JSONArray fields = jsonScript.getJSONArray("fields"); + Map variablesMap = new HashMap<>(); + variablesMap.putAll(inputParam); + Map fieldsMap = new HashMap<>(); + for(int i = 0; i < fields.size(); i++){ + JSONObject jsonObject = fields.getJSONObject(i); + fieldsMap.put(jsonObject.getString("fieldCode"), jsonObject.getIntValue("valueType")); + } + JevalUtil.convertVariables(fieldsMap, variablesMap); + + String nextNode = ""; + if (conditions == null || conditions.isEmpty()) { + //TODO 如果为空,如何处理 + return nextNode; + } else { + int size = conditions.size(); + boolean flag = false; + JSONObject formula = null; + for (int i = 0; i < size; i++) { + formula = conditions.getJSONObject(i); + //公式为空,则为else条件分支 + if (CommonConst.STRING_EMPTY.equals(formula.getString("formula"))) { + //else条件 + if (nextNode.equals(CommonConst.STRING_EMPTY)) { + nextNode = formula.getString("nextNode"); + } + } else { + //正常条件分支 + flag = JevalUtil.evaluateBoolean(formula.getString("formula"), variablesMap); + if (flag) { + nextNode = formula.getString("nextNode"); + break; + } + } + } + return nextNode; + } + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/RuleSetNode.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/RuleSetNode.java new file mode 100644 index 0000000..971aacf --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/RuleSetNode.java @@ -0,0 +1,866 @@ +package com.baoying.enginex.executor.node.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.common.constants.CommonConst; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.common.ksession.KSessionPool; +import com.baoying.enginex.executor.common.model.ExpressionParam; +import com.baoying.enginex.executor.datamanage.model.Field; +import com.baoying.enginex.executor.datamanage.service.FieldService; +import com.baoying.enginex.executor.engine.model.EngineNode; +import com.baoying.enginex.executor.engine.model.InputParam; +import com.baoying.enginex.executor.engine.model.Result; +import com.baoying.enginex.executor.node.service.CommonService; +import com.baoying.enginex.executor.node.service.EngineNodeService; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.rule.consts.RuleConst; +import com.baoying.enginex.executor.rule.model.RuleInfo; +import com.baoying.enginex.executor.rule.model.RuleLoopGroupAction; +import com.baoying.enginex.executor.rule.model.RuleScriptVersion; +import com.baoying.enginex.executor.rule.model.vo.RuleConditionVo; +import com.baoying.enginex.executor.rule.model.vo.RuleVersionVo; +import com.baoying.enginex.executor.rule.service.*; +import com.baoying.enginex.executor.tactics.consts.TacticsType; +import com.baoying.enginex.executor.util.ExecuteUtils; +import com.baoying.enginex.executor.util.JevalUtil; +import com.baoying.enginex.executor.util.MD5; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import org.apache.commons.lang3.StringUtils; +import org.drools.runtime.StatefulKnowledgeSession; +import org.drools.runtime.rule.FactHandle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +@Service +public class RuleSetNode implements EngineNodeService { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private CommonService commonService; + @Autowired + private RuleFieldInfoService ruleFieldInfoService; + @Resource + private RuleService ruleService; + @Autowired + private RuleConditionService conditionService; + @Resource + private RuleScriptVersionService ruleScriptVersionService; + @Resource + private FieldService fieldService; + @Autowired + private RuleVersionService versionService; + @Autowired + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + @Autowired + private KSessionPool kSessionPool; + @Autowired + private RedisManager redisManager; + + @Override + public void getNodeField(EngineNode engineNode, Map inputParam) { + logger.info("start【获取规则集节点指标】RuleSetNode.getNodeField engineNode:{},inputParam:{}", JSONObject.toJSONString(engineNode), JSONObject.toJSONString(inputParam)); + JSONObject nodeJson = JSONObject.parseObject(engineNode.getNodeJson()); + List ids = new ArrayList<>(); + List ruleIds = new ArrayList<>(); // 基础规则集 + List versionIds = new ArrayList<>(); // 复杂规则集 + List scriptVersionIds = new ArrayList<>(); // 脚本规则集 + + JSONArray jsonArray = null; + int groupType = nodeJson.getInteger("groupType"); + if (groupType == Constants.ruleNode.MUTEXGROUP) { + jsonArray = nodeJson.getJSONObject("mutexGroup").getJSONArray("rules"); + } else { + jsonArray = nodeJson.getJSONObject("executeGroup").getJSONArray("rules"); + } + + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject ruleObj = jsonArray.getJSONObject(i); + Long versionId = ruleObj.getLong("ruleVersionId"); + Long ruleId = ruleObj.getLong("id"); + Long difficulty = ruleObj.getLong("difficulty"); + if (difficulty != null && difficulty == 3) { + scriptVersionIds.add(versionId); // 脚本式规则 + } else if (versionId != null) { + versionIds.add(versionId); // 复杂规则 + } else if (ruleId != null) { + ruleIds.add(ruleId); // 简单规则 + } + } + + //获取字段en + List fieldEnList = new ArrayList<>(); + if (!ruleIds.isEmpty()) { + fieldEnList.addAll(ruleFieldInfoService.getFieldEnList(ruleIds)); + } + if (!versionIds.isEmpty()) { + fieldEnList.addAll(conditionService.queryFieldEnByVersionIds(versionIds)); + } + if (!scriptVersionIds.isEmpty()) { + fieldEnList.addAll(ruleScriptVersionService.queryFieldEnByVersionIds(scriptVersionIds)); + } + + //筛选调那些循环或者嵌套中的字段 + fieldEnList = fieldEnList.stream().distinct().filter(f -> f != null && !f.contains(".") && !f.contains("%")).collect(Collectors.toList()); + if (fieldEnList != null && !fieldEnList.isEmpty()) { + List fieldList = fieldService.selectFieldListByEns(fieldEnList); + for (Field field : fieldList) { + ids.add(field.getId()); + } + } + + if (!ids.isEmpty()) { + commonService.getFieldByIds(ids, inputParam); + } + } + + @Override + public void runNode(EngineNode engineNode, Map inputParam, Map outMap) { + JSONObject nodeJson = JSONObject.parseObject(engineNode.getNodeJson()); + //监控中心--记录节点快照信息 + if (engineNode != null && engineNode.getSnapshot() != null) { + outMap.put("nodeSnapshot", engineNode.getSnapshot()); + } + JSONObject nodeInfo = new JSONObject(); + nodeInfo.put("engineNode", engineNode); + nodeInfo.put("nodeId", engineNode.getNodeId()); + nodeInfo.put("nodeName", engineNode.getNodeName()); + nodeInfo.put("nodeType", engineNode.getNodeType()); + outMap.put("nodeInfo", nodeInfo); + int groupType = nodeJson.getInteger("groupType") == null ? Constants.ruleNode.EXECUTEGROUP : nodeJson.getInteger("groupType"); + CopyOnWriteArrayList ruleResultList = new CopyOnWriteArrayList<>();// 规则执行结果集合 + List ruleHitList = new ArrayList<>(); // 命中的规则集合 + + // 互斥组(串行) + if (groupType == Constants.ruleNode.MUTEXGROUP) { + JSONArray jsonArray = nodeJson.getJSONObject("mutexGroup").getJSONArray("rules"); + List ruleInfoList = getRuleFromJsonArray(jsonArray); + //监控中心--循环获取策略层面的快照信息 + recordStrategySnopshot(ruleInfoList, outMap); + ruleHitList = serialRule(inputParam, outMap, ruleInfoList, ruleResultList); + } + // 执行组(并行) + else if (groupType == Constants.ruleNode.EXECUTEGROUP) { + JSONArray jsonArray = nodeJson.getJSONObject("executeGroup").getJSONArray("rules"); + List ruleInfoList = getRuleFromJsonArray(jsonArray); + //监控中心--循环获取策略层面的快照信息 + recordStrategySnopshot(ruleInfoList, outMap); + ruleHitList = parallelRule(inputParam, outMap, ruleInfoList, ruleResultList); + } + + // 终止条件处理 + terminalCondition(engineNode, nodeJson, outMap, ruleHitList); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("nodeId", engineNode.getNodeId()); + jsonObject.put("nodeName", engineNode.getNodeName()); + jsonObject.put("ruleResultList", ruleResultList); + + if (outMap.containsKey("ruleJson")) { + JSONArray resultJson = (JSONArray) outMap.get("ruleJson"); + resultJson.add(jsonObject); + } else { + JSONArray resultJson = new JSONArray(); + resultJson.add(jsonObject); + outMap.put("ruleJson", resultJson); + } + int hitSize = 0; + double scoreSum = 0d; + for (Map map : ruleResultList) { + Object ruleScore = map.get("ruleScore"); + Object ruleResult = map.get("ruleResult"); + if (null != ruleResult && "命中".equals(ruleResult)) { + hitSize++; + if (null != ruleScore) { + try { + scoreSum += Double.valueOf(ruleScore.toString()); + } catch (Exception e) { + continue; + } + } + } + } + String hitKey = "" + engineNode.getNodeType() + "_" + engineNode.getNodeId() + "_size"; + String scoreKey = "" + engineNode.getNodeType() + "_" + engineNode.getNodeId() + "_score"; + inputParam.put(hitKey, hitSize); + inputParam.put(scoreKey, scoreSum); + //监控中心==》记录节点输出结果 + //记录整个规则集中的所有规则的命中情况,以及总的统计次数 放到输出变量池 + JSONObject nodeResult = new JSONObject(); + nodeResult.put("ruleResultList", ruleResultList); + nodeResult.put("hitNum", hitSize); + nodeResult.put("scoreTotal", scoreSum); + outMap.put("nodeResult", nodeResult); + } + + /** + * 监控中心--获取策略层面快照信息 + * + * @param ruleInfoList + * @param outMap + */ + private void recordStrategySnopshot(List ruleInfoList, Map outMap) { + JSONArray jsonObject = new JSONArray(); + ruleInfoList.stream().forEach(ruleInfo -> { + if (ruleInfo.getVersion().getSnapshot() != null) { + jsonObject.add(ruleInfo.getVersion().getSnapshot()); + + } + }); + JSONObject jsonObject1 = new JSONObject(); + jsonObject1.put("snopshot", jsonObject); + outMap.put("strategySnopshot", jsonObject1); + } + + /** + * 串行执行规则 + * + * @param inputParam + * @param outMap + * @param ruleInfoList + * @param ruleResultList + * @return + */ + private List serialRule(Map inputParam, Map outMap, List ruleInfoList, CopyOnWriteArrayList ruleResultList) { + logger.info("请求参数--串行执行规则" + "map:" + JSONObject.toJSONString(inputParam)); + List resultList = new ArrayList<>(); + for (int i = 0; i < ruleInfoList.size(); i++) { + RuleInfo rule = ruleInfoList.get(i); + boolean hitFlag = executeByDifficulty(inputParam, outMap, rule, ruleResultList); + if (hitFlag) { + resultList.add(rule); + break; + } + } + return resultList; + } + + + /** + * 并行执行规则 + * + * @param inputParam + * @param outMap + * @param ruleInfoList + * @param ruleResultList + * @return + */ + private List parallelRule(Map inputParam, Map outMap, List ruleInfoList, CopyOnWriteArrayList ruleResultList) { + logger.info("请求参数--并行执行规则" + "map:" + JSONObject.toJSONString(inputParam)); + List resultList = new ArrayList<>(); + List> futureList = new ArrayList<>(); + for (int i = 0; i < ruleInfoList.size(); i++) { + final int index = i; + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + RuleInfo rule = ruleInfoList.get(index); + boolean hitFlag = executeByDifficulty(inputParam, outMap, rule, ruleResultList); + if (hitFlag) { + return rule; + } else { + return null; + } + }, threadPoolTaskExecutor); + + futureList.add(future); + } + + for (CompletableFuture future : futureList) { + try { + RuleInfo rule = future.get(); + if (rule != null) { + resultList.add(rule); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + return resultList; + } + + /** + * 根究规则类型选择执行 + * + * @param inputParam + * @param outMap + * @param rule + * @param ruleResultList + * @return + */ + private boolean executeByDifficulty(Map inputParam, Map outMap, RuleInfo rule, CopyOnWriteArrayList ruleResultList) { + boolean hitFlag = false; + if (rule.getDifficulty() == 1) { + hitFlag = executeBasicRule(inputParam, outMap, rule, ruleResultList); + } else if (rule.getDifficulty() == 2) { + hitFlag = executeComplexRule(inputParam, outMap, rule, ruleResultList); + } else if (rule.getDifficulty() == 3) { + hitFlag = executeScriptRule(inputParam, outMap, rule, ruleResultList); + } + return hitFlag; + } + + /** + * 执行复杂规则 + * + * @param input + * @param output + * @param rule + * @param ruleResultList + * @return + */ + public boolean executeComplexRule(Map input, Map output, RuleInfo rule, CopyOnWriteArrayList ruleResultList) { + boolean hitFlag = false; + //获取需要执行的整个规则。 +// RuleVo rule = ruleService.queryByVersionId(ruleId); +// Long versionId = rule.getVersionId(); +// if (versionId==null){ +// return false; +// } +// RuleVersionVo ruleVersion = versionService.queryByVersionId(versionId); + RuleVersionVo ruleVersion = rule.getVersion(); + if (ruleVersion == null) { + return false; + } + + //取出本规则的条件列表 + Map ruleMap = new HashMap<>(); + ruleMap.put("ruleId", rule.getId()); + ruleMap.put("ruleVersionId",ruleVersion.getId()); + ruleMap.put("ruleCode", rule.getCode()); + ruleMap.put("ruleName", rule.getName()); + ruleMap.put("versionCode", ruleVersion.getVersionCode()); + ruleMap.put("versionDesc", ruleVersion.getDescription()); + ruleMap.put("desc", rule.getDescription()); + ruleMap.put("ruleResult", "未命中"); + + //获取规则需要执行的condition逻辑。 + RuleConditionVo ruleCondition = ruleVersion.getRuleConditionVo(); + //传入输入参数、中间变量、输出参数和需要执行的condition逻辑获取执行结果 + Map temp = JSON.parseObject(JSON.toJSONString(input), Map.class); + boolean result = this.executeRuleCondition(temp, output, ruleCondition); + String resultFieldEn = ruleVersion.getResultFieldEn(); + if (resultFieldEn == null || "".equals(resultFieldEn)) { + resultFieldEn = "rule_2_"+rule.getId()+"_"+ruleVersion.getId()+"_hitResult"; + } + String scoreFieldEn = ruleVersion.getScoreFieldEn(); + if (StringUtils.isNotBlank(scoreFieldEn)){ + scoreFieldEn = "rule_2_"+rule.getId()+"_"+ruleVersion.getId()+"_score"; + } + input.put(resultFieldEn, "未命中"); + //根据执行的最终结果处理此规则输出内容 + List fieldList = new ArrayList<>(); + JSONObject resultJson = new JSONObject(); + if (result) { + ruleMap.put("ruleResult", "命中"); + ruleMap.put("ruleScore", rule.getScore()); + JSONObject scoreJson = new JSONObject(); + resultJson.put(resultFieldEn, "命中"); + fieldList.add(resultJson); +// if (StringUtils.isNotBlank(ruleVersion.getScoreFieldEn())) { + scoreJson.put(scoreFieldEn, ruleVersion.getScore()); + fieldList.add(scoreJson); + input.put(scoreFieldEn, ruleVersion.getScore()); +// } + input.put(resultFieldEn, "命中"); + //处理此规则需要输出的内容 + fieldList.addAll(ruleService.setComplexRuleOutput(ruleVersion.getId(), temp, input, TacticsType.OutType.SUCCESS_OUT)); + ruleMap.put("fieldList", fieldList); + hitFlag = true; + } else { + resultJson.put(resultFieldEn, "未命中"); + ruleMap.put("ruleScore", 0); + input.put(scoreFieldEn,0); + fieldList.add(resultJson); + fieldList.addAll(ruleService.setComplexRuleOutput(ruleVersion.getId(), temp, input, TacticsType.OutType.FAIL_OUT)); + ruleMap.put("fieldList", fieldList); + } + ruleResultList.add(ruleMap); + return hitFlag; + } + + //执行规则的条件 + private boolean executeRuleCondition(Map input, Map output, RuleConditionVo ruleCondition) { + Integer conditionType = ruleCondition.getConditionType(); + boolean result = false; + switch (conditionType) { + //关系条件节点 &&和|| + case RuleConst.RELATION_CONDITION: + //循环结果的条件 + case RuleConst.LOOP_RESULT_CONDITION: + case RuleConst.CONDITION_RESULT_CONDITION: + result = executeRelation(input, output, ruleCondition); + break; + //表达式条件节点 + case RuleConst.EXPRESSION_CONDITION: + result = executeExpression(input, output, ruleCondition); + break; + //循环条件根节点 + case RuleConst.LOOP_CONDITION: + result = executeLoop(input, output, ruleCondition); + break; + //条件组根节点 + case RuleConst.CONDITION_GROUP_CONDITION: + result = executeCondGroup(input, output, ruleCondition); + break; + } + return result; + } + + //执行条件组 + private boolean executeCondGroup(Map input, Map output, RuleConditionVo ruleCondition) { + //取出子条件 + List children = ruleCondition.getChildren(); + //存储命中条数 + int hitNum = 0; + if (children == null) { + return false; + } + //执行条件组中条件列表,命中则添加命中条数 + for (RuleConditionVo child : children) { + boolean childResult = executeRuleCondition(input, output, child); + if (childResult) { + hitNum++; + } + } + //获取条件组命中条件,为null直接不命中 + RuleConditionVo condGroup = ruleCondition.getCondGroupResultCondition(); + if (condGroup == null) { + return false; + } + //传入命中条件和组内命中条数执行并返回 + Map map = new HashMap<>(); + //将命中条数存入map然后判断执行结果 + map.put("hitNum", hitNum); + return executeRuleCondition(map, output, condGroup); + } + + //关系条件节点 &&和|| + private boolean executeRelation(Map input, Map output, RuleConditionVo ruleCondition) { + //获取关系逻辑 + String logical = ruleCondition.getLogical(); + //处理子逻辑 + List children = ruleCondition.getChildren(); + + boolean result = false; + switch (logical) { + case "||": + result = false; + for (RuleConditionVo child : children) { + boolean childResult = executeRuleCondition(input, output, child); + if (childResult) { + return true; + } + } + break; + case "&&": + result = true; + for (RuleConditionVo child : children) { + boolean childResult = executeRuleCondition(input, output, child); + if (!childResult) { + return false; + } + } + break; + } + return result; + } + + //表达式条件节点 + private boolean executeExpression(Map input, Map output, RuleConditionVo ruleCondition) { + String executionLogic = ruleCondition.getExecutionLogic(); + boolean result = false; + ExpressionParam expressionParam = new ExpressionParam(); + //复制执行的关键参数到统一入参 + BeanUtils.copyProperties(ruleCondition, expressionParam); + result = ExecuteUtils.getExpressionResult(expressionParam, input); + return result; + } + + //循环条件根节点 + private boolean executeLoop(Map input, Map output, RuleConditionVo ruleCondition) { + List children = ruleCondition.getChildren(); + String fieldEn = ruleCondition.getFieldEn(); + + //对循环中每个条件进行处理 + String[] split = fieldEn.split("\\."); + //从map中取元素返回最终取到的对象 + Object obj = ExecuteUtils.getObjFromMap(input, fieldEn); + List arrayList = new ArrayList(); + if (obj != null) { + arrayList.addAll(JSON.parseObject(JSON.toJSONString(obj), ArrayList.class)); + } + //取不到这个数组 + if (arrayList.isEmpty()) { + return false; + } + //拼接当前对象的key + String currentKey = "%" + split[split.length - 1] + "%"; + for (RuleConditionVo child : children) { + List loopGroupActions = child.getLoopGroupActions(); + // 调用for循环条件下的操作,并且将其存入input中 + for (RuleLoopGroupAction loopGroupAction : loopGroupActions) { + this.initLoopGroupAction(loopGroupAction, input); + } + } + for (Object currentObj : arrayList) { + //将循环时的当前对象存入input + input.put(currentKey, currentObj); + //循环执行当前for中的每个判断单元 + for (RuleConditionVo child : children) { + if (executeRuleCondition(input, output, child)) { + List loopGroupActions = child.getLoopGroupActions(); + // 调用for循环条件下的操作,并且将其存入input中 + for (RuleLoopGroupAction loopGroupAction : loopGroupActions) { + this.saveLoopGroupAction(loopGroupAction, input); + } + } + } + } + //计算for的返回结果 + RuleConditionVo loopResultCondition = ruleCondition.getLoopResultCondition(); + boolean result = executeRuleCondition(input, output, loopResultCondition); + return result; + } + + //保存循环规则的动作 + private void saveLoopGroupAction(RuleLoopGroupAction loopGroupAction, Map input) { + Integer actionType = loopGroupAction.getActionType(); + String actionKey = loopGroupAction.getActionKey(); + String actionValue = loopGroupAction.getActionValue(); + if (actionType == null) { + return; + } + switch (actionType) { + case RuleConst.LOOP_GROUP_ACTION_TYPE_SUM: + Integer count = 1; + if (input.containsKey(actionKey) && StringUtils.isNumeric(ExecuteUtils.getObjFromMap(input, actionKey).toString())) { + count = count + Integer.parseInt(ExecuteUtils.getObjFromMap(input, actionKey).toString()); + } + input.put(actionKey, count); + break; + case RuleConst.LOOP_GROUP_ACTION_TYPE_ASSIGNMENT: + //赋值待添加 + break; + case RuleConst.LOOP_GROUP_ACTION_TYPE_OUT_CONST: + input.put(actionKey, actionValue); + break; + case RuleConst.LOOP_GROUP_ACTION_TYPE_OUT_VARIABLE: + input.put(actionKey, ExecuteUtils.getObjFromMap(input, actionValue)); + break; + } + } + + + private void initLoopGroupAction(RuleLoopGroupAction loopGroupAction, Map input){ + Integer actionType = loopGroupAction.getActionType(); + String actionKey = loopGroupAction.getActionKey(); + String actionValue = loopGroupAction.getActionValue(); + if (actionType == null) { + return; + } + switch (actionType) { + case RuleConst.LOOP_GROUP_ACTION_TYPE_SUM: + input.put(actionKey, 0); + break; + case RuleConst.LOOP_GROUP_ACTION_TYPE_ASSIGNMENT: + //赋值待添加 + break; + case RuleConst.LOOP_GROUP_ACTION_TYPE_OUT_CONST: + input.put(actionKey, ""); + break; + case RuleConst.LOOP_GROUP_ACTION_TYPE_OUT_VARIABLE: + input.put(actionKey,new HashSet<>()); + break; + } + } + /** + * 执行基础规则(drools) + * + * @param map + * @param outMap + * @param rule + * @param ruleResultList + * @return + */ + private boolean executeBasicRule(Map map, Map outMap, RuleInfo rule, CopyOnWriteArrayList ruleResultList) { + boolean hitFlag = false; + StatefulKnowledgeSession kSession = null; + String keyMd5 = null; + try { + String ruleString = rule.getContent().replace("\\r\\n", "\r\n"); + ruleString = ruleString.replace("\\t", "\t"); + keyMd5 = CommonConst.DROOLS_KSESSION_KEY_PREFIX + MD5.GetMD5Code(ruleString); + redisManager.set(keyMd5, ruleString, 120); + kSession = kSessionPool.borrowObject(keyMd5); + + List resultList = new ArrayList<>(); + InputParam inputParam = new InputParam(); + inputParam.setInputParam(map); + inputParam.setResult(resultList); + FactHandle fact = kSession.insert(inputParam); + kSession.fireAllRules(); + kSession.retract(fact); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("ruleId", rule.getId()); + jsonObject.put("ruleCode", rule.getCode()); + jsonObject.put("ruleName", rule.getName()); + jsonObject.put("desc", rule.getDescription()); + String resultEn = rule.getResultFieldEn(); + if (StringUtils.isBlank(resultEn)){ + resultEn = "rule_1_"+rule.getId()+"_hitResult"; + } + String scoreEn = rule.getScoreFieldEn(); + if (StringUtils.isBlank(resultEn)){ + scoreEn = "rule_1_"+rule.getId()+"_score"; + } + if (resultList.size() > 0) { + jsonObject.put("ruleResult", "命中"); + jsonObject.put("ruleScore", rule.getScore()); + map.put(scoreEn, rule.getScore()); + map.put(resultEn, "命中"); + + List fieldList = new ArrayList<>(); + JSONObject resultJson = new JSONObject(); + JSONObject scoreJson = new JSONObject(); + resultJson.put(rule.getResultFieldEn(), "命中"); + scoreJson.put(rule.getScoreFieldEn(), rule.getScore()); + fieldList.add(resultJson); + fieldList.add(scoreJson); + if (null != rule && null != rule.getId()) { + fieldList.addAll(ruleService.setBaseRuleOutput(rule.getId(), map)); + jsonObject.put("fieldList", fieldList); + } + ruleResultList.add(jsonObject); + hitFlag = true; + + } else { + // 未命中返回信息 + jsonObject.put("ruleResult", "未命中"); + map.put(resultEn, "未命中"); + map.put(scoreEn, 0); + ruleResultList.add(jsonObject); + } + } catch (Exception e) { + e.printStackTrace(); + logger.error("请求异常", e); + } finally { + if (keyMd5 != null && kSession != null) { + kSessionPool.returnObject(keyMd5, kSession); + } + } + return hitFlag; + } + + /** + * 终止条件判断 + * + * @param engineNode + * @param inputParam + * @param outMap + * @param ruleHitList + */ + private void terminalCondition(EngineNode engineNode, Map inputParam, Map outMap, List ruleHitList) { + if (StringUtils.isBlank(engineNode.getNodeScript())) { + return; + } + JSONObject nodeScript = JSONObject.parseObject(engineNode.getNodeScript()); + JSONObject terminationInfo = nodeScript.getJSONObject("terminationInfo"); + JSONArray selectedRule = terminationInfo.getJSONArray("selectedRule"); + String conditions = terminationInfo.getString("conditions"); + if (!selectedRule.isEmpty()) { + if (!selectedRule.isEmpty()) { + List selectedRuleList = JSONObject.parseArray(JSONObject.toJSONString(selectedRule), JSONObject.class); + // 查找已选规则中命名的规则集合 + List selectedHitRules = new ArrayList<>(); + for (JSONObject jsonObject : selectedRuleList) { + Optional rule = ruleHitList.stream().filter(item -> item.getId().equals(jsonObject.getLong("id"))).findFirst(); + if (rule.isPresent()) { + selectedHitRules.add(rule.get()); + } + } + + int totalSize = selectedHitRules.size(); // 规则命名个数 + double totalScore = selectedHitRules.stream().mapToDouble(RuleInfo::getScore).sum(); // 规则总得分 + String sizeKey = engineNode.getNodeType() + "_" + engineNode.getNodeId() + "_terminal_size"; + String scoreKey = engineNode.getNodeType() + "_" + engineNode.getNodeId() + "_terminal_score"; + Map variablesMap = new HashMap<>(); + variablesMap.put(sizeKey, totalSize); + variablesMap.put(scoreKey, totalScore); + + ExecuteUtils.terminalCondition(engineNode,inputParam,outMap,variablesMap); + } + } + } + + private List getRuleFromJsonArray(JSONArray jsonArray) { + List ruleIds = new ArrayList<>(); + Map map = new HashMap<>(); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject ruleObj = jsonArray.getJSONObject(i); + Long versionId = ruleObj.getLong("ruleVersionId"); + Long ruleId = ruleObj.getLong("id"); + if (ruleId != null) { + ruleIds.add(ruleId); + if (versionId != null) { + map.put(ruleId, versionId); + } + } + } + + List ruleInfoList = ruleService.getRuleList(ruleIds); + for (RuleInfo ruleInfo : ruleInfoList) { + if (ruleInfo.getDifficulty() == 2 || ruleInfo.getDifficulty() == 3) { + Long versionId = map.get(ruleInfo.getId()); + ruleInfo.setVersionId(versionId); + if (versionId != null) { + switch (ruleInfo.getDifficulty()) { + case 2: + RuleVersionVo ruleVersionVo = versionService.queryById(versionId); + ruleInfo.setVersion(ruleVersionVo); + ruleInfo.setScore(ruleVersionVo.getScore()); + break; + case 3: + RuleScriptVersion ruleScriptVersion = ruleScriptVersionService.queryById(versionId); + ruleInfo.setScriptVersion(ruleScriptVersion); + ruleInfo.setVersion(JSON.parseObject(JSON.toJSONString(ruleScriptVersion), RuleVersionVo.class)); + ruleInfo.setScore(0); + break; + } + } else { + ruleInfo.setScore(0); + } + } + } + + return ruleInfoList; + } + + /** + * 执行脚本规则 + * + * @param inputParam + * @param outMap + * @param rule + * @param ruleResultList + * @return + */ + private boolean executeScriptRule(Map inputParam, Map outMap, RuleInfo rule, CopyOnWriteArrayList ruleResultList) { + boolean hitFlag = false; + RuleScriptVersion scriptVersion = rule.getScriptVersion(); + if (RuleConst.ScriptType.GROOVY.equals(rule.getScriptType())&&RuleConst.ScriptType.GROOVY.equals( scriptVersion.getScriptType())) { + //groovy脚本执行 + //取出需要执行的版本 + if (scriptVersion == null || StringUtils.isBlank(scriptVersion.getScriptContent())) { + return false; + } + //取出脚本内容 + String scriptContent = scriptVersion.getScriptContent(); + //取出本规则集的规则列表 + Map ruleMap = new HashMap<>(); + ruleMap.put("ruleId", rule.getId()); + ruleMap.put("ruleCode", rule.getCode()); + ruleMap.put("ruleName", rule.getName()); + ruleMap.put("versionCode", scriptVersion.getVersionCode()); + ruleMap.put("versionDesc", scriptVersion.getDescription()); + ruleMap.put("desc", rule.getDescription()); + ruleMap.put("ruleResult", "未命中"); + + + String resultFieldEn = "hitResult"; + String resultEn = "rule_"+rule.getDifficulty()+"_"+rule.getId()+"_"+scriptVersion.getId()+"_hitResult"; + String scoreEn = "rule_"+rule.getDifficulty()+"_"+rule.getId()+"_"+scriptVersion.getId()+"_score"; + inputParam.put(resultEn, "未命中"); + inputParam.put(scoreEn,0); + //根据执行的最终结果处理此规则输出内容 + List fieldList = new ArrayList<>(); + JSONObject resultJson = new JSONObject(); + try { + Object resp = ExecuteUtils.getObjFromScript(inputParam, scriptContent); + String result = "未命中"; + JSONObject executeResult = null; + int ruleScore = 0; + if (resp instanceof HashMap) { + Map resultMap = (HashMap) resp; + executeResult = JSON.parseObject(JSON.toJSONString(resultMap)); + ruleScore = executeResult.getIntValue("ruleScore"); + result = executeResult.getString(resultFieldEn); + JSONArray fieldListJson = executeResult.getJSONArray("fieldList"); + JSONObject updateInputMap = executeResult.getJSONObject("updateInputMap"); + if (fieldListJson != null) { + fieldList = fieldListJson.toJavaList(Object.class); + List list = new ArrayList(); + for (Object o : fieldList) { + if (o!=null&& o instanceof Map){ + Map map = ExecuteUtils.handleGroovyResult((Map) o); + list.add(map); + } + } + fieldList = list; + } + + if (executeResult != null) { + ruleMap.put("ruleResult", result); + ruleMap.put("ruleScore", ruleScore); + resultJson.put(resultFieldEn, result); + fieldList.add(resultJson); + inputParam.put(resultFieldEn, result); + //处理此规则需要输出的内容 + ruleMap.put("fieldList", fieldList); + } + if ("命中".equals(result)) { + hitFlag = true; + inputParam.put(resultEn,"命中"); + inputParam.put(scoreEn,ruleScore); + } + //更新入参 + if (updateInputMap!=null&&!updateInputMap.isEmpty()){ + Set> entries = ExecuteUtils.handleGroovyResult(updateInputMap).entrySet(); + for (Map.Entry entry : entries) { + inputParam.put(entry.getKey(),entry.getValue()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + logger.error("脚本规则集执行错误:{}" + e); + } + ruleResultList.add(ruleMap); + } + return hitFlag; + } + +// public static void main(String[] args) { +//// HashMap result = new HashMap<>(); //规则执行的返回值 +//// int ruleScore = 0; //规则命中时得分 +//// String hitResult = "未命中"; //命中结果,可选类型为:命中、未命中 +//// HashMap updateInputMap = new HashMap<>(); //用于更新入参的map,此map中的所有内容将被更新到入参中,key重复的将被覆盖。 +//// ArrayList> fieldList = new ArrayList<>(); //用于存放输出字段的值 +//// //自定义代码区域,根据需要书写逻辑代码 +//// +//// +//// +//// //返回固定格式的结果用于后续执行 +//// result.put("hitResult",hitResult); +//// result.put("ruleScore",ruleScore); +//// result.put("fieldList",fieldList); +//// result.put("updateInputMap",updateInputMap); +//// return result; +// } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/SandboxProportionNode.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/SandboxProportionNode.java new file mode 100644 index 0000000..3a10a88 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/node/service/impl/SandboxProportionNode.java @@ -0,0 +1,114 @@ +package com.baoying.enginex.executor.node.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.engine.model.EngineNode; +import com.baoying.enginex.executor.engine.model.Sandbox; +import com.baoying.enginex.executor.node.service.EngineNodeService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +@Service +public class SandboxProportionNode implements EngineNodeService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void getNodeField(EngineNode engineNode, Map inputParam) { + + } + + @Override + public void runNode(EngineNode engineNode, Map inputParam, Map outMap) { + if (null != engineNode.getNodeScript()) { + List list = JSON.parseArray(engineNode.getNodeScript(), Sandbox.class); + //监控中心-- 节点配置信息记录(不需要策略层面的监控) + JSONObject nodeSnapshot = new JSONObject(); + nodeSnapshot.put("nodeSnapshot",JSON.parseArray(engineNode.getNodeJson())); + outMap.put("nodeSnapshot",nodeSnapshot); + JSONObject nodeInfo = new JSONObject(); + nodeInfo.put("engineNode",engineNode); + nodeInfo.put("nodeId",engineNode.getNodeId()); + nodeInfo.put("nodeName",engineNode.getNodeName()); + nodeInfo.put("nodeType",engineNode.getNodeType()); + outMap.put("nodeInfo",nodeInfo); + int num = 0;//随机生成的数 + int startNum = 0; + int endNum = 0; + for (int i = 0; i < list.size(); i++) { + Sandbox sandbox = list.get(i); + endNum = startNum + sandbox.getProportion(); + if (num == 0) + num = getRandoms(0, sandbox.getSum(), 1)[0]; + int[] range = getRandoms(startNum, endNum, sandbox.getProportion()); + for (int j = 0; j < range.length; j++) { + if (range[j] == num) { + if (StringUtils.isBlank(sandbox.getNextNode())) { + List sblist = JSON.parseArray(engineNode.getNodeJson(), Sandbox.class); + for (Sandbox sb : sblist) { + if (sb.getSandbox() == sandbox.getSandbox()) { + sandbox.setNextNode(sb.getNextNode()); + break; + } + } + } + + outMap.put("nextNode", sandbox.getNextNode()); + JSONObject nodeResult = new JSONObject(); + nodeResult.put("nodeResult",sandbox.getNextNode()); + outMap.put("nodeResult",nodeResult); + break; + } + } + startNum = endNum; + } + } + } + + /** + * 根据min和max随机生成count个不重复的随机数组 + * + * @param min + * @param max + * @param count + * @return int[] + */ + public int[] getRandoms(int min, int max, int count) { + int[] randoms = new int[count]; + List listRandom = new ArrayList(); + + if (count > (max - min + 1)) { + return null; + } + // 将所有的可能出现的数字放进候选list + for (int i = min; i < max; i++) { + listRandom.add(i); + } + // 从候选list中取出放入数组,已经被选中的就从这个list中移除 + for (int i = 0; i < count; i++) { + int index = getRandom(0, listRandom.size() - 1); + randoms[i] = listRandom.get(index); + listRandom.remove(index); + } + + return randoms; + } + + /** + * 根据min和max随机生成一个范围在[min,max]的随机数,包括min和max + * + * @param min + * @param max + * @return int + */ + public int getRandom(int min, int max) { + Random random = new Random(); + return random.nextInt(max - min + 1) + min; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisManager.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisManager.java new file mode 100644 index 0000000..f7dc255 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisManager.java @@ -0,0 +1,683 @@ +package com.baoying.enginex.executor.redis; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.PropertyNamingStrategy; +import com.alibaba.fastjson.parser.ParserConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import redis.clients.jedis.*; + +import java.util.*; + +@Component +public class RedisManager { + // 0-表示永远不过期 + private int expire = 0; + + @Autowired + private JedisPool jedisPool; + + public RedisManager() {} + + /** + * @Title: init + * @Description: 初始化方法,用来初始化 + * @param 设定文件 + * @return void 返回类型 + * @throws + */ + public void init() { + } + + /** + * @Title: get + * @Description: 根据key来获得一条特定的缓存数据 + * @param @param key 序列化后的key + * @param @return 设定文件 + * @return byte[] 返回类型 + * @throws + */ + public byte[] get(byte[] key) { + byte[] value = null; + Jedis jedis =null; + try { + jedis = jedisPool.getResource(); + value = jedis.get(key); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + jedis.close(); + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + + /** + * @Title: get + * @Description: 根据key来获得一条特定的缓存数据 + * @param @param key string类型的key + * @param @return 设定文件 + * @return String 返回类型 + * @throws + */ + public String get(String key) { + String value = null; + Jedis jedis = jedisPool.getResource(); + try { + value = jedis.get(key); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + /** + * @Title: set + * @Description: 向redis数据库中缓存数据,key,value都为byte[](已经序列化) + * @param @param key + * @param @param value + * @param @return 设定文件 + * @return byte[] 返回类型 + * @throws + */ + public byte[] set(byte[] key, byte[] value) { + Jedis jedis = jedisPool.getResource(); + try { + jedis.set(key, value); + if (this.expire != 0) { + jedis.expire(key, this.expire); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + /** + * @Title: set + * @Description: 向redis数据库中缓存数据,key,value为字符串类型,缓存时间为永不过期 + * @param @param key + * @param @param value + * @param @return 设定文件 + * @return String 返回类型 + * @throws + */ + public String set(String key, String value) { + Jedis jedis = jedisPool.getResource(); + try { + jedis.set(key, value); + if (this.expire != 0) { + jedis.expire(key, this.expire); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + /** + * @Title: set + * @Description: 向redis数据库中缓存数据,key,value都为byte[](已经序列化) + * @param @param key + * @param @param value + * @param @param expire 0为永不过期,其他时间则会设置对应的过期时间 + * @param @return 设定文件 + * @return byte[] 返回类型 + * @throws + */ + public byte[] set(byte[] key, byte[] value, int expire) { + Jedis jedis = null; + try { + jedis = jedisPool.getResource(); + jedis.set(key, value); + if (expire != 0) { + jedis.expire(key, expire); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + /** + * @Title: set + * @Description: 向redis数据库中缓存数据,key,value都为字符串的类型 + * @param @param key + * @param @param value + * @param @param expire 0为永不过期,其他时间则会设置对应的过期时间 + * @param @return 设定文件 + * @return String 返回类型 + * @throws + */ + public String set(String key, String value, int expire) { + Jedis jedis = jedisPool.getResource(); + try { + jedis.set(key, value); + if (expire != 0) { + jedis.expire(key, expire); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + /** + * @Title: del + * @Description: 根据byte数组(已经序列化的key)来删除redis数据库中缓存的数据 + * @param @param key 设定文件 + * @return void 返回类型 + * @throws + */ + public void del(byte[] key) { + Jedis jedis = jedisPool.getResource(); + try { + jedis.del(key); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + } + + /** + * @Title: del + * @Description: 根据特定的string类型的key来删除redis数据库中的缓存数据 + * @param @param key 设定文件 + * @return void 返回类型 + * @throws + */ + public void del(String key) { + Jedis jedis = jedisPool.getResource(); + try { + jedis.del(key); + } finally { + if (jedis != null) { + jedis.close(); + } + } + } + + /** + * @Title: flushDB + * @Description: 清除指定redis数据库中的数据 + * @param 设定文件 + * @return void 返回类型 + * @throws + */ + public void flushDB() { + Jedis jedis = jedisPool.getResource(); + try { + jedis.flushDB(); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + } + + /** + * @Title: dbSize + * @Description: 获得redis缓存数据的大小 + * @param @return 设定文件 + * @return Long 返回类型 + * @throws + */ + public Long dbSize() { + Long dbSize = 0L; + Jedis jedis = jedisPool.getResource(); + try { + dbSize = jedis.dbSize(); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return dbSize; + } + + /** + * @Title: keys + * @Description: 根据泛型来查询所有符合泛型的缓存数据 + * @param @param pattern + * @param @return 设定文件 + * @return Set 返回类型 + * @throws + */ + public Set keys(String pattern) { + Set keys = null; + Jedis jedis = jedisPool.getResource(); + try { + keys = jedis.keys(pattern.getBytes()); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return keys; + } + + /** + * @Title: dels + * @Description: 根据提供的泛型来删除reids中缓存的数据 + * @param @param pattern 设定文件 + * @return void 返回类型 + * @throws + */ + public void dels(String pattern) { + Set keys = null; + Jedis jedis = jedisPool.getResource(); + try { + keys = jedis.keys(pattern.getBytes()); + Iterator ito = keys.iterator(); + while (ito.hasNext()) { + jedis.del(ito.next()); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + } + + + + // ------------------------------------ 缓存改造 ----------------------------------- + + /** + * hash类型 添加键值对 + * + * @param key + * @param field + * @param value + * @return + */ + public Long hset(String key, String field, String value) { + Jedis jedis = jedisPool.getResource(); + Long result = null; + try { + result = jedis.hset(key, field, value); + if (this.expire != 0) { + jedis.expire(key, this.expire); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return result; + } + + /** + * hash类型 获取所有键值对 + * + * @param key + * @return + */ + public Map hgetAll(String key) { + Map value = null; + Jedis jedis = jedisPool.getResource(); + try { + value = jedis.hgetAll(key); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + public ScanResult scan(String cursor, ScanParams params) { + ScanResult value = null; + Jedis jedis = jedisPool.getResource(); + try { + value = jedis.scan(cursor, params); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return value; + } + + public List scan(String key) { + List result = null; + // 游标初始值为0 + String cursor = ScanParams.SCAN_POINTER_START; + ScanParams scanParams = new ScanParams(); + scanParams.match(key);// pattern + scanParams.count(1000); + while (true) { + //使用scan命令获取数据,使用cursor游标记录位置,下次循环使用 + ScanResult scanResult = scan(cursor, scanParams); + cursor = scanResult.getStringCursor();// 返回0 说明遍历完成 + List list = scanResult.getResult(); + if (result == null) { + result = new ArrayList<>(); + } + result.addAll(list); + if ("0".equals(cursor)) { + break; + } + } + return result; + } + + /** + * hash类型 模糊查询 + * + * @param keyPattern + * @param clazz + * @param + * @return + */ + public List scanHash(String keyPattern, Class clazz) { + List result = null; + List keys = scan(keyPattern); + for (String key : keys) { + Map map = hgetAll(key); + if (result == null) { + result = new ArrayList<>(); + } + result.add(JSONObject.parseObject(JSONObject.toJSONString(map), clazz)); + } + return result; + } + + /** + * list类型 从尾部添加元素 + * + * @param key + * @param strings + * @return + */ + public Long rpush(String key, String... strings) { + Jedis jedis = jedisPool.getResource(); + Long result = null; + try { + result = jedis.rpush(key, strings); + if (this.expire != 0) { + jedis.expire(key, this.expire); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return result; + } + + /** + * list类型 获取链表中从start到end的元素的值 + * (start、end可为负数,若为-1则表示链表尾部的元素) + * + * @param key + * @param start + * @param end + * @return + */ + public List lrange(String key, long start, long end) { + Jedis jedis = jedisPool.getResource(); + List result = null; + try { + result = jedis.lrange(key, 0, -1); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return result; + } + + /** + * set类型 添加成员 + * + * @param key + * @param members + * @return + */ + public Long sadd(String key, String... members) { + Jedis jedis = jedisPool.getResource(); + Long result = null; + try { + result = jedis.sadd(key, members); + if (this.expire != 0) { + jedis.expire(key, this.expire); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return result; + } + + /** + * set类型 获取所有的成员 + * + * @param key + * @return + */ + public Set smembers(String key) { + Jedis jedis = jedisPool.getResource(); + Set result = null; + try { + result = jedis.smembers(key); + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + return result; + } + + /** + * 根据主键查询对象 + * + * @param key + * @param clazz + * @param + * @return + */ + public T getByPrimaryKey(String key, Class clazz) { + long start = System.currentTimeMillis(); + ParserConfig.getGlobalInstance().propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; + T result = null; + Map map = hgetAll(key); + if (map != null) { + result = JSONObject.parseObject(JSONObject.toJSONString(map), clazz); + } + long end = System.currentTimeMillis(); + System.out.println("============= getByPrimaryKey ======== 耗时:" + (end -start)); + return result; + } + + /** + * 根据外键查询对应的集合 + * + * @param key + * @param clazz + * @param + * @return + */ + public List getByForeignKey(String key, Class clazz) { + long start = System.currentTimeMillis(); + ParserConfig.getGlobalInstance().propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; + List result = null; + Set members = smembers(key); + if (members != null) { + result = hgetAllBatchByPrimaryKeys(new ArrayList<>(members), clazz); + } + long end = System.currentTimeMillis(); + System.out.println("============= getByForeignKey ======== 耗时:" + (end -start)); + return result; + } + + /** + * pipeline批量获取hash数据 + * + * @param keys 主键列表 + * @param clazz + * @param + * @return + */ + public List hgetAllBatchByPrimaryKeys(List keys, Class clazz) { + long start = System.currentTimeMillis(); + Jedis jedis = jedisPool.getResource(); + List result = null; + try { + Pipeline pipelined = jedis.pipelined(); + for (String key : keys) { + pipelined.hgetAll(key); + } + // 阻塞拿到所有返回信息 + List objectList = pipelined.syncAndReturnAll(); + + for (Object object : objectList) { + if (result == null) { + result = new ArrayList<>(); + } + result.add(JSONObject.parseObject(JSONObject.toJSONString(object), clazz)); + } + } catch (Exception e) { + //释放redis对象 + jedisPool.returnBrokenResource(jedis); + e.printStackTrace(); + } finally { + //返还到连接池 + if (jedis != null) { + jedisPool.returnResource(jedis); + } + } + long end = System.currentTimeMillis(); + System.out.println("============= hgetAllBatchByPrimaryKeys ======== 耗时:" + (end -start)); + return result; + } + + /** + * pipeline批量获取hash数据 + * @param keys 外键列表 + * @param clazz + * @param + * @return + */ + public List hgetAllBatchByForeignKeys(List keys, Class clazz) { + long start = System.currentTimeMillis(); + List result = null; + for (String key : keys) { + List keyResult = getByForeignKey(key, clazz); + if(keyResult == null){ + continue; + } + if (result == null) { + result = new ArrayList<>(); + } + result.addAll(keyResult); + } + long end = System.currentTimeMillis(); + System.out.println("============= hgetAllBatchByForeignKeys ======== 耗时:" + (end -start)); + return result; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisPipeline.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisPipeline.java new file mode 100644 index 0000000..3bf52b3 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisPipeline.java @@ -0,0 +1,46 @@ +//package com.baoying.enginex.executor.redis; +// +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.dao.DataAccessException; +//import org.springframework.data.redis.connection.RedisConnection; +//import org.springframework.data.redis.core.RedisCallback; +//import org.springframework.data.redis.core.RedisOperations; +//import org.springframework.data.redis.core.RedisTemplate; +//import org.springframework.data.redis.core.SessionCallback; +//import org.springframework.stereotype.Component; +// +//import java.util.List; +// +//@Component +//public class RedisPipeline { +// +// @Autowired +// private RedisTemplate redisTemplate; +// +// public List hGetAllBatch(List keys) { +// List list = redisTemplate.executePipelined(new RedisCallback() { +// @Override +// public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { +// for(String key : keys){ +// redisConnection.hGetAll(key.getBytes()); +// } +// return null; +// } +// }); +// return list; +// } +// +// public List usePipeline(List keys) { +// List list = redisTemplate.executePipelined(new SessionCallback() { +// @Override +// public Object execute(RedisOperations redisOperations) throws DataAccessException { +// for(String key : keys){ +// redisOperations.opsForHash().entries(null); +// } +// return null; +// } +// }); +// return list; +// } +// +//} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisUtils.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisUtils.java new file mode 100644 index 0000000..0354020 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/redis/RedisUtils.java @@ -0,0 +1,49 @@ +package com.baoying.enginex.executor.redis; + +import com.baoying.enginex.executor.canal.TableEnum; + +import java.util.List; +import java.util.stream.Collectors; + +public class RedisUtils { + + /** + * 获取主键redis key + * @param tableName + * @param primaryId + * @return + */ + public static String getPrimaryKey(String tableName, Object primaryId){ + String key = "primary_key" + ":" + tableName + ":" + primaryId; + return key; + } + + public static String getPrimaryKey(TableEnum tableEnum, Object primaryId){ + return getPrimaryKey(tableEnum.getTableName(), primaryId); + } + + public static List getPrimaryKey(TableEnum tableEnum, List primaryIds){ + List keys = primaryIds.stream().map(item -> getPrimaryKey(tableEnum, item)).collect(Collectors.toList()); + return keys; + } + + /** + * 获取外键redis key + * @param tableName + * @param foreignId + * @return + */ + public static String getForeignKey(String tableName, Object foreignId){ + String key = "foreign_key" + ":" + tableName + ":" + foreignId; + return key; + } + + public static String getForeignKey(TableEnum tableEnum, Object foreignId){ + return getForeignKey(tableEnum.getTableName(), foreignId); + } + + public static List getForeignKey(TableEnum tableEnum, List foreignIds){ + List keys = foreignIds.stream().map(item -> getForeignKey(tableEnum, item)).collect(Collectors.toList()); + return keys; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConditionConst.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConditionConst.java new file mode 100644 index 0000000..7960f1e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConditionConst.java @@ -0,0 +1,12 @@ +package com.baoying.enginex.executor.rule.consts; + +public class RuleConditionConst { + + public static final long DEFAULT_CONDITION_PARENT_ID = 0;//根节点父ID + + public static final String LOOP_RULE_LOGICAL = "for";//循环规则的逻辑符号 + public static final int LOOP_RULE_RESULT_CONDITION = 4;//循环规则的结果条件 + + public static final String CONDITION_GROUP_LOGICAL = "condGroup";//条件组的逻辑符号 + public static final int CONDITION_GROUP_RESULT_CONDITION = 6;//条件组的结果条件 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConst.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConst.java new file mode 100644 index 0000000..177144a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleConst.java @@ -0,0 +1,28 @@ +package com.baoying.enginex.executor.rule.consts; + +public class RuleConst { + + public static final int CONST_TYPE = 1;//常量 + public static final int VARIABLE_TYPE = 2;//变量 + + public static final int RELATION_CONDITION = 1;//关系节点表示&&或者|| + public static final int EXPRESSION_CONDITION = 2;//表达式条件 + public static final int LOOP_CONDITION = 3;//循环条件 + public static final int LOOP_RESULT_CONDITION = 4;//循环规则条件 + public static final int CONDITION_GROUP_CONDITION = 5;//条件组节点 + public static final int CONDITION_RESULT_CONDITION = 6;//条件组节点 + + + public static final int LOOP_GROUP_ACTION_TYPE_SUM = 1;//循环中求和 + public static final int LOOP_GROUP_ACTION_TYPE_ASSIGNMENT = 2;//赋值 + + public static final int LOOP_GROUP_ACTION_TYPE_OUT_VARIABLE = 3;//输出变量 + public static final int LOOP_GROUP_ACTION_TYPE_OUT_CONST = 4;//输出常量 + + public static class ScriptType { + public static final String GROOVY = "groovy"; + public static final String PYTHON = "python"; + public static final String JAVASCRIPT = "js"; + + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleRunnerConst.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleRunnerConst.java new file mode 100644 index 0000000..89f54f5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/consts/RuleRunnerConst.java @@ -0,0 +1,86 @@ +package com.baoying.enginex.executor.rule.consts; + +import java.util.Map; + +public class RuleRunnerConst { + public static final String RULE_FILE_HEAD = " package com.baoying.enginex.executor.drools \\r\\n" + + " import java.util.Map;\\r\\n" + + " import java.util.List;\\r\\n" + + " import java.util.ArrayList;\\r\\n" + + " import java.util.HashMap;\\r\\n" + + " import com.baoying.enginex.executor.engine.model.InputParam;\\r\\n" + + " import com.baoying.enginex.executor.engine.model.Result;\\r\\n" + + " import com.baoying.enginex.executor.engine.model.EngineRule;\\r\\n"; + public static final String RULE_NAME_PREFIX = " rule \t"; + public static final String RULE_SALIENCE_PREFIX = "\\r\\n salience\\t "; + public static final String RULE_CONDITION_PREFIX = "\\r\\n when \\r\\n"; + public static final String CONDITION_DETAIL_PREFIX = "\\t $inputParam : InputParam();\\r\\n Map"; + public static final String CONDITION_DETAIL_SUFFIX = " \t from $inputParam.inputParam;\\r\\n"; + public static final String RULE_DISPOSE_PREFIX = "\\t then \\r\\n"; + public static final String DISPOSE_PREFIX = + "\\t List resultList =$inputParam.getResult();\\r\\n" + + "\\t Result result =new Result(); \\r\\n" + + "\\t result.setResultType(\""; + public static final String DISPOSE_INFIX = "\"); \\r\\n" + + "\\t result.setVersionCode(\""; + public static final String DISPOSE_SUFFIX = "\"); \\r\\n" + + "\\t Map map =new HashMap<>(); \\r\\n"; + + public static final String SCORE_PREFIX = "\\t map.put(\"score\","; + public static final String SCORE_SUFFIX = "); \\r\\n"; + + public static final String RULE_END = "\\t result.setMap(map); \\r\\n" + + " resultList.add(result); \\r\\n" + + "\\t $inputParam.setResult(resultList); \\r\\n" + + " end\\r\\n"; + + public static final int DEFAULT_TYPE = 1; + + //拼装规则执行的content + public static String fitRuleContent(String code, Integer salience, String rule, Integer type, Integer score, Map contentMap) { + String content = ""; + if (salience == null || salience < 0) { + salience = 0; + } + if (type == null) { + type = DEFAULT_TYPE; + } + content += RULE_FILE_HEAD + + RULE_NAME_PREFIX + code + + RULE_SALIENCE_PREFIX + salience + + RULE_CONDITION_PREFIX + + CONDITION_DETAIL_PREFIX + rule + + CONDITION_DETAIL_SUFFIX + + RULE_DISPOSE_PREFIX + + DISPOSE_PREFIX + type + + DISPOSE_INFIX + code+ + DISPOSE_SUFFIX; + if (score!=null){ + content += SCORE_PREFIX+score+SCORE_SUFFIX; + } + + if (contentMap!=null&&!contentMap.isEmpty()){ + for (String s : contentMap.keySet()) { + content+="\\t\\t map.put(\""+s+"\",\""+contentMap.get(s)+"\");\\n"; + } + + } + content += RULE_END; + + return content; + } + +// public static void main(String[] args) { +// String versionCode = "rule1"; +// Integer salience = null; +// String rule = "age>10"; +// Integer type = null; +// Integer score = 10; +// Map contentMap = new HashMap<>(); +// contentMap.put("a","a"); +// contentMap.put("b","b"); +// contentMap.put("c","c"); +// String s = fitContent(versionCode, salience, rule, type, score,contentMap); +// System.out.println(s); +// } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleConditionInfoMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleConditionInfoMapper.java new file mode 100644 index 0000000..3504c81 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleConditionInfoMapper.java @@ -0,0 +1,14 @@ +package com.baoying.enginex.executor.rule.mapper; + + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.rule.model.RuleConditionInfo; +import org.apache.ibatis.annotations.Mapper; + + +@Mapper +public interface RuleConditionInfoMapper extends BaseMapper { + +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.java new file mode 100644 index 0000000..fe19561 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.java @@ -0,0 +1,14 @@ +package com.baoying.enginex.executor.rule.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.rule.model.RuleFieldInfo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface RuleFieldInfoMapper extends BaseMapper { + + List getFieldEnList(@Param("ruleIds") List ruleIds); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.xml new file mode 100644 index 0000000..2f96e06 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleFieldInfoMapper.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.java new file mode 100644 index 0000000..ad4ffbe --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.java @@ -0,0 +1,16 @@ +package com.baoying.enginex.executor.rule.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.rule.model.RuleInfo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +@Mapper +public interface RuleInfoMapper extends BaseMapper { + + List getRuleList(@Param("ruleIds")List ruleIds); +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.xml new file mode 100644 index 0000000..2ad03d9 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleInfoMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, name, code, description, priority, parent_id, author, user_id, organ_id, engine_id, status, type, is_non, content, created, updated, rule_type, rule_audit, score, score_field_en, last_logical,difficulty,script_type,result_field_en + + + + + + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleLoopGroupActionMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleLoopGroupActionMapper.java new file mode 100644 index 0000000..411fcda --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleLoopGroupActionMapper.java @@ -0,0 +1,9 @@ +package com.baoying.enginex.executor.rule.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.rule.model.RuleLoopGroupAction; + +public interface RuleLoopGroupActionMapper extends BaseMapper { + +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleScriptVersionMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleScriptVersionMapper.java new file mode 100644 index 0000000..e6fdaa9 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleScriptVersionMapper.java @@ -0,0 +1,15 @@ +package com.baoying.enginex.executor.rule.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.rule.model.RuleScriptVersion; +import org.apache.ibatis.annotations.Mapper; + + +@Mapper +public interface RuleScriptVersionMapper extends BaseMapper { + + + +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleVersionMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleVersionMapper.java new file mode 100644 index 0000000..a77463b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/mapper/RuleVersionMapper.java @@ -0,0 +1,9 @@ +package com.baoying.enginex.executor.rule.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.rule.model.RuleVersion; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface RuleVersionMapper extends BaseMapper { +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleConditionInfo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleConditionInfo.java new file mode 100644 index 0000000..5796e03 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleConditionInfo.java @@ -0,0 +1,66 @@ +package com.baoying.enginex.executor.rule.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baoying.enginex.executor.rule.model.vo.RuleConditionVo; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors +@TableName("`t_rule_condition`") +public class RuleConditionInfo + implements Serializable { + private static final long serialVersionUID = -55937038829167862L; + + @TableId(type = IdType.AUTO) + private Long id;//主键ID + + private String logical;//关系节点的逻辑符号:&&(并关系),||(或关系) + + private Long fieldId;//表达式节点对应的字段id + private String fieldEn;//字段en + private String fieldType;//字段的类型:1中间变量 2 入参 + private String operator;//表达式节点的操作符 + private Integer variableType;//变量类型,1常量 2变量 + private String fieldValue;//表达式节点对应字段的限定值 + private String executionLogic;//执行逻辑 + private Long ruleId;//规则表的id + private Long versionId;//规则版本id + private Long parentId;//父节点的id + + private Integer conditionType;//规则节点的类型:1-关系节点,2-表达式节点 + + private Date createTime;//创建时间 + + private Date updateTime;//修改时间 + + @TableField(exist = false) + private String insertTempId;//插入时临时id + + @TableField(exist = false) + private String TempParentId;//插入时临时父id + + @TableField(exist = false) + private Integer valueType;//字段值类型 + + @TableField(exist = false) + private List loopGroupActions = new ArrayList<>();//循环组对应的条件 + + @TableField(exist = false) + private RuleConditionVo loopResultCondition;//for对应的结果条件的计算条件树 + @TableField(exist = false) + private RuleConditionVo condGroupResultCondition;//条件组对应的结果计算条件树 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleFieldInfo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleFieldInfo.java new file mode 100644 index 0000000..f98a773 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleFieldInfo.java @@ -0,0 +1,38 @@ +package com.baoying.enginex.executor.rule.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("t_rule_field") +public class RuleFieldInfo implements Serializable { + private static final long serialVersionUID = -132321133324148507L; + @TableId(type = IdType.AUTO) + private Long id; + + private String logical;//逻辑运算符 + + private String operator;//运算符 + + private String fieldValue;//字段值 + + private Long ruleId;//关联的规则的id + + private String fieldId;//关联的字段的id + @TableField(exist = false) + private String fieldEn;//关联的字段的英文名称 + @TableField(exist = false) + private String field;//字段内容 + @TableField(exist = false) + private Integer valueType;//关联的字段的值类型 + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleInfo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleInfo.java new file mode 100644 index 0000000..a6a9923 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleInfo.java @@ -0,0 +1,86 @@ +package com.baoying.enginex.executor.rule.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baoying.enginex.executor.rule.model.vo.RuleVersionVo; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors +@TableName("t_rule") +public class RuleInfo implements Serializable { + private static final long serialVersionUID = -13354133324148507L; + @TableId(type = IdType.AUTO) + private Long id;//主键 + + private String name;//规则名称 + + private String code;//规则代码 + + private String description;//规则描述 + + private Integer priority;//规则优先级 + + private Long parentId;//父节点id + + private Long author;//创建人id + + private Long userId;//修改人id + + private Long organId;//组织id + + private Integer engineId; + + private Integer status;//状态 0 :停用 ,1 : 启用,-1:删除 + + private Integer type;//规则类型 0 : 系统的规则 1:组织的规则 2: 引擎的规则 + + private Integer isNon;//逻辑关系“非”,0:否 ,1:是 + + private String content;//规则具体内容 + + private Date created; + + private Date updated; + + private Integer ruleType;//0硬性拒绝规则1加减分规则 + + private Integer ruleAudit; + + private Integer score;//得分 + + private String lastLogical;//逻辑关系符 + + private Integer difficulty;//规则难度:1-简单规则,2复杂规则 + + private String scriptType; + + private String resultFieldEn;//存放结果的字段en + + private String scoreFieldEn;//存放是否命中的字段 + + @TableField(exist = false) + private String authorName;//创建人名称,需要去其他表查询 + @TableField(exist = false) + private List parentIds; + @TableField(exist = false) + private Long versionId;//执行版本的id + @TableField(exist = false) + private RuleVersionVo version; + @TableField(exist = false) + private List ruleScriptVersionList; + @TableField(exist = false) + private RuleScriptVersion scriptVersion; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleLoopGroupAction.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleLoopGroupAction.java new file mode 100644 index 0000000..0136dc6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleLoopGroupAction.java @@ -0,0 +1,55 @@ +package com.baoying.enginex.executor.rule.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("t_rule_loop_group_action") +public class RuleLoopGroupAction implements Serializable { + private static final long serialVersionUID = -47370055295043749L; + /** + * 循环组动作表主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + /** + * 对应条件表中for的id + */ + private Long conditionForId; + /** + * 对应条件表中条件id + */ + private Long conditionGroupId; + /** + * 动作类型 1-求和,2-赋值,3-输出输出变量,4-输出常量 + */ + private Integer actionType; + /** + * 动作的key + */ + private String actionKey; + /** + * 动作的value + */ + private String actionValue; + /** + * 创建时间 + */ + private Date createTime; + /** + * 修改时间 + */ + private Date updateTime; + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleScriptVersion.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleScriptVersion.java new file mode 100644 index 0000000..f0e9393 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleScriptVersion.java @@ -0,0 +1,74 @@ +package com.baoying.enginex.executor.rule.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("`t_rule_script_version`") +public class RuleScriptVersion implements Serializable { + private static final long serialVersionUID = -78864192587533951L; + /** + * 主键:规则版本id + */ + @TableId(type = IdType.AUTO) + private Long id; + /** + * 规则id + */ + private Long ruleId; + /** + * 版本号 + */ + private String versionCode; + /** + * 版本描述 + */ + private String description; + /** + * 状态:-1删除 ,1启用,0停用 + */ + private Integer status; + /** + * 脚本类型:groovy,python,js + */ + private String scriptType; + /** + * 脚本规则集内容json,包含脚本内容和脚本所用字段两个值 + */ + private String scriptContent; + /** + * 组织id + */ + private Long organId; + /** + * 创建者id + */ + private Long createUserId; + /** + * 修改者id + */ + private Long updateUserId; + /** + * 创建时间 + */ + private Date createTime; + /** + * 修改时间 + */ + private Date updateTime; + /** + * 规则版本配置快照 + */ + private String snapshot; + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleVersion.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleVersion.java new file mode 100644 index 0000000..50651a0 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/RuleVersion.java @@ -0,0 +1,76 @@ +package com.baoying.enginex.executor.rule.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("`t_rule_version`") +public class RuleVersion implements Serializable { + private static final long serialVersionUID = -1850194333747447612L; + /** + * 规则版本主键id + */ + @TableId( type = IdType.AUTO) + private Long id; + /** + * 规则id + */ + private Long ruleId; + /** + * 规则版本号 + */ + private String versionCode; + /** + * 描述信息 + */ + private String description; + /** + * 状态:-1 删除 0停用 1启用 + */ + private Integer status; + /** + * 规则结果en(命中情况) + */ + private String resultFieldEn; + /** + * 规则得分 + */ + private Integer score; + /** + * 规则得分的en + */ + private String scoreFieldEn; + /** + * 组织id + */ + private Long organId; + /** + * 创建者id + */ + private Long createUserId; + /** + * 修改者id + */ + private Long updateUserId; + /** + * 创建时间 + */ + private Date createTime; + /** + * 修改时间 + */ + private Date updateTime; + /** + * 规则版本快照 + */ + private String snapshot; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleConditionVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleConditionVo.java new file mode 100644 index 0000000..ebcde12 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleConditionVo.java @@ -0,0 +1,56 @@ +package com.baoying.enginex.executor.rule.model.vo; + + +import com.baoying.enginex.executor.rule.model.RuleConditionInfo; +import com.baoying.enginex.executor.rule.model.RuleLoopGroupAction; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors +public class RuleConditionVo extends RuleConditionInfo { + +// private Long id;//主键ID +// +// private String logical;//关系节点的逻辑符号:&&(并关系),||(或关系) +// +// private Long fieldId;//表达式节点对应的字段id +// +// private String fieldEn;//字段en +// private String fieldType;//字段的类型:1中间变量 2 入参 +// private String operator;//表达式节点的操作符 +// private Integer variableType;//变量类型,1常量 2变量 +// private String fieldValue;//表达式节点对应字段的限定值 +// private String executionLogic;//执行逻辑 +// private Long ruleId;//规则表的id +// +// private Long parentId;//父节点的id +// +// private Integer conditionType;//规则节点的类型:1-关系节点,2-表达式节点 +// +// private Date createTime;//创建时间 +// +// private Date updateTime;//修改时间 +// +// private String insertTempId;//插入时临时id +// +// private String TempParentId;//插入时临时父id + + private List children;//规则子节点 + +// private Integer valueType;//字段类型 +// +// private List loopGroupActions = new ArrayList<>();//循环组对应的条件,循环内的组条件下的操作列表 +// +// private RuleConditionVo loopResultCondition;//for对应的结果条件的计算条件树 +// +// private RuleConditionVo condGroupResultCondition;//条件组对应的结果计算条件树 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVersionVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVersionVo.java new file mode 100644 index 0000000..8661bec --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVersionVo.java @@ -0,0 +1,19 @@ +package com.baoying.enginex.executor.rule.model.vo; + + +import com.baoying.enginex.executor.rule.model.RuleVersion; +import com.baoying.enginex.executor.tactics.model.TacticsOutput; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RuleVersionVo extends RuleVersion { + private RuleConditionVo ruleConditionVo;//规则对应的结点树 + + private List tacticsOutputList;//成功输出字段 + private List failOutputList;//失败输出字段 +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVo.java new file mode 100644 index 0000000..85805a4 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/model/vo/RuleVo.java @@ -0,0 +1,29 @@ +package com.baoying.enginex.executor.rule.model.vo; + + +import com.baoying.enginex.executor.rule.model.RuleFieldInfo; +import com.baoying.enginex.executor.rule.model.RuleInfo; +import com.baoying.enginex.executor.rule.model.RuleScriptVersion; +import com.baoying.enginex.executor.tactics.model.TacticsOutput; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors +public class RuleVo extends RuleInfo { + + private RuleConditionVo ruleConditionVo;//规则对应的结点树 + + + private List ruleFieldList;//简单规则条件列表 + + private List tacticsOutputList;//输出字段 + + private List ruleVersionList;//版本列表 + private List ruleScriptVersionList; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleConditionService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleConditionService.java new file mode 100644 index 0000000..0bc281d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleConditionService.java @@ -0,0 +1,15 @@ +package com.baoying.enginex.executor.rule.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.rule.model.RuleConditionInfo; +import com.baoying.enginex.executor.rule.model.vo.RuleConditionVo; + +import java.util.List; + + +public interface RuleConditionService extends IService { + + RuleConditionVo queryByVersionId(Long versionId); + + List queryFieldEnByVersionIds(List versionIds); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleFieldInfoService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleFieldInfoService.java new file mode 100644 index 0000000..014a3b8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleFieldInfoService.java @@ -0,0 +1,13 @@ +package com.baoying.enginex.executor.rule.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.rule.model.RuleFieldInfo; + +import java.util.List; + +public interface RuleFieldInfoService extends IService { + + List queryByRuleId(Long ruleId); + + List getFieldEnList(List ruleIds); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleLoopGroupActionService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleLoopGroupActionService.java new file mode 100644 index 0000000..cd94ef0 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleLoopGroupActionService.java @@ -0,0 +1,14 @@ +package com.baoying.enginex.executor.rule.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.rule.model.RuleLoopGroupAction; + +import java.util.List; + + +public interface RuleLoopGroupActionService extends IService { + + List getRuleLoopList(Long forId, Long conditionId); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleScriptVersionService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleScriptVersionService.java new file mode 100644 index 0000000..3012fc8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleScriptVersionService.java @@ -0,0 +1,19 @@ +package com.baoying.enginex.executor.rule.service; + + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.rule.model.RuleScriptVersion; + +import java.util.List; + + +public interface RuleScriptVersionService extends IService { + RuleScriptVersion queryById(Long id); + + List queryVersionListByRuleId(Long ruleId); + + List queryFieldEnByVersionId(Long versionId); + List queryFieldEnByVersionIds(List versionId); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleService.java new file mode 100644 index 0000000..6a75acd --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleService.java @@ -0,0 +1,28 @@ +package com.baoying.enginex.executor.rule.service; + + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.rule.model.RuleInfo; +import com.baoying.enginex.executor.rule.model.vo.RuleVo; + + +import java.util.List; +import java.util.Map; + +public interface RuleService extends IService { + + /** + * 通过ID查询单条数据 + * @param id 主键 + * @return 实例对象 + */ + RuleVo queryById(Long id); + + List setComplexRuleOutput(Long versionId, Map temp, Map input, String outType); + + List setBaseRuleOutput(Long ruleId, Map input); + + List getRuleList(List ruleIds); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleVersionService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleVersionService.java new file mode 100644 index 0000000..be7aacc --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/RuleVersionService.java @@ -0,0 +1,14 @@ +package com.baoying.enginex.executor.rule.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.rule.model.RuleVersion; +import com.baoying.enginex.executor.rule.model.vo.RuleVersionVo; + +import java.util.List; + +public interface RuleVersionService extends IService { + + RuleVersionVo queryById(Long id); + + List queryVersionListByRuleId(Long ruleId); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleConditionServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleConditionServiceImpl.java new file mode 100644 index 0000000..7104395 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleConditionServiceImpl.java @@ -0,0 +1,158 @@ +package com.baoying.enginex.executor.rule.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import com.baoying.enginex.executor.rule.consts.RuleConditionConst; +import com.baoying.enginex.executor.rule.mapper.RuleConditionInfoMapper; +import com.baoying.enginex.executor.rule.model.RuleConditionInfo; +import com.baoying.enginex.executor.rule.model.RuleLoopGroupAction; +import com.baoying.enginex.executor.rule.model.vo.RuleConditionVo; +import com.baoying.enginex.executor.rule.service.RuleConditionService; +import com.baoying.enginex.executor.rule.service.RuleLoopGroupActionService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +@Service("ruleConditionService2") +public class RuleConditionServiceImpl extends ServiceImpl implements RuleConditionService { + + @Resource + private RuleConditionInfoMapper ruleConditionInfoMapper; + @Resource + private RuleLoopGroupActionService loopGroupActionService; + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + + @Override + public RuleConditionVo queryByVersionId(Long versionId) { + if (versionId == null) { + return null; + } + //构造查询条件,查询条件列表 + RuleConditionVo result = null; + + List ruleConditionInfoList = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getForeignKey(TableEnum.T_RULE_CONDITION, versionId); + ruleConditionInfoList = redisManager.getByForeignKey(key, RuleConditionInfo.class); + } else { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(RuleConditionInfo::getVersionId, versionId); + ruleConditionInfoList = ruleConditionInfoMapper.selectList(queryWrapper); + } + + //组装为需要的树形结构 + if (ruleConditionInfoList != null) { + result = this.assemble(ruleConditionInfoList); + } + return result; + } + + @Override + public List queryFieldEnByVersionIds(List versionIds) { + List ruleConditions = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + List keys = RedisUtils.getForeignKey(TableEnum.T_RULE_CONDITION, versionIds); + ruleConditions = redisManager.hgetAllBatchByForeignKeys(keys, RuleConditionInfo.class); + + ruleConditions = ruleConditions.stream() + .filter(item -> StringUtils.isNotBlank(item.getFieldEn()) && !"1".equals(item.getFieldType())) + .collect(Collectors.toList()); + + } else { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(RuleConditionInfo::getVersionId,versionIds); + queryWrapper.isNotNull(RuleConditionInfo::getFieldEn); + queryWrapper.ne(RuleConditionInfo::getFieldType,1); + queryWrapper.select(RuleConditionInfo::getFieldEn); + ruleConditions = ruleConditionInfoMapper.selectList(queryWrapper); + } + + List result = new ArrayList<>(); + if (ruleConditions != null){ + for (RuleConditionInfo condition : ruleConditions) { + result.add(condition.getFieldEn()); + } + } + return result; + } + + //装配方法,将规则条件List装配成一个规则树并返回 + public RuleConditionVo assemble(List list) { + RuleConditionVo root = null; + //转换为Vo + List rcVoList = transferToVoList(list); + //获取根节点,根节点只有一个的时候进行操作,并且返回拼装好的规则树,否则返回null + List collect = rcVoList.stream().filter(rc -> { + return rc.getParentId() == RuleConditionConst.DEFAULT_CONDITION_PARENT_ID; + }).collect(Collectors.toList()); + if (collect.size() == 1) { + root = collect.get(0); + RuleConditionVo ruleTree = coupling(rcVoList, root); + return ruleTree; + } + return null; + } + + //耦合方法:将规则节点列表耦合规则树(),循环规则的子节点需要去查循环表获取 + private RuleConditionVo coupling(List list, RuleConditionVo root) { + List children = new ArrayList<>(); + for (RuleConditionVo rc : list) { + //处理root的子节点 + if (root.getId().equals(rc.getParentId())) { + RuleConditionVo rcVo = coupling(list, rc); + String logical = root.getLogical(); + + if (logical!=null&&!"".equals(logical)){ + switch (logical){ + //当root为for节点,则此子节点需要拼上循环动作 + case RuleConditionConst.LOOP_RULE_LOGICAL: + List loopList = loopGroupActionService.getRuleLoopList(root.getId(),rc.getId()); + rcVo.setLoopGroupActions(loopList); + if (rc.getConditionType()==RuleConditionConst.LOOP_RULE_RESULT_CONDITION){ + root.setLoopResultCondition(rcVo); + continue; + } + break; + //当root为条件组节点,则此子节点需要拼上条件组结果 + case RuleConditionConst.CONDITION_GROUP_LOGICAL: + if (rc.getConditionType()==RuleConditionConst.CONDITION_GROUP_RESULT_CONDITION){ + root.setCondGroupResultCondition(rcVo); + continue; + } + break; + } + + } + children.add(rcVo); + } + } + root.setChildren(children); + return root; + } + + //List转换为List + private List transferToVoList(List list) { + List rcVoList = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + RuleConditionVo rcVo = new RuleConditionVo(); + BeanUtils.copyProperties(list.get(i), rcVo); + rcVoList.add(rcVo); + } + return rcVoList; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleFieldInfoServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleFieldInfoServiceImpl.java new file mode 100644 index 0000000..2ee9479 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleFieldInfoServiceImpl.java @@ -0,0 +1,58 @@ +package com.baoying.enginex.executor.rule.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import com.baoying.enginex.executor.rule.mapper.RuleFieldInfoMapper; +import com.baoying.enginex.executor.rule.model.RuleFieldInfo; +import com.baoying.enginex.executor.rule.service.RuleFieldInfoService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class RuleFieldInfoServiceImpl extends ServiceImpl implements RuleFieldInfoService { + + @Resource + private RuleFieldInfoMapper ruleFieldInfoMapper; + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + + @Override + public List queryByRuleId(Long ruleId) { + RuleFieldInfo ruleFieldInfo = new RuleFieldInfo(); + ruleFieldInfo.setRuleId(ruleId); + List ruleFieldInfoList = ruleFieldInfoMapper.selectList(new QueryWrapper<>(ruleFieldInfo)); + return ruleFieldInfoList; + } + + @Override + public List getFieldEnList(List ruleIds) { + List list = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + List keys = RedisUtils.getForeignKey(TableEnum.T_RULE_FIELD, ruleIds); + List ruleFieldInfos = redisManager.hgetAllBatchByForeignKeys(keys, RuleFieldInfo.class); + Set set = ruleFieldInfos.stream().map(item -> { + String[] fieldIdArr = item.getFieldId().split("\\|"); // 587|f_hr_age + return fieldIdArr[1]; + }).collect(Collectors.toSet()); + + list = new ArrayList<>(set); + } else { + list = ruleFieldInfoMapper.getFieldEnList(ruleIds); + } + return list; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleLoopGroupActionServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleLoopGroupActionServiceImpl.java new file mode 100644 index 0000000..da28c09 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleLoopGroupActionServiceImpl.java @@ -0,0 +1,50 @@ +package com.baoying.enginex.executor.rule.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import com.baoying.enginex.executor.rule.mapper.RuleLoopGroupActionMapper; +import com.baoying.enginex.executor.rule.model.RuleLoopGroupAction; +import com.baoying.enginex.executor.rule.service.RuleLoopGroupActionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +@Service("ruleLoopGroupActionService") +public class RuleLoopGroupActionServiceImpl extends ServiceImpl implements RuleLoopGroupActionService { + @Resource + private RuleLoopGroupActionMapper ruleLoopGroupActionMapper; + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + + @Override + public List getRuleLoopList(Long forId, Long conditionId) { + List loopList = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getForeignKey(TableEnum.T_RULE_LOOP_GROUP_ACTION, forId); + loopList = redisManager.getByForeignKey(key, RuleLoopGroupAction.class); + loopList = loopList.stream().filter(item -> item.getConditionGroupId().equals(conditionId)).collect(Collectors.toList()); + } else { + RuleLoopGroupAction ruleLoopGroupAction = new RuleLoopGroupAction(); + ruleLoopGroupAction.setConditionForId(forId); + ruleLoopGroupAction.setConditionGroupId(conditionId); + loopList = ruleLoopGroupActionMapper.selectList(new QueryWrapper<>(ruleLoopGroupAction)); + } + + if (loopList==null){ + loopList = new ArrayList<>(); + } + return loopList; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleScriptVersionServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleScriptVersionServiceImpl.java new file mode 100644 index 0000000..bca56a4 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleScriptVersionServiceImpl.java @@ -0,0 +1,90 @@ +package com.baoying.enginex.executor.rule.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.baoying.enginex.executor.datamanage.model.Field; +import com.baoying.enginex.executor.rule.mapper.RuleScriptVersionMapper; +import com.baoying.enginex.executor.rule.model.RuleScriptVersion; +import com.baoying.enginex.executor.rule.service.RuleScriptVersionService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +@Service("ruleScriptVersionService") +public class RuleScriptVersionServiceImpl extends ServiceImpl implements RuleScriptVersionService { + @Resource + private RuleScriptVersionMapper ruleScriptVersionMapper; + @Autowired + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + @Override + public RuleScriptVersion queryById(Long id) { + if (id!=null){ + return this.getById(id); + } + return null; + } + + @Override + public List queryVersionListByRuleId(Long ruleId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(RuleScriptVersion::getRuleId,ruleId); + wrapper.eq(RuleScriptVersion::getStatus,1); + wrapper.orderByDesc(RuleScriptVersion::getId); + List list = this.list(wrapper); + return list; + } + + @Override + public List queryFieldEnByVersionId(Long versionId) { + + RuleScriptVersion ruleScriptVersion = this.queryById(versionId); + Set fieldEnSet = new HashSet<>(); + if (ruleScriptVersion==null){ + return new ArrayList<>(); + } + collectFieldEn(ruleScriptVersion,fieldEnSet); + return new ArrayList<>(fieldEnSet); + } + + @Override + public List queryFieldEnByVersionIds(List versionIds) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(RuleScriptVersion::getId,versionIds); + List list = this.list(wrapper); + Set fieldEnSet = new HashSet<>(); + if (list!=null&&!list.isEmpty()){ + for (RuleScriptVersion ruleScriptVersion : list) { + collectFieldEn(ruleScriptVersion,fieldEnSet); + } + } + return new ArrayList<>(fieldEnSet); + } + private void collectFieldEn(RuleScriptVersion ruleScriptVersion,Set fieldEnSet){ + String scriptContent = ruleScriptVersion.getScriptContent(); + if (StringUtils.isNotBlank(scriptContent)){ + JSONObject jsonObject = JSON.parseObject(scriptContent); + Object farr = jsonObject.get("farr"); + if (farr!=null&&!"".equals(farr)){ + List fieldList = JSONArray.parseArray(JSON.toJSONString(farr), Field.class); + fieldEnSet.addAll(fieldList.stream().map(item->{return item.getFieldEn();}).collect(Collectors.toSet())); + } + } + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleServiceImpl.java new file mode 100644 index 0000000..911e78d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleServiceImpl.java @@ -0,0 +1,107 @@ +package com.baoying.enginex.executor.rule.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import com.baoying.enginex.executor.rule.mapper.RuleInfoMapper; + +import com.baoying.enginex.executor.rule.model.RuleFieldInfo; +import com.baoying.enginex.executor.rule.model.RuleInfo; +import com.baoying.enginex.executor.rule.model.RuleScriptVersion; +import com.baoying.enginex.executor.rule.model.vo.RuleVersionVo; +import com.baoying.enginex.executor.rule.model.vo.RuleVo; +import com.baoying.enginex.executor.rule.service.RuleFieldInfoService; +import com.baoying.enginex.executor.rule.service.RuleScriptVersionService; +import com.baoying.enginex.executor.rule.service.RuleService; +import com.baoying.enginex.executor.rule.service.RuleVersionService; +import com.baoying.enginex.executor.tactics.consts.TacticsType; +import com.baoying.enginex.executor.tactics.model.TacticsOutput; +import com.baoying.enginex.executor.tactics.service.TacticsOutputService; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; + + +@Service("ruleService2") +public class RuleServiceImpl extends ServiceImpl implements RuleService { + @Resource + private RuleInfoMapper ruleInfoMapper; + @Autowired + private RuleVersionService versionService; + @Resource + private RuleFieldInfoService ruleFieldInfoService; + @Resource + private TacticsOutputService outputService; + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + @Resource + private RuleScriptVersionService ruleScriptVersionService; + + @Override + public RuleVo queryById(Long id) { + //查询规则 + RuleInfo ruleInfo = ruleInfoMapper.selectById(id); + if (ruleInfo==null){ + return null; + } + RuleVo ruleVo = new RuleVo(); + BeanUtils.copyProperties(ruleInfo, ruleVo); + Integer difficulty = ruleInfo.getDifficulty(); + switch (difficulty) { + case 1: + List list = ruleFieldInfoService.queryByRuleId(id); + List tacticsOutputList = outputService.queryByTactics(new TacticsOutput(id, TacticsType.BASE_RULE)); + ruleVo.setTacticsOutputList(tacticsOutputList); + ruleVo.setRuleFieldList(list); + break; + case 2: + //查询版本 + List ruleVersionList = versionService.queryVersionListByRuleId(id); + ruleVo.setRuleVersionList(ruleVersionList); + break; + case 3: + //脚本规则集 + List ruleScriptVersionList = ruleScriptVersionService.queryVersionListByRuleId(id); + ruleVo.setRuleScriptVersionList(ruleScriptVersionList); + break; + } + return ruleVo; + } + + @Override + public List setComplexRuleOutput(Long versionId, Map temp, Map input, String outType) { + List jsonObjectList = outputService.setOutput(new TacticsOutput(versionId, TacticsType.COMPLEX_RULE,outType), temp); + for (JSONObject jsonObject : jsonObjectList) { + input.putAll(jsonObject); + } + return jsonObjectList; + } + + @Override + public List setBaseRuleOutput(Long ruleId, Map input) { + List jsonObjectList = outputService.setOutput(new TacticsOutput(ruleId, TacticsType.BASE_RULE), input); + return jsonObjectList; + } + + @Override + public List getRuleList(List ruleIds) { + List ruleInfoList = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + List keys = RedisUtils.getPrimaryKey(TableEnum.T_RULE, ruleIds); + ruleInfoList = redisManager.hgetAllBatchByPrimaryKeys(keys, RuleInfo.class); + } else { + ruleInfoList = ruleInfoMapper.getRuleList(ruleIds); + } + return ruleInfoList; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleVersionServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleVersionServiceImpl.java new file mode 100644 index 0000000..a6f65f5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/rule/service/impl/RuleVersionServiceImpl.java @@ -0,0 +1,84 @@ +package com.baoying.enginex.executor.rule.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import com.baoying.enginex.executor.rule.mapper.RuleVersionMapper; +import com.baoying.enginex.executor.rule.model.RuleVersion; +import com.baoying.enginex.executor.rule.model.vo.RuleConditionVo; +import com.baoying.enginex.executor.rule.model.vo.RuleVersionVo; +import com.baoying.enginex.executor.rule.service.RuleConditionService; +import com.baoying.enginex.executor.rule.service.RuleVersionService; +import com.baoying.enginex.executor.tactics.consts.TacticsType; +import com.baoying.enginex.executor.tactics.model.TacticsOutput; +import com.baoying.enginex.executor.tactics.service.TacticsOutputService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class RuleVersionServiceImpl extends ServiceImpl implements RuleVersionService { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + private RuleVersionMapper versionMapper; + @Autowired + private RuleConditionService conditionService; + @Autowired + private TacticsOutputService outputService; + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + + @Override + public RuleVersionVo queryById(Long id) { + RuleVersion ruleVersion = null; + if (Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())) { + String key = RedisUtils.getPrimaryKey(TableEnum.T_RULE_VERSION, id); + ruleVersion = redisManager.getByPrimaryKey(key, RuleVersion.class); + } else { + ruleVersion = versionMapper.selectById(id); + } + + RuleVersionVo result = new RuleVersionVo(); + if (ruleVersion == null) { + return result; + } + BeanUtils.copyProperties(ruleVersion, result); + //查询ruleCondition组装成树形结构 + RuleConditionVo ruleConditionVo = conditionService.queryByVersionId(id); + List tacticsOutputList = outputService.queryByTactics(new TacticsOutput(id, TacticsType.COMPLEX_RULE,TacticsType.OutType.SUCCESS_OUT)); + List failOutputList = outputService.queryByTactics(new TacticsOutput(id, TacticsType.COMPLEX_RULE,TacticsType.OutType.FAIL_OUT)); + result.setRuleConditionVo(ruleConditionVo); + result.setTacticsOutputList(tacticsOutputList); + result.setFailOutputList(failOutputList); + return result; + } + + @Override + public List queryVersionListByRuleId(Long ruleId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(RuleVersion::getRuleId,ruleId); + queryWrapper.eq(RuleVersion::getStatus,1); + queryWrapper.orderByDesc(RuleVersion::getUpdateTime); + List ruleVersionList = versionMapper.selectList(queryWrapper); + List ruleVersionVoList = new ArrayList<>(); + for (RuleVersion ruleVersion : ruleVersionList) { + RuleVersionVo versionVo = new RuleVersionVo(); + BeanUtils.copyProperties(ruleVersion,versionVo); + ruleVersionVoList.add(versionVo); + } + return ruleVersionVoList; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.java new file mode 100644 index 0000000..cadfb7c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.java @@ -0,0 +1,26 @@ + +package com.baoying.enginex.executor.system.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.system.model.Department; + + +public interface DepartmentMapper extends BaseMapper { + + /** + * isExist:(根据相应的条件判断是否存在重复值).
+ * @author wz + * @param departmentVo 部门实体类 + * @return 返回行数 + */ + Integer isExist(Department department); + + /** + * deleteDept:(根据部门ids删除部门信息).
+ * @author wz + * @param deletIds 部门ids + */ + void deleteDept(Long[] deletIds); +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.xml new file mode 100644 index 0000000..2199b8d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/DepartmentMapper.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + INSERT INTO + manager_organization + + + org_name, + + + org_code, + + status, + updateTime + + values + + + #{deptName}, + + + #{deptCode}, + + 1, + now() + + + + + + + UPDATE manager_organization SET + + org_name = #{deptName} + + + ,org_code = #{deptCode} + + ,updateTime = now() + WHERE + status=1 + + and id = #{id} + + + + + UPDATE manager_organization SET + status=-1 + WHERE + id + IN + + #{deletIds} + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.java new file mode 100644 index 0000000..a2fa119 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.java @@ -0,0 +1,57 @@ + +package com.baoying.enginex.executor.system.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.system.model.Menu; + +import java.util.List; + + +public interface MenuMapper extends BaseMapper { + /** + * isExist:(根据相应的条件判断是否存在重复值).
+ * @author wz + * @param menu 菜单实体类 + * @return 返回行数 + */ + Integer isExist(Menu menu); + + /** + * deleteRole:(根据菜单ids删除菜单信息).
+ * @author wz + * @param deletIds 菜单ids + */ + void deleteMenu(Long[] deletIds); + + /** + * selectByRole:(根据角色查询).
+ * @author wz + * @param menu + * @return 查询的菜单类 + */ + List selectByRole(Menu menu); + + /** + * deleteMenuRole:(删除菜单角色关联表).
+ * @author wz + * @param menu 菜单实体类 + */ + void deleteMenuRole(Menu menu); + + /** + * insertMenuRole:(添加菜单角色关联表).
+ * @author wz + * @param menu 菜单实体类 + */ + void insertMenuRole(Menu menu); + + /** + * findUserMenuByUser:(根据登录用户名查询相应授权菜单列表).
+ * @author wz + * @param loginName + * @return 根据登录用户名查询相应授权菜单列表 + */ + List findUserMenuByUser(String loginName); +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.xml new file mode 100644 index 0000000..bf94775 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/MenuMapper.xml @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO + manager_menu + + + pid, + + + name, + + + menu_code, + + + type, + + + url, + + + icon, + + status, + updateTime + + values + + + #{pid}, + + + #{name}, + + + #{menuCode}, + + + #{type}, + + + #{url}, + + + #{icon}, + + 1, + now() + + + + + + + UPDATE manager_menu SET + + pid = #{pid}, + + + name = #{name}, + + + type = #{type}, + + + url = #{url}, + + + icon = #{icon}, + + + name = #{name} + + + ,menu_code = #{menuCode} + + ,updateTime = now() + WHERE + status=1 + + and id = #{id} + + + + + UPDATE manager_menu SET + status=-1 + WHERE + id + IN + + #{deletIds} + + + + + DELETE FROM + manager_menu_role + WHERE + 1=1 + + and role_code = #{roleCode} + + + + + INSERT INTO + manager_menu_role + + + menu_id, + + + role_code + + + values + + + #{id}, + + + #{roleCode} + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.java new file mode 100644 index 0000000..9e07db8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.java @@ -0,0 +1,26 @@ + +package com.baoying.enginex.executor.system.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.system.model.Role; + + +public interface RoleMapper extends BaseMapper { + + /** + * isExist:(根据相应的条件判断是否存在重复值).
+ * @author wz + * @param role 角色实体类 + * @return 返回行数 + */ + Integer isExist(Role role); + + /** + * deleteRole:(根据角色ids删除角色信息).
+ * @author wz + * @param deletIds 角色ids + */ + void deleteRole(Long[] deletIds); +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.xml new file mode 100644 index 0000000..6452fb1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/RoleMapper.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + INSERT INTO + manager_role + + + name, + + + role_code, + + status, + updateTime + + values + + + #{name}, + + + #{roleCode}, + + 1, + now() + + + + + + + UPDATE manager_role SET + + name = #{name} + + + ,role_code = #{roleCode} + + ,updateTime = now() + WHERE + status=1 + + and id = #{id} + + + + + UPDATE manager_role SET + status=-1 + WHERE + id + IN + + #{deletIds} + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..5b78b9d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.java @@ -0,0 +1,111 @@ + +package com.baoying.enginex.executor.system.mapper; + +import com.baoying.enginex.executor.system.model.SysMenu; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + + +public interface SysMenuMapper { + + /** + * 查询所有资源 + * @return + */ + public List getAllSysMenu(); + /** + * 查询单条资源 + * @param id + * @return + */ + public SysMenu findById(long id); + /** + * 新增资源 + * @param sysMenu + * @return + */ + public int createSysMenu(SysMenu sysMenu); + /** + * 修改资源 + * @param sysMenu + * @return + */ + public int updateSysMenu(SysMenu sysMenu); + /** + * 删除资源 + * @param id + * @return + */ + public int deleteSysMenu(long id); + + /** + * 修改资源状态 + * @param id + * @param idList + * @return + */ + public int updateStatus(@Param("status") int status, @Param("list") List list); + + /** + * 通过父节点查询子菜单 + * @param id + * @return + */ + public List findChildByParent(long parentId); + + /** + * 通过角色菜单关系表及父节点查询菜单 + */ + public List findRoleMenuByParent(@Param("parentId") long parentId, @Param("roleId") long roleId); + + /** + * 保存角色菜单关系 + */ + public int insertRoleMenu(@Param("roleId") long roleId, @Param("list") List list); + + /** + * 删除角色菜单关系(实现修改) + */ + public int deleteRoleMenu(long roleId); + /** + * 分配资源树 + * @param roleId + * @return + */ + public List findTreeList(long roleId); + /** + * 获取所有启用资源 + * @return + */ + public List getAllValidMenu(); + + /** + * 获取引擎资源树 + */ + public List> getEngineTree(long roleId); + + /** + * 保存引擎树 + */ + public int insertRoleEngine(@Param("roleId") long roleId, @Param("list") List list); + /** + * 删除引擎树(实现修改) + */ + public int deleteRoleEngine(long roleId); + + /** + * 验证唯一性 + */ + public List validateMenuOnly(SysMenu sysMenu); + /** + * 验证是否有查看菜单的权限 + */ + public List validatePermission(@Param("roleId") long roleId, @Param("url") String url); + /** + * 验证是否有该引擎的权限 + */ + public List> validateEnginePermission(@Param("roleId") long roleId, @Param("id_e") String id_e); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.xml new file mode 100644 index 0000000..71ae388 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysMenuMapper.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + resource_id, user_id, name, code, url, parent_id, des, birth, icon, status + + + + + + + + + + + + + + + + + + + + + + + + + + insert into t_resource (user_id, name, code, url, parent_id, des, birth, icon) + values (#{userId}, #{name}, #{code}, #{url}, #{parentId}, #{des}, now(), #{icon}) + + + + + insert into t_role_resource_rel (role_id, resource_id) + values + + (#{roleId}, #{ids}) + + + + + + insert into t_role_engine (role_id, id_str) + values + + (#{roleId}, #{ids}) + + + + + update t_resource set name=#{name}, + url=#{url}, + + des=#{des}, + + + icon=#{icon}, + + + user_id=#{userId}, + + + parent_id=#{parentId}, + + code=#{code} + where resource_id = #{id} + + + + update t_resource set status = -1 where resource_id = #{id} + + + + + delete from t_role_resource_rel where role_id = #{roleId} + + + + + delete from t_role_engine where role_id = #{roleId} + + + + update t_resource set status=#{status} + + + resource_id in + #{ids} + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.java new file mode 100644 index 0000000..c6bd78b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.java @@ -0,0 +1,57 @@ + +package com.baoying.enginex.executor.system.mapper; + +import com.baoying.enginex.executor.system.model.SysOrganization; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +public interface SysOrganizationMapper { + /** + * 查询所有公司 + * @return + */ + public List getAllSysOrganization(); + /** + * 获取所有启用公司 + * @return + */ + public List getAllValidOrgan(); + /** + * 查询单个公司 + * @param id + * @return + */ + public SysOrganization findById(long id); + /** + * 创建公司 + * @param SysOrganization + * @return + */ + public int createSysOrganization(SysOrganization SysOrganization); + /** + * 修改组织 + * @param SysOrganization + * @return + */ + public int updateSysOrganization(SysOrganization SysOrganization); + /** + * 删除组织 + * @param id + * @return + */ + public int deleteSysOrganization(long id); + + /** + * 修改公司状态(停用/启用、删除) + * @param states + * @return + */ + public int updateStatus(@Param("status") int status, @Param("list") List list); + /** + * 验证唯一性 + */ + public List validateOrganOnly(SysOrganization SysOrganization); + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.xml new file mode 100644 index 0000000..4c9805d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysOrganizationMapper.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + organ_id, name, code, email, telephone, status, birth, author,token + + + + + + + + + + + + + insert into t_organization (name, code, email, telephone, status, author, birth, token) + values (#{name}, #{code}, #{email}, #{telephone}, 1, #{author}, now(), #{token}) + + + + update t_organization set name=#{name}, + + email=#{email}, + + + telephone=#{telephone}, + + + author=#{author}, + + code=#{code} + where organ_id=#{id} + + + + update t_organization set status = -1 where organ_id=#{id} + + + + update t_organization set status=#{status} + + + organ_id in + #{ids} + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..1dd6763 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.java @@ -0,0 +1,90 @@ + +package com.baoying.enginex.executor.system.mapper; + +import com.baoying.enginex.executor.system.model.SysRole; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + + +public interface SysRoleMapper { + /** + * 获取本组织所有角色 + * @return + */ + public List getAllSysRole(long organId); + /** + * 获取所有角色 + */ + public List getAllRoles(); + /** + * 获取本组织启用的角色 + * @param organId + * @return + */ + public List getAllValidRole(@Param("organId") long organId, @Param("author") String author); + /** + * 查询本组织的单个角色 + * @param id + * @return + */ + public SysRole findById(@Param("id") long id, @Param("organId") long organId); + /** + * 查询单个角色 + * @param id + * @return + */ + public SysRole findByAId(long id); + + /** + * 创建本组织角色 + * @param sysRole + * @return + */ + public int createSysRole(SysRole sysRole); + /** + * 修改本公司角色 + * @param sysRole + * @return + */ + public int updateSysRole(SysRole sysRole); + /** + * 删除本公司角色 + * @param id + * @return + */ + public int deleteSysRole(long id); + /** + * 创建公司管理员角色 + */ + public int createOrganRole(SysRole sysRole); + /** + * 修改角色状态(停用、启用、删除) + * @param id + * @param idList + * @return + */ + public int updateStatus(@Param("status") int status, @Param("list") List list); + /** + * 根据角色查询角色所在公司 + */ + public long getOrganByRoleId(long roleId); + + /** + * 验证角色唯一性 + */ + public List validateRoleOnly(SysRole sysRole); + /** + * 查询公司管理员角色id + */ + public List getOrganRoleByAuthor(SysRole sysRole); + /** + * 删除本公司所有角色 + */ + public int deleteAllRoles(long organId); + /** + * 删除多个公司的所有角色 + */ + public int deleteRolesByOrgans(@Param("status") Integer status, @Param("list") List list); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.xml new file mode 100644 index 0000000..abddcb6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysRoleMapper.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + role_id, organ_id, role_name, role_code, role_desc, author, birth, status + + + + + + + + + + + + + + + + + + + + + + insert into t_role (role_id, organ_id, role_name, role_code, role_desc, author, birth, status) + values (#{id}, #{organId}, #{roleName}, #{roleCode}, #{roleDesc}, #{author}, now(), 1) + + + + insert into t_role (role_id, organ_id, role_name, role_code, role_desc, author, birth, status) + values (#{id}, #{organId}, #{roleName}, #{roleCode}, #{roleDesc}, #{author}, now(), 1) + + + + update t_role set + + role_desc=#{roleDesc}, + + + role_code=#{roleCode}, + + role_name=#{roleName} + where role_id = #{id} and organ_id = #{organId} + + + + update t_role set status = -1 where role_id = #{id} + + + + update t_role set status=#{status} + + + role_id in + #{ids} + + + + + + + update t_role set status = -1 where organ_id = #{organId} + + + + update t_role set status = #{status} + + + organ_id in + #{ids} + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..bf99eda --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.java @@ -0,0 +1,120 @@ + +package com.baoying.enginex.executor.system.mapper; + +import com.baoying.enginex.executor.system.model.SysUser; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + + +public interface SysUserMapper { + /** + * 查询搜索用户 + */ + public List getAllUsers(SysUser sysUser); + /** + * 查询本组织单个用户 + * @param id + * @return + */ + public SysUser findById(SysUser sysUser); + /** + * 创建本公司用户 + * @param sysUser + * @return + */ + public long createSysUser(SysUser sysUser); + /** + * 添加用户角色关系 + * @param userId + * @param roleId + * @return + */ + public int insertUserRole(@Param("userId") long userId, + @Param("roleId") long roleId, + @Param("organId") long organId); + /** + * 修改本公司用户 + * @param sysUser + * @return + */ + public int updateSysUser(SysUser sysUser); + /** + * 修改用户角色关系 + * @param sysUser + * @return + */ + public int updateUserRole(SysUser sysUser); + /** + * 删除本公司用户 + * @param id + * @return + */ + public int deleteSysUser(long id); + /** + * 修改用户状态(停用/启用/删除) + * @param states + * @return + */ + public int updateStates(@Param("status") int status, @Param("list") List list); + /** + * 通过用户id查询角色 + * @param userId + * @return + */ + public SysUser findRoleByUserId(long userId); + + /** + * 重置密码 + */ + public int resetPassword(SysUser sysUser); + + /** + * 修改密码 + */ + public int updatePassword(SysUser sysUser); + + /** + * 本公司账号员工编号唯一性 + */ + public List validateUserOnly(SysUser sysUser); + + /** + * 删除本公司的所有账号 + */ + public int deleteAllUser(long organId); + /** + * 删除本公司的用户角色关系 + */ + public int deleteAllUserRole(long organId); + /** + * 删除多个公司的所有账号 + */ + public int deleteUsersByOrgans(@Param("status") Integer status, @Param("list") List list); + /** + * 删除多个公司的用户角色关系 + */ + public int deleteUserRoleByOrgan(@Param("status") Integer status, @Param("list") List list); + /** + * 删除角色账号关联关系 + */ + public int deleteUserRoleById(long RoleId); + /** + * 查询本角色下的所有账号 + */ + public List getUserIdsByRoleId(long roleId); + /** + * 删除角色关联的所有账号 + */ + public int deleteUsersByIds(@Param("status") Integer status, @Param("list") List list); + + /** + * 批量删除角色账号关系 + */ + public int deleteBatchUserRole(@Param("status") Integer status, @Param("list") List list); + /** + * 批量查询角色关联的账号 + */ + public List getBatchUserIdsByRoleId(List list); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.xml new file mode 100644 index 0000000..99059a2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/SysUserMapper.xml @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + user_id, organ_id, employee_id, account, password, nick_name, email, cellphone, qq, latest_time, latest_ip, status, birth, author + + + + + + + + + + + + + + + + + + + + + + + insert into t_user (organ_id, employee_id, account, password, nick_name, email, cellphone, qq, latest_time, latest_ip, status, birth, author) + values (#{organId}, #{employeeId}, #{account}, #{password}, #{nickName}, #{email}, #{cellphone}, #{qq}, #{latestTime}, #{latestIp}, 1, now(), #{author}) + + + + + insert into t_user_role_rel (user_id, role_id,organ_id) + values (#{userId}, #{roleId},#{organId}) + + + + + update t_user set account=#{account}, + + password = #{password} + + + employee_id=#{employeeId}, + + + email=#{email}, + + + cellphone=#{cellphone}, + + + qq=#{qq}, + + + latest_time=#{latestTime}, + + + latest_ip=#{latestIp}, + + + birth=#{birth}, + + + author=#{author}, + + nick_name=#{nickName} + where user_id=#{id} + + + + + update t_user_role_rel set user_id=#{id}, role_id=#{sysRole.id} + where user_id=#{id} + + + + + update t_user set status=-1 + where user_id=#{id} + + + + + update t_user set status=#{status} + + + user_id in + #{ids} + + + + + + + + update t_user set password = #{password} where user_id=#{id} + + + + + update t_user set password = #{password} where user_id=#{id} + + + + + update t_user set status = -1 where organ_id=#{organId} + + + + + update t_user_role_rel set status = -1 where organ_id = #{organId} + + + + update t_user set status = #{status} + + + organ_id in + #{ids} + + + + + + + update t_user_role_rel set status = #{status} + + + organ_id in + #{ids} + + + + + + + + update t_user_role_rel set status = -1 where role_id = #{roleId} + + + + + update t_user set status = #{status} + + + user_id in + #{ids} + + + + + + + + update t_user_role_rel set status = #{status} + + + role_id in + #{ids} + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.java new file mode 100644 index 0000000..90e262f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.java @@ -0,0 +1,57 @@ +package com.baoying.enginex.executor.system.mapper; + + +import com.baoying.enginex.executor.common.mapper.BaseMapper; +import com.baoying.enginex.executor.system.model.User; +import com.baoying.enginex.executor.system.model.UserRole; + +import java.util.List; +import java.util.Set; + + +public interface UserMapper extends BaseMapper { + /** + * isExist:(根据相应的条件判断是否存在重复值).
+ * @author wz + * @param user 用户实体类 + * @return 返回行数 + */ + Integer isExist(User user); + + /** + * selectLoginInfo:(用户登录判断).
+ * @author wz + * @param user 用户实体类 + * @return User + */ + User selectLoginInfo(User user); + + /** + * deleteDept:(根据用户ids删除用户信息).
+ * @author wz + * @param user 用户id + */ + void deleteUser(User user); + + /** + * insertUserRole:(增加记录到用户角色关系表).
+ * @author wz + * @param userRolelist 用户角色关系list + */ + void insertUserRole(List userRolelist); + + /** + * deleteUserRole:(根据用户ids删除用户角色关联表信息).
+ * @author wz + * @param deletIds 用户ids + */ + void deleteUserRole(Long[] deletIds); + + /** + * findUserMenuSet:(根据用户名获取所授权的菜单).
+ * @author wz + * @param loginName + * @return 根据用户名获取所授权的菜单 + */ + Set findUserMenuSet(String loginName); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.xml b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.xml new file mode 100644 index 0000000..027cfe8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/mapper/UserMapper.xml @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO + manager_user + + + login_name, + + + name, + + + password, + + + email, + + + phone, + + + dept_code, + + status, + updateTime + + values + + + #{loginName}, + + + #{name}, + + + #{password}, + + + #{email}, + + + #{phone}, + + + #{deptCode}, + + 0, + now() + + + + + UPDATE manager_user SET + + login_name = #{loginName}, + + + name = #{name}, + + + password = #{password}, + + + email = #{email}, + + + phone = #{phone}, + + + dept_code = #{deptCode}, + + updateTime = now() + WHERE + status !=-1 + + and id = #{id} + + + + + INSERT INTO manager_user_role + (user_id,role_code) + values + + (#{item.userId},#{item.roleCode}) + + + + + + + + + DELETE FROM + manager_user_role + WHERE + user_id + IN + + #{deletIds} + + + + + UPDATE manager_user SET + status=#{status} + WHERE + id + IN + + #{item} + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Department.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Department.java new file mode 100644 index 0000000..52a60c4 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Department.java @@ -0,0 +1,119 @@ +package com.baoying.enginex.executor.system.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; + +public class Department extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + /** + * 部门id + */ + private Long id; + /** + * 部门名称 + */ + private String deptName; + /** + * 部门编号 + */ + private String deptCode; + /** + * 部门排序 + */ + private Integer deptOrder; + /** + * 状态 + */ + private Integer status; + /** + * 创建人 + */ + private String creator; + /** + * 创建时间 + */ + private String createTime; + /** + * 最后修改人 + */ + private String updater; + /** + * 最后修改时间 + */ + private String updateTime; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDeptName() { + return deptName; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptCode() { + return deptCode; + } + + public void setDeptCode(String deptCode) { + this.deptCode = deptCode; + } + + public Integer getDeptOrder() { + return deptOrder; + } + + public void setDeptOrder(Integer deptOrder) { + this.deptOrder = deptOrder; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getUpdater() { + return updater; + } + + public void setUpdater(String updater) { + this.updater = updater; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Menu.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Menu.java new file mode 100644 index 0000000..37bb55b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Menu.java @@ -0,0 +1,247 @@ +package com.baoying.enginex.executor.system.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; +import java.util.Arrays; + + +public class Menu extends BasePage implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 菜单主键id + */ + private Long id; + /** + * 父id + */ + private Long pid; + /** + * 菜单名称 + */ + private String name; + /** + * 菜单类型 + */ + private Integer type; + /** + * 菜单顺序 + */ + private Integer muneOrder; + /** + * easyui展开 + */ + private String state; + /** + * 菜单链接 + */ + private String url; + /** + * 菜单编号 + */ + private String menuCode; + /** + * 菜单图标 + */ + private String icon; + /** + * 菜单描述 + */ + private String description; + /** + * 菜单状态 + */ + private Integer status; + /** + * 创建人 + */ + private String creator; + /** + * 创建时间 + */ + private String createTime; + /** + * 最后修改人 + */ + private String updater; + /** + * 最后修改时间 + */ + private String updateTime; + + /** + * 父类 + */ + private Long _parentId; + + /** + * 角色编码 + */ + private String roleCode; + + /** + * 删除ids数组 + */ + private Long []deletIds; + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Long[] getDeletIds() { + return deletIds; + } + + public void setDeletIds(Long[] deletIds) { + this.deletIds = deletIds; + } + + public String getRoleCode() { + return roleCode; + } + + public void setRoleCode(String roleCode) { + this.roleCode = roleCode; + } + + public Long get_parentId() { + return _parentId; + } + + public void set_parentId(Long _parentId) { + this._parentId = _parentId; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getPid() { + return pid; + } + + public void setPid(Long pid) { + this.pid = pid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getMuneOrder() { + return muneOrder; + } + + public void setMuneOrder(Integer muneOrder) { + this.muneOrder = muneOrder; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMenuCode() { + return menuCode; + } + + public void setMenuCode(String menuCode) { + this.menuCode = menuCode; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getUpdater() { + return updater; + } + + public void setUpdater(String updater) { + this.updater = updater; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return "Menu [id=" + id + ", pid=" + pid + ", name=" + name + ", type=" + + type + ", muneOrder=" + muneOrder + ", state=" + state + + ", url=" + url + ", menuCode=" + menuCode + ", icon=" + icon + + ", description=" + description + ", status=" + status + + ", creator=" + creator + ", createTime=" + createTime + + ", updater=" + updater + ", updateTime=" + updateTime + + ", _parentId=" + _parentId + ", roleCode=" + roleCode + + ", deletIds=" + Arrays.toString(deletIds) + "]"; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/MenuJson.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/MenuJson.java new file mode 100644 index 0000000..a197ed6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/MenuJson.java @@ -0,0 +1,54 @@ +package com.baoying.enginex.executor.system.model; + +import java.util.List; + + +public class MenuJson { + + private String menuid; + private String icon; + private String menuname; + private String url; + private List menus; + + public String getMenuid() { + return menuid; + } + + public void setMenuid(String menuid) { + this.menuid = menuid; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getMenuname() { + return menuname; + } + + public void setMenuname(String menuname) { + this.menuname = menuname; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public List getMenus() { + return menus; + } + + public void setMenus(List menus) { + this.menus = menus; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Pager.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Pager.java new file mode 100644 index 0000000..387d61e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Pager.java @@ -0,0 +1,78 @@ +package com.baoying.enginex.executor.system.model; + +public class Pager { + + private int rows = 10; + private int totalResult; + private int page; + + private String sortField; + private String order; + + public Pager() { + } + + public Pager(int totalResult) { + this.totalResult = totalResult; + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = rows; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getTotalPage() { + return (totalResult + rows - 1) / rows; + } + + public int getTotalResult() { + return totalResult; + } + + public void setTotalResult(int totalResult) { + this.totalResult = totalResult; + } + + public int getCurrentResult() { + if (page == 0) return 0; + else return (page - 1) * rows; + } + + public String getSortField() { + return sortField; + } + + public void setSortField(String sortField) { + this.sortField = sortField; + } + + public String getOrder() { + return order; + } + + public void setOrder(String order) { + this.order = order; + } + + public int getPageCount() { + int pagecount = 0; + if (totalResult % rows == 0) { + pagecount = totalResult / rows; + } else { + pagecount = totalResult / rows + 1; + } + return pagecount; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Role.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Role.java new file mode 100644 index 0000000..8d4b8c2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/Role.java @@ -0,0 +1,130 @@ +package com.baoying.enginex.executor.system.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; + + +public class Role extends BasePage implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + private Long id; + /** + * 角色名称 + */ + private String name; + /** + * 角色编码 + */ + private String roleCode; + /** + * 角色描述 + */ + private String descripttion; + /** + * 状态 + */ + private Integer status; + /** + * 创建人 + */ + private String creator; + /** + * 创建时间 + */ + private String createTime; + /** + * 最后修改人 + */ + private String updater; + /** + * 最后修改时间 + */ + private String updateTime; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRoleCode() { + return roleCode; + } + + public void setRoleCode(String roleCode) { + this.roleCode = roleCode; + } + + public String getDescripttion() { + return descripttion; + } + + public void setDescripttion(String descripttion) { + this.descripttion = descripttion; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getUpdater() { + return updater; + } + + public void setUpdater(String updater) { + this.updater = updater; + } + + public String getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(String updateTime) { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return "Role [id=" + id + ", name=" + name + ", roleCode=" + roleCode + + ", descripttion=" + descripttion + ", status=" + status + + ", creator=" + creator + ", createTime=" + createTime + + ", updater=" + updater + ", updateTime=" + updateTime + "]"; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysMenu.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysMenu.java new file mode 100644 index 0000000..7bcb694 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysMenu.java @@ -0,0 +1,127 @@ + +package com.baoying.enginex.executor.system.model; + +import java.io.Serializable; +import java.util.Date; + + + +public class SysMenu implements Serializable { + + private static final long serialVersionUID = -1L; + + private long id; + private long userId;//分配者 + private String name; //资源名称 + private String code;//资源代号 + private String url;//路径 + private long parentId;//父节点 + private String des; + private Date birth;//创建时间 + private String icon;//图标 + private int status;//状态 + private long roleId;//角色id + private boolean checked;//菜单默认选中 + private boolean chkDisabled;//节点是否禁用 + private boolean isHidden;//节点是否隐藏 + + + + + public boolean isisHidden() { + return isHidden; + } + public void setisHidden(boolean isHidden) { + this.isHidden = isHidden; + } + public boolean isChkDisabled() { + return chkDisabled; + } + public void setChkDisabled(boolean chkDisabled) { + this.chkDisabled = chkDisabled; + } + public boolean isChecked() { + return checked; + } + public void setChecked(boolean checked) { + this.checked = checked; + } + public long getRoleId() { + return roleId; + } + public void setRoleId(long roleId) { + this.roleId = roleId; + } + public int getStatus() { + return status; + } + public void setStatus(int status) { + this.status = status; + } + public String getDes() { + return des; + } + public void setDes(String des) { + this.des = des; + } + public long getId() { + return id; + } + public void setId(long id) { + this.id = id; + } + public long getUserId() { + return userId; + } + public void setUserId(long userId) { + this.userId = userId; + } + public long getParentId() { + return parentId; + } + public void setParentId(long parentId) { + this.parentId = parentId; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getUrl() { + return url; + } + public void setUrl(String url) { + this.url = url; + } + public String getIcon() { + return icon; + } + public void setIcon(String icon) { + this.icon = icon; + } + public Date getBirth() { + return birth; + } + public void setBirth(Date birth) { + this.birth = birth; + } + @Override + public String toString() { + return "SysMenu [id=" + id + ", userId=" + userId + ", name=" + name + + ", versionCode=" + code + ", url=" + url + ", parentId=" + parentId + + ", des=" + des + ", birth=" + birth + ", icon=" + icon + + ", status=" + status + ", roleId=" + roleId + ", checked=" + + checked + ", chkDisabled=" + chkDisabled + ", isHidden=" + + isHidden + "]"; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysOrganization.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysOrganization.java new file mode 100644 index 0000000..8bfb985 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysOrganization.java @@ -0,0 +1,89 @@ + +package com.baoying.enginex.executor.system.model; + +import java.io.Serializable; +import java.util.Date; + + + +public class SysOrganization implements Serializable { + + private static final long serialVersionUID = -1L; + + private long id;//组织编号 + private String name;//组织名称 + private String code;//组织代号 + private String email; + private String telephone; + private int status;//0禁用1启用 + private String author;//创建者 + private Date birth;//创建时间 + private String token;//唯一标识 + + + + public String getToken() { + return token; + } + public void setToken(String token) { + this.token = token; + } + public String getAuthor() { + return author; + } + public void setAuthor(String author) { + this.author = author; + } + public long getId() { + return id; + } + public void setId(long id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } + public String getTelephone() { + return telephone; + } + public void setTelephone(String telephone) { + this.telephone = telephone; + } + public int getStatus() { + return status; + } + public void setStatus(int status) { + this.status = status; + } + public Date getBirth() { + return birth; + } + public void setBirth(Date birth) { + this.birth = birth; + } + @Override + public String toString() { + return "SysOrganization [id=" + id + ", name=" + name + ", versionCode=" + code + + ", email=" + email + ", telephone=" + telephone + ", status=" + + status + ", author=" + author + ", birth=" + birth + + ", token=" + token + "]"; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysRole.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysRole.java new file mode 100644 index 0000000..cfb9aa7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysRole.java @@ -0,0 +1,83 @@ + +package com.baoying.enginex.executor.system.model; + +import java.io.Serializable; +import java.util.Date; + + + +public class SysRole implements Serializable { + + private static final long serialVersionUID = -1L; + + private long id; + private long organId; + private String roleName; + private String roleCode;//角色代号 + private String roleDesc; + private String author;//创建者 + private Date birth;//创建时间 + private int status; //状态0禁用1启用 + + + + + public String getAuthor() { + return author; + } + public void setAuthor(String author) { + this.author = author; + } + public Date getBirth() { + return birth; + } + public void setBirth(Date birth) { + this.birth = birth; + } + public long getOrganId() { + return organId; + } + public void setOrganId(long organId) { + this.organId = organId; + } + public long getId() { + return id; + } + public void setId(long id) { + this.id = id; + } + public String getRoleName() { + return roleName; + } + public void setRoleName(String roleName) { + this.roleName = roleName; + } + public String getRoleCode() { + return roleCode; + } + public void setRoleCode(String roleCode) { + this.roleCode = roleCode; + } + public String getRoleDesc() { + return roleDesc; + } + public void setRoleDesc(String roleDesc) { + this.roleDesc = roleDesc; + } + public int getStatus() { + return status; + } + public void setStatus(int status) { + this.status = status; + } + @Override + public String toString() { + return "SysRole [id=" + id + ", organId=" + organId + ", roleName=" + + roleName + ", roleCode=" + roleCode + ", roleDesc=" + roleDesc + + ", author=" + author + ", birth=" + birth + ", status=" + + status + "]"; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysSuccess.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysSuccess.java new file mode 100644 index 0000000..ed50b75 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysSuccess.java @@ -0,0 +1,40 @@ +package com.baoying.enginex.executor.system.model; + +public class SysSuccess { + + private boolean success; + private String msg; + + public SysSuccess() { + } + + public SysSuccess(boolean value, String msg) { + super(); + this.success = value; + this.msg = msg; + } + + public boolean getSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + @Override + public String toString() { + return "SysSuccess [success=" + success + ", msg=" + msg + "]"; + } + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysUser.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysUser.java new file mode 100644 index 0000000..143255a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/SysUser.java @@ -0,0 +1,141 @@ + +package com.baoying.enginex.executor.system.model; + +import java.io.Serializable; +import java.util.Date; + + + +public class SysUser implements Serializable { + + private static final long serialVersionUID = -1L; + + private long id;//用户(主键) + private long organId;//组织编号 + private String employeeId;//员工编号 + private String account;//账户 + private String password; + private String nickName;//昵称 + private String email; + private String cellphone; + private String qq; + private String latestTime; + private String latestIp; + private int status; + private Date birth;//创建时间 + private String author;//创建人 + private SysRole sysRole;//角色对象 + private SysOrganization sysOrgan;//公司对象 + + + + + + public SysOrganization getSysOrgan() { + return sysOrgan; + } + public void setSysOrgan(SysOrganization sysOrgan) { + this.sysOrgan = sysOrgan; + } + public String getAuthor() { + return author; + } + public void setAuthor(String author) { + this.author = author; + } + public String getEmployeeId() { + return employeeId; + } + public void setEmployeeId(String employeeId) { + this.employeeId = employeeId; + } + public long getId() { + return id; + } + public void setId(long id) { + this.id = id; + } + public long getOrganId() { + return organId; + } + public void setOrganId(long organId) { + this.organId = organId; + } + public String getAccount() { + return account; + } + public void setAccount(String account) { + this.account = account; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + public String getNickName() { + return nickName; + } + public void setNickName(String nickName) { + this.nickName = nickName; + } + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } + public String getCellphone() { + return cellphone; + } + public void setCellphone(String cellphone) { + this.cellphone = cellphone; + } + public String getQq() { + return qq; + } + public void setQq(String qq) { + this.qq = qq; + } + public String getLatestTime() { + return latestTime; + } + public void setLatestTime(String latestTime) { + this.latestTime = latestTime; + } + public String getLatestIp() { + return latestIp; + } + public void setLatestIp(String latestIp) { + this.latestIp = latestIp; + } + public int getStatus() { + return status; + } + public void setStatus(int status) { + this.status = status; + } + public SysRole getSysRole() { + return sysRole; + } + public void setSysRole(SysRole sysRole) { + this.sysRole = sysRole; + } + public Date getBirth() { + return birth; + } + public void setBirth(Date birth) { + this.birth = birth; + } + @Override + public String toString() { + return "SysUser [id=" + id + ", organId=" + organId + ", employeeId=" + + employeeId + ", account=" + account + ", password=" + password + + ", nickName=" + nickName + ", email=" + email + ", cellphone=" + + cellphone + ", qq=" + qq + ", latestTime=" + latestTime + + ", latestIp=" + latestIp + ", status=" + status + ", birth=" + + birth + ", author=" + author + ", sysOrgan="+sysOrgan+", sysRole=" + sysRole + "]"; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/User.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/User.java new file mode 100644 index 0000000..28d66d1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/User.java @@ -0,0 +1,203 @@ +package com.baoying.enginex.executor.system.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; + + +public class User extends BasePage implements Serializable { + + /** + * serialVersionUID:TODO(序列化对象使用) + */ + private static final long serialVersionUID = 1L; + + /** + * 关系id + */ + private Long userId; + /** + * 企业id + */ + private Long organId; + /** + * 用户id + */ + private String account; + /** + * 用户密码 + */ + private String password; + + /** + * 昵称 + */ + private String nickName; + /** + * 邮箱 + */ + private String email; + /** + * 电话 + */ + private String cellphone; + /** + * qq + */ + private String qq; + /** + * + */ + private String latestTime; + /** + * 最后一次登录IP + */ + private String latestIp; + /** + * 状态 + */ + private Integer status; + /** + * 创建时间 + */ + private Date birth; + /** + * 创建时间 + */ + private Long parentId; + /** + * 删除ids数组 + */ + private Long []deletIds; + + public Long[] getDeletIds() { + return deletIds; + } + + public void setDeletIds(Long[] deletIds) { + this.deletIds = deletIds; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getOrganId() { + return organId; + } + + public void setOrganId(Long organId) { + this.organId = organId; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getCellphone() { + return cellphone; + } + + public void setCellphone(String cellphone) { + this.cellphone = cellphone; + } + + public String getQq() { + return qq; + } + + public void setQq(String qq) { + this.qq = qq; + } + + + + public String getLatestTime() { + return latestTime; + } + + public void setLatestTime(String latestTime) { + this.latestTime = latestTime; + } + + public String getLatestIp() { + return latestIp; + } + + public void setLatestIp(String latestIp) { + this.latestIp = latestIp; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Date getBirth() { + return birth; + } + + public void setBirth(Date birth) { + this.birth = birth; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + @Override + public String toString() { + return "User [userId=" + userId + ", organId=" + organId + ", account=" + + account + ", password=" + password + ", nickName=" + nickName + + ", email=" + email + ", cellphone=" + cellphone + ", qq=" + qq + + ", latestTime=" + latestTime + ", latestIp=" + latestIp + + ", status=" + status + ", birth=" + birth + ", parentId=" + + parentId + ", deletIds=" + Arrays.toString(deletIds) + "]"; + } + + + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/UserRole.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/UserRole.java new file mode 100644 index 0000000..802fa8f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/system/model/UserRole.java @@ -0,0 +1,52 @@ + +package com.baoying.enginex.executor.system.model; + + +import com.baoying.enginex.executor.common.model.BasePage; + +import java.io.Serializable; + + +public class UserRole extends BasePage implements Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + private Long id; + /** + * 用户id + */ + private Long userId; + /** + * 角色编码 + */ + private String roleCode; + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public Long getUserId() { + return userId; + } + public void setUserId(Long userId) { + this.userId = userId; + } + public String getRoleCode() { + return roleCode; + } + public void setRoleCode(String roleCode) { + this.roleCode = roleCode; + } + @Override + public String toString() { + return "UserRole [id=" + id + ", userId=" + userId + ", roleCode=" + + roleCode + "]"; + } + + +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/consts/TacticsType.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/consts/TacticsType.java new file mode 100644 index 0000000..8d31c97 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/consts/TacticsType.java @@ -0,0 +1,16 @@ +package com.baoying.enginex.executor.tactics.consts; + +public class TacticsType { + public static final String DECISION_TABLES = "decision_tables";//决策表 + public static final String SCORECARD = "scorecard";//评分卡 + public static final String LIST_DB = "list_db";//名单库 + public static final String MODELS = "models"; + public static final String COMPLEX_RULE = "complex_rule"; + public static final String BASE_RULE = "base_rule"; + public static final String DECISION_TREE = "decision_tree";//决策树 + + public static class OutType{ + public static final String SUCCESS_OUT = "success";//成功时输出 + public static final String FAIL_OUT = "fail";//失败时输出 + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/mapper/TacticsOutputMapper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/mapper/TacticsOutputMapper.java new file mode 100644 index 0000000..5d4d08b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/mapper/TacticsOutputMapper.java @@ -0,0 +1,10 @@ +package com.baoying.enginex.executor.tactics.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baoying.enginex.executor.tactics.model.TacticsOutput; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface TacticsOutputMapper extends BaseMapper { +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/OutCondition.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/OutCondition.java new file mode 100644 index 0000000..7d60b50 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/OutCondition.java @@ -0,0 +1,18 @@ +package com.baoying.enginex.executor.tactics.model; + + +import com.baoying.enginex.executor.common.model.ExpressionParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OutCondition { + private String logical; + private List conditionList; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/TacticsOutput.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/TacticsOutput.java new file mode 100644 index 0000000..0ba6040 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/model/TacticsOutput.java @@ -0,0 +1,78 @@ +package com.baoying.enginex.executor.tactics.model; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("t_tactics_output") +public class TacticsOutput implements Serializable { + @TableField(exist = false) + private static final long serialVersionUID = 699491471584300246L; + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + /** + * 字段id + */ + private Long fieldId; + /** + * 字段的en + */ + private String fieldEn; + /** + * 字段值 + */ + private String fieldValue; + /** + *字段值的类型:1 常量、2 变量,3.自定义 + */ + private Integer variableType; + /** + * 关联的策略id + */ + private Long tacticsId; + /** + * 关联的策略类型 base_rule.基础规则 scorecard.评分卡 decision_tables.决策表 decision_tree.决策树 complex_rule.复杂规则 list_db.名单库 models.机器学习模型 + */ + private String tacticsType; + /** + * 输出条件 + */ + private String outCondition; + /** + * 输出类型 success成功输出, fail失败输出 + */ + private String outType; + /** + * 创建时间 + */ + private Date createTime; + /** + * 修改时间 + */ + private Date updateTime; + + public TacticsOutput(Long tacticsId, String tacticsType) { + this.tacticsId = tacticsId; + this.tacticsType = tacticsType; + } + + public TacticsOutput(Long tacticsId, String tacticsType, String outType) { + this.tacticsId = tacticsId; + this.tacticsType = tacticsType; + this.outType = outType; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/TacticsOutputService.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/TacticsOutputService.java new file mode 100644 index 0000000..8cd1636 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/TacticsOutputService.java @@ -0,0 +1,18 @@ +package com.baoying.enginex.executor.tactics.service; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baoying.enginex.executor.tactics.model.TacticsOutput; + +import java.util.List; +import java.util.Map; + + +public interface TacticsOutputService extends IService { + + List queryByTactics(TacticsOutput entity); + + List setOutput(TacticsOutput entity,Map input); + + boolean judgeOutCondition(String condition,Map input); +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/impl/TacticsOutputServiceImpl.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/impl/TacticsOutputServiceImpl.java new file mode 100644 index 0000000..9c111c5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/tactics/service/impl/TacticsOutputServiceImpl.java @@ -0,0 +1,141 @@ +package com.baoying.enginex.executor.tactics.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baoying.enginex.executor.canal.TableEnum; +import com.baoying.enginex.executor.common.constants.Constants; +import com.baoying.enginex.executor.common.model.ExpressionParam; +import com.baoying.enginex.executor.config.ConfigHolder; +import com.baoying.enginex.executor.redis.RedisManager; +import com.baoying.enginex.executor.redis.RedisUtils; +import com.baoying.enginex.executor.tactics.consts.TacticsType; +import com.baoying.enginex.executor.tactics.mapper.TacticsOutputMapper; +import com.baoying.enginex.executor.tactics.model.OutCondition; +import com.baoying.enginex.executor.tactics.model.TacticsOutput; +import com.baoying.enginex.executor.tactics.service.TacticsOutputService; + +import com.baoying.enginex.executor.util.ExecuteUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class TacticsOutputServiceImpl extends ServiceImpl implements TacticsOutputService { + + @Autowired + private ConfigHolder configHolder; + @Autowired + private RedisManager redisManager; + + @Override + public List queryByTactics(TacticsOutput entity) { + List tacticsOutputList = null; + if(Constants.switchFlag.ON.equals(configHolder.getCacheSwitch())){ + String key = RedisUtils.getForeignKey(TableEnum.T_TACTICS_OUTPUT, entity.getTacticsId()); + tacticsOutputList = redisManager.getByForeignKey(key, TacticsOutput.class); + if(tacticsOutputList != null){ + tacticsOutputList = tacticsOutputList.stream().filter(item -> item.getTacticsType().equals(entity.getTacticsType())) + .collect(Collectors.toList()); + } + if (tacticsOutputList!=null&&!tacticsOutputList.isEmpty()&& TacticsType.COMPLEX_RULE.equals(entity.getVariableType())&&entity.getOutType()!=null){ + //复杂规则需要过滤出类型 + tacticsOutputList = tacticsOutputList.stream().filter(item ->entity.getOutType().equals( item.getOutType())).collect(Collectors.toList()); + } + } else { + tacticsOutputList = this.list(new QueryWrapper<>(entity)); + } + + return tacticsOutputList; + } + + @Transactional + + //设置输出,传入map向map中放入输出并且返回输出列表 + @Override + public List setOutput(TacticsOutput entity, Map input) { + List tacticsOutputList = this.queryByTactics(entity); + List jsonList = new ArrayList<>(); + if (tacticsOutputList != null && tacticsOutputList.size() > 0) { + for (TacticsOutput tacticsOutput : tacticsOutputList) { + if (!this.judgeOutCondition(tacticsOutput.getOutCondition(),input)){ + continue; + } + JSONObject json = new JSONObject(); + String fieldEn = tacticsOutput.getFieldEn(); + String fieldValue = tacticsOutput.getFieldValue(); + Object value = fieldValue; + Integer variableType = tacticsOutput.getVariableType(); + if (variableType != null) { + switch (variableType) { + case 2: + value = ExecuteUtils.getObjFromMap(input, fieldValue); + break; + case 3: + value = ExecuteUtils.getObjFromScript(input,fieldValue); + break; + } + } + if (value != null ) { + if (!"".equals(value)&&!"'".equals(value)&&value.toString().startsWith("'")&&value.toString().endsWith("'")){ + value = value.toString().substring(1,value.toString().length()-1); + } + json.put(fieldEn, value); + input.put(fieldEn, value); + jsonList.add(json); + } + } + } + return jsonList; + } + + //判断是否符合输出条件 + @Override + public boolean judgeOutCondition(String condition, Map input) { + //条件为空则符合输出 + if (null == condition || "".equals(condition)) { + return true; + } + OutCondition outCondition; + try { + outCondition = JSON.parseObject(condition, OutCondition.class); + }catch (Exception e){ + //字符串转json失败 + return true; + } + String logical = outCondition.getLogical(); + List conditionList = outCondition.getConditionList(); + if (null == logical || null == conditionList||conditionList.size()<1){ + return true; + } + boolean result=false; + switch (logical) { + case "||": + result = false; + for (ExpressionParam expression : conditionList) { + if (ExecuteUtils.getExpressionResult(expression, input)){ + return true; + } + } + break; + case "&&": + result = true; + for (ExpressionParam expression : conditionList) { + if (!ExecuteUtils.getExpressionResult(expression, input)){ + return false; + } + } + break; + } + return result; + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CollectionUtil.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CollectionUtil.java new file mode 100644 index 0000000..724f049 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CollectionUtil.java @@ -0,0 +1,104 @@ +package com.baoying.enginex.executor.util; + +import java.util.*; + +public class CollectionUtil { + + /** + * 集合判非空 + * + * @param collection + * @return + */ + public static boolean isNotNullOrEmpty(Collection collection) { + if (null == collection || collection.isEmpty()){ + return false; + } + return true; + } + + /** + * map判非空 + * + * @param map + * @return + */ + public static boolean isNotNullOrEmpty(Map map) { + if (null == map || map.isEmpty()){ + return false; + } + return true; + } + + /** + * 获取多个集合并集 + * @param list + * @return + */ + public static Set getUnion(List> list) { + Set set = new HashSet(); + if (list == null) { + list = new ArrayList>(); + } + int size = list.size(); + if (size > 1) { + for (int i = 0; i < size; i++) { + int j = i + 1; + if (j < size) { + list.get(0).removeAll(list.get(j)); + list.get(0).addAll(list.get(j)); + if (i == size - 2) { + List resultList = list.get(0); + for (Object result : resultList) { + set.add(result); + } + } + } + } + } else { + // 只有一个集合则直接插入结果 + for (List subList : list) { + for (Object result : subList) { + set.add(result); + } + } + } + return set; + + } + + /** + * 获取多个集合交集 + * @param list + * @return + */ + public static Set getIntersection(List> list) { + Set set = new HashSet(); + int size = list.size(); + if (size > 1) { + // 集合个数大于1,取交集 + for (int i = 0; i < size; i++) { + int j = i + 1; + if (j < size) { + list.get(0).retainAll(list.get(j)); + if (i == size - 2) { + List resultList = list.get(0); + for (Object result : resultList) { + set.add(result); + } + } + } + } + } else { + // 只有一个集合则不取交集 + for (List subList : list) { + for (Object result : subList) { + set.add(result); + } + } + } + + return set; + + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CustomValueUtils.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CustomValueUtils.java new file mode 100644 index 0000000..4590857 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/CustomValueUtils.java @@ -0,0 +1,32 @@ +package com.baoying.enginex.executor.util; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import java.util.HashSet; +import java.util.Set; + +public class CustomValueUtils { + + public static final Set getFieldEnSet(String custom) { + Set fieldEns = new HashSet<>(); + if (custom != null && !"".equals(custom)) { + JSONObject jsonObject = JSON.parseObject(custom); + Object farr = jsonObject.get("farr"); + if (farr != null) { + JSONArray jsonArray = JSONArray.parseArray(JSON.toJSONString(farr)); + if (jsonArray != null && jsonArray.size() > 0) { + for (Object o : jsonArray) { + JSONObject field = JSON.parseObject(JSON.toJSONString(o)); + Object fieldEn = field.get("fieldEn"); + if (fieldEn != null && !"".equals(fieldEn)) { + fieldEns.add(fieldEn.toString()); + } + } + } + } + } + return fieldEns; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DataHelp.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DataHelp.java new file mode 100644 index 0000000..4f5c854 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DataHelp.java @@ -0,0 +1,40 @@ +package com.baoying.enginex.executor.util; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +public class DataHelp { + public static int day=0; + public static String getNowDate(){ + Date date = new Date(); + SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String s = simple.format(date); + return s; + } + public static String getEndDate(){ + SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Calendar c = Calendar.getInstance(); + c.add(Calendar.DATE, + DataHelp.day); + Date monday = c.getTime(); + String s = simple.format(monday); + return s; + } + public static String getNowDateString(){ + Date date = new Date(); + SimpleDateFormat simple = new SimpleDateFormat("yyyyMMddHHmmss"); + String s = simple.format(date); + return s; + } + public static String getDay(){ + Date date = new Date(); + SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd"); + String s = simple.format(date); + return s; + } + public static void main(String[] args) { + System.out.println(getNowDate()); + System.out.println(getNowDateString()); + + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DictVariableUtils.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DictVariableUtils.java new file mode 100644 index 0000000..bafc7c1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/DictVariableUtils.java @@ -0,0 +1,29 @@ +package com.baoying.enginex.executor.util; + +import cn.hutool.core.date.DateUtil; +import com.alibaba.fastjson.JSONObject; + +import java.util.Date; + +public class DictVariableUtils { + + public static Object getValueFromJsonObject(JSONObject jsonObject){ + Object result = ""; + if (jsonObject.get("value") != null) { + switch (jsonObject.getString("type")){ + case "date": + try { + result = DateUtil.format(new Date(),jsonObject.getString("value")); + }catch (Exception e){ + e.printStackTrace(); + result = DateUtil.format(new Date(),"yyyyMMdd"); + } + break; + default: + result = jsonObject.get("value"); + } + } + return result; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ExecuteUtils.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ExecuteUtils.java new file mode 100644 index 0000000..66fa29b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ExecuteUtils.java @@ -0,0 +1,674 @@ +package com.baoying.enginex.executor.util; + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.common.constants.ParamTypeConst; +import com.baoying.enginex.executor.common.model.ExpressionParam; +import com.baoying.enginex.executor.datamanage.model.Field; +import com.baoying.enginex.executor.datamanage.service.FieldService; +import com.baoying.enginex.executor.engine.model.EngineNode; +import com.baoying.enginex.executor.node.service.CommonService; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.math.Groovy; +import org.apache.commons.lang.StringUtils; +import com.baoying.enginex.executor.util.jeval.function.math.Python; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +//底层执行的工具类 +@Component +public class ExecuteUtils { + private static final Logger logger = LoggerFactory.getLogger(ExecuteUtils.class); + private static Groovy groovy; + private static CommonService commonService; + private static Python python; + private static FieldService fieldService; + + @Autowired + public ExecuteUtils(Groovy groovy, CommonService commonService, Python python, FieldService fieldService) { + ExecuteUtils.groovy = groovy; + ExecuteUtils.commonService = commonService; + ExecuteUtils.python = python; + ExecuteUtils.fieldService = fieldService; + } + + //获取基本单元的执行结果 + public final static boolean getExpressionResult(ExpressionParam expressionParam, Map params) { + //如果是规则的条件的话,判断是否为叶子节点,如果不是则直接返回false + if (expressionParam.getConditionType() != null && expressionParam.getConditionType() != 2) { + return false; + } + String operator = expressionParam.getOperator(); +// //获取第一个参数的key +// String paramOneKey = expressionParam.getFieldEn(); +// //获取第二个参数的key或者常量值 +// String paramTwoKey = expressionParam.getFieldValue(); + //获取第二个参数的类型 + Integer variableType = expressionParam.getVariableType(); + //给每个参数取值 + Object paramOne = getValueByKey(2, params, expressionParam.getFieldEn(),null); + //默认为常量 + Object paramTwo = getValueByKey(variableType, params, expressionParam.getFieldValue(),null); + //如果第二个参数类型为变量则取出参数,为自定义则计算出值返回 +// if (variableType != null) { +// switch (variableType) { +// case 1: +// //常量类型 +// break; +// case 2: +// //变量类型 +// paramTwo = getObjFromMap(params, paramTwoKey); +// break; +// case 3: +// //自定义脚本类型 +// paramTwo = getObjFromScript(params, expressionParam.getFieldValue()); +// break; +// case 4: +// //正则表达式类型 +// paramTwo = getObjFromRegex(params, expressionParam.getFieldValue()); +// } +// } + //如果两个参数有没取到的则直接返回false + if (paramOne == null || "".equals(paramOne) || paramTwo == null || "".equals(paramTwo)) { + return false; + } + return getCondResult(operator, paramOne, paramTwo); +// //协商获取操作类型 +// boolean result = false; +// //预处理:如果是数字类型的话提前取出数值 +// Double numOne = StrUtils.strToDouble(paramOne.toString()); +// Double numTwo = StrUtils.strToDouble(paramTwo.toString()); +// switch (operator) { +// //数值之间的比较 +// case "==": +// if (numOne != null && numTwo != null) { +// result = numOne.equals(numTwo); +// } else if (paramOne != null && paramTwo != null) { +// result = paramOne.toString().equals(paramTwo.toString()); +// } +// break; +// case "!=": +// if (numOne != null && numTwo != null) { +// result = !numOne.equals(numTwo); +// } else if (paramOne != null && paramTwo != null) { +// result = !paramOne.toString().equals(paramTwo.toString()); +// } +// break; +// case ">": +// if (numOne != null && numTwo != null) { +// result = numOne > numTwo; +// } +// break; +// case "<": +// if (numOne != null && numTwo != null) { +// result = numOne < numTwo; +// } +// break; +// case ">=": +// if (numOne != null && numTwo != null) { +// result = numOne >= numTwo; +// } +// break; +// case "<=": +// if (numOne != null && numTwo != null) { +// result = numOne <= numTwo; +// } +// break; +// //字符串之间的比较 +// case "equals": +// result = paramOne.toString().equals(paramTwo.toString()); +// break; +// case "not equals": +// result = !paramOne.toString().equals(paramTwo.toString()); +// break; +// case "contains": +// result = paramOne.toString().contains(paramTwo.toString()); +// break; +// case "not contains": +// result = !paramOne.toString().contains(paramTwo.toString()); +// break; +// case "regex": +// Pattern pattern = Pattern.compile(paramTwo.toString()); +// Matcher matcher = pattern.matcher(paramOne.toString()); +// result = matcher.find(); +// break; +// } +// return result; + } + + + //传入两个参数和一个操作符进行比对获取结果 + public final static boolean getCondResult(String operator, Object paramOne, Object paramTwo) { + boolean result = false; + Double numOne = StrUtils.strToDouble(paramOne.toString()); + Double numTwo = StrUtils.strToDouble(paramTwo.toString()); + switch (operator) { + //数值之间的比较 + case "==": + if (numOne != null && numTwo != null) { + result = numOne.equals(numTwo); + } else if (paramOne != null && paramTwo != null) { + result = paramOne.toString().equals(paramTwo.toString()); + } + break; + case "!=": + if (numOne != null && numTwo != null) { + result = !numOne.equals(numTwo); + } else if (paramOne != null && paramTwo != null) { + result = !paramOne.toString().equals(paramTwo.toString()); + } + break; + case ">": + if (numOne != null && numTwo != null) { + result = numOne > numTwo; + } + break; + case "<": + if (numOne != null && numTwo != null) { + result = numOne < numTwo; + } + break; + case ">=": + if (numOne != null && numTwo != null) { + result = numOne >= numTwo; + } + break; + case "<=": + if (numOne != null && numTwo != null) { + result = numOne <= numTwo; + } + break; + //字符串之间的比较 + case "equals": + result = paramOne.toString().equals(paramTwo.toString()); + break; + case "not equals": + result = !paramOne.toString().equals(paramTwo.toString()); + break; + case "contains": + result = paramOne.toString().contains(paramTwo.toString()); + break; + case "not contains": + result = !paramOne.toString().contains(paramTwo.toString()); + break; + case "regex": + Pattern pattern = Pattern.compile(paramTwo.toString()); + Matcher matcher = pattern.matcher(paramOne.toString()); + result = matcher.find(); + break; + case "in": + if (paramTwo instanceof List){ + List list = (List) paramTwo; + result = list.contains(paramOne); + }else if (paramTwo instanceof Map){ + Map map= (Map)paramTwo; + result = map.containsKey(paramOne); + } + break; + case "not in": + if (paramTwo instanceof List){ + List list = (List) paramTwo; + result = !list.contains(paramOne); + }else if (paramTwo instanceof Map){ + Map map= (Map)paramTwo; + result = !map.containsKey(paramOne); + } + break; + } + return result; + } + + //根据key,分不同类型取出值 + public final static Object getValueByKey(Integer variableType, Map params, String paramKey,List list) { + Object result = paramKey; + if (variableType != null) { + switch (variableType) { + case ParamTypeConst + .CONSTANT: + //常量类型 + result = paramKey; + break; + case ParamTypeConst + .VARIABLE: + //变量类型 + result = getObjFromMap(params, paramKey); + break; + case ParamTypeConst + .CUSTOM: + //自定义脚本类型 + if (list==null || list.isEmpty()){ + result = getObjFromScript(params, paramKey); + }else { + result = getObjFromScript(params,paramKey,list); + } + break; + case ParamTypeConst + .REGEX: + //正则表达式类型 + result = getObjFromRegex(params, paramKey); + } + } + return result; + } + + + //从map中取值 + public final static Object getObjFromMap(Map input, String key) { + if (StringUtils.isBlank(key)) { + return ""; + } + if (input == null) { + input = new ConcurrentHashMap<>(); + } + String[] array = key.split("\\."); + //如果当前变量池中未找到此变量则需要获取 + if (input.get(array[0]) == null && !array[0].startsWith("%")) { + List strings = new ArrayList(); + strings.add(array[0]); + boolean result = getFieldToInputByEns(strings,input); + if (!result) { + return ""; + } + } + return getObjFromMap(input, array); + } + + //从map中找到需要的对象并返回 + public final static Object getObjFromMap(Map input, String[] array) { + if (array.length == 1) { + Object o = input.get(array[0]); + if (o == null) { + return ""; + } + return o; + } + Map map = input; + for (int i = 0; i < array.length; i++) { + String childKey = array[i]; + //判断是否能找到key + if (map.containsKey(childKey)) { + Object o = map.get(childKey); + if (i == array.length - 1) { + return map.get(childKey); + } + //如果是数组取length + if (i == array.length - 2) { + if ("length()".equals(array[array.length - 1])) { + return JSON.toJavaObject(JSON.parseObject(JSON.toJSONString(o)), ArrayList.class).size(); + } + } + //未找到最后一个数组元素则将其识别为map + map = JSON.toJavaObject(JSON.parseObject(JSON.toJSONString(o)), Map.class); + } + } + return ""; + } + + //根据入参map,通过公式和groovy计算出返回结果 + public final static Object getObjFromScript(Map input, String fieldValue) { + JSONObject formulaJson = JSON.parseObject(fieldValue); + //找到脚本中引用的字段,放入data中 + Map data = new HashMap<>(); + Object farr = formulaJson.get("farr"); + List fieldIds = new ArrayList<>(); + //字段cn为key,字段en为value + Map fieldMap = new HashMap<>(); + if (farr != null && !"".equals(farr)) { + List fieldList = JSONArray.parseArray(JSON.toJSONString(farr), Field.class); + for (Field field : fieldList) { + String fieldCn = field.getFieldCn(); + String fieldEn = field.getFieldEn(); + if (fieldCn != null && fieldEn != null && !"".equals(fieldCn) && !"".equals(fieldEn)) { + fieldMap.put(fieldCn, field); + } + fieldIds.add(field.getId()); + } + } + if (fieldIds.size() > 0) { + getFieldToInputByIds(fieldIds,input); + } + Object result = executeScript(formulaJson, fieldMap, input); + //取出groovy脚本 +// String formula = (String) formulaJson.get("formula"); +// //替换掉特殊的字符 +// formula = formula.replace(">", ">"); //3>=6 && 3< 12 +// formula = formula.replace("<", "<"); +// //正则匹配自定义中用到的变量对其进行替换 +// Pattern pattern = Pattern.compile("@[a-zA-Z0-9_\u4e00-\u9fa5()()-]+@"); +// Matcher matcher = pattern.matcher(formula); +// String subexp = formula; +// String exp = ""; +// int j = 0; +// while (matcher.find()) { +// String fieldCN = matcher.group(0).replace("@", ""); +// Field subField = fieldMap.get(fieldCN); +// if (subField == null) { +// return ""; +// } +// String fieldEn = subField.getFieldEn(); +// String v = ""; +// v = "" + input.get(fieldEn); +// data.put(fieldEn, input.get(fieldEn)); +// if (subexp.contains("def main")) { +// // groovy脚本替换为动态参数 +// v = "_['" + fieldEn + "']"; +// exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", v); +// } else { +// if (subField.getValueType() == 1 || subField.getValueType() == 4) { +// exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", v); +// } else { +// exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", "'" + v + "'"); +// } +// } +// j = matcher.end(); +// } +// exp += formula.substring(j, formula.length()); +// Evaluator evaluator = new Evaluator(); +// Object result = ""; +// try { +// if (exp.contains("def main")) { +// // 执行groovy脚本 +// +// logger.warn("groovy:{},{}", exp, data); +// result = groovy.executeForObject(exp, data); +// } else if (exp.contains("def python_main(_):")) { +// //执行python脚本 +// result = python.executeForObject(exp, data); +// } else { +// //执行公式 +// result = evaluator.evaluate(exp); +// } +// } catch (EvaluationException e) { +// e.printStackTrace(); +// logger.error("请求异常", e); +// } + return result; + } + + //处理集合中的特殊自定义 + public final static Object getObjFromScript(Map input, String fieldValue, List current) { + JSONObject formulaJson = JSON.parseObject(fieldValue); + //找到脚本中引用的字段,放入data中 + Object farr = formulaJson.get("farr"); + List fieldIds = new ArrayList<>(); + //字段cn为key,字段en为value + Map fieldMap = new HashMap<>(); + if (farr != null && !"".equals(farr)) { + List fieldList = JSONArray.parseArray(JSON.toJSONString(farr), JSONObject.class); + String inputParamStr = JSON.toJSONString(input); + for (JSONObject jsonObject : fieldList) { + String fieldCn = jsonObject.getString("fieldCn"); + String fieldEn = jsonObject.getString("fieldEn"); + Field field = new Field(); + field.setFieldEn(fieldEn); + field.setFieldCn(fieldCn); + field.setValueType(jsonObject.getInteger("valueType")); + Long id = jsonObject.getLong("id"); + if (fieldCn != null && fieldEn != null && !"".equals(fieldCn) && !"".equals(fieldEn)) { + fieldMap.put(fieldCn, field); + } + if (!jsonObject.containsKey("paramList")) { + fieldIds.add(id); + continue; + } + Map temp = JSON.parseObject(inputParamStr, Map.class); + //存在paramList需要单个字段取出 + JSONArray paramList = jsonObject.getJSONArray("paramList"); + for (int i = 0; i < paramList.size(); i++) { + JSONObject param = paramList.getJSONObject(i); + String en = param.getString("en"); + String value = param.getString("value"); + switch (param.getIntValue("type")) { + case ParamTypeConst.CONSTANT: + temp.put(en,value ); + break; + case ParamTypeConst.VARIABLE: + temp.put(en, ListOpUtils.getObjByKeyAndJson(current.get(0), value)); + break; + } + } + getFieldToInputByIds(Arrays.asList(id), temp); + input.put(fieldEn,temp.get(fieldEn)); + } + } + + if (fieldIds.size() > 0) { + getFieldToInputByIds(fieldIds, input); + } + //取出groovy脚本 + Object result = executeScript(formulaJson, fieldMap, input); + return result; + } + + //对正则取值 + public final static Object getObjFromRegex(Map input, String fieldValue) { + String result = fieldValue; + //校验是否使用了字段如果使用了则需要替换为值 + Pattern pattern = Pattern.compile("@[a-zA-Z0-9_\u4e00-\u9fa5()()-]+@"); + Matcher matcher = pattern.matcher(fieldValue); + while (matcher.find()) { + String fieldEn = matcher.group().replace("@", ""); + Object value = ExecuteUtils.getObjFromMap(input, fieldEn); + String valueStr = ""; + if (value != null) { + valueStr = value.toString(); + } + result = result.replace("@" + fieldEn + "@", valueStr); + } + return result; + } + + //执行自定义脚本 + public final static Object executeScript(JSONObject formulaJson, Map fieldMap, Map input) { + String formula = formulaJson.getString("formula"); + //替换掉特殊的字符 + formula = formula.replace(">", ">"); //3>=6 && 3< 12 + formula = formula.replace("<", "<"); + //正则匹配自定义中用到的变量对其进行替换 + Pattern pattern = Pattern.compile("@[a-zA-Z0-9_\u4e00-\u9fa5()()-]+@"); + Matcher matcher = pattern.matcher(formula); + String subexp = formula; + String exp = ""; + int j = 0; + Map data = new HashMap<>(); + while (matcher.find()) { + String fieldCN = matcher.group(0).replace("@", ""); + Field subField = fieldMap.get(fieldCN); + if (subField == null) { + return ""; + } + String fieldEn = subField.getFieldEn(); + String v = ""; + v = "" + input.get(fieldEn); + data.put(fieldEn, input.get(fieldEn)); + if (subexp.contains("def main")) { + // groovy脚本替换为动态参数 + v = "_['" + fieldEn + "']"; + exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", v); + } else { + if (subField.getValueType() == 1 || subField.getValueType() == 4) { + exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", v); + } else { + exp += subexp.substring(j, matcher.end()).replace("@" + fieldCN + "@", "'" + v + "'"); + } + } + j = matcher.end(); + } + + + exp += formula.substring(j, formula.length()); + Evaluator evaluator = new Evaluator(); + Object result = ""; + try { + if (exp.contains("def main")) { + // 执行groovy脚本 + + logger.warn("groovy:{},{}", exp, data); + result = groovy.executeForObject(exp, data); + } else if (exp.contains("def python_main(_):")) { + //执行python脚本 + result = python.executeForObject(exp, data); + } else { + //执行公式 + result = evaluator.evaluate(exp); + } + if (result.toString().startsWith("'")){ + //字符串 + result = result.toString().replace("'",""); + }else { + //数值 + if (StrUtils.isNum(result.toString())){ + String[] split = result.toString().split("\\."); + if (split.length>1&&StrUtils.strToLong(split[1])>0){ + result = StrUtils.strToDouble(result.toString()); + }else { + result = StrUtils.strToLong(split[0]); + } + } + } + } catch (EvaluationException e) { + e.printStackTrace(); + logger.error("请求异常", e); + } + + return result; + } + + //对groovy脚本执行结果进一步处理 + public static Map handleGroovyResult(Map map) { + + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getKey().startsWith("_['") && entry.getKey().endsWith("']")) { + map.remove(entry.getKey()); + String key = entry.getKey().replace("_['", "").replace("']", ""); + map.put(key, entry.getValue()); + } + } + return map; + } + + //调用commonService根据ens取参数 + public static boolean getFieldToInputByEns(List fieldEns, Map input ){ + boolean result = commonService.getEngineField(fieldService.selectFieldListByEns(fieldEns), input); + return result; + } + //调用commonService根据ids取参数 + public static boolean getFieldToInputByIds(List ids, Map input ){ + boolean result = commonService.getFieldByIds(ids, input); + return result; + } + + // 解析nodeJson + public final static List getExecuteListFromNodeJson(EngineNode engineNode) { + JSONObject nodeJson = JSON.parseObject(engineNode.getNodeJson()); + String strategyStr = null; + switch (engineNode.getNodeType()) { + case 2://规则集 + strategyStr = JSON.toJSONString(nodeJson.getJSONObject("executeGroup").get("strategyList")); + break; + case 4://评分卡 + strategyStr = JSON.toJSONString(nodeJson.getJSONArray("scorecardList")); + break; + case 5://名单库 + strategyStr = JSON.toJSONString(nodeJson.getJSONArray("listDbList")); + break; + case 15://模型 + strategyStr = JSON.toJSONString(nodeJson.getJSONArray("modelList")); + break; + case 16://决策表 + strategyStr = JSON.toJSONString(nodeJson.getJSONArray("decisionTableList")); + break; + case 17://决策树 + strategyStr = JSON.toJSONString(nodeJson.getJSONArray("decisionTreeList")); + break; + + } + List maps = JSON.parseArray(strategyStr, Map.class); + return maps; + } + + //获取执行用的id列表 + public final static List getExecuteIdList(EngineNode engineNode, String idKey) { + List maps = ExecuteUtils.getExecuteListFromNodeJson(engineNode); + List executeIdList = new ArrayList<>(); + if (maps != null && maps.size() > 0) { + for (Map map : maps) { + if (map.containsKey(idKey)) { + Object o = map.get(idKey); + if (o != null) { + Long id = StrUtils.strToLong(String.valueOf(o)); + if (id != null) { + executeIdList.add(id); + } + } + + } + } + } + return executeIdList; + } + + //判断终止条件是否满足,满足则结束 + public final static void terminalCondition(EngineNode engineNode, Map inputParam, Map outMap, Map variablesMap) { + if (StringUtils.isBlank(engineNode.getNodeScript())) { + return; + } + JSONObject nodeScript = JSONObject.parseObject(engineNode.getNodeScript()); + JSONObject terminationInfo = nodeScript.getJSONObject("terminationInfo"); + String conditions = terminationInfo.getString("conditions"); + Map fieldTypeMap = terminationInfo.getObject("fieldTypeMap", Map.class); + JevalUtil.convertVariables(fieldTypeMap, variablesMap); + // 判断终止条件 + boolean result = false; + try { + result = JevalUtil.evaluateBoolean(conditions, variablesMap); + } catch (EvaluationException e) { + logger.error("终止条件执行异常,执行内容:{},参数:{}", conditions, variablesMap); + e.printStackTrace(); + } + + if (result) { + Object outValue = ""; + JSONObject output = terminationInfo.getJSONObject("output"); + String fieldValue = output.getString("fieldValue"); + String fieldCode = output.getString("fieldCode"); + int variableType = output.getInteger("variableType"); + switch (variableType) { + case 1: + outValue = fieldValue; + break; + case 2: + outValue = ExecuteUtils.getObjFromMap(inputParam, fieldValue); + break; + case 3: + outValue = ExecuteUtils.getObjFromScript(inputParam, fieldValue); + break; + } + // 输出终止结果 + if (outValue == null) { + outValue = ""; + } + if (outValue instanceof String) { + outMap.put("result", outValue); + } else { + outMap.put("result", JSONObject.toJSON(outValue)); + } + if (StringUtils.isNotBlank(fieldCode)) { + inputParam.put(fieldCode, outValue); + } + engineNode.setNextNodes(null); + } + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/JevalUtil.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/JevalUtil.java new file mode 100644 index 0000000..4662733 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/JevalUtil.java @@ -0,0 +1,168 @@ +package com.baoying.enginex.executor.util; + +import com.baoying.enginex.executor.common.constants.CommonConst; +import com.baoying.enginex.executor.engine.consts.EngineOperator; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import com.baoying.enginex.executor.util.jeval.Evaluator; + +import java.util.Map; + +/** + * 表达式解析器入口 + * @author sunyk + * + */ +public class JevalUtil { + + /** + * 获取执行布尔值结果 + * @param expression + * @param params + * @return + * @throws EvaluationException + */ + public static Boolean evaluateBoolean(String expression,Map params) throws EvaluationException { + Evaluator evaluator = getEvaluator(params); + return evaluator.getBooleanResult(expression); + } + + /** + * 获取执行数字结果 + * @param expression + * @param params + * @return + * @throws EvaluationException + */ + public static Double evaluateNumric(String expression,Map params) throws EvaluationException{ + Evaluator evaluator = getEvaluator(params); + return evaluator.getNumberResult(expression); + } + + /** + * 获取执行String结果 + * @param expression + * @param params + * @return + * @throws EvaluationException + */ + public static String evaluateString(String expression,Map params) throws EvaluationException{ + Evaluator evaluator = getEvaluator(params); + return evaluator.evaluate(expression,false,true); + } + + /** + * 获取绑定参数的Evaluator + * @param params + * @return + * @throws EvaluationException + */ + private static Evaluator getEvaluator(Map params){ + Evaluator evaluator = new Evaluator(); + if(params != null && !params.isEmpty()){ + for (Map.Entry entry : params.entrySet()) { + if(null!=entry.getValue()){ + evaluator.putVariable(entry.getKey(), entry.getValue().toString()); + } + } + } + return evaluator; + } + + /** + * 根据区间表达式解析区间 + * @param expression,eg:[3,5],param:字段code + * @return + */ + public static String getNumericInterval(String expression,String param){ + StringBuffer result = new StringBuffer(); + //先把变量进行加工#{param} + param = EngineOperator.OPERATOR_VARIABLE_LEFT+param+EngineOperator.OPERATOR_VARIABLE_RIGHT; + //如果是纯数字,代表==,直接拼接 + if(!expression.startsWith(EngineOperator.OPERATOR_LEFT_PARENTHESES) && !expression.startsWith(EngineOperator.OPERATOR_LEFT_BRACKET) + && !expression.endsWith(EngineOperator.OPERATOR_RIGHT_PARENTHESES) && !expression.endsWith(EngineOperator.OPERATOR_RIGHT_BRACKET)){ + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_EQUALS_RELATION).append(CommonConst.SYMBOL_BLANK).append(expression); + return result.toString(); + } + //获取到取值区间 + String exp = expression.substring(1, expression.length()-1); + String[] segments = null; + if(exp.startsWith(CommonConst.SYMBOL_COMMA)){ + segments = new String[1]; + segments[0] = exp.substring(1); + }else{ + segments = exp.split(CommonConst.SYMBOL_COMMA); + } + //判断取值范围(,3)(4,) + if(segments.length == 1){ + //说明是(,3),(4,) + if(expression.substring(1, expression.length()-1).startsWith(CommonConst.SYMBOL_COMMA)){ + //以逗号开始(,3) + if(expression.endsWith(EngineOperator.OPERATOR_RIGHT_PARENTHESES)){ + //小括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_LESS_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[0]); + }else if(expression.endsWith(EngineOperator.OPERATOR_RIGHT_BRACKET)){ + //大括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_LESS_EQUALS_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[0]); + } + }else{ + //以逗号结尾(4,) + if(expression.startsWith(EngineOperator.OPERATOR_LEFT_PARENTHESES)){ + //小括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_GREATER_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[0]); + }else if(expression.startsWith(EngineOperator.OPERATOR_LEFT_BRACKET)){ + //大括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_GREATER_EQUALS_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[0]); + } + } + }else if(segments.length == 2){ + //开始符号 + if(expression.startsWith(EngineOperator.OPERATOR_LEFT_PARENTHESES)){ + //小括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_GREATER_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[0]); + }else if(expression.startsWith(EngineOperator.OPERATOR_LEFT_BRACKET)){ + //大括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_GREATER_EQUALS_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[0]); + } + //都是&&关系 + result.append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_AND_RELATION).append(CommonConst.SYMBOL_BLANK); + //结束符号 + if(expression.endsWith(EngineOperator.OPERATOR_RIGHT_PARENTHESES)){ + //小括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_LESS_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[1]); + }else if(expression.endsWith(EngineOperator.OPERATOR_RIGHT_BRACKET)){ + //大括号 + result.append(param).append(CommonConst.SYMBOL_BLANK).append(EngineOperator.OPERATOR_LESS_EQUALS_RELATION).append(CommonConst.SYMBOL_BLANK).append(segments[1]); + } + } + return result.toString(); + } + + /** + * 变量值转义 + * @param fieldsMap + * @param variablesMap + * @return + */ + public static Map convertVariables(Map fieldsMap,Map variablesMap){ + if(CollectionUtil.isNotNullOrEmpty(variablesMap)){ + if(!CollectionUtil.isNotNullOrEmpty(fieldsMap)){ + return variablesMap; + } + String key = ""; + Integer value = null; + for (Map.Entry entry : variablesMap.entrySet()) { + key = entry.getKey(); + value = fieldsMap.get(key); + if(value == null){ + continue; + } + //2代表字符串 + if(value == 2){ + String variableValue = CommonConst.SYMBOL_SINGLE_QUOTA+String.valueOf(variablesMap.get(key))+CommonConst.SYMBOL_SINGLE_QUOTA; + variablesMap.put(key, variableValue); + } + } + } + return variablesMap; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ListOpUtils.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ListOpUtils.java new file mode 100644 index 0000000..4857a7f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/ListOpUtils.java @@ -0,0 +1,106 @@ +package com.baoying.enginex.executor.util; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class ListOpUtils { + public static Map> recursionGroup(List param, List keys){ + return param.stream().collect(Collectors.groupingBy(item->{ + String cond = ""; + for (String key : keys) { + if (StringUtils.isNotBlank(cond)){ + cond+="_"; + } + cond += getObjByKeyAndJson(item,key); +// String[] split = key.split("\\."); +// if (split.length>1){ +// JSONObject jsonObject = item; +// for (int i = 0; i < split.length; i++) { +// if (i + * Description: md5加密工具类.
+ * Date: 2015年6月30日 上午10:30:23
+ * @since JDK 1.7 + * @see + */ +public class MD5 { + /** + * 全局数组 + */ + private static final String[] DIGITS = { "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "H", "i", + "j", "k", "l", "m", "n", "~", "$", "@", "%", "*", "#", "&", "!" }; + + public MD5() { + } + + /** + * byteToArrayString:(返回形式为数字跟字符串).
+ * @author wz + * @param bByte byte + * @return 返回形式为数字跟字符串 + */ + private static String byteToArrayString(byte bByte) { + int iRet = bByte; + if (iRet < 0) { + iRet += 256; + } + int iD1 = iRet / 32; + int iD2 = iRet % 32; + return DIGITS[iD1] + DIGITS[iD2]; + } + + /** + * byteToNum:(返回形式只为数字).
+ * @param bByte byte + * @return 返回形式只为数字 + */ + private static String byteToNum(byte bByte) { + int iRet = bByte; + System.out.println("iRet1=" + iRet); + if (iRet < 0) { + iRet += 256; + } + return String.valueOf(iRet); + } + + /** + * byteToString:(转换字节数组为16进制字串).
+ * @param bByte byte数组 + * @return 返回转换字节数组为16进制字串 + */ + private static String byteToString(byte[] bByte) { + StringBuffer sBuffer = new StringBuffer(); + for (int i = 0; i < bByte.length; i++) { + sBuffer.append(byteToArrayString(bByte[i])); + } + return sBuffer.toString(); + } + + /** + * GetMD5Code:(md5加密).
+ * @author wz + * @param param 需要加密的字段 + * @return 加密后的字段 + */ + public static String GetMD5Code(String param) { + String resultString = null; + try { + resultString = new String(param); + MessageDigest md = MessageDigest.getInstance("MD5"); + // md.digest() 该函数返回值为存放哈希值结果的byte数组 + resultString = byteToString(md.digest(param.getBytes())); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + } + return resultString; + } + + +// public static void main(String[] args) { +// MD5 getMD5 = new MD5(); +// System.out.println(getMD5.GetMD5Code("123456")); +// } + +} + diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/NumUtils.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/NumUtils.java new file mode 100644 index 0000000..39b9f4f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/NumUtils.java @@ -0,0 +1,24 @@ +package com.baoying.enginex.executor.util; + + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class NumUtils { + + public static double toDouble(Object o){ + double d = 0D; + if (o==null){ + return d; + } + try { + d = Double.valueOf(o.toString()).doubleValue(); + + }catch (Exception e){ + log.error("转换为double失败,原值:{}",o); + e.printStackTrace(); + } + return d; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StrUtils.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StrUtils.java new file mode 100644 index 0000000..a402980 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StrUtils.java @@ -0,0 +1,32 @@ +package com.baoying.enginex.executor.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class StrUtils { + //判断是否为一个数字 + public static boolean isNum(String str){ + if (str==null||"".equals(str)){ + return false; + } + Pattern pattern = Pattern.compile("^(-|\\+)?\\d+(\\.\\d+)?$"); + Matcher isNum = pattern.matcher(str); + if (!isNum.matches()) { + return false; + } + return true; + } + //将字符串转为Long类型 + public static Long strToLong(String str){ + if (isNum(str)){ + return Long.valueOf(str); + } + return null; + } + public static Double strToDouble(String str){ + if (isNum(str)){ + return Double.valueOf(str); + } + return null; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StringUtil.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StringUtil.java new file mode 100644 index 0000000..95dc1d2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/StringUtil.java @@ -0,0 +1,465 @@ +package com.baoying.enginex.executor.util; + +import java.math.BigDecimal; +import java.util.*; + +/** + * @ClassName: StringUtil + * @Description: String 工具类 + * @author Bai_Keyang + * @date 2015-06-24 16:00:16 + */ +public class StringUtil { + + /** + *

判断是否是有效的字符串,空字符串为无效字符串

+ * + * @param str + * @return boolean + */ + public static boolean isValidStr(String str) { + return str != null && str.trim().length() > 0; + } + + /** + *

判断是字符串否是为空,字符串为空,返回 "",反之返回其字符串本身.

+ * + *

if str is null then convret str to "".

+ * + * @param str + * @return String + */ + public static String convertStrIfNull(String str) { + return str == null ? SysConstant.EMPTY_STRING : str; + } + + /** + *

根据字符串转换为布尔值.

+ * + * @param str + * @return boolean + */ + public static boolean getStrToBoolean(String str) { + return isValidStr(str) ? str.toLowerCase().trim().equals(SysConstant.TRUE) : false; + } + + /** + *

根据字符串转换为 整型(int)并返回;转换失败,则返回0.

+ * + *

convert str value to int. if fail,then return 0.

+ * + * @param str + * @return int + */ + public static int getStrToInt(String str) { + try { + return Integer.parseInt(str); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + *

根据字符串转换为 整型(int)并返回;转换失败,则返回 指定的值.

+ * + *

convert str value to int. if fail,then return defaultvalue.

+ * + * @param str + * @param defaultValue + * @return int + */ + public static int getStrToInt(String str, int defaultValue) { + try { + return Integer.parseInt(str); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + *

根据字符串转换为long.

+ * + * @param str + * @return long + */ + public static long getStrTolong(String str) { + long result = 0; + if (!isValidStr(str)) { + return result; + } + try { + result = Long.parseLong(str); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + return result; + } + + /** + *

根据字符串转换为double.

+ * + *

convert String to double

+ * + * @param str + * @return double + */ + public static double getStrTodouble(String str) { + double result = 0; + if (!isValidStr(str)) { + return result; + } + try { + result = Double.parseDouble(str); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + return result; + } + + /** + *

根据字符串转换为BigDecimal.

+ * + *

convert String object to BigDecimal

+ * + * @param str + * @return BigDecimal + */ + public static BigDecimal getStrToBigDecimal(String str) { + BigDecimal result = new BigDecimal(0); + if (!isValidStr(str)) { + return result; + } + try { + result = new BigDecimal(str); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + return result; + } + + /** + *

根据字符串转换为Integer.

+ * + *

convert String to Integer.

+ * + * @param str + * @return Integer + */ + public static Integer getStrToInteger(String str) { + Integer result = new Integer(0); + if (!isValidStr(str)) { + return result; + } + try { + result = Integer.valueOf(str); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + return result; + } + + /** + *

根据字符串转换为Long.

+ * + *

convert String to Long.

+ * + * @param str + * @return Long + */ + public static Long getStrToLong(String str) { + Long result = new Long(0); + if (!isValidStr(str)) { + return result; + } + try { + result = Long.valueOf(str.trim()); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + return result; + } + + /** + *

根据字符串转换为Double.

+ * + *

convert String to Double

+ * + * @param str + * @return Double + */ + public static Double getStrToDouble(String str) { + Double result = new Double(0); + if (!isValidStr(str)) { + return result; + } + try { + result = Double.valueOf(str); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + return result; + } + + /** + *

根据数组转换为字符串,用","拼接.

+ *

例:

+ *

    String[] strArray = new String[]{"How","do","you","do"};

+ *

    拼接后字符串样例:How,do,you,do

+ * + *

convert Object array to String use ",".

+ * + * @param Object[] + * @return String + */ + public static String getArrToStr(Object[] obj) { + if (obj == null) { + return null; + } + + StringBuffer buffer = new StringBuffer(); + if (obj.length > 0) { + buffer.append(obj[0]); + } + + for (int m = 1; m < obj.length; m++) { + buffer.append(SysConstant.COMMA).append(obj[m]); + } + + return buffer.toString(); + } + + /** + *

去掉重复数据(1,2,3,2,4 => 1,2,3,4)

+ * + * @param metadata + * @param tagStr + * @return String + */ + public static String removeEqualStr(String metadata, String tagStr) { + if (!StringUtil.isValidStr(metadata)) { + return SysConstant.EMPTY_STRING; + } + Set set = new HashSet(); + String[] arr = metadata.split(tagStr); + for (String temp : arr) { + if (StringUtil.isValidStr(temp)) { + set.add(temp); + } + } + Iterator it = set.iterator(); + StringBuffer returnMetadata = new StringBuffer(); + while (it.hasNext()) { + returnMetadata.append(it.next() + tagStr); + } + return returnMetadata.toString().substring(0,returnMetadata.length() - 1); + } + + /** + *

查询是否有重复数据

+ * + * @param strArr + * @param str + * @param tagStr + * @return boolean + */ + public static boolean hasEqualStr(String strArr, String str, String tagStr) { + boolean bool = false; + if (StringUtil.isValidStr(strArr)) { + String[] arr = strArr.split(tagStr); + for (String temp : arr) { + if (temp.equals(str)) { + bool = true; + break; + } + } + } + return bool; + } + + /** + *

根据字符串将其转换编码为UTF-8的字符串.

+ * + *

convert type to utf-8

+ * + * @param str + * @return utf-8 string + */ + public static String toUtf8String(String str) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c >= 0 && c <= 255) { + sb.append(c); + } else { + byte[] b; + try { + b = Character.toString(c).getBytes("utf-8"); + } catch (Exception ex) { + b = new byte[0]; + } + for (int j = 0; j < b.length; j++) { + int k = b[j]; + if (k < 0) + k += 256; + sb.append("%" + Integer.toHexString(k).toUpperCase()); + } + } + } + return sb.toString(); + } + + /** + *

根据字符串指定的位置更新字符串内容

+ * + * @param formString 被更新字符串 + * @param updateIndex 选择更新位数 + * @param updateValue 更新为值 + * + * @return String + */ + public static String formatIntegrity(String formatString, int updateIndex, char updateValue) { + if (!isValidStr(formatString)) { + return formatString; + } + if (updateIndex < 1) { + return formatString; + } + if (updateIndex > formatString.length()) { + return formatString; + } + char[] formatStringChar = formatString.toCharArray(); + formatStringChar[updateIndex] = updateValue; + + return String.valueOf(formatStringChar); + } + + /** + *

转换特殊字符

+ * + * @param str 含有特殊字符的字符串 + * + * @return String + */ + public static String converSpecialChar(String str) { + if (!isValidStr(str)) { + return str; + } + str = str.trim(); + if (str.indexOf("\\") >= 0) { + str = str.replaceAll("\\\\", "\\\\\\\\\\\\\\\\"); + } + if (str.indexOf("'") >= 0) { + str = str.replaceAll("'", "\\\\'"); + } + if (str.indexOf("\"") >= 0) { + str = str.replaceAll("\"", "\\\\\""); + } + if (str.indexOf("%") >= 0) { + str = str.replaceAll("%", "\\\\%"); + } + return str; + } + + /** + *

获取字符串字节长度(包含中文和中文符号)

+ * + * @param str 含有中文和中文符号的字符串 + * + * @return int + */ + public static int getLength(String str){ + return str.replaceAll("[\u4E00-\u9FA5\u3000-\u303F\uFF00-\uFFEF]", "rr").length(); + } + + /** + *

此排序方法仅应用于数字类型的string数字排序

+ * + * @param arrays 有long类型的数字组成的String 数组 + * @return 排序后的数组 + */ + public static String[] sortArrays(String[] arrays){ + for(int i = 0; i < arrays.length - 1; i++) { + String temp =""; + for(int j = 0; j < arrays.length - i - 1; j++) { + if(StringUtil.getStrTolong(arrays[j]) >StringUtil.getStrTolong(arrays[j +1])) { + temp = arrays[j + 1]; + arrays[j + 1] = arrays[j]; + arrays[j] = temp; + } + } + } + return arrays; + } + + /** + *

此排序方法仅应用于浮点类型的string数字排序

+ * + * @param arrays 有浮点类型的数字组成的String 数组 + * @return 排序后的数组 + */ + public static String[] sortArraystoBigDecimal(String[] arrays) { + for (int i = 0; i < arrays.length - 1; i++) { + String temp = ""; + for (int j = 0; j < arrays.length - i - 1; j++) { + if (StringUtil.getStrToBigDecimal(arrays[j]).compareTo( + StringUtil.getStrToBigDecimal(arrays[j + 1])) == 1) { + temp = arrays[j + 1]; + arrays[j + 1] = arrays[j]; + arrays[j] = temp; + } + } + } + return arrays; + } + + /** + *

判断字符串是否为NUll,或者为空字符

+ * @param str 字符串 + * @return boolean + * */ + public static boolean isBlank(String str){ + if(str == null || str.equals("")){ + return true; + } + return false; + } + + + /** + *

将字符拆分,并放入list集合

+ * @param str 字符串 + * @return list + * */ + public static List toLongList(String str){ + List idList = new ArrayList(); + if(!isBlank(str)){ + String[] idsArray = str.split(","); + for (int i = 0; i < idsArray.length; i++) { + idList.add(Long.parseLong(idsArray[i])); + } + } + return idList; + } + + + public static String listToString(List list, char separator) { + return org.apache.commons.lang.StringUtils.join(list.toArray(),separator); + } + + public static void main(String[] args) { + //String result = "aaaa ,,aaa"; + //System.out.println(getLength(result)); + + String[] strArray = new String[]{"How","d$o","you","do"}; + strArray = new String[]{"5.36","5.003","1.36","9.87","3.33333379"}; + //strArray = StringUtil.sortArrays(strArray); + strArray = StringUtil.sortArraystoBigDecimal(strArray); + String str = StringUtil.getArrToStr(strArray); + System.out.println(str); + + //System.out.println(StringUtil.formatIntegrity("dkkemnkn", 2, '6')); + + } + + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/SysConstant.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/SysConstant.java new file mode 100644 index 0000000..3b63848 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/SysConstant.java @@ -0,0 +1,96 @@ +package com.baoying.enginex.executor.util; + +/** + * @ClassName: SysConstant + * @Description: 系统常量类 + * @author Bai_Keyang + * @date 2015-06-24 16:00:16 + */ +public final class SysConstant { + + public static final String EMPTY_STRING =""; + + public static final String SPACE =" "; + + public static final String COMMA =","; + + public static final String SEMICOLON =";"; + + public static final String MINUS ="-"; + + public static final String UNDERLINE ="_"; + + public static final String DATA_POINT ="."; + + public static final String POINT ="\\."; + + public static final String COLON =":"; + + public static final String WN ="// "; + + public static final String AT ="@"; + + public static final String SLASH ="/"; + + public static final String BACKSLASH ="\\\\"; + + public static final String YES ="Y"; + + public static final String NO ="N"; + + public static final String TRUE ="true"; + + public static final String FALSE ="false"; + + public static final String LEFT_BRACKET ="("; + + public static final String RIGHT_BRACKET =")"; + + public static final String ELLIPSIS ="..."; + + public static final String ESCAPE ="\\"; + + public static final String EXCLAMATION ="!"; + + public static final String INFINITY ="∞"; + + /** + * 系统统一操作成功码 + */ + public static final String SUCCESS_CODE = "200"; + + /** + * 系统统一操作成功提示信息 + */ + public static final String SUCCESS_MESSAGE = "操作成功"; + + /** + * 系统统一操作失败码 + */ + public static final String FAIL_CODE = "500"; + + /** + * 系统统一操作失败提示信息 + */ + public static final String FAIL_MESSAGE = "操作失败"; + + /** + * 系统统一操作失败码 + */ + public static final String UNUSE_CODE = "300"; + + /** + * 系统统一操作失败提示信息 + */ + public static final String UNUSE_MESSAGE = "已停用"; + + /** + * 系统统一操作失败码 + */ + public static final String DEL_CODE = "400"; + + /** + * 系统统一操作失败提示信息 + */ + public static final String DEL_MESSAGE = "已删除"; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpClient.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpClient.java new file mode 100644 index 0000000..f9d8b54 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpClient.java @@ -0,0 +1,320 @@ +package com.baoying.enginex.executor.util.https; + +import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.multipart.PartBase; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.httpclient.params.HttpConnectionManagerParams; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.apache.commons.httpclient.protocol.Protocol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +/** + * @author sinaWeibo + * + */ +public class HttpClient implements java.io.Serializable { + + static Logger log=LoggerFactory.getLogger(HttpClient.class); + + private static final long serialVersionUID = -176092625883595547L; + private static final int OK = 200; // OK: Success! + private static final int NOT_MODIFIED = 304; // Not Modified: There was no new data to return. + private static final int BAD_REQUEST = 400; // Bad Request: The request was invalid. An accompanying error message will explain why. This is the status versionCode will be returned during rate limiting. + private static final int NOT_AUTHORIZED = 401; // Not Authorized: Authentication credentials were missing or incorrect. + private static final int FORBIDDEN = 403; // Forbidden: The request is understood, but it has been refused. An accompanying error message will explain why. + private static final int NOT_FOUND = 404; // Not Found: The URI requested is invalid or the resource requested, such as a user, does not exists. + private static final int NOT_ACCEPTABLE = 406; // Not Acceptable: Returned by the Search API when an invalid format is specified in the request. + private static final int INTERNAL_SERVER_ERROR = 500;// Internal Server Error: Something is broken. Please post to the group so the Weibo team can investigate. + private static final int BAD_GATEWAY = 502;// Bad Gateway: Weibo is down or being upgraded. + private static final int SERVICE_UNAVAILABLE = 503;// Service Unavailable: The Weibo servers are up, but overloaded with requests. Try again later. The search and trend methods use this to indicate when you are being rate limited. + + private final static boolean DEBUG = true; + org.apache.commons.httpclient.HttpClient client = null; + + private MultiThreadedHttpConnectionManager connectionManager; + private int maxSize; + + public HttpClient() { + this(150, 30000, 30000, 1024 * 1024); + } + + public HttpClient(int maxConPerHost, int conTimeOutMs, int soTimeOutMs, + int maxSize) { + connectionManager = new MultiThreadedHttpConnectionManager(); + HttpConnectionManagerParams params = connectionManager.getParams(); + params.setDefaultMaxConnectionsPerHost(maxConPerHost); + params.setConnectionTimeout(conTimeOutMs); + params.setSoTimeout(soTimeOutMs); + + HttpClientParams clientParams = new HttpClientParams(); + // 忽略cookie 避免 Cookie rejected 警告 + clientParams.setCookiePolicy(CookiePolicy.IGNORE_COOKIES); + client = new org.apache.commons.httpclient.HttpClient(clientParams, + connectionManager); + Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443); + Protocol.registerProtocol("https", myhttps); + this.maxSize = maxSize; + } + + /** + * log调试 + * + */ + private static void log(String message) { + if (DEBUG) { + log.debug(message); + } + } + + /** + * 处理http getmethod 请求 + * + */ + public int getHttps(String url){ + log.debug(" in getHttps,url="+url); + GetMethod method = new GetMethod(url); + int responseCode = -1; + try { + method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, + new DefaultHttpMethodRetryHandler(3, false)); + client.executeMethod(method); + responseCode = method.getStatusCode(); + log.info("https StatusCode:" + responseCode); + } catch (IOException ioe) { + ioe.printStackTrace(); + } finally { + method.releaseConnection(); + return responseCode; + } + } + public String get(String url) throws Exception { + return get(url, new PostParameter[0]).asString(); + } + + public Response get(String url, PostParameter[] params) + throws Exception { + log("Request:"); + log("GET:" + url); + if (null != params && params.length > 0) { + String encodedParams = HttpClient.encodeParameters(params); + if (-1 == url.indexOf("?")) { + url += "?" + encodedParams; + } else { + url += "&" + encodedParams; + } + } + GetMethod getmethod = new GetMethod(url); + return httpRequest(getmethod); + + } + + /** + * 处理http post请求 + * + */ + + public Response post(String url, PostParameter[] params) + throws Exception { + return post(url, params, true); + + } + + public Response post(String url, PostParameter[] params, + Boolean WithTokenHeader) throws HttpsException { + log("Request:"); + log("POST" + url); + PostMethod postMethod = new PostMethod(url); + for (int i = 0; i < params.length; i++) { + postMethod.addParameter(params[i].getName(), params[i].getValue()); + } + HttpMethodParams param = postMethod.getParams(); + param.setContentCharset("UTF-8"); + if (WithTokenHeader) { + return httpRequest(postMethod); + } else { + return httpRequest(postMethod, WithTokenHeader); + } + } + + /** + * 处理http post请求 + * + */ + + public String post(String url, Map params) + throws Exception { + String strResult = null; + + Response response= post(url, params, true); + if (response !=null) { + // 取出回应字串 + strResult = response.getResponseAsString(); + } + return strResult; + } + + public Response post(String url, Map params, + Boolean WithTokenHeader) throws HttpsException { + log("Request:"); + log("POST" + url); + PostMethod postMethod = new PostMethod(url); + postMethod.addRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8"); + + for (Map.Entry entry : params.entrySet()) { + postMethod.addParameter(entry.getKey(), entry + .getValue()); + } + + HttpMethodParams param = postMethod.getParams(); + param.setContentCharset("UTF-8"); + if (WithTokenHeader) { + return httpRequest(postMethod); + } else { + return httpRequest(postMethod, WithTokenHeader); + } + } + public Response httpRequest(HttpMethod method) throws HttpsException { + return httpRequest(method, true); + } + + public Response httpRequest(HttpMethod method, Boolean WithTokenHeader) + throws HttpsException { + InetAddress ipaddr; + int responseCode = -1; + try { + ipaddr = InetAddress.getLocalHost(); + List
headers = new ArrayList
(); + method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, + new DefaultHttpMethodRetryHandler(3, false)); + client.executeMethod(method); + Header[] resHeader = method.getResponseHeaders(); + responseCode = method.getStatusCode(); + log("Response:"); + log("https StatusCode:" + String.valueOf(responseCode)); + + for (Header header : resHeader) { + log(header.getName() + ":" + header.getValue()); + } + Response response = new Response(); + response.setResponseAsString(method.getResponseBodyAsString()); + log(response.toString() + "\n"); + + if (responseCode != OK) + + { + try { + throw new HttpsException(getCause(responseCode)); + } catch (Exception e) { + e.printStackTrace(); + } + } + return response; + + } catch (IOException ioe) { + throw new HttpsException(ioe.getMessage(), ioe, responseCode); + } finally { + method.releaseConnection(); + } + + } + + /* + * 对parameters进行encode处理 + */ + public static String encodeParameters(PostParameter[] postParams) { + StringBuffer buf = new StringBuffer(); + for (int j = 0; j < postParams.length; j++) { + if (j != 0) { + buf.append("&"); + } + try { + buf.append(URLEncoder.encode(postParams[j].getName(), "UTF-8")) + .append("=") + .append(URLEncoder.encode(postParams[j].getValue(), + "UTF-8")); + } catch (java.io.UnsupportedEncodingException neverHappen) { + } + } + return buf.toString(); + } + + private static class ByteArrayPart extends PartBase { + private byte[] mData; + private String mName; + + public ByteArrayPart(byte[] data, String name, String type) + throws IOException { + super(name, type, "UTF-8", "binary"); + mName = name; + mData = data; + } + + protected void sendData(OutputStream out) throws IOException { + out.write(mData); + } + + protected long lengthOfData() throws IOException { + return mData.length; + } + + protected void sendDispositionHeader(OutputStream out) + throws IOException { + super.sendDispositionHeader(out); + StringBuilder buf = new StringBuilder(); + buf.append("; filename=\"").append(mName).append("\""); + out.write(buf.toString().getBytes()); + } + } + + private static String getCause(int statusCode) { + String cause = null; + switch (statusCode) { + case NOT_MODIFIED: + break; + case BAD_REQUEST: + cause = "The request was invalid. An accompanying error message will explain why. This is the status versionCode will be returned during rate limiting."; + break; + case NOT_AUTHORIZED: + cause = "Authentication credentials were missing or incorrect."; + break; + case FORBIDDEN: + cause = "The request is understood, but it has been refused. An accompanying error message will explain why."; + break; + case NOT_FOUND: + cause = "The URI requested is invalid or the resource requested, such as a user, does not exists."; + break; + case NOT_ACCEPTABLE: + cause = "Returned by the Search API when an invalid format is specified in the request."; + break; + case INTERNAL_SERVER_ERROR: + cause = "Something is broken. Please post to the group so the Weibo team can investigate."; + break; + case BAD_GATEWAY: + cause = "Weibo is down or being upgraded."; + break; + case SERVICE_UNAVAILABLE: + cause = "Service Unavailable: The Weibo servers are up, but overloaded with requests. Try again later. The search and trend methods use this to indicate when you are being rate limited."; + break; + default: + cause = ""; + } + return statusCode + ":" + cause; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpsException.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpsException.java new file mode 100644 index 0000000..b28487f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/HttpsException.java @@ -0,0 +1,49 @@ +package com.baoying.enginex.executor.util.https; + +/** + * An exception class that will be thrown when WeiboAPI calls are failed.
+ * In case the Weibo server returned HTTP error versionCode, you can get the HTTP status versionCode using getStatusCode() method. + * @author Yusuke Yamamoto - yusuke at mac.com + */ +public class HttpsException extends Exception { + private int statusCode = -1; + private int errorCode = -1; + private String request; + private String error; + private static final long serialVersionUID = -2623309261327598087L; + + public HttpsException(String msg) { + super(msg); + } + + public HttpsException(Exception cause) { + super(cause); + } + + public HttpsException(String msg, Exception cause) { + super(msg, cause); + } + + public HttpsException(String msg, Exception cause, int statusCode) { + super(msg, cause); + this.statusCode = statusCode; + + } + + public int getStatusCode() { + return this.statusCode; + } + + public int getErrorCode() { + return errorCode; + } + + public String getRequest() { + return request; + } + + public String getError() { + return error; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/MySSLSocketFactory.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/MySSLSocketFactory.java new file mode 100644 index 0000000..f415676 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/MySSLSocketFactory.java @@ -0,0 +1,101 @@ +package com.baoying.enginex.executor.util.https; + +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.IOException; +import java.net.*; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** +Provide a custom socket factory that implements org.apache.commons.httpclient.protocol.ProtocolSocketFactory interface. +The socket factory is responsible for opening a socket to the target server using either the standard or a third party +SSL library and performing any required initialization such as performing the connection handshake. Generally the initialization +is performed automatically when the socket is created. +@author sinaWeibo +*/ +public class MySSLSocketFactory implements ProtocolSocketFactory { + private SSLContext sslcontext = null; + private SSLContext createSSLContext() { + SSLContext sslcontext = null; + try { + sslcontext = SSLContext.getInstance("SSL"); + sslcontext.init(null, + new TrustManager[] { new TrustAnyTrustManager() }, + new java.security.SecureRandom()); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } + return sslcontext; + } + + private SSLContext getSSLContext() { + if (this.sslcontext == null) { + this.sslcontext = createSSLContext(); + } + return this.sslcontext; + } + + public Socket createSocket(Socket socket, String host, int port, + boolean autoClose) throws IOException, UnknownHostException { + return getSSLContext().getSocketFactory().createSocket(socket, host, + port, autoClose); + } + + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + return getSSLContext().getSocketFactory().createSocket(host, port); + } + + public Socket createSocket(String host, int port, InetAddress clientHost, + int clientPort) throws IOException, UnknownHostException { + return getSSLContext().getSocketFactory().createSocket(host, port, + clientHost, clientPort); + } + + public Socket createSocket(String host, int port, InetAddress localAddress, + int localPort, HttpConnectionParams params) throws IOException, + UnknownHostException, ConnectTimeoutException { + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null"); + } + int timeout = params.getConnectionTimeout(); + SocketFactory socketfactory = getSSLContext().getSocketFactory(); + if (timeout == 0) { + return socketfactory.createSocket(host, port, localAddress, + localPort); + } else { + Socket socket = socketfactory.createSocket(); + SocketAddress localaddr = new InetSocketAddress(localAddress, + localPort); + SocketAddress remoteaddr = new InetSocketAddress(host, port); + socket.bind(localaddr); + socket.connect(remoteaddr, timeout); + return socket; + } + } + + private static class TrustAnyTrustManager implements X509TrustManager { + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[] {}; + } + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/PostParameter.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/PostParameter.java new file mode 100644 index 0000000..699056d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/PostParameter.java @@ -0,0 +1,208 @@ +package com.baoying.enginex.executor.util.https; + +import java.io.File; +import java.net.URLEncoder; +import java.util.List; + + +/** + * A data class representing HTTP Post parameter + * @author Yusuke Yamamoto - yusuke at mac.com + */ +public class PostParameter implements java.io.Serializable{ + String name; + String value; + private File file = null; + + private static final long serialVersionUID = -8708108746980739212L; + + public PostParameter(String name, String value) { + this.name = name; + this.value = value; + } + + public PostParameter(String name, double value) { + this.name = name; + this.value = String.valueOf(value); + } + + public PostParameter(String name, int value) { + this.name = name; + this.value = String.valueOf(value); + } + + public PostParameter(String name, File file) { + this.name = name; + this.file = file; + } + + public String getName(){ + return name; + } + public String getValue(){ + return value; + } + + public File getFile() { + return file; + } + + public boolean isFile(){ + return null != file; + } + + private static final String JPEG = "image/jpeg"; + private static final String GIF = "image/gif"; + private static final String PNG = "image/png"; + private static final String OCTET = "application/octet-stream"; + + /** + * + * @return content-type + */ + public String getContentType() { + if (!isFile()) { + throw new IllegalStateException("not a file"); + } + String contentType; + String extensions = file.getName(); + int index = extensions.lastIndexOf("."); + if (-1 == index) { + // no extension + contentType = OCTET; + } else { + extensions = extensions.substring(extensions.lastIndexOf(".") + 1).toLowerCase(); + if (extensions.length() == 3) { + if ("gif".equals(extensions)) { + contentType = GIF; + } else if ("png".equals(extensions)) { + contentType = PNG; + } else if ("jpg".equals(extensions)) { + contentType = JPEG; + } else { + contentType = OCTET; + } + } else if (extensions.length() == 4) { + if ("jpeg".equals(extensions)) { + contentType = JPEG; + } else { + contentType = OCTET; + } + } else { + contentType = OCTET; + } + } + return contentType; + } + + + public static boolean containsFile(PostParameter[] params) { + boolean containsFile = false; + if(null == params){ + return false; + } + for (PostParameter param : params) { + if (param.isFile()) { + containsFile = true; + break; + } + } + return containsFile; + } + /*package*/ static boolean containsFile(List params) { + boolean containsFile = false; + for (PostParameter param : params) { + if (param.isFile()) { + containsFile = true; + break; + } + } + return containsFile; + } + + public static PostParameter[] getParameterArray(String name, String value) { + return new PostParameter[]{new PostParameter(name,value)}; + } + public static PostParameter[] getParameterArray(String name, int value) { + return getParameterArray(name,String.valueOf(value)); + } + + public static PostParameter[] getParameterArray(String name1, String value1 + , String name2, String value2) { + return new PostParameter[]{new PostParameter(name1, value1) + , new PostParameter(name2, value2)}; + } + public static PostParameter[] getParameterArray(String name1, int value1 + , String name2, int value2) { + return getParameterArray(name1,String.valueOf(value1),name2,String.valueOf(value2)); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + value.hashCode(); + result = 31 * result + (file != null ? file.hashCode() : 0); + return result; + } + + @Override public boolean equals(Object obj) { + if (null == obj) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof PostParameter) { + PostParameter that = (PostParameter) obj; + + if (file != null ? !file.equals(that.file) : that.file != null) + return false; + + return this.name.equals(that.name) && + this.value.equals(that.value); + } + return false; + } + + @Override + public String toString() { + return "PostParameter{" + + "name='" + name + '\'' + + ", value='" + value + '\'' + + ", file=" + file + + '}'; + } + + public int compareTo(Object o) { + int compared; + PostParameter that = (PostParameter) o; + compared = name.compareTo(that.name); + if (0 == compared) { + compared = value.compareTo(that.value); + } + return compared; + } + + public static String encodeParameters(PostParameter[] httpParams) { + if (null == httpParams) { + return ""; + } + StringBuffer buf = new StringBuffer(); + for (int j = 0; j < httpParams.length; j++) { + if (httpParams[j].isFile()) { + throw new IllegalArgumentException("parameter [" + httpParams[j].name + "]should be text"); + } + if (j != 0) { + buf.append("&"); + } + try { + buf.append(URLEncoder.encode(httpParams[j].name, "UTF-8")) + .append("=").append(URLEncoder.encode(httpParams[j].value, "UTF-8")); + } catch (java.io.UnsupportedEncodingException neverHappen) { + } + } + return buf.toString(); + + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/Response.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/Response.java new file mode 100644 index 0000000..9d60b9a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/https/Response.java @@ -0,0 +1,225 @@ +package com.baoying.enginex.executor.util.https; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.*; +import java.net.HttpURLConnection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + +/** + * A data class representing HTTP Response + * + * @author Yusuke Yamamoto - yusuke at mac.com + */ +public class Response { + private final static boolean DEBUG = true; + static Logger log = LoggerFactory.getLogger(Response.class.getName()); + + + private static ThreadLocal builders = + new ThreadLocal() { + @Override + protected DocumentBuilder initialValue() { + try { + return + DocumentBuilderFactory.newInstance() + .newDocumentBuilder(); + } catch (ParserConfigurationException ex) { + throw new ExceptionInInitializerError(ex); + } + } + }; + + private int statusCode; + private Document responseAsDocument = null; + private String responseAsString = null; + private InputStream is; + private HttpURLConnection con; + private boolean streamConsumed = false; + + public Response() { + + } + public Response(HttpURLConnection con) throws IOException { + this.con = con; + this.statusCode = con.getResponseCode(); + if(null == (is = con.getErrorStream())){ + is = con.getInputStream(); + } + if (null != is && "gzip".equals(con.getContentEncoding())) { + // the response is gzipped + is = new GZIPInputStream(is); + } + } + + // for test purpose + /*package*/ Response(String content) { + this.responseAsString = content; + } + + public int getStatusCode() { + return statusCode; + } + + public String getResponseHeader(String name) { + if (con != null) + return con.getHeaderField(name); + else + return null; + } + + /** + * Returns the response stream.
+ * This method cannot be called after calling asString() or asDcoument()
+ * It is suggested to call disconnect() after consuming the stream. + * + * Disconnects the internal HttpURLConnection silently. + * @return response body stream + * @throws HttpsException + * @see #disconnect() + */ + public InputStream asStream() { + if(streamConsumed){ + throw new IllegalStateException("Stream has already been consumed."); + } + return is; + } + + /** + * Returns the response body as string.
+ * Disconnects the internal HttpURLConnection silently. + * @return response body + * @throws HttpsException + */ + public String asString() throws HttpsException{ + if(null == responseAsString){ + BufferedReader br; + try { + InputStream stream = asStream(); + if (null == stream) { + return null; + } + br = new BufferedReader(new InputStreamReader(stream, "UTF-8")); + StringBuffer buf = new StringBuffer(); + String line; + while (null != (line = br.readLine())) { + buf.append(line).append("\n"); + } + this.responseAsString = buf.toString(); + /* if(Configuration.isDalvik()){ + this.responseAsString = unescape(responseAsString); + }*/ + log(responseAsString); + stream.close(); + con.disconnect(); + streamConsumed = true; + } catch (NullPointerException npe) { + // don't remember in which case npe can be thrown + throw new HttpsException(npe.getMessage(), npe); + } catch (IOException ioe) { + throw new HttpsException(ioe.getMessage(), ioe); + } + } + return responseAsString; + } + + /** + * Returns the response body as org.w3c.dom.Document.
+ * Disconnects the internal HttpURLConnection silently. + * @return response body as org.w3c.dom.Document + * @throws HttpsException + */ + public Document asDocument() throws HttpsException { + if (null == responseAsDocument) { + try { + // it should be faster to read the inputstream directly. + // but makes it difficult to troubleshoot + this.responseAsDocument = builders.get().parse(new ByteArrayInputStream(asString().getBytes("UTF-8"))); + } catch (SAXException saxe) { + throw new HttpsException("The response body was not well-formed:\n" + responseAsString, saxe); + } catch (IOException ioe) { + throw new HttpsException("There's something with the connection.", ioe); + } + } + return responseAsDocument; + } + + public InputStreamReader asReader() { + try { + return new InputStreamReader(is, "UTF-8"); + } catch (java.io.UnsupportedEncodingException uee) { + return new InputStreamReader(is); + } + } + + public void disconnect(){ + con.disconnect(); + } + + private static Pattern escaped = Pattern.compile("&#([0-9]{3,5});"); + + /** + * Unescape UTF-8 escaped characters to string. + * @author pengjianq...@gmail.com + * + * @param original The string to be unescaped. + * @return The unescaped string + */ + public static String unescape(String original) { + Matcher mm = escaped.matcher(original); + StringBuffer unescaped = new StringBuffer(); + while (mm.find()) { + mm.appendReplacement(unescaped, Character.toString( + (char) Integer.parseInt(mm.group(1), 10))); + } + mm.appendTail(unescaped); + return unescaped.toString(); + } + + @Override + public String toString() { + if(null != responseAsString){ + return responseAsString; + } + return "Response{" + + "statusCode=" + statusCode + + ", response=" + responseAsDocument + + ", responseString='" + responseAsString + '\'' + + ", is=" + is + + ", con=" + con + + '}'; + } + + private void log(String message) { + if (DEBUG) { + log.debug("[" + new java.util.Date() + "]" + message); + } + } + + private void log(String message, String message2) { + if (DEBUG) { + log(message + message2); + } + } + + public String getResponseAsString() { + return responseAsString; + } + + public void setResponseAsString(String responseAsString) { + this.responseAsString = responseAsString; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ArgumentTokenizer.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ArgumentTokenizer.java new file mode 100644 index 0000000..5b9597f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ArgumentTokenizer.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +import java.util.Enumeration; + +/** + * This class allow for tokenizer methods to be called on a String of arguments. + */ +public class ArgumentTokenizer implements Enumeration { + + /** + * The default delimitor. + */ + public final char defaultDelimiter = + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR; + + // The arguments to be tokenized. This is updated every time the nextToken + // method is called. + private String arguments = null; + + // The separator between the arguments. + private char delimiter = defaultDelimiter; + + /** + * Constructor that takes a String of arguments and a delimitoer. + * + * @param arguments + * The String of srguments to be tokenized. + * @param delimiter + * The argument tokenizer. + */ + public ArgumentTokenizer(final String arguments, final char delimiter) { + this.arguments = arguments; + this.delimiter = delimiter; + } + + /** + * Indicates if there are more elements. + * + * @return True if there are more elements and false if not. + */ + public boolean hasMoreElements() { + return hasMoreTokens(); + } + + /** + * Indicates if there are more tokens. + * + * @return True if there are more tokens and false if not. + */ + public boolean hasMoreTokens() { + + if (arguments.length() > 0) { + return true; + } + + return false; + } + + /** + * Returns the next element. + * + * @return The next element. + */ + public Object nextElement() { + return nextToken(); + } + + /** + * Returns the next token. + * + * @return The next element. + */ + public String nextToken() { + int charCtr = 0; + int size = arguments.length(); + int parenthesesCtr = 0; + String returnArgument = null; + + // Loop until we hit the end of the arguments String. + while (charCtr < size) { + if (arguments.charAt(charCtr) == '(') { + parenthesesCtr++; + } else if (arguments.charAt(charCtr) == ')') { + parenthesesCtr--; + } else if (arguments.charAt(charCtr) == delimiter + && parenthesesCtr == 0) { + + returnArgument = arguments.substring(0, charCtr); + arguments = arguments.substring(charCtr + 1); + break; + } + + charCtr++; + } + + if (returnArgument == null) { + returnArgument = arguments; + arguments = ""; + } + + return returnArgument; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationConstants.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationConstants.java new file mode 100644 index 0000000..877eb1a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationConstants.java @@ -0,0 +1,63 @@ +package com.baoying.enginex.executor.util.jeval; + +/** + * Contains constants used by classes in this package. + */ +public class EvaluationConstants { + + /** + * The single quote character. + */ + public static final char SINGLE_QUOTE = '\''; + + /** + * The double quote character. + */ + public static final char DOUBLE_QUOTE = '"'; + + /** + * The open brace character. + */ + public static final char OPEN_BRACE = '{'; + + /** + * The closed brace character. + */ + public static final char CLOSED_BRACE = '}'; + + /** + * The pound sign character. + */ + public static final char POUND_SIGN = '#'; + + /** + * The open variable string. + */ + public static final String OPEN_VARIABLE = String.valueOf(POUND_SIGN) + + String.valueOf(OPEN_BRACE); + + /** + * The closed brace string. + */ + public static final String CLOSED_VARIABLE = String.valueOf(CLOSED_BRACE); + + /** + * The true value for a Boolean string. + */ + public static final String BOOLEAN_STRING_TRUE = "1.0"; + + /** + * The false value for a Boolean string. + */ + public static final String BOOLEAN_STRING_FALSE = "0.0"; + + /** + * The comma character. + */ + public static final char COMMA = ','; + + /** + * The function argument separator. + */ + public static final char FUNCTION_ARGUMENT_SEPARATOR = COMMA; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationException.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationException.java new file mode 100644 index 0000000..548a14f --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationException.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +/** + * This exception is thrown when an error occurs during the evaluation process. + */ +public class EvaluationException extends Exception { + + private static final long serialVersionUID = -3010333364122748053L; + + /** + * This constructor takes a custom message as input. + * + * @param message + * A custom message for the exception to display. + */ + public EvaluationException(String message) { + super(message); + } + + /** + * This constructor takes an exception as input. + * + * @param exception + * An exception. + */ + public EvaluationException(Exception exception) { + super(exception); + } + + /** + * This constructor takes an exception as input. + * + * @param message + * A custom message for the exception to display. + * @param exception + * An exception. + */ + public EvaluationException(String message, Exception exception) { + super(message, exception); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationHelper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationHelper.java new file mode 100644 index 0000000..3a94b9e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationHelper.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +public class EvaluationHelper { + + /** + * Replaces all old string within the expression with new strings. + * + * @param expression + * The string being processed. + * @param oldString + * The string to replace. + * @param newString + * The string to replace the old string with. + * + * @return The new expression with all of the old strings replaced with new + * strings. + */ + public static String replaceAll(final String expression, + final String oldString, final String newString) { + + String replacedExpression = expression; + + if (replacedExpression != null) { + int charCtr = 0; + int oldStringIndex = replacedExpression.indexOf(oldString, charCtr); + + while (oldStringIndex > -1) { + // Remove the old string from the expression. + final StringBuffer buffer = new StringBuffer(replacedExpression + .substring(0, oldStringIndex) + + replacedExpression.substring(oldStringIndex + + oldString.length())); + + // Insert the new string into the expression. + buffer.insert(oldStringIndex, newString); + + replacedExpression = buffer.toString(); + + charCtr = oldStringIndex + newString.length(); + + // Determine if we need to continue to search. + if (charCtr < replacedExpression.length()) { + oldStringIndex = replacedExpression.indexOf(oldString, + charCtr); + } else { + oldStringIndex = -1; + } + } + } + + return replacedExpression; + } + + /** + * Determines if a character is a space or white space. + * + * @param character + * The character being evaluated. + * + * @return True if the character is a space or white space and false if not. + */ + public static boolean isSpace(final char character) { + + if (character == ' ' || character == '\t' || character == '\n' + || character == '\r' || character == '\f') { + return true; + } + + return false; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationResult.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationResult.java new file mode 100644 index 0000000..59e820c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/EvaluationResult.java @@ -0,0 +1,177 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +/** + * This class can be used to wrap the result of an expression evaluation. It + * contains useful methods for evaluating the contents of the result. + */ +public class EvaluationResult { + + // The value returned from the evaluation of an expression. + private String result; + + // The quote character specified in the evaluation of the expression. + private char quoteCharacter; + + /** + * Constructor. + * + * @param result + * The value returned from the evaluation of an expression. + * @param quoteCharacter + * The quote character specified in the evaluation of the + * expression. + */ + public EvaluationResult(String result, char quoteCharacter) { + + this.result = result; + this.quoteCharacter = quoteCharacter; + } + + /** + * Returns the quote character. + * + * @return The quote character. + */ + public char getQuoteCharacter() { + return quoteCharacter; + } + + /** + * Sets the quote character. + * + * @param quoteCharacter + * The quoteCharacter to set. + */ + public void setQuoteCharacter(char quoteCharacter) { + this.quoteCharacter = quoteCharacter; + } + + /** + * Returns the result value. + * + * @return The result value. + */ + public String getResult() { + return result; + } + + /** + * Sets the result value. + * + * @param result + * The result to set. + */ + public void setResult(String result) { + this.result = result; + } + + /** + * Returns true if the result value is equal to the value of a Boolean true + * string (1.0). + * + * @return True if the result value is equal to the value of a Boolean true + * string (1.0). + */ + public boolean isBooleanTrue() { + + if (result != null + && EvaluationConstants.BOOLEAN_STRING_TRUE.equals(result)) { + + return true; + } + + return false; + } + + /** + * Returns true if the result value is equal to the value of a Boolean false + * string (0.0). + * + * @return True if the result value is equal to the value of a Boolean false + * string (0.0). + */ + public boolean isBooleanFalse() { + + if (result != null + && EvaluationConstants.BOOLEAN_STRING_FALSE.equals(result)) { + + return true; + } + + return false; + } + + /** + * Returns true if the result value starts with a quote character and ends + * with a quote character. + * + * @return True if the result value starts with a quote character and ends + * with a quote character. + */ + public boolean isString() { + + if (result != null && result.length() >= 2) { + + if (result.charAt(0) == quoteCharacter + && result.charAt(result.length() - 1) == quoteCharacter) { + + return true; + } + } + + return false; + } + + /** + * Returns a Double for the result value. + * + * @return A Double for the result value. + * + * @throws NumberFormatException + * Thrown if the result value is not a double. + */ + public Double getDouble() throws NumberFormatException { + + return new Double(result); + } + + /** + * Returns the unwrapped string for the result value. An unwrapped string is + * a string value without the quote characters that wrap the result value. + * For a string to be returned, then the first character must be a quote + * character and the last character must be a quote character. Otherwise, a + * null value is returned. + * + * @return The normal string for the result value. Null will be returned if + * the result value is not of a string type. + */ + public String getUnwrappedString() { + + if (result != null && result.length() >= 2) { + + if (result.charAt(0) == quoteCharacter + && result.charAt(result.length() - 1) == quoteCharacter) { + + return result.substring(1, result.length() - 1); + } + } + + return null; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/Evaluator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/Evaluator.java new file mode 100644 index 0000000..9c6fc7b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/Evaluator.java @@ -0,0 +1,1703 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +import com.baoying.enginex.executor.util.jeval.function.*; +import com.baoying.enginex.executor.util.jeval.function.math.MathFunctions; +import com.baoying.enginex.executor.util.jeval.function.string.StringFunctions; +import com.baoying.enginex.executor.util.jeval.operator.*; + +import java.util.*; + +/** + * This class is used to evaluate mathematical, string, Boolean and functional + * expressions. It is the main entry point into the JEval API.
+ *
+ * The following types of expressions are supported:
+ *
    + *
  • mathematical Expression involving numbers. Numbers are treated + * as doubles, so resulting numbers will contain at least one decimal place.
  • + *
  • string String can also be added together, compared, etc...
  • + *
  • Boolean Expression that evaluate to true (1.0) and false (0.0).
  • + *
  • functional Custom functions can be created or there are many + * Math and String functions that JEval supplies with this class.
  • + *
+ * The following operators are supported:
+ *
    + *
  • ( open parentheses
  • + *
  • ) closed parentheses
  • + *
  • + addition (for numbers and strings)
  • + *
  • - subtraction
  • + *
  • * multiplication
  • + *
  • / division
  • + *
  • % modulus
  • + *
  • + unary plus
  • + *
  • - unary minus
  • + *
  • = equal (for numbers and strings)
  • + *
  • != not equal (for numbers and strings)
  • + *
  • < less than (for numbers and strings)
  • + *
  • <= less than or equal (for numbers and strings)
  • + *
  • > greater than (for numbers and strings)
  • + *
  • >= greater than or equal (for numbers and strings)
  • + *
  • && boolean and
  • + *
  • || boolean or
  • + *
  • ! boolean not
  • + *
+ * Allows for prebuilt and custom functions.
+ *
    + *
  • JEval already comes with many functions which represent most of the + * methods in the Math and String classes in the standard JDK.
  • + *
  • Thirty-nine math and string functions come built in. See the + * net.sourceforge.jeval.functions.math and + * net.sourceforge.jeval.functions.string packages for details on these ready to + * use functions. You can choose to not load these functions if we you want to + * gain a small improvement in performance.
  • + *
  • Functions must be followed by an open parentheses and a closed + * parentheses which contain any required parameters.
  • + *
  • For more details on functions, see the Function class and the test + * classes.
  • + *
+ * Allows for variables.
+ *
    + *
  • Variable must be enclosed by a pound sign and open brace #{ and a closed + * brace }. i.e. expression = "#{a} + #{b}"
  • + *
  • Two math variables come built in. The E and PI variables represent the + * same value as the Math.E and Math.PI constants in the standard Java SDK. You + * can choose not to load these variables.
  • + *
+ * Notes on expression parsing: + *
    + *
  • Spaces are ignored when parsing expressions.
  • + *
  • The order of precedence used by this class is as follows from highest to + * lowest.
  • + *
  • The expression is evaluated as one or more subexpressions. + * Subexpressions within open parentheses and closed parentheses are evaluated + * before other parts of the expression.
  • + *
  • Inner most subexpression are evaluated first working outward.
  • + *
  • Subexpressions at the same level are evaluated from left to right.
  • + *
  • When evaluating expressions and subexpressions, operators are evaluated + * with the following precedence listed below.
  • + *
  • Operators with with the same precedence are evaluated from left to + * right.
  • + *
  • Once the expression is parsed, Variables are replaced with their values. + * The evaluator has its own internal variable map that it used to resolve + * variable values. All of the variable related methods on the evaluator refer + * to this internal map. You can choose to set you own variable resolver on your + * evaluator instance. If you do this, then variables resolved by your resolver + * will override any variables in the evaluator's internal variable map.
  • + *
  • Functions are then executed and replaced with their results. Function + * arguments are each individually evaluated as subexpressions that are comma + * separated. This gives you the ability to use nested functions in your + * expressions. You can choose not to evaluate function arguments as expressions + * and instead let the functions handle the arguments themselves. This in effect + * turns off nested expressions, unless you versionCode nested expression support into + * your own custom functions.
  • + *
  • Once all variables and functions are resolved, then the parsed + * expression and subexpressions are evaluated according to operator precedence.
  • + *
+ * Operator precedence: + *
    + *
  • + unary plus, - unary minus, ! boolean not
  • + *
  • * multiplication, / division, % modulus
  • + *
  • + addition, - subtraction
  • + *
  • < less than, <= less than or equal, > greater than, >= greater than or + * equal
  • + *
  • = equal, != not equal
  • + *
  • && boolean and
  • + *
  • || boolean or
  • + *
+ * Function and variable names can not break any of the following rules:
+ *
    + *
  • can not start with a number
  • + *
  • can not contain an operator (see the above list of operators)/li> + *
  • can not contain a quote character - single or double/li> + *
  • can not contain a brace character - open or closed/li> + *
  • can not contain one of the following special characters: #, ~ , ^ !
  • + *
+ * Other Notes: + *
    + *
  • This class is not thread safe.
  • + *
  • Allows for the quote character (single or double) to be specified at run + * time. Quote characters are required for specifying string values.
  • + *
  • Expressions can contain different types of expressions within the same + * expression. However, Numeric and string types can not be mixed in a left / + * right operand pair.
  • + *
  • An expression can be parsed before being evaluated by calling the parse() + * method. This may save on response time if parsing takes more than a few + * seconds. However, parsing is usually very fast, so this is probably not + * needed.
  • + *
  • If an expression does not change, it will not be parsed each + * time the expression is evaluated. Therefore, variables values can change and + * the expression can be evaluated again without having to re-parse the + * expression.
  • + *
  • Nested functions calls are supported. Nested function support can be + * turned off to improve performance. Custom functions can be coded to handle + * nested calls instead if desired.
  • + *
  • The string used to start variables, "#{", can not appear in an + * expression. + *
  • See the evaluate methods in this class, JUnit tests and samples for more + * details.
  • + *
+ */ +public class Evaluator { + + // Contains all of the operators. + private List operators = new ArrayList(); + + // Contains all of the functions in use. + private Map functions = new HashMap(); + + // Contains all of the variables in use. + private Map variables = new HashMap(); + + // The quote character in use. + private char quoteCharacter = EvaluationConstants.SINGLE_QUOTE; + + // The open parentheses operator. + private Operator openParenthesesOperator = new OpenParenthesesOperator(); + + // The closed parentheses operator. + private Operator closedParenthesesOperator = new ClosedParenthesesOperator(); + + // Indicates if the user wants to load the system math variables. + private boolean loadMathVariables; + + // Indicates if the user wants to load the system math functions. + private boolean loadMathFunctions; + + // Indicates if the user wants to load the system string functions. + private boolean loadStringFunctions; + + // Indicates if the user wants to process nested function calls. + private boolean processNestedFunctions; + + // Saves the previous expression, because we do not want to parse it, if + // it did not change. + private String previousExpression = null; + + // The previous stack of parsed operators + private Stack previousOperatorStack = null; + + // The previous stack of parsed operands. + private Stack previousOperandStack = null; + + // The stack of parsed operators + private Stack operatorStack = null; + + // The stack of parsed operands. + private Stack operandStack = null; + + // Allows for user to set their own variable resolver. + private VariableResolver variableResolver = null; + + /** + * The default constructor. This constructor calls the five parameter + * Evaluator constructor and passes in the following default values: + * SINGLE_QUOTE, true, true, true and true. + */ + public Evaluator() { + this(EvaluationConstants.SINGLE_QUOTE, true, true, true, true); + } + + /** + * The main constructor for Evaluator. + * + * @param quoteCharacter + * The quote character to use when evaluating expression. + * @param loadMathVariables + * Indicates if the standard Math variables should be loaded or + * not. + * @param loadMathFunctions + * Indicates if the standard Math functions should be loaded or + * not. + * @param loadStringFunctions + * Indicates if the standard String functions should be loaded or + * not. + * @param processNestedFunctions + * Indicates if nested function calls should be processed or not. + * + * @exception IllegalArgumentException + * Thrown when the quote character is not a valid quote + * character. + */ + public Evaluator(final char quoteCharacter, + final boolean loadMathVariables, final boolean loadMathFunctions, + final boolean loadStringFunctions, + final boolean processNestedFunctions) { + + // Install the operators used by Evaluator. + installOperators(); + + // Install the system variables. + this.loadMathVariables = loadMathVariables; + loadSystemVariables(); + + // Install the system functions. + this.loadMathFunctions = loadMathFunctions; + this.loadStringFunctions = loadStringFunctions; + loadSystemFunctions(); + + // Set the default quote character. + setQuoteCharacter(quoteCharacter); + + // Process nested function calls. + this.processNestedFunctions = processNestedFunctions; + } + + /** + * Returns the current quote character in use. + * + * @return The quote character in use. + */ + public char getQuoteCharacter() { + return quoteCharacter; + } + + /** + * Sets the quote character to use when evaluating expressions. + * + * @param quoteCharacter + * The quote character to use when evaluating expressions. + * + * @exception IllegalArgumentException + * Thrown when the quote character is not a valid quote + * character. + */ + public void setQuoteCharacter(final char quoteCharacter) { + if (quoteCharacter == EvaluationConstants.SINGLE_QUOTE + || quoteCharacter == EvaluationConstants.DOUBLE_QUOTE) { + this.quoteCharacter = quoteCharacter; + } else { + throw new IllegalArgumentException("Invalid quote character."); + } + } + + /** + * Adds a function to the list of functions to use when evaluating + * expressions. + * + * @param function + * The function being added. + * + * @exception IllegalArgumentException + * Thrown when the function name is not valid or the function + * name is already in use. + */ + public void putFunction(final Function function) { + // Make sure the function name is valid. + isValidName(function.getName()); + + // Make sure the function name isn't already in use. + final Function existingFunction = (Function) functions.get(function + .getName()); + + if (existingFunction == null) { + functions.put(function.getName(), function); + } else { + throw new IllegalArgumentException("A function with the same name " + + "already exists."); + } + } + + /** + * Returns a funtion from the list of functions. If the function can not be + * found in the list of functions, then null will be returned. + * + * @param functionName + * The name of the function to retrieve the value for. + * + * @return The value for a function in the list of function. + */ + public Function getFunction(final String functionName) { + return (Function) functions.get(functionName); + } + + /** + * Removes the function from the list of functions to use when evaluating + * expressions. + * + * @param functionName + * The name of the function to remove. + */ + public void removeFunction(final String functionName) { + if (functions.containsKey(functionName)) { + functions.remove(functionName); + } else { + throw new IllegalArgumentException("The function does not exist."); + } + } + + /** + * Removes all of the functions at one time. + */ + public void clearFunctions() { + // Remove all functions. + functions.clear(); + + // Reload the system functions if necessary. + loadSystemFunctions(); + } + + /** + * Rturns the map of functions currently set on this object. + * + * @return the map of functions currently set on this object. + */ + public Map getFunctions() { + return functions; + } + + /** + * Sets the map of functions for this object. + * + * @param functions The map of functions for this object. + */ + public void setFunctions(Map functions) { + this.functions = functions; + } + + /** + * Adds or replaces a variable to the list of variables to use when + * evaluating expressions. If the variable already exists, then its value + * will be overlaid. + * + * @param variableName + * The name of the variable being set. + * @param variableValue + * The value for the variable being set. + */ + public void putVariable(final String variableName, + final String variableValue) { + // Make sure the variable name is valid. + isValidName(variableName); + + variables.put(variableName, variableValue); + } + + /** + * Returns the value for a variable in the list of variables. If the + * variable can not be found in the list of variables, then null will be + * returned. + * + * @param variableName + * The name of the variable to retrieve the value for. + * + * @return The value for a variable in the list of variables. + * + * @throws Throws + * an EvaluatorException if the variable name can not be + * resolved. + */ + public String getVariableValue(final String variableName) + throws EvaluationException { + + String variableValue = null; + + /* + * If the user has implemented a variable resolver and set it onto this + * object, then use it before looking in the variable map. + */ + if (variableResolver != null) { + + try { + variableValue = variableResolver.resolveVariable(variableName); + } catch (FunctionException fe) { + throw new EvaluationException(fe.getMessage(), fe); + } + } + + /* + * If no variable value at this point, then go to the internal variable + * map to resolve the variable. + */ + if (variableValue == null) { + + variableValue = (String) variables.get(variableName); + } + + if (variableValue == null) { + + throw new EvaluationException( + "Can not resolve variable with name equal to \"" + + variableName + "\"."); + } + + return variableValue; + } + + /** + * Removes the variable from the list of variables to use when evaluating + * expressions. + * + * @param variableName + * The name of the variable to remove. + */ + public void removeVaraible(final String variableName) { + if (variables.containsKey(variableName)) { + variables.remove(variableName); + } else { + throw new IllegalArgumentException("The variable does not exist."); + } + } + + /** + * Removes all of the variables at one time. + */ + public void clearVariables() { + // Remove all functions. + variables.clear(); + + // Reload the system variables if necessary. + loadSystemVariables(); + } + + /** + * Rturns the map of variables currently set on this object. + * + * @return the map of variables currently set on this object. + */ + public Map getVariables() { + return variables; + } + + /** + * Sets the map of variables for this object. + * + * @param variables The map of variables for this object. + */ + public void setVariables(Map variables) { + this.variables = variables; + } + + /** + * Returns the variable resolver. The variable resolver can be used by + * the user to resolve their own variables. Variables in the variable + * resolver override any variables that are in this classes internal + * variable map. + * + * @return The variable resolver. + */ + public VariableResolver getVariableResolver() { + return variableResolver; + } + + /** + * Sets the variable resolver for this class. Varaibles resolved by the + * variable resolver will override any variables in this class's internal + * variable map. + * + * @param variableResolver The variable resolver for this class. + */ + public void setVariableResolver(VariableResolver variableResolver) { + this.variableResolver = variableResolver; + } + + /** + * This method evaluates mathematical, boolean or functional expressions. + * See the class description and test classes for more information on how to + * write an expression. If quotes exist around a string expression, then + * they will be left in the result string. Function will also have their + * results wrapped with the appripriate quote characters. + * + * @param expression + * The expression to evaluate. + * + * @return The result of the evaluated. expression. Numbers are treated as + * doubles, so resulting numbers will contain at least one decimal + * place. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. + */ + public String evaluate(final String expression) throws EvaluationException { + return evaluate(expression, true, true); + } + + /** + * This method evaluates mathematical, boolean or functional expressions. + * See the class description and test classes for more information on how to + * write an expression. The expression used will be the one previously + * specified when using the parse method. If the parse method has not been + * called before calling this method, then an exception will be thrown. If + * quotes exist around a string expression, then they will be left in the + * result string. Function will also have their results wrapped with the + * appropriate quote characters. + * + * @return The result of the evaluated. expression. Numbers are treated as + * doubles, so resulting numbers will contain at least one decimal + * place. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. + */ + public String evaluate() throws EvaluationException { + // Get the previously parsed expression. + final String expression = previousExpression; + + if (expression == null || expression.length() == 0) { + throw new EvaluationException("No expression has been specified."); + } + + return evaluate(expression, true, true); + } + + /** + * This method evaluates mathematical, boolean or functional expressions. + * See the class description and test classes for more information on how to + * write an expression. + * + * @param expression + * The expression to evaluate. + * @param keepQuotes + * Indicates if the the quotes should be kept in the result or + * not. This is only for string expression that are enclosed in + * quotes prior to being evaluated. + * @param wrapStringFunctionResults + * Indicates if the results from functions that return strings + * should be wrapped in quotes. The quote character used will be + * whatever is the current quote character for this object. + * + * @return The result of the evaluated expression. Numbers are treated as + * doubles, so resulting numbers will contain at least one decimal + * place. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. + */ + public String evaluate(final String expression, final boolean keepQuotes, + final boolean wrapStringFunctionResults) throws EvaluationException { + + // Parse the expression. + parse(expression); + + String result = getResult(operatorStack, operandStack, + wrapStringFunctionResults); + + // Remove the quotes if necessary. + if (isExpressionString(result) && !keepQuotes) { + result = result.substring(1, result.length() - 1); + } + + return result; + } + + /** + * This method evaluates mathematical, boolean or functional expressions. + * The expression used will be the one previously specified when using the + * parse method. If the parse method has not been called before calling this + * method, then an exception will be thrown. See the class description and + * test classes for more information on how to write an expression. + * + * @param keepQuotes + * Indicates if the the quotes should be kept in the result or + * not. This is only for string expressions that are enclosed in + * quotes prior to being evaluated. + * @param wrapStringFunctionResults + * Indicates if the results from functions that return strings + * should be wrapped in quotes. The quote character used will be + * whatever is the current quote character for this object. + * + * @return The result of the evaluated expression. Numbers are treated as + * doubles, so resulting numbers will contain at least one decimal + * place. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. + */ + public String evaluate(final boolean keepQuotes, + final boolean wrapStringFunctionResults) throws EvaluationException { + + // Get the previously parsed expression. + final String expression = previousExpression; + + if (expression == null || expression.length() == 0) { + throw new EvaluationException("No expression has been specified."); + } + + return evaluate(expression, keepQuotes, wrapStringFunctionResults); + } + + /** + * This method is a simple wrapper around the evaluate(String) method. Its + * purpose is to return a more friendly boolean return value instead of the + * string "1.0" (for true) and "0.0" (for false) that is normally returned. + * + * @param expression + * The expression to evaluate. + * + * @return A boolean value that represents the result of the evaluated + * expression. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. It is also thrown if the result is not able to + * be converted to a boolean value. + */ + public boolean getBooleanResult(final String expression) + throws EvaluationException { + + final String result = evaluate(expression); + + try { + Double doubleResult = new Double(result); + + if (doubleResult.doubleValue() == 1.0) { + return true; + } + } catch (NumberFormatException exception) { + return false; + } + + return false; + } + + /** + * This method is a simple wrapper around the evaluate(String) method. Its + * purpose is to return a more friendly double return value instead of the + * string number that is normally returned. + * + * @param expression + * The expression to evaluate. + * + * @return A double value that represents the result of the evaluated + * expression. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. It is also thrown if the result is not able to + * be converted to a double value. + */ + public double getNumberResult(final String expression) + throws EvaluationException { + + final String result = evaluate(expression); + Double doubleResult = null; + + try { + doubleResult = new Double(result); + } catch (NumberFormatException nfe) { + throw new EvaluationException( + "Expression does not produce a number.", nfe); + } + + return doubleResult.doubleValue(); + } + + /** + * This method parses a mathematical, boolean or functional expressions. + * When the expression is eventually evaluated, as long as the expression + * has not changed, it will not have to be reparsed. See the class + * description and test classes for more information on how to write an + * expression. + * + * @param expression + * The expression to evaluate. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. + */ + public void parse(final String expression) throws EvaluationException { + + // Save the expression. + boolean parse = true; + if (!expression.equals(previousExpression)) { + previousExpression = expression; + } else { + parse = false; + operatorStack = (Stack) previousOperatorStack.clone(); + operandStack = (Stack) previousOperandStack.clone(); + } + + try { + if (parse) { + // These stacks will keep track of the operands and operators. + operandStack = new Stack(); + operatorStack = new Stack(); + + // Flags to help us keep track of what we are processing. + boolean haveOperand = false; + boolean haveOperator = false; + Operator unaryOperator = null; + + // We are going to process until we get to the end, so get the + // length. + int numChars = expression.length(); + int charCtr = 0; + + // Process until the counter exceeds the length. The goal is to + // get + // all of the operands and operators. + while (charCtr < numChars) { + Operator operator = null; + int operatorIndex = -1; + + // Skip any white space. + if (EvaluationHelper.isSpace(expression.charAt(charCtr))) { + charCtr++; + continue; + } + + // Get the next operator. + NextOperator nextOperator = getNextOperator(expression, + charCtr, null); + + if (nextOperator != null) { + operator = nextOperator.getOperator(); + operatorIndex = nextOperator.getIndex(); + } + + // Check if it is time to process an operand. + if (operatorIndex > charCtr || operatorIndex == -1) { + charCtr = processOperand(expression, charCtr, + operatorIndex, operandStack, unaryOperator); + + haveOperand = true; + haveOperator = false; + unaryOperator = null; + } + + // Check if it is time to process an operator. + if (operatorIndex == charCtr) { + if (nextOperator.getOperator().isUnary() + && (haveOperator || charCtr == 0)) { + charCtr = processUnaryOperator(operatorIndex, + nextOperator.getOperator()); + + if (unaryOperator == null) { + // We have an unary operator. + unaryOperator = nextOperator.getOperator(); + } else { + throw new EvaluationException( + "Consecutive unary " + + "operators are not allowed (index=" + + charCtr + ")."); + } + } else { + charCtr = processOperator(expression, + operatorIndex, operator, operatorStack, + operandStack, haveOperand, unaryOperator); + + unaryOperator = null; + } + + if (!(nextOperator.getOperator() instanceof ClosedParenthesesOperator)) { + haveOperand = false; + haveOperator = true; + } + } + } + + // Save the parsed operators and operands. + previousOperatorStack = (Stack) operatorStack.clone(); + previousOperandStack = (Stack) operandStack.clone(); + } + } catch (Exception e) { + // Clear the previous expression, because it is invalid. + previousExpression = ""; + + throw new EvaluationException(e.getMessage(), e); + } + } + + /** + * Install all of the operators into the list of operators to use when + * evaluating expressions. + */ + private void installOperators() { + // Install the most used operators first. + operators.add(openParenthesesOperator); + operators.add(closedParenthesesOperator); + operators.add(new AdditionOperator()); + operators.add(new SubtractionOperator()); + operators.add(new MultiplicationOperator()); + operators.add(new DivisionOperator()); + operators.add(new EqualOperator()); + operators.add(new NotEqualOperator()); + + // If there is a first character conflict between two operators, + // then install the operator with the greatest length first. + operators.add(new LessThanOrEqualOperator()); // Length of 2. + operators.add(new LessThanOperator()); // Length of 1. + operators.add(new GreaterThanOrEqualOperator()); // Length of 2. + operators.add(new GreaterThanOperator()); // Length of 1. + + // Install the least used operators last. + operators.add(new BooleanAndOperator()); + operators.add(new BooleanOrOperator()); + operators.add(new BooleanNotOperator()); + operators.add(new ModulusOperator()); + } + + /** + * Processes the operand that has been found in the expression. + * + * @param expression + * The expression being evaluated. + * @param operatorIndex + * The position in the expression where the current operator + * being processed is located. + * @param operandStack + * The stack of operands. + * @param unaryOperator + * The unary operator if we are working with one. + * + * @return The new position in the expression where processing should + * continue. + * + * @exception EvaluateException + * Thrown is an error is encoutnered while processing the + * expression. + */ + private int processOperand(final String expression, final int charCtr, + final int operatorIndex, final Stack operandStack, + final Operator unaryOperator) throws EvaluationException { + + String operandString = null; + int rtnCtr = -1; + + // Get the operand to process. + if (operatorIndex == -1) { + operandString = expression.substring(charCtr).trim(); + rtnCtr = expression.length(); + } else { + operandString = expression.substring(charCtr, operatorIndex).trim(); + rtnCtr = operatorIndex; + } + + if (operandString.length() == 0) { + throw new EvaluationException("Expression is invalid."); + } + + final ExpressionOperand operand = new ExpressionOperand(operandString, + unaryOperator); + operandStack.push(operand); + + return rtnCtr; + } + + /** + * Processes the operator that has been found in the expression. + * + * @param expression + * The expression being evaluated. + * @param operatorIndex + * The position in the expression where the current operator + * being processed is located. + * @param operator + * The operator being processed. + * @param operatorStack + * The stack of operators. + * @param operandStack + * The stack of operands. + * @param haveOperand + * Indicates if have an operand to process. + * @param unaryOperator + * The unary operand associated with thi operator. This may be + * null. + * + * @return The new position in the expression where processing should + * continue. + * + * @exception EvaluateException + * Thrown is an error is encoutnered while processing the + * expression. + */ + private int processOperator(final String expression, + final int originalOperatorIndex, final Operator originalOperator, + final Stack operatorStack, final Stack operandStack, + final boolean haveOperand, final Operator unaryOperator) + throws EvaluationException { + + int operatorIndex = originalOperatorIndex; + Operator operator = originalOperator; + + // If we have and operand and the current operator is an instance + // of OpenParenthesesOperator, then we are ready to process a function. + if (haveOperand && operator instanceof OpenParenthesesOperator) { + NextOperator nextOperator = processFunction(expression, + operatorIndex, operandStack); + + operator = nextOperator.getOperator(); + operatorIndex = nextOperator.getIndex() + operator.getLength(); + + nextOperator = getNextOperator(expression, operatorIndex, null); + + // Look to see if there is another operator. + // If there is, the process it, else get out of this routine. + if (nextOperator != null) { + operator = nextOperator.getOperator(); + operatorIndex = nextOperator.getIndex(); + } else { + return operatorIndex; + } + } + + // Determine what type of operator we are left with and process + // accordingly. + if (operator instanceof OpenParenthesesOperator) { + final ExpressionOperator expressionOperator = new ExpressionOperator( + operator, unaryOperator); + operatorStack.push(expressionOperator); + } else if (operator instanceof ClosedParenthesesOperator) { + ExpressionOperator stackOperator = null; + + if (operatorStack.size() > 0) { + stackOperator = (ExpressionOperator) operatorStack.peek(); + } + + // Process until we reach an open parentheses. + while (stackOperator != null + && !(stackOperator.getOperator() instanceof OpenParenthesesOperator)) { + processTree(operandStack, operatorStack); + + if (operatorStack.size() > 0) { + stackOperator = (ExpressionOperator) operatorStack.peek(); + } else { + stackOperator = null; + } + } + + if (operatorStack.isEmpty()) { + throw new EvaluationException("Expression is invalid."); + } + + // Pop the open parameter from the stack. + final ExpressionOperator expressionOperator = (ExpressionOperator) operatorStack + .pop(); + + if (!(expressionOperator.getOperator() instanceof OpenParenthesesOperator)) { + throw new EvaluationException("Expression is invalid."); + } + + // Process the unary operator if we have one. + if (expressionOperator.getUnaryOperator() != null) { + Object operand = operandStack.pop(); + + ExpressionTree tree = new ExpressionTree(this, operand, null, + null, expressionOperator.getUnaryOperator()); + + operandStack.push(tree); + } + } else { + // Process non-param operator. + if (operatorStack.size() > 0) { + ExpressionOperator stackOperator = (ExpressionOperator) operatorStack + .peek(); + + while (stackOperator != null + && stackOperator.getOperator().getPrecedence() >= operator + .getPrecedence()) { + processTree(operandStack, operatorStack); + + if (operatorStack.size() > 0) { + stackOperator = (ExpressionOperator) operatorStack + .peek(); + } else { + stackOperator = null; + } + } + } + + ExpressionOperator expressionOperator = new ExpressionOperator( + operator, unaryOperator); + + operatorStack.push(expressionOperator); + } + + final int rtnCtr = operatorIndex + operator.getLength(); + + return rtnCtr; + } + + /** + * Processes the unary operator that has been found in the expression. + * + * @param operatorIndex + * The position in the expression where the current operator + * being processed is located. + * @param operator + * The operator being processed. + * + * @return The new position in the expression where processing should + * continue. + */ + private int processUnaryOperator(final int operatorIndex, + final Operator operator) { + + final int rtnCtr = operatorIndex + operator.getSymbol().length(); + + return rtnCtr; + } + + /** + * Processes the function that has been found in the expression. + * + * @param expression + * The expression being evaluated. + * @param operatorIndex + * The position in the expression where the current operator + * being processed is located. + * @param operandStack + * The stack of operands. + * @param operatorStack + * The stack of operators. + * @param operator + * The current operator being processed. + * @param unaryOperator + * The unary operator associated with this function. This can be + * null. + * + * @return The next operator in the expression. This should be the closed + * parentheses operator. + * + * @exception EvaluateException + * Thrown is an error is encoutnered while processing the + * expression. + */ + private NextOperator processFunction(final String expression, + final int operatorIndex, final Stack operandStack) + throws EvaluationException { + + int parenthesisCount = 1; + NextOperator nextOperator = null; + int nextOperatorIndex = operatorIndex; + + // Loop until we find the function's closing parentheses. + while (parenthesisCount > 0) { + nextOperator = getNextOperator(expression, nextOperatorIndex + 1, + null); + + if (nextOperator == null) { + throw new EvaluationException("Function is not closed."); + } else if (nextOperator.getOperator() instanceof OpenParenthesesOperator) { + parenthesisCount++; + } else if (nextOperator.getOperator() instanceof ClosedParenthesesOperator) { + parenthesisCount--; + } + + // Get the next operator index. + nextOperatorIndex = nextOperator.getIndex(); + } + + // Get the function argument. + String arguments = expression.substring(operatorIndex + 1, + nextOperatorIndex); + + // Pop the function name from the stack. + final ExpressionOperand operand = (ExpressionOperand) operandStack + .pop(); + final Operator unaryOperator = operand.getUnaryOperator(); + final String functionName = operand.getValue(); + + // Validate that the function name is valid. + try { + isValidName(functionName); + } catch (IllegalArgumentException iae) { + throw new EvaluationException("Invalid function name of \"" + + functionName + "\".", iae); + } + + // Get the function object. + final Function function = (Function) functions.get(functionName); + + if (function == null) { + throw new EvaluationException("A function is not defined (index=" + + operatorIndex + ")."); + } + + final ParsedFunction parsedFunction = new ParsedFunction(function, + arguments, unaryOperator); + operandStack.push(parsedFunction); + + return nextOperator; + } + + /** + * Processes an expresssion tree that has been parsed into an operand stack + * and oeprator stack. + * + * @param operandStack + * The stack of operands. + * @param operatorStack + * The stack of operators. + */ + private void processTree(final Stack operandStack, final Stack operatorStack) { + + Object rightOperand = null; + Object leftOperand = null; + Operator operator = null; + + // Get the right operand node from the tree. + if (operandStack.size() > 0) { + rightOperand = operandStack.pop(); + } + + // Get the left operand node from the tree. + if (operandStack.size() > 0) { + leftOperand = operandStack.pop(); + } + + // Get the operator node from the tree. + operator = ((ExpressionOperator) operatorStack.pop()).getOperator(); + + // Build an expressin tree from the nodes. + final ExpressionTree tree = new ExpressionTree(this, leftOperand, + rightOperand, operator, null); + + // Push the tree onto the stack. + operandStack.push(tree); + } + + /** + * Returns the final result of the evaluated expression. + * + * @param operatorStack + * The stack of operators. + * @param operandStack + * The stack of operands. + * @param wrapStringFunctionResults + * Indicates if the results from functions that return strings + * should be wrapped in quotes. The quote character used will be + * whatever is the current quote character for this object. + * + * @return The final result of the evaluated expression. + * + * @exception EvaluateException + * Thrown is an error is encoutnered while processing the + * expression. + */ + private String getResult(final Stack operatorStack, + final Stack operandStack, final boolean wrapStringFunctionResults) + throws EvaluationException { + + // The result to return. + String resultString = null; + + // Process the rest of the operators left on the stack. + while (operatorStack.size() > 0) { + processTree(operandStack, operatorStack); + } + + // At this point only one operand should be left on the tree. + // It may be a tree operand that contains other tree and/or + // other operands. + if (operandStack.size() != 1) { + throw new EvaluationException("Expression is invalid."); + } + + final Object finalOperand = operandStack.pop(); + + // Check if the final operand is a tree. + if (finalOperand instanceof ExpressionTree) { + // Get the final result. + resultString = ((ExpressionTree) finalOperand) + .evaluate(wrapStringFunctionResults); + } + // Check if the final operand is an operand. + else if (finalOperand instanceof ExpressionOperand) { + ExpressionOperand resultExpressionOperand = (ExpressionOperand) finalOperand; + + resultString = ((ExpressionOperand) finalOperand).getValue(); + resultString = replaceVariables(resultString); + + // Check if the operand is a string or not. If it not a string, + // then it must be a number. + if (!isExpressionString(resultString)) { + Double resultDouble = null; + try { + resultDouble = new Double(resultString); + } catch (Exception e) { + throw new EvaluationException("Expression is invalid.", e); + } + + // Process a unary operator if one exists. + if (resultExpressionOperand.getUnaryOperator() != null) { + resultDouble = new Double(resultExpressionOperand + .getUnaryOperator().evaluate( + resultDouble.doubleValue())); + } + + // Get the final result. + resultString = resultDouble.toString(); + } else { + if (resultExpressionOperand.getUnaryOperator() != null) { + throw new EvaluationException("Invalid operand for " + + "unary operator."); + } + } + } else if (finalOperand instanceof ParsedFunction) { + final ParsedFunction parsedFunction = (ParsedFunction) finalOperand; + final Function function = parsedFunction.getFunction(); + String arguments = parsedFunction.getArguments(); + + if (processNestedFunctions) { + arguments = processNestedFunctions(arguments); + } + + arguments = replaceVariables(arguments); + + // Get the final result. + try { + FunctionResult functionResult = + function.execute(this, arguments); + resultString = functionResult.getResult(); + + if (functionResult.getType() == + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC) { + + Double resultDouble = new Double(resultString); + + // Process a unary operator if one exists. + if (parsedFunction.getUnaryOperator() != null) { + resultDouble = new Double(parsedFunction + .getUnaryOperator().evaluate( + resultDouble.doubleValue())); + } + + // Get the final result. + resultString = resultDouble.toString(); + } + else if (functionResult.getType() == + FunctionConstants.FUNCTION_RESULT_TYPE_STRING) { + + // The result must be a string result. + if (wrapStringFunctionResults) { + resultString = quoteCharacter + resultString + + quoteCharacter; + } + + if (parsedFunction.getUnaryOperator() != null) { + throw new EvaluationException("Invalid operand for " + + "unary operator."); + } + } + } catch (FunctionException fe) { + throw new EvaluationException(fe.getMessage(), fe); + } + } else { + throw new EvaluationException("Expression is invalid."); + } + + return resultString; + } + + /** + * Returns the next operator in the expression. + * + * @param expression + * The expression being evaluated. + * @param start + * The position in the expression to start searching for the next + * operator. + * @param match + * The operator to search for. This can be null if you want the + * very next operator. If it is not null, it searches until it + * finds the match. + * + * @return The next operator in the expression. Returns null if no next + * operator is returned. + */ + private NextOperator getNextOperator(final String expression, + final int start, final Operator match) { + + final int numChars = expression.length(); + int numQuoteCharacters = 0; + + for (int charCtr = start; charCtr < numChars; charCtr++) { + // Keep track of open strings. + if (expression.charAt(charCtr) == quoteCharacter) { + numQuoteCharacters++; + } + + // Do not look into open strings. + if ((numQuoteCharacters % 2) == 1) { + continue; + } + + // Assumes the operators are installed in order of length. + final int numOperators = operators.size(); + for (int operatorCtr = 0; operatorCtr < numOperators; operatorCtr++) { + Operator operator = (Operator) operators.get(operatorCtr); + + if (match != null) { + // Look through the operators until we find the + // one we are searching for. + if (!match.equals(operator)) { + continue; + } + } + + // The operator can 1 or 2 characters in length. + if (operator.getLength() == 2) { + int endCtr = -1; + if (charCtr + 2 <= expression.length()) { + endCtr = charCtr + 2; + } else { + endCtr = expression.length(); + } + + // Look for a match. + if (expression.substring(charCtr, endCtr).equals( + operator.getSymbol())) { + NextOperator nextOperator = new NextOperator(operator, + charCtr); + + return nextOperator; + } + } else { + // Look for a match. + if (expression.charAt(charCtr) == operator.getSymbol() + .charAt(0)) { + NextOperator nextOperator = new NextOperator(operator, + charCtr); + + return nextOperator; + } + } + } + } + + return null; + } + + /** + * Determines if the string represents a valid expression string or not. + * Valid expression strings must start and end with a quote character. + * + * @param expressionString + * The string being evaluated. + * + * @return True if the string is a valid string and false if not. + */ + protected boolean isExpressionString(final String expressionString) + throws EvaluationException { + + if (expressionString.length() > 1 + && expressionString.charAt(0) == quoteCharacter + && expressionString.charAt(expressionString.length() - 1) == quoteCharacter) { + return true; + } + + if (expressionString.indexOf(quoteCharacter) >= 0) { + throw new EvaluationException("Invalid use of quotes."); + } + + return false; + } + + /** + * This method verifies if a function or variable name is valid or not. + * + * Function and variable names must follow these rules... + *
    + *
  • can not start with a number
  • + *
  • can not contain an operator (see the above list of operators)
  • + *
  • can not contain a quote character - single or double
  • + *
  • can not contain a brace character - open or closed
  • + *
  • can not contain one of the following special characters: #, ~ , ^ !
  • + *
      + * + * @param name + * The function or variable name being validated. + * + * @exception IllegalArgumentException + * Thrown if the name is invalid. + */ + public void isValidName(final String name) throws IllegalArgumentException { + + if (name.length() == 0) { + throw new IllegalArgumentException("Variable is empty."); + } + + // Check if name starts with a number. + final char firstChar = name.charAt(0); +// if (firstChar >= '0' && firstChar <= '9') { +// throw new IllegalArgumentException("A variable or function name " +// + "can not start with a number."); +// } + + // Check if name contains with a quote character. + if (name.indexOf(EvaluationConstants.SINGLE_QUOTE) > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a quote character."); + } else if (name.indexOf(EvaluationConstants.DOUBLE_QUOTE) > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a quote character."); + } + + // Check if name contains with a brace character. + if (name.indexOf(EvaluationConstants.OPEN_BRACE) > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain an open brace character."); + } else if (name.indexOf(EvaluationConstants.CLOSED_BRACE) > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a closed brace character."); + } else if (name.indexOf(EvaluationConstants.POUND_SIGN) > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a pound sign character."); + } + + // Check if name contains an operator character. + final Iterator operatorIterator = operators.iterator(); + + while (operatorIterator.hasNext()) { + final Operator operator = (Operator) operatorIterator.next(); + + if (name.indexOf(operator.getSymbol()) > -1) { + throw new IllegalArgumentException( + "A variable or function name " + + "can not contain an operator symbol."); + } + } + + // Check if name contains other special characters. + if (name.indexOf("!") > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a special character."); + } else if (name.indexOf("~") > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a special character."); + } else if (name.indexOf("^") > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a special character."); + } else if (name.indexOf(",") > -1) { + throw new IllegalArgumentException("A variable or function name " + + "can not contain a special character."); + } + } + + /** + * This method loads the system functions is necessary. + */ + private void loadSystemFunctions() { + // Install the math functions. + if (loadMathFunctions) { + final FunctionGroup mathFunctions = new MathFunctions(); + + mathFunctions.load(this); + } + + // Install the string functions. + if (loadStringFunctions) { + final FunctionGroup stringFunctions = new StringFunctions(); + + stringFunctions.load(this); + } + } + + /** + * This method loads the system variables is necessary. + */ + private void loadSystemVariables() { + // Install the math variables. + if (loadMathVariables) { + // Add the two math variables. + putVariable("E", new Double(Math.E).toString()); + putVariable("PI", new Double(Math.PI).toString()); + } + } + + /** + * Replaces the variables in the expression with the values of the variables + * for this instance of the evaluator. + * + * @param expression + * The expression being processed. + * + * @return A new expression with the variables replaced with their values. + * + * @exception EvaluateException + * Thrown is an error is encoutnered while processing the + * expression. + */ + public String replaceVariables(final String expression) + throws EvaluationException { + + int openIndex = expression.indexOf(EvaluationConstants.OPEN_VARIABLE); + + if (openIndex < 0) { + return expression; + } + + String replacedExpression = expression; + + while (openIndex >= 0) { + + int closedIndex = -1; + if (openIndex >= 0) { + + closedIndex = replacedExpression.indexOf( + EvaluationConstants.CLOSED_VARIABLE, openIndex + 1); + if (closedIndex > openIndex) { + + String variableName = replacedExpression.substring( + openIndex + + EvaluationConstants.OPEN_VARIABLE + .length(), closedIndex); + + // Validate that the variable name is valid. + try { + isValidName(variableName); + } catch (IllegalArgumentException iae) { + throw new EvaluationException("Invalid variable name of \"" + + variableName + "\".", iae); + } + + String variableValue = getVariableValue(variableName); + + String variableString = EvaluationConstants.OPEN_VARIABLE + + variableName + + EvaluationConstants.CLOSED_VARIABLE; + + replacedExpression = EvaluationHelper.replaceAll( + replacedExpression, variableString, variableValue); + } else { + + break; + } + } + + // Start looking at the beginning of the string, since + // the length string has changed and characters have moved + // positions. + openIndex = replacedExpression.indexOf( + EvaluationConstants.OPEN_VARIABLE); + } + + // If an open brace is left over, then a variable could not be replaced. + int openBraceIndex = replacedExpression + .indexOf(EvaluationConstants.OPEN_VARIABLE); + if (openBraceIndex > -1) { + throw new EvaluationException( + "A variable has not been closed (index=" + openBraceIndex + + ")."); + } + + return replacedExpression; + } + + /** + * This method process nested function calls that may be in the arguments + * passed into a higher level function. + * + * @param arguments The arguments to process. + * + * @return The arguments with any nested function calls evaluated. + * + * @throws EvaluationException Thrown if an error occurs. + */ + protected String processNestedFunctions(final String arguments) + throws EvaluationException { + + StringBuffer evaluatedArguments = new StringBuffer(); + + // Process nested function calls. + if (arguments.length() > 0) { + + Evaluator argumentsEvaluator = new Evaluator(quoteCharacter, + loadMathVariables, loadMathFunctions, loadStringFunctions, + processNestedFunctions); + argumentsEvaluator.setFunctions(getFunctions()); + argumentsEvaluator.setVariables(getVariables()); + argumentsEvaluator.setVariableResolver(getVariableResolver()); + + final ArgumentTokenizer tokenizer = new ArgumentTokenizer( + arguments, EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + List evalautedArgumentList = new ArrayList(); + while (tokenizer.hasMoreTokens()) { + + String argument = tokenizer.nextToken().trim(); + + try { + argument = argumentsEvaluator.evaluate(argument); + } catch (Exception e) { + throw new EvaluationException(e.getMessage(), e); + } + + evalautedArgumentList.add(argument); + } + + Iterator evaluatedArgumentIterator = evalautedArgumentList + .iterator(); + + while (evaluatedArgumentIterator.hasNext()) { + + if (evaluatedArguments.length() > 0) { + + evaluatedArguments + .append(EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + } + + String evaluatedArgument = (String) evaluatedArgumentIterator + .next(); + evaluatedArguments.append(evaluatedArgument); + } + } + + return evaluatedArguments.toString(); + } + + /** + * Returns the value used during construction of this object to specify if + * math variables should be loaded. + * + * @return the loadMathVariables + */ + public boolean isLoadMathVariables() { + return loadMathVariables; + } + + /** + * Returns the value used during construction of this object to specify if + * math functions should be loaded. + * + * @return the loadMathFunctions + */ + public boolean getLoadMathFunctions() { + return loadMathFunctions; + } + + /** + * Returns the value used during construction of this object to specify if + * string functions should be loaded. + * + * @return the loadStringFunctions + */ + public boolean getLoadStringFunctions() { + return loadStringFunctions; + } + + /** + * Returns the value used during construction of this object to specify if + * nested functions should be processed. + * + * @return the processNestedFunctions + */ + public boolean getProcessNestedFunctions() { + return processNestedFunctions; + } + + public String getPreviousExpression() { + return previousExpression; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperand.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperand.java new file mode 100644 index 0000000..69289f1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperand.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +import com.baoying.enginex.executor.util.jeval.operator.Operator; + +/** + * Represents an operand being processed in the expression. + */ +public class ExpressionOperand { + + // The value of the operand. + private String value = null; + + // The unary operator for the operand, if one exists. + private Operator unaryOperator = null; + + /** + * Create a new ExpressionOperand. + * + * @param value + * The value for the new ExpressionOperand. + * @param unaryOperator + * The unary operator for this object. + */ + public ExpressionOperand(final String value, final Operator unaryOperator) { + this.value = value; + this.unaryOperator = unaryOperator; + } + + /** + * Returns the value of this object. + * + * @return The value of this object. + */ + public String getValue() { + return value; + } + + /** + * Returns the unary operator for this object. + * + * @return The unary operator for this object. + */ + public Operator getUnaryOperator() { + return unaryOperator; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperator.java new file mode 100644 index 0000000..275a978 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionOperator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +import com.baoying.enginex.executor.util.jeval.operator.Operator; + +/** + * Represents an operator being processed in the expression. + */ +public class ExpressionOperator { + + // The operator that this object represents. + private Operator operator = null; + + // The unary operator for this object, if there is one. + private Operator unaryOperator = null; + + /** + * Creates a new ExpressionOperator. + * + * @param operator + * The operator this object represents. + * @param unaryOperator + * The unary operator for this object. + */ + public ExpressionOperator(final Operator operator, + final Operator unaryOperator) { + this.operator = operator; + this.unaryOperator = unaryOperator; + } + + /** + * Returns the operator for this object. + * + * @return The operator for this object. + */ + public Operator getOperator() { + return operator; + } + + /** + * Returns the unary operator for this object. + * + * @return The unary operator for this object. + */ + public Operator getUnaryOperator() { + return unaryOperator; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionTree.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionTree.java new file mode 100644 index 0000000..c29df68 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ExpressionTree.java @@ -0,0 +1,368 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; +import com.baoying.enginex.executor.util.jeval.operator.Operator; + +/** + * Represents an expression tree made up of a left operand, right operand, + * operator and unary operator. + */ +public class ExpressionTree { + + // The left node in the tree. + private Object leftOperand = null; + + // The right node in the tree. + private Object rightOperand = null; + + // The operator for the two operands. + private Operator operator = null; + + // The unary operator, if one exists. + private Operator unaryOperator = null; + + // The Evaluator object processing this tree. + private Evaluator evaluator = null; + + /** + * Creates a new ExpressionTree. + * + * @param evaluator + * The Evaluator object processing this tree. + * @param leftOperand + * The left operand to place as the left node of the tree. + * @param rightOperand + * The right operand to place as the right node of the tree. + * @param operator + * The operator to place as the operator node of the tree. + * @param unaryOperator + * The new unary operator for this tree. + */ + public ExpressionTree(final Evaluator evaluator, final Object leftOperand, + final Object rightOperand, final Operator operator, + final Operator unaryOperator) { + + this.evaluator = evaluator; + this.leftOperand = leftOperand; + this.rightOperand = rightOperand; + this.operator = operator; + this.unaryOperator = unaryOperator; + } + + /** + * Returns the left operand of this tree. + * + * @return The left operand of this tree. + */ + public Object getLeftOperand() { + return leftOperand; + } + + /** + * Returns the right operand of this tree. + * + * @return The right operand of this tree. + */ + public Object getRightOperand() { + return rightOperand; + } + + /** + * Returns the operator for this tree. + * + * @return The operator of this tree. + */ + public Operator getOperator() { + return operator; + } + + /** + * Returns the unary operator for this tree. + * + * @return The unary operator of this tree. + */ + public Operator getUnaryOperator() { + return unaryOperator; + } + + /** + * Evaluates the operands for this tree using the operator and the unary + * operator. + * + * @param wrapStringFunctionResults + * Indicates if the results from functions that return strings + * should be wrapped in quotes. The quote character used will be + * whatever is the current quote character for this object. + * + * @exception EvaluateException + * Thrown is an error is encountered while processing the + * expression. + */ + public String evaluate(final boolean wrapStringFunctionResults) + throws EvaluationException { + + String rtnResult = null; + + // Get the left operand. + String leftResultString = null; + Double leftResultDouble = null; + + if (leftOperand instanceof ExpressionTree) { + leftResultString = ((ExpressionTree) leftOperand) + .evaluate(wrapStringFunctionResults); + + try { + leftResultDouble = new Double(leftResultString); + leftResultString = null; + } catch (NumberFormatException exception) { + leftResultDouble = null; + } + } else if (leftOperand instanceof ExpressionOperand) { + + final ExpressionOperand leftExpressionOperand = (ExpressionOperand) leftOperand; + + leftResultString = leftExpressionOperand.getValue(); + leftResultString = evaluator.replaceVariables(leftResultString); + + // Check if the operand is a string or not. If it not a string, + // then it must be a number. + if (!evaluator.isExpressionString(leftResultString)) { + try { + leftResultDouble = new Double(leftResultString); + leftResultString = null; + } catch (NumberFormatException nfe) { + throw new EvaluationException("Expression is invalid.", nfe); + } + + if (leftExpressionOperand.getUnaryOperator() != null) { + leftResultDouble = new Double(leftExpressionOperand + .getUnaryOperator().evaluate( + leftResultDouble.doubleValue())); + } + } else { + if (leftExpressionOperand.getUnaryOperator() != null) { + throw new EvaluationException("Invalid operand for " + + "unary operator."); + } + } + } else if (leftOperand instanceof ParsedFunction) { + + final ParsedFunction parsedFunction = (ParsedFunction) leftOperand; + final Function function = parsedFunction.getFunction(); + String arguments = parsedFunction.getArguments(); + arguments = evaluator.replaceVariables(arguments); + + if (evaluator.getProcessNestedFunctions()) { + arguments = evaluator.processNestedFunctions(arguments); + } + + try { + FunctionResult functionResult = + function.execute(evaluator, arguments); + leftResultString = functionResult.getResult(); + + if (functionResult.getType() == + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC) { + + Double resultDouble = new Double(leftResultString); + + // Process a unary operator if one exists. + if (parsedFunction.getUnaryOperator() != null) { + resultDouble = new Double(parsedFunction + .getUnaryOperator().evaluate( + resultDouble.doubleValue())); + } + + // Get the final result. + leftResultString = resultDouble.toString(); + } + else if (functionResult.getType() == + FunctionConstants.FUNCTION_RESULT_TYPE_STRING) { + + // The result must be a string result. + if (wrapStringFunctionResults) { + leftResultString = evaluator.getQuoteCharacter() + + leftResultString + + evaluator.getQuoteCharacter(); + } + + if (parsedFunction.getUnaryOperator() != null) { + throw new EvaluationException("Invalid operand for " + + "unary operator."); + } + } + } catch (FunctionException fe) { + throw new EvaluationException(fe.getMessage(), fe); + } + + if (!evaluator.isExpressionString(leftResultString)) { + try { + leftResultDouble = new Double(leftResultString); + leftResultString = null; + } catch (NumberFormatException nfe) { + throw new EvaluationException("Expression is invalid.", nfe); + } + } + } else { + if (leftOperand != null) { + throw new EvaluationException("Expression is invalid."); + } + } + + // Get the right operand. + String rightResultString = null; + Double rightResultDouble = null; + + if (rightOperand instanceof ExpressionTree) { + rightResultString = ((ExpressionTree) rightOperand) + .evaluate(wrapStringFunctionResults); + + try { + rightResultDouble = new Double(rightResultString); + rightResultString = null; + } catch (NumberFormatException exception) { + rightResultDouble = null; + } + + } else if (rightOperand instanceof ExpressionOperand) { + + final ExpressionOperand rightExpressionOperand = (ExpressionOperand) rightOperand; + rightResultString = ((ExpressionOperand) rightOperand).getValue(); + rightResultString = evaluator.replaceVariables(rightResultString); + + // Check if the operand is a string or not. If it not a string, + // then it must be a number. + if (!evaluator.isExpressionString(rightResultString)) { + try { + rightResultDouble = new Double(rightResultString); + rightResultString = null; + } catch (NumberFormatException nfe) { + throw new EvaluationException("Expression is invalid.", nfe); + } + + if (rightExpressionOperand.getUnaryOperator() != null) { + rightResultDouble = new Double(rightExpressionOperand + .getUnaryOperator().evaluate( + rightResultDouble.doubleValue())); + } + } else { + if (rightExpressionOperand.getUnaryOperator() != null) { + throw new EvaluationException("Invalid operand for " + + "unary operator."); + } + } + } else if (rightOperand instanceof ParsedFunction) { + + final ParsedFunction parsedFunction = (ParsedFunction) rightOperand; + final Function function = parsedFunction.getFunction(); + String arguments = parsedFunction.getArguments(); + arguments = evaluator.replaceVariables(arguments); + + if (evaluator.getProcessNestedFunctions()) { + arguments = evaluator.processNestedFunctions(arguments); + } + + try { + FunctionResult functionResult = + function.execute(evaluator, arguments); + rightResultString = functionResult.getResult(); + + if (functionResult.getType() == + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC) { + + Double resultDouble = new Double(rightResultString); + + // Process a unary operator if one exists. + if (parsedFunction.getUnaryOperator() != null) { + resultDouble = new Double(parsedFunction + .getUnaryOperator().evaluate( + resultDouble.doubleValue())); + } + + // Get the final result. + rightResultString = resultDouble.toString(); + } + else if (functionResult.getType() == + FunctionConstants.FUNCTION_RESULT_TYPE_STRING) { + + // The result must be a string result. + if (wrapStringFunctionResults) { + rightResultString = evaluator.getQuoteCharacter() + + rightResultString + + evaluator.getQuoteCharacter(); + } + + if (parsedFunction.getUnaryOperator() != null) { + throw new EvaluationException("Invalid operand for " + + "unary operator."); + } + } + } catch (FunctionException fe) { + throw new EvaluationException(fe.getMessage(), fe); + } + + if (!evaluator.isExpressionString(rightResultString)) { + try { + rightResultDouble = new Double(rightResultString); + rightResultString = null; + } catch (NumberFormatException nfe) { + throw new EvaluationException("Expression is invalid.", nfe); + } + } + } else if (rightOperand == null) { + // Do nothing. + } else { + throw new EvaluationException("Expression is invalid."); + } + + // Evaluate the the expression. + if (leftResultDouble != null && rightResultDouble != null) { + double doubleResult = operator.evaluate(leftResultDouble + .doubleValue(), rightResultDouble.doubleValue()); + + if (getUnaryOperator() != null) { + doubleResult = getUnaryOperator().evaluate(doubleResult); + } + + rtnResult = new Double(doubleResult).toString(); + } else if (leftResultString != null && rightResultString != null) { + rtnResult = operator.evaluate(leftResultString, rightResultString); + } else if (leftResultDouble != null && rightResultDouble == null) { + double doubleResult = -1; + + if (unaryOperator != null) { + doubleResult = unaryOperator.evaluate(leftResultDouble + .doubleValue()); + } else { + // Do not allow numeric (left) and + // string (right) to be evaluated together. + throw new EvaluationException("Expression is invalid."); + } + + rtnResult = new Double(doubleResult).toString(); + } else { + throw new EvaluationException("Expression is invalid."); + } + + return rtnResult; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/NextOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/NextOperator.java new file mode 100644 index 0000000..87547d7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/NextOperator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +import com.baoying.enginex.executor.util.jeval.operator.Operator; + +/** + * Represents the next operator in the expression to process. + */ +class NextOperator { + + // The operator this object represetns. + private Operator operator = null; + + // The index of the operator in the expression. + private int index = -1; + + /** + * Create a new NextOperator from an operator and index. + * + * @param operator + * The operator this object represents. + * @param index + * The index of the operator in the expression. + */ + public NextOperator(final Operator operator, final int index) { + this.operator = operator; + this.index = index; + } + + /** + * Returns the operator for this object. + * + * @return The operator represented by this object. + */ + public Operator getOperator() { + return operator; + } + + /** + * Returns the index for this object. + * + * @return The index of the operator in the expression. + */ + public int getIndex() { + return index; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ParsedFunction.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ParsedFunction.java new file mode 100644 index 0000000..dd4b433 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/ParsedFunction.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval; + +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.operator.Operator; + +/** + * This class represents a function that has been parsed. + */ +public class ParsedFunction { + + // The function that has been parsed. + // FIXME Make all class instance methods final if possible. + private final Function function; + + // The arguments to the function. + private final String arguments; + + // The unary operator for this object, if there is one. + private final Operator unaryOperator; + + /** + * The constructor for this class. + * + * @param function + * The function that has been parsed. + * @param arguments + * The arguments to the function. + * @param unaryOperator + * The unary operator for this object, if there is one. + */ + public ParsedFunction(final Function function, final String arguments, + final Operator unaryOperator) { + + this.function = function; + this.arguments = arguments; + this.unaryOperator = unaryOperator; + } + + /** + * Returns the function that has been parsed. + * + * @return The function that has been parsed. + */ + public Function getFunction() { + return function; + } + + /** + * Returns the arguments to the function. + * + * @return The arguments to the function. + */ + public String getArguments() { + return arguments; + } + + /** + * Returns the unary operator for this object, if there is one. + * + * @return The unary operator for this object, if there is one. + */ + public Operator getUnaryOperator() { + return unaryOperator; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/VariableResolver.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/VariableResolver.java new file mode 100644 index 0000000..6ac8ce3 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/VariableResolver.java @@ -0,0 +1,26 @@ +package com.baoying.enginex.executor.util.jeval; + +import com.baoying.enginex.executor.util.jeval.function.FunctionException; + +/** + * This interface can be implement with a custom resolver and set onto the + * Evaluator class. It will then be used to resolve variables when they are + * replaced in an expression as it gets evaluated. Varaibles resolved by the + * resolved will override any varibles that exist in the variable map of an + * Evaluator instance. + */ +public interface VariableResolver { + + /** + * Returns a variable value for the specified variable name. + * + * @param variableName + * The name of the variable to return the variable value for. + * + * @return A variable value for the specified variable name. If the variable + * name can not be resolved, then null should be returned. + * + * @throws Can throw a FunctionException if needed. + */ + public String resolveVariable(String variableName) throws FunctionException; +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/Function.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/Function.java new file mode 100644 index 0000000..5150299 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/Function.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function; + +import com.baoying.enginex.executor.util.jeval.Evaluator; + +/** + * A function that can be specified in an expression. + */ +public interface Function { + + /** + * Returns the name of the function. + * + * @return The name of this function class. + */ + public String getName(); + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * The arguments that will be evaluated by the function. It is up + * to the function implementation to break the string into one or + * more arguments. + * + * @return The value of the evaluated argument and its type. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException; +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionConstants.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionConstants.java new file mode 100644 index 0000000..4512b00 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function; + +/** + * Contains constants used by classes in this package. + */ +public class FunctionConstants { + + /** + * Indicates that the function result is a numeric or Boolean value. + */ + public static final int FUNCTION_RESULT_TYPE_NUMERIC = 0; + + /** + * Indicates that the function result is a string value. + */ + public static final int FUNCTION_RESULT_TYPE_STRING = 1; +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionException.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionException.java new file mode 100644 index 0000000..9fc83cb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionException.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function; + +/** + * This exception is thrown when an error occurs while processing a function. + */ +public class FunctionException extends Exception { + + private static final long serialVersionUID = 4767250768467137620L; + + /** + * This constructor takes a custom message as input. + * + * @param message + * A custom message for the exception to display. + */ + public FunctionException(String message) { + super(message); + } + + /** + * This constructor takes an exception as input. + * + * @param exception + * An exception. + */ + public FunctionException(Exception exception) { + super(exception); + } + + /** + * This constructor takes an exception as input. + * + * @param message + * A custom message for the exception to display. + * @param exception + * An exception. + */ + public FunctionException(String message, Exception exception) { + super(message, exception); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionGroup.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionGroup.java new file mode 100644 index 0000000..96db715 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionGroup.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import java.util.List; + +/** + * A groups of functions that can loaded at one time into an instance of + * Evaluator. + */ +public interface FunctionGroup { + /** + * Returns the name of the function group. + * + * @return The name of this function group class. + */ + public String getName(); + + /** + * Returns a list of the functions that are loaded by this class. + * + * @return A list of the functions loaded by this class. + */ + public List getFunctions(); + + /** + * Loads the functions in this function group into an instance of Evaluator. + * + * @param evaluator + * An instance of Evaluator to load the functions into. + */ + public void load(Evaluator evaluator); + + /** + * Unloads the functions in this function group from an instance of + * Evaluator. + * + * @param evaluator + * An instance of Evaluator to unload the functions from. + */ + public void unload(Evaluator evaluator); +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionHelper.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionHelper.java new file mode 100644 index 0000000..6aa8d4d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionHelper.java @@ -0,0 +1,284 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function; + +import com.baoying.enginex.executor.util.jeval.ArgumentTokenizer; + +import java.util.ArrayList; + +/** + * This class contains many methods that are helpful when writing functions. + * Some of these methods were created to help with the creation of the math and + * string functions packaged with Evaluator. + */ +public class FunctionHelper { + + /** + * This method first removes any white space at the beginning and end of the + * input string. It then removes the specified quote character from the the + * first and last characters of the string if a quote character exists in + * those positions. If quote characters are not in the first and last + * positions after the white space is trimmed, then a FunctionException will + * be thrown. + * + * @param arguments + * The arguments to trim and revove quote characters from. + * @param quoteCharacter + * The character to remove from the first and last position of + * the trimmed string. + * + * @return The arguments with white space and quote characters removed. + * + * @exception FunctionException + * Thrown if quote characters do not exist in the first and + * last positions after the white space is trimmed. + */ + public static String trimAndRemoveQuoteChars(final String arguments, + final char quoteCharacter) throws FunctionException { + + String trimmedArgument = arguments; + + trimmedArgument = trimmedArgument.trim(); + + if (trimmedArgument.charAt(0) == quoteCharacter) { + trimmedArgument = trimmedArgument.substring(1, trimmedArgument + .length()); + } else { + throw new FunctionException("Value does not start with a quote."); + } + + if (trimmedArgument.charAt(trimmedArgument.length() - 1) == quoteCharacter) { + trimmedArgument = trimmedArgument.substring(0, trimmedArgument + .length() - 1); + } else { + throw new FunctionException("Value does not end with a quote."); + } + + return trimmedArgument; + } + + /** + * This methods takes a string of input function arguments, evaluates each + * argument and creates a Double value for each argument from the result of + * the evaluations. + * + * @param arguments + * The arguments to parse. + * @param delimiter + * The delimiter to use while parsing. + * + * @return An array list of Double values found in the input string. + * + * @exception FunctionException + * Thrown if the string does not properly parse into Double + * values. + */ + public static ArrayList getDoubles(final String arguments, + final char delimiter) throws FunctionException { + + ArrayList returnValues = new ArrayList(); + + try { + + final ArgumentTokenizer tokenizer = new ArgumentTokenizer( + arguments, delimiter); + + while (tokenizer.hasMoreTokens()) { + final String token = tokenizer.nextToken().trim(); + returnValues.add(new Double(token)); + } + } catch (Exception e) { + throw new FunctionException("Invalid values in string.", e); + } + + return returnValues; + } + + /** + * This methods takes a string of input function arguments, evaluates each + * argument and creates a String value for each argument from the result of + * the evaluations. + * + * @param arguments + * The arguments of values to parse. + * @param delimiter + * The delimiter to use while parsing. + * + * @return An array list of String values found in the input string. + * + * @exception FunctionException + * Thrown if the stirng does not properly parse into String + * values. + */ + public static ArrayList getStrings(final String arguments, + final char delimiter) throws FunctionException { + + final ArrayList returnValues = new ArrayList(); + + try { + ArgumentTokenizer tokenizer = new ArgumentTokenizer(arguments, + delimiter); + + while (tokenizer.hasMoreTokens()) { + final String token = tokenizer.nextToken(); + returnValues.add(token); + } + } catch (Exception e) { + throw new FunctionException("Invalid values in string.", e); + } + + return returnValues; + } + + /** + * This methods takes a string of input function arguments, evaluates each + * argument and creates a one Integer and one String value for each argument + * from the result of the evaluations. + * + * @param arguments + * The arguments of values to parse. + * @param delimiter + * The delimiter to use while parsing. + * + * @return An array list of object values found in the input string. + * + * @exception FunctionException + * Thrown if the stirng does not properly parse into the + * proper objects. + */ + public static ArrayList getOneStringAndOneInteger(final String arguments, + final char delimiter) throws FunctionException { + + ArrayList returnValues = new ArrayList(); + + try { + final ArgumentTokenizer tokenizer = new ArgumentTokenizer( + arguments, delimiter); + + int tokenCtr = 0; + while (tokenizer.hasMoreTokens()) { + if (tokenCtr == 0) { + final String token = tokenizer.nextToken(); + returnValues.add(token); + } else if (tokenCtr == 1) { + final String token = tokenizer.nextToken().trim(); + returnValues.add(new Integer(new Double(token).intValue())); + } else { + throw new FunctionException("Invalid values in string."); + } + + tokenCtr++; + } + } catch (Exception e) { + throw new FunctionException("Invalid values in string.", e); + } + + return returnValues; + } + + /** + * This methods takes a string of input function arguments, evaluates each + * argument and creates a two Strings and one Integer value for each + * argument from the result of the evaluations. + * + * @param arguments + * The arguments of values to parse. + * @param delimiter + * The delimiter to use while parsing. + * + * @return An array list of object values found in the input string. + * + * @exception FunctionException + * Thrown if the stirng does not properly parse into the + * proper objects. + */ + public static ArrayList getTwoStringsAndOneInteger(final String arguments, + final char delimiter) throws FunctionException { + + final ArrayList returnValues = new ArrayList(); + + try { + final ArgumentTokenizer tokenizer = new ArgumentTokenizer( + arguments, delimiter); + + int tokenCtr = 0; + while (tokenizer.hasMoreTokens()) { + if (tokenCtr == 0 || tokenCtr == 1) { + final String token = tokenizer.nextToken(); + returnValues.add(token); + } else if (tokenCtr == 2) { + final String token = tokenizer.nextToken().trim(); + returnValues.add(new Integer(new Double(token).intValue())); + } else { + throw new FunctionException("Invalid values in string."); + } + + tokenCtr++; + } + } catch (Exception e) { + throw new FunctionException("Invalid values in string.", e); + } + + return returnValues; + } + + /** + * This methods takes a string of input function arguments, evaluates each + * argument and creates a one String and two Integers value for each + * argument from the result of the evaluations. + * + * @param arguments + * The arguments of values to parse. + * @param delimiter + * The delimiter to use while parsing. + * + * @return An array list of object values found in the input string. + * + * @exception FunctionException + * Thrown if the stirng does not properly parse into the + * proper objects. + */ + public static ArrayList getOneStringAndTwoIntegers(final String arguments, + final char delimiter) throws FunctionException { + + final ArrayList returnValues = new ArrayList(); + + try { + final ArgumentTokenizer tokenizer = new ArgumentTokenizer( + arguments, delimiter); + + int tokenCtr = 0; + while (tokenizer.hasMoreTokens()) { + if (tokenCtr == 0) { + final String token = tokenizer.nextToken().trim(); + returnValues.add(token); + } else if (tokenCtr == 1 || tokenCtr == 2) { + final String token = tokenizer.nextToken().trim(); + returnValues.add(new Integer(new Double(token).intValue())); + } else { + throw new FunctionException("Invalid values in string."); + } + + tokenCtr++; + } + } catch (Exception e) { + throw new FunctionException("Invalid values in string.", e); + } + + return returnValues; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionResult.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionResult.java new file mode 100644 index 0000000..59ad1f9 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/FunctionResult.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function; + +/** + * This is a wrapper for the result value returned from a function that not only + * contains the result, but the type. All custom functions must return a + * FunctionResult. + */ +public class FunctionResult { + + // The value returned from a function call. + private String result; + + // The type of the result. Can be a numberic or string. Boolean values come + // back as numeric values. + private int type; + + /** + * Constructor. + * + * @param result + * The result value. + * @param type + * The result type. + * + * @throws FunctionException + * Thrown if result type is invalid. + */ + public FunctionResult(String result, int type) throws FunctionException { + + if (type < FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC + || type > FunctionConstants.FUNCTION_RESULT_TYPE_STRING) { + + throw new FunctionException("Invalid function result type."); + } + + this.result = result; + this.type = type; + } + + /** + * Returns the result value. + * + * @return The result value. + */ + public String getResult() { + return result; + } + + /** + * Returns the result type. + * + * @return The result type. + */ + public int getType() { + return type; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Abs.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Abs.java new file mode 100644 index 0000000..595fc5c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Abs.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the absolute value of a double value. See the Math.abs(double) method in the + * JDK for a complete description of how this function works. + */ +public class Abs implements Function { + /** + * Returns the name of the function - "abs". + * + * @return The name of this function class. + */ + public String getName() { + return "abs"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The absolute value of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.abs(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Acos.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Acos.java new file mode 100644 index 0000000..c57f8cf --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Acos.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the arc cosine of an angle. See the Math.ceil(double) method in the JDK for a + * complete description of how this function works. + */ +public class Acos implements Function { + /** + * Returns the name of the function - "acos". + * + * @return The name of this function class. + */ + public String getName() { + return "acos"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The arc cosine value of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.acos(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Asin.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Asin.java new file mode 100644 index 0000000..8d610e1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Asin.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the arc sine of an angle See the Math.asin(double) method in the JDK for a + * complete description of how this function works. + */ +public class Asin implements Function { + /** + * Returns the name of the function - "asin". + * + * @return The name of this function class. + */ + public String getName() { + return "asin"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The arc sine of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.asin(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } + +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan.java new file mode 100644 index 0000000..360dbc8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the arc tangent of an angle. See the Math.atan(double) method in the JDK for + * a complete description of how this function works. + */ +public class Atan implements Function { + /** + * Returns the name of the function - "atan". + * + * @return The name of this function class. + */ + public String getName() { + return "atan"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The arc tangent of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.atan(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan2.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan2.java new file mode 100644 index 0000000..a5d677d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Atan2.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function + * converts rectangular coordinates to polar. See the Math.atan2(double, double) + * method in the JDK for a complete description of how this function works. + */ +public class Atan2 implements Function { + /** + * Returns the name of the function - "atan2". + * + * @return The name of this function class. + */ + public String getName() { + return "atan2"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two double + * values and evaluated. + * + * @return The arc tangent2 value of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + + ArrayList numbers = FunctionHelper.getDoubles(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (numbers.size() != 2) { + throw new FunctionException("Two numeric arguments are required."); + } + + try { + double argumentOne = ((Double) numbers.get(0)).doubleValue(); + double argumentTwo = ((Double) numbers.get(1)).doubleValue(); + result = new Double(Math.atan2(argumentOne, argumentTwo)); + } catch (Exception e) { + throw new FunctionException("Two numeric arguments are required.", e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Average.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Average.java new file mode 100644 index 0000000..657f219 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Average.java @@ -0,0 +1,51 @@ +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.math.BigDecimal; +import java.util.ArrayList; + + +/** + * 计算多个数字的平均值 + * @author sunyk + * + */ +public class Average implements Function { + + @Override + public String getName() { + return "avg"; + } + + @Override + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + + ArrayList numbers = FunctionHelper.getDoubles(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + int count = numbers.size() ; + if (count < 2) { + throw new FunctionException("Two numeric arguments are required at least."); + } + + double sum=0; + for (Double num : numbers) { + //为什么使用这样的方法,而不直接相加呢? + //原因是Java中的简单浮点数类型float和double不能够进行运算,会出现类似如下情况 + //eg:sum(1,2,3,1.2,2.0,3.6)=12.79999999999而不等于12.8 + BigDecimal b1=new BigDecimal(Double.toString(sum)); + BigDecimal b2=new BigDecimal(Double.toString(num)); + sum=b1.add(b2).doubleValue(); + } + + result = new Double(sum/count); + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ceil.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ceil.java new file mode 100644 index 0000000..20afe35 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ceil.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the ceiling value of a double value. See the Math.ceil(double) method in the + * JDK for a complete description of how this function works. + */ +public class Ceil implements Function { + /** + * Returns the name of the function - "ceil". + * + * @return The name of this function class. + */ + public String getName() { + return "ceil"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The ceiling of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.ceil(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Cos.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Cos.java new file mode 100644 index 0000000..743a7b9 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Cos.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the trigonometric cosine of an angle. See the Math.cos(double) method in the + * JDK for a complete description of how this function works. + */ +public class Cos implements Function { + /** + * Returns the name of the function - "cos". + * + * @return The name of this function class. + */ + public String getName() { + return "cos"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The cosine of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.cos(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Exp.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Exp.java new file mode 100644 index 0000000..9002ced --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Exp.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the exponential number e (i.e., 2.718...) raised to the power of a double + * value. See the Math.exp(double) method in the JDK for a complete description + * of how this function works. + */ +public class Exp implements Function { + /** + * Returns the name of the function - "exp". + * + * @return The name of this function class. + */ + public String getName() { + return "exp"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The the value e to the argument power, where e is the base of the + * natural logarithms + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.exp(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Floor.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Floor.java new file mode 100644 index 0000000..7b92197 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Floor.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the floor value of a double value. See the Math.floor(double) method in the + * JDK for a complete description of how this function works. + */ +public class Floor implements Function { + /** + * Returns the name of the function - "floor". + * + * @return The name of this function class. + */ + public String getName() { + return "floor"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The floor of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.floor(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Groovy.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Groovy.java new file mode 100644 index 0000000..9b5341e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Groovy.java @@ -0,0 +1,172 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.util.MD5; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.mysql.cj.xdevapi.JsonArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * This class is a function that executes within Evaluator. The function returns + * the ceiling value of a double value. See the Math.ceil(double) method in the + * JDK for a complete description of how this function works. + */ +@Component +public class Groovy implements Function { + + private static final Logger logger = LoggerFactory.getLogger(Groovy.class); + + private static final ScriptEngineManager factory = new ScriptEngineManager(); + + public static String GROOVY_SHELL_KEY_PREFIX = "GROOVY_SHELL#"; + +// private RedisManager redisManager; + + private static Cache scriptClassCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + + /** + * Returns the name of the function - "def main". + * + * @return The name of this function class. + */ + public String getName() { + return "def main"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The ceiling of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + return null; + } + +// public String execute(final String expression, Map data) throws EvaluationException { +// String result = null; +// try { +// ScriptEngine scriptEngine = null; +// String scriptMd5 = PYTHON_SHELL_KEY_PREFIX + MD5.GetMD5Code(expression); +// ScriptEngine value = (ScriptEngine) SerializeUtils.deserialize(redisManager.get(scriptMd5.getBytes())); +// if(value != null){ +// scriptEngine = value; +// } else { +// scriptEngine = factory.getEngineByName("groovy"); +// scriptEngine.eval(expression); +// redisManager.set(scriptMd5.getBytes(), SerializeUtils.serialize(scriptEngine), 120); +// } +// +// Object functionResult = ((Invocable) scriptEngine).invokeFunction("main", data); +// result = functionResult.toString(); +// } catch (Exception e) { +// throw new EvaluationException("执行groovy脚本出错", e); +// } +// return result; +// } + + public String execute(final String expression, Map data) throws EvaluationException { + String result = null; + try { + ScriptEngine scriptEngine = null; + String scriptMd5 = GROOVY_SHELL_KEY_PREFIX + MD5.GetMD5Code(expression); + ScriptEngine value = scriptClassCache.getIfPresent(scriptMd5); + if(value != null){ + scriptEngine = value; + } else { + scriptEngine = factory.getEngineByName("groovy"); + scriptEngine.eval(expression); + scriptClassCache.put(scriptMd5, scriptEngine); + } + + Object functionResult = ((Invocable) scriptEngine).invokeFunction("main", data); + if (functionResult!=null){ + result = functionResult.toString(); + } +// result = functionResult.toString(); + } catch (Exception e) { + throw new EvaluationException("执行groovy脚本出错", e); + } + return result; + } + @Autowired + private Python python; + public Object executeForObject(final String expression, Map data) throws EvaluationException { + Object result = null; + try { + ScriptEngine scriptEngine = null; + String scriptMd5 = GROOVY_SHELL_KEY_PREFIX + MD5.GetMD5Code(expression); + ScriptEngine value = scriptClassCache.getIfPresent(scriptMd5); + if(value != null){ + scriptEngine = value; + } else { + scriptEngine = factory.getEngineByName("groovy"); + scriptEngine.eval(expression); + scriptClassCache.put(scriptMd5, scriptEngine); + } + + Object functionResult = ((Invocable) scriptEngine).invokeFunction("main", data); + if (functionResult!=null){ +// if (functionResult instanceof HashMap||functionResult instanceof ArrayList){ +// result = JSON.toJSONString(functionResult); +// }else +// if (functionResult instanceof String){ +// result = functionResult.toString(); +// }else { +// result = functionResult; +// } + result = functionResult; + } + } catch (Exception e) { + throw new EvaluationException("执行groovy脚本出错", e); + } + return result; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/IEEEremainder.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/IEEEremainder.java new file mode 100644 index 0000000..78fb2dd --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/IEEEremainder.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * the remainder operation on two arguments as prescribed by the IEEE 754 + * standard. See the Math.IEEERemainder(double, double) method in the JDK for a + * complete description of how this function works. + */ +public class IEEEremainder implements Function { + /** + * Returns the name of the function - "IEEEremainder". + * + * @return The name of this function class. + */ + public String getName() { + return "IEEEremainder"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two double + * values and evaluated. + * + * @return The the remainder operation on two arguments as prescribed by the + * IEEE 754 standard. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + + ArrayList numbers = FunctionHelper.getDoubles(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (numbers.size() != 2) { + throw new FunctionException("Two numeric arguments are required."); + } + + try { + double argumentOne = ((Double) numbers.get(0)).doubleValue(); + double argumentTwo = ((Double) numbers.get(1)).doubleValue(); + result = new Double(Math.IEEEremainder(argumentOne, argumentTwo)); + } catch (Exception e) { + throw new FunctionException("Two numeric arguments are required.", e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ln.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ln.java new file mode 100644 index 0000000..f5ba912 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Ln.java @@ -0,0 +1,41 @@ +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * log以e为底的对数 + * + * @author Administrator + * + */ +public class Ln implements Function { + + @Override + public String getName() { + return "ln"; + } + + @Override + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.log(number)/Math.log(Math.E)); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Log.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Log.java new file mode 100644 index 0000000..1b4e6ce --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Log.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the natural logarithm (base e) of a double value. See the Math.log(double) + * method in the JDK for a complete description of how this function works. + */ +public class Log implements Function { + /** + * Returns the name of the function - "log". + * + * @return The name of this function class. + */ + public String getName() { + return "log"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The natural logarithm of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.log(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/MathFunctions.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/MathFunctions.java new file mode 100644 index 0000000..f3aac2d --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/MathFunctions.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionGroup; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A groups of functions that can loaded at one time into an instance of + * Evaluator. This group contains all of the functions located in the + * net.sourceforge.jeval.function.math package. + */ +public class MathFunctions implements FunctionGroup { + /** + * Used to store instances of all of the functions loaded by this class. + */ + private List functions = new ArrayList(); + + /** + * Default contructor for this class. The functions loaded by this class are + * instantiated in this constructor. + */ + public MathFunctions() { + functions.add(new Abs()); + functions.add(new Acos()); + functions.add(new Asin()); + functions.add(new Atan()); + functions.add(new Atan2()); + functions.add(new Ceil()); + functions.add(new Cos()); + functions.add(new Exp()); + functions.add(new Floor()); + functions.add(new IEEEremainder()); + functions.add(new Log()); + functions.add(new Pow()); + functions.add(new Random()); + functions.add(new Rint()); + functions.add(new Round()); + functions.add(new Sin()); + functions.add(new Sqrt()); + functions.add(new Tan()); + functions.add(new ToDegrees()); + functions.add(new ToRadians()); + functions.add(new Max()); + functions.add(new Min()); + functions.add(new Sum()); + functions.add(new Ln()); + functions.add(new Average()); + functions.add(new Groovy()); + } + + /** + * Returns the name of the function group - "numberFunctions". + * + * @return The name of this function group class. + */ + public String getName() { + return "numberFunctions"; + } + + /** + * Returns a list of the functions that are loaded by this class. + * + * @return A list of the functions loaded by this class. + */ + public List getFunctions() { + return functions; + } + + /** + * Loads the functions in this function group into an instance of Evaluator. + * + * @param evaluator + * An instance of Evaluator to load the functions into. + */ + public void load(final Evaluator evaluator) { + Iterator functionIterator = functions.iterator(); + + while (functionIterator.hasNext()) { + evaluator.putFunction((Function) functionIterator.next()); + } + } + + /** + * Unloads the functions in this function group from an instance of + * Evaluator. + * + * @param evaluator + * An instance of Evaluator to unload the functions from. + */ + public void unload(final Evaluator evaluator) { + Iterator functionIterator = functions.iterator(); + + while (functionIterator.hasNext()) { + evaluator.removeFunction(((Function) functionIterator.next()) + .getName()); + } + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Max.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Max.java new file mode 100644 index 0000000..02a0b72 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Max.java @@ -0,0 +1,40 @@ +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * 获取多个值得最大值 + * @author sunyk + * + */ +public class Max implements Function { + + @Override + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + + ArrayList numbers = FunctionHelper.getDoubles(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + int count = numbers.size() ; + if (count < 2) { + throw new FunctionException("Two numeric arguments are required at least."); + } + + result = (Double)Collections.max(numbers); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } + + @Override + public String getName() { + return "max"; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Min.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Min.java new file mode 100644 index 0000000..3f9ba55 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Min.java @@ -0,0 +1,40 @@ +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * 获取多个值最小值 + * @author sunyk + * + */ +public class Min implements Function { + + @Override + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + + ArrayList numbers = FunctionHelper.getDoubles(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + int count = numbers.size() ; + if (count < 2) { + throw new FunctionException("Two numeric arguments are required at least."); + } + + result = (Double)Collections.min(numbers); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } + + @Override + public String getName() { + return "min"; + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Pow.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Pow.java new file mode 100644 index 0000000..369f939 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Pow.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * the value of the first argument raised to the second power of the second + * argument. See the Math.pow(double, double) method in the JDK for a complete + * description of how this function works. + */ +public class Pow implements Function { + /** + * Returns the name of the function - "pow". + * + * @return The name of this function class. + */ + public String getName() { + return "pow"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two double + * values and evaluated. + * + * @return The value of the first argument raised to the second power of the + * second argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + + ArrayList numbers = FunctionHelper.getDoubles(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (numbers.size() != 2) { + throw new FunctionException("Two numeric arguments are required."); + } + + try { + double argumentOne = ((Double) numbers.get(0)).doubleValue(); + double argumentTwo = ((Double) numbers.get(1)).doubleValue(); + result = new Double(Math.pow(argumentOne, argumentTwo)); + } catch (Exception e) { + throw new FunctionException("Two numeric arguments are required.", e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Python.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Python.java new file mode 100644 index 0000000..093592c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Python.java @@ -0,0 +1,183 @@ +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baoying.enginex.executor.util.MD5; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.hadoop.hbase.shaded.org.apache.avro.data.Json; +import org.python.core.*; +import org.python.util.PythonInterpreter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +@Component +public class Python implements Function { + + private static final Logger logger = LoggerFactory.getLogger(Groovy.class); + + private static final ScriptEngineManager factory = new ScriptEngineManager(); + + public static String PYTHON_SHELL_KEY_PREFIX = "JYTHON_SHELL#"; + + private static Cache scriptClassCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + /** + * Returns the name of the function - "def main". + * + * @return The name of this function class. + */ + @Override + public String getName() { + return "if __name__ == \"__main__\":"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The ceiling of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + @Override + public FunctionResult execute(Evaluator evaluator, String arguments) throws FunctionException { + return null; + } + + public Object executeForObject(final String expression, Map data) throws EvaluationException { + Object result = null; + try { + ScriptEngine scriptEngine = null; + String scriptMd5 = PYTHON_SHELL_KEY_PREFIX + MD5.GetMD5Code(expression); + ScriptEngine value = scriptClassCache.getIfPresent(scriptMd5); + Object functionResult = null; + + if(value != null){ + scriptEngine = value; + } else { + scriptEngine = factory.getEngineByName("python"); + scriptEngine.eval(expression); + scriptClassCache.put(scriptMd5, scriptEngine); + } + PyDictionary pyDictionary = new PyDictionary(); + for (Map.Entry entry : data.entrySet()) { + pyDictionary.put(entry.getKey(),entry.getValue()); + } + +// PythonInterpreter interpreter = new PythonInterpreter(); +// interpreter.exec(new String(expression.getBytes())); +// PyFunction python_main = interpreter.get("python_main", PyFunction.class); +// PyObject pyObject = python_main.__call__(pyDictionary); +// System.out.println(pyObject); +// String ret = pyObject.toString(); +// String newStr = new String(ret.getBytes("iso8859-1"), "utf-8"); //通过new String(ret.getBytes("iso8859-1"), "utf-8")转一下就好了 +// System.out.println(newStr); //newStr就不会乱码了 +// System.out.println(getEncode(String.valueOf(pyObject))); + functionResult = ((Invocable) scriptEngine).invokeFunction("python_main", pyDictionary); + if (functionResult instanceof PyDictionary){ + PyObject resultPy = (PyObject)functionResult; + String ret = resultPy.toString();//这里ret可能会乱码 + System.out.println(ret); + } + result = functionResult; + } catch (Exception e) { + e.printStackTrace(); + throw new EvaluationException("执行Python脚本出错", e); + } + return result; + } + // 这里可以提供更多地编码格式,另外由于部分编码格式是一致的所以会返回 第一个匹配的编码格式 GBK 和 GB2312 + public static final String[] encodes = new String[] { "UTF-8", "GBK", "GB2312", "ISO-8859-1", "ISO-8859-2" }; + + /** + * 获取字符串编码格式 + * + * @param str + * @return + */ + public static String getEncode(String str) { + byte[] data = str.getBytes(); + byte[] b = null; + a:for (int i = 0; i < encodes.length; i++) { + try { + b = str.getBytes(encodes[i]); + if (b.length!=data.length) + continue; + for (int j = 0; j < b.length; j++) { + if (b[j] != data[j]) { + continue a; + } + } + return encodes[i]; + } catch (UnsupportedEncodingException e) { + continue; + } + } + return null; + } + + public static void main(String[] args) throws IOException { +// Properties props = new Properties(); +//// props.put("python.home", "F:\\Java\\jython\\jython2.7.1\\Lib"); +// props.put("python.console.encoding", "UTF-8"); +// props.put("python.security.respectJavaAccessibility", "false"); +// props.put("python.import.site", "false"); +// Properties preprops = System.getProperties(); +// PythonInterpreter.initialize(preprops, props, new String[0]); +// PythonInterpreter interpreter = new PythonInterpreter(); +// interpreter.exec("#coding=UTF-8 \n" + +// "print('a智障v')"); +// interpreter.execfile("E:\\python\\迭代求阶乘.py"); + + +// PythonInterpreter interpreter = new PythonInterpreter(); +// interpreter.exec("# -*- encoding: utf-8 -*- \na='智障'; "); +// interpreter.exec("print a;"); +// interpreter.exec("print '智障';"); +// String s = "python \ndef python_main(_):\n" + +// " # result 为返回结果其中内部字段解释为:ruleScore(规则命中时得分),hitResult规则是否命中可选值为:命中/未命中\n" + +// " # fieldList 为输出字段列表,内部为字典表,updateInputMap 为需要更新到入参的变量是一个字典表\n" + +// "\n" + +// " result = {\"ruleScore\":0,\"hitResult\":\"未命中\",\"fieldList\":[],\"updateInputMap\":{}}\n" + +// " print(_)\n" + +// " print(\"未命中\")\n" + +// " result[\"ruleScore\"] = 420\n" + +// " result[\"hitResult\"] = \"命中\"\n" + +// " return result\n" + +// "\n" + +// "if __name__ == \"__main__\":\n" + +// " python_main(params)"; +// String[] param = new String[2]; +// param[0] = "python3"; +// param[1] = s; +// Runtime.getRuntime().exec(s); + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Random.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Random.java new file mode 100644 index 0000000..c1facd7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Random.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * a random double value greater than or equal to 0.0 and less than 1.0. See the + * Math.random() method in the JDK for a complete description of how this + * function works. + */ +public class Random implements Function { + /** + * Returns the name of the function - "random". + * + * @return The name of this function class. + */ + public String getName() { + return "random"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * Not used. + * + * @return A random double value greater than or equal to 0.0 and less than + * 1.0. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = new Double(Math.random()); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Rint.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Rint.java new file mode 100644 index 0000000..ce74086 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Rint.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the double value that is closest in value to the argument and is equal to a + * mathematical integer. See the Math.rint(double) method in the JDK for a + * complete description of how this function works. + */ +public class Rint implements Function { + /** + * Returns the name of the function - "rint". + * + * @return The name of this function class. + */ + public String getName() { + return "rint"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return A double value that is closest in value to the argument and is + * equal to a mathematical integer. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.rint(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Round.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Round.java new file mode 100644 index 0000000..b3e7768 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Round.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the closet long to a double value. See the Math.round(double) method in the + * JDK for a complete description of how this function works. + */ +public class Round implements Function { + /** + * Returns the name of the function - "round". + * + * @return The name of this function class. + */ + public String getName() { + return "round"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return A long value that is closest to the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Long result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Long(Math.round(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sin.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sin.java new file mode 100644 index 0000000..026368e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sin.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the sine of an angle. See the Math.sin(double) method in the JDK for a + * complete description of how this function works. + */ +public class Sin implements Function { + /** + * Returns the name of the function - "sin". + * + * @return The name of this function class. + */ + public String getName() { + return "sin"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return The sine of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.sin(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sqrt.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sqrt.java new file mode 100644 index 0000000..1fb0089 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sqrt.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * a square root of a double value. See the Math.sqrt(double) method in the JDK + * for a complete description of how this function works. + */ +public class Sqrt implements Function { + /** + * Returns the name of the function - "sqrt". + * + * @return The name of this function class. + */ + public String getName() { + return "sqrt"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return A square root of the argument. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.sqrt(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sum.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sum.java new file mode 100644 index 0000000..36bf40c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Sum.java @@ -0,0 +1,49 @@ +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.math.BigDecimal; +import java.util.ArrayList; + +/** + * 计算多个数字的和 + * @author sunyk + * + */ +public class Sum implements Function { + + @Override + public String getName() { + return "sum"; + } + + @Override + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + + ArrayList numbers = FunctionHelper.getDoubles(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + int count = numbers.size() ; + if (count < 2) { + throw new FunctionException("Two numeric arguments are required at least."); + } + + double sum=0; + for (Double num : numbers) { + //为什么使用这样的方法,而不直接相加呢? + //原因是Java中的简单浮点数类型float和double不能够进行运算,会出现类似如下情况 + //eg:sum(1,2,3,1.2,2.0,3.6)=12.79999999999而不等于12.8 + BigDecimal b1=new BigDecimal(Double.toString(sum)); + BigDecimal b2=new BigDecimal(Double.toString(num)); + sum=b1.add(b2).doubleValue(); + } + result = new Double(sum); + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } + +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Tan.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Tan.java new file mode 100644 index 0000000..a5d1716 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/Tan.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * trigonometric tangent of an angle. See the Math.tan(double) method in the JDK + * for a complete description of how this function works. + */ +public class Tan implements Function { + /** + * Returns the name of the function - "tan". + * + * @return The name of this function class. + */ + public String getName() { + return "tan"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return A trigonometric tangent of an angle. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.tan(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToDegrees.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToDegrees.java new file mode 100644 index 0000000..71191ba --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToDegrees.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function + * converts an angle measured in radians to the equivalent angle measured in + * degrees. See the Math.toDegrees(double) method in the JDK for a complete + * description of how this function works. + */ +public class ToDegrees implements Function { + /** + * Returns the name of the function - "toDegrees". + * + * @return The name of this function class. + */ + public String getName() { + return "toDegrees"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return A measurement of the argument in degrees. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.toDegrees(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToRadians.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToRadians.java new file mode 100644 index 0000000..1beb88e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/math/ToRadians.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.math; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function + * converts an angle measured in degress to the equivalent angle measured in + * radians. See the Math.toRadians(double) method in the JDK for a complete + * description of how this function works. + */ +public class ToRadians implements Function { + /** + * Returns the name of the function - "toRadians". + * + * @return The name of this function class. + */ + public String getName() { + return "toRadians"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted to a double value and + * evaluated. + * + * @return A measurement of the argument in radians. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(Evaluator evaluator, String arguments) + throws FunctionException { + Double result = null; + Double number = null; + + try { + number = new Double(arguments); + } catch (Exception e) { + throw new FunctionException("Invalid argument.", e); + } + + result = new Double(Math.toRadians(number.doubleValue())); + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CharAt.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CharAt.java new file mode 100644 index 0000000..f32dce5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CharAt.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * the character at the specified index in the source string. See the + * String.charAt(int) method in the JDK for a complete description of how this + * function works. + */ +public class CharAt implements Function { + /** + * Returns the name of the function - "charAt". + * + * @return The name of this function class. + */ + public String getName() { + return "charAt"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into one string and + * one integer argument. The first argument is the source string + * and the second argument is the index. The string argument(s) + * HAS to be enclosed in quotes. White space that is not enclosed + * within quotes will be trimmed. Quote characters in the first + * and last positions of any string argument (after being + * trimmed) will be removed also. The quote characters used must + * be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return A character that is located at the specified index. The value is + * returned as a string. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "One string and one integer argument " + + "are required."; + + ArrayList values = FunctionHelper.getOneStringAndOneInteger(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (values.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(0), evaluator.getQuoteCharacter()); + int index = ((Integer) values.get(1)).intValue(); + + char[] character = new char[1]; + character[0] = argumentOne.charAt(index); + + result = new String(character); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_STRING); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareTo.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareTo.java new file mode 100644 index 0000000..53f6dcf --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareTo.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function + * compares two strings lexicographically. See the String.compareTo(String) + * method in the JDK for a complete description of how this function works. + */ +public class CompareTo implements Function { + /** + * Returns the name of the function - "compareTo". + * + * @return The name of this function class. + */ + public String getName() { + return "compareTo"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments. The first argument is the first string to compare + * and the second argument is the second string to compare. The + * string argument(s) HAS to be enclosed in quotes. White space + * that is not enclosed within quotes will be trimmed. Quote + * characters in the first and last positions of any string + * argument (after being trimmed) will be removed also. The quote + * characters used must be the same as the quote characters used + * by the current instance of Evaluator. If there are multiple + * arguments, they must be separated by a comma (","). + * + * @return Returns an integer value of zero if the strings are equal, an + * integer value less than zero if the first string precedes the + * second string or an integer value greater than zero if the first + * string follows the second string. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Integer result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + result = new Integer(argumentOne.compareTo(argumentTwo)); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareToIgnoreCase.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareToIgnoreCase.java new file mode 100644 index 0000000..8f98fce --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/CompareToIgnoreCase.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function + * compares two strings lexicographically, ignoreing case considerations. See + * the String.compareTo(String) method in the JDK for a complete description of + * how this function works. + */ +public class CompareToIgnoreCase implements Function { + /** + * Returns the name of the function - "compareToIgnoreCase". + * + * @return The name of this function class. + */ + public String getName() { + return "compareToIgnoreCase"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments. The first argument is the first string to compare + * and the second argument is the second argument to compare. The + * string argument(s) HAS to be enclosed in quotes. White space + * that is not enclosed within quotes will be trimmed. Quote + * characters in the first and last positions of any string + * argument (after being trimmed) will be removed also. The quote + * characters used must be the same as the quote characters used + * by the current instance of Evaluator. If there are multiple + * arguments, they must be separated by a comma (","). + * + * @return Returns an integer value of zero if the strings are equal, an + * integer value less than zero if the first string precedes the + * second string or an integer value greater than zero if the first + * string follows the second string. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Integer result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + result = new Integer(argumentOne.compareToIgnoreCase(argumentTwo)); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Concat.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Concat.java new file mode 100644 index 0000000..4115ebb --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Concat.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function + * concatenates the second string to the end of the first. See the + * String.concat(String) method in the JDK for a complete description of how + * this function works. + */ +public class Concat implements Function { + /** + * Returns the name of the function - "concat". + * + * @return The name of this function class. + */ + public String getName() { + return "concat"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments. The first argument is the string in which the + * second argument string will be concatenated. The string + * argument(s) HAS to be enclosed in quotes. White space that is + * not enclosed within quotes will be trimmed. Quote characters + * in the first and last positions of any string argument (after + * being trimmed) will be removed also. The quote characters used + * must be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return Returns a strng that is made up the first string, followed by the + * second string. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + result = argumentOne.concat(argumentTwo); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_STRING); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Contains.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Contains.java new file mode 100644 index 0000000..1ee0c16 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Contains.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * the index within the source string of the first occurrence of the substring, + * starting at the specified index. See the String.indexOf(String, int) method + * in the JDK for a complete description of how this function works. + */ +public class Contains implements Function { + /** + * Returns the name of the function - "indexOf". + * + * @return The name of this function class. + */ + public String getName() { + return "contains"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments and one integer argument. The first argument is the + * source string, the second argument is the substring and the + * third argument is the index. The string argument(s) HAS to be + * enclosed in quotes. White space that is not enclosed within + * quotes will be trimmed. Quote characters in the first and last + * positions of any string argument (after being trimmed) will be + * removed also. The quote characters used must be the same as + * the quote characters used by the current instance of + * Evaluator. If there are multiple arguments, they must be + * separated by a comma (","). + * + * @return Returns The index at where the substring is found. If the + * substring is not found, then -1 is returned. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + + if (argumentOne.contains(argumentTwo)) { + result = EvaluationConstants.BOOLEAN_STRING_TRUE; + } else { + result = EvaluationConstants.BOOLEAN_STRING_FALSE; + } + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EndsWith.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EndsWith.java new file mode 100644 index 0000000..15afc4c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EndsWith.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function tests + * if the string ends with a specified suffix. See the String.endswith(String) + * method in the JDK for a complete description of how this function works. + */ +public class EndsWith implements Function { + /** + * Returns the name of the function - "endsWith". + * + * @return The name of this function class. + */ + public String getName() { + return "endsWith"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments. The first argument is the string to test and second + * argument is the suffix. The string argument(s) HAS to be + * enclosed in quotes. White space that is not enclosed within + * quotes will be trimmed. Quote characters in the first and last + * positions of any string argument (after being trimmed) will be + * removed also. The quote characters used must be the same as + * the quote characters used by the current instance of + * Evaluator. If there are multiple arguments, they must be + * separated by a comma (","). + * + * @return Returns "1.0" (true) if the string ends with the suffix, + * otherwise it returns "0.0" (false). The return value respresents + * a Boolean value that is compatible with the Boolean operators + * used by Evaluator. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + + if (argumentOne.endsWith(argumentTwo)) { + result = EvaluationConstants.BOOLEAN_STRING_TRUE; + } else { + result = EvaluationConstants.BOOLEAN_STRING_FALSE; + } + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Equals.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Equals.java new file mode 100644 index 0000000..21b52d0 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Equals.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function tests + * one string equals another. See the String.equals(String) method in the JDK + * for a complete description of how this function works. + */ +public class Equals implements Function { + /** + * Returns the name of the function - "equals". + * + * @return The name of this function class. + */ + public String getName() { + return "equals"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments. The first argument is a string that will be + * compared to the second argument / string. The string + * argument(s) HAS to be enclosed in quotes. White space that is + * not enclosed within quotes will be trimmed. Quote characters + * in the first and last positions of any string argument (after + * being trimmed) will be removed also. The quote characters used + * must be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return Returns "1.0" (true) if the string ends with the suffix, + * otherwise it returns "0.0" (false). The return value respresents + * a Boolean value that is compatible with the Boolean operators + * used by Evaluator. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + + if (argumentOne.equals(argumentTwo)) { + result = EvaluationConstants.BOOLEAN_STRING_TRUE; + } else { + result = EvaluationConstants.BOOLEAN_STRING_FALSE; + } + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EqualsIgnoreCase.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EqualsIgnoreCase.java new file mode 100644 index 0000000..fc941bf --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/EqualsIgnoreCase.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function tests + * one string equals another, but ignores case. See the + * String.equalsIgnoreCase(String) method in the JDK for a complete description + * of how this function works. + */ +public class EqualsIgnoreCase implements Function { + /** + * Returns the name of the function - "equalsIgnoreCase". + * + * @return The name of this function class. + */ + public String getName() { + return "equalsIgnoreCase"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments. The first argument is a string that will be + * compared to the second argument / string. The string + * argument(s) HAS to be enclosed in quotes. White space that is + * not enclosed within quotes will be trimmed. Quote characters + * in the first and last positions of any string argument (after + * being trimmed) will be removed also. The quote characters used + * must be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return Returns "1.0" (true) if the string ends with the suffix, + * otherwise it returns "0.0" (false). The return value respresents + * a Boolean value that is compatible with the Boolean operators + * used by Evaluator. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + + if (argumentOne.equalsIgnoreCase(argumentTwo)) { + result = EvaluationConstants.BOOLEAN_STRING_TRUE; + } else { + result = EvaluationConstants.BOOLEAN_STRING_FALSE; + } + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Eval.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Eval.java new file mode 100644 index 0000000..7383b5c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Eval.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionConstants; +import com.baoying.enginex.executor.util.jeval.function.FunctionException; +import com.baoying.enginex.executor.util.jeval.function.FunctionResult; + +/** + * This class is a function that executes within Evaluator. The function returns + * the result of a Evaluator compatible expression. See the + * Evaluator.evaluate(String) method for a complete description of how this + * function works. + */ +public class Eval implements Function { + /** + * Returns the name of the function - "eval". + * + * @return The name of this function class. + */ + public String getName() { + return "eval"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of evaluator. + * @param arguments + * A string expression that is compatible with Evaluator. *** THE + * STRING ARGUMENT SHOULD NOT BE ENCLOSED IN QUOTES OR THE + * EXPRESSION MAY NOT BE EVALUATED CORRECTLY.*** *** FUNCTION + * CALLS ARE VALID WITHIN THE EVAL FUNCTION. *** + * + * @return The evaluated result fot the input expression. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, + final String arguments) throws FunctionException { + String result = null; + + try { + result = evaluator.evaluate(arguments, false, true); + } catch (EvaluationException ee) { + throw new FunctionException(ee.getMessage(), ee); + } + + int resultType = FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC; + try { + Double.parseDouble(result); + } catch (NumberFormatException nfe) { + resultType = FunctionConstants.FUNCTION_RESULT_TYPE_STRING; + } + + return new FunctionResult(result, resultType); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/IndexOf.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/IndexOf.java new file mode 100644 index 0000000..06d2401 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/IndexOf.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * the index within the source string of the first occurrence of the substring, + * starting at the specified index. See the String.indexOf(String, int) method + * in the JDK for a complete description of how this function works. + */ +public class IndexOf implements Function { + /** + * Returns the name of the function - "indexOf". + * + * @return The name of this function class. + */ + public String getName() { + return "indexOf"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments and one integer argument. The first argument is the + * source string, the second argument is the substring and the + * third argument is the index. The string argument(s) HAS to be + * enclosed in quotes. White space that is not enclosed within + * quotes will be trimmed. Quote characters in the first and last + * positions of any string argument (after being trimmed) will be + * removed also. The quote characters used must be the same as + * the quote characters used by the current instance of + * Evaluator. If there are multiple arguments, they must be + * separated by a comma (","). + * + * @return Returns The index at where the substring is found. If the + * substring is not found, then -1 is returned. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Integer result = null; + String exceptionMessage = "Two string arguments and one integer " + + "argument are required."; + + ArrayList values = FunctionHelper.getTwoStringsAndOneInteger(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (values.size() != 3) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(1), evaluator.getQuoteCharacter()); + int index = ((Integer) values.get(2)).intValue(); + result = new Integer(argumentOne.indexOf(argumentTwo, index)); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/LastIndexOf.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/LastIndexOf.java new file mode 100644 index 0000000..d899277 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/LastIndexOf.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * the index within the source string of the last occurrence of the substring, + * starting at the specified index. See the String.lastIndexOf(String, int) + * method in the JDK for a complete description of how this function works. + */ +public class LastIndexOf implements Function { + /** + * Returns the name of the function - "lastIndexOf". + * + * @return The name of this function class. + */ + public String getName() { + return "lastIndexOf"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments and one integer argument. The first argument is the + * source string, the second argument is the substring and the + * third argument is the index. The string argument(s) HAS to be + * enclosed in quotes. White space that is not enclosed within + * quotes will be trimmed. Quote characters in the first and last + * positions of any string argument (after being trimmed) will be + * removed also. The quote characters used must be the same as + * the quote characters used by the current instance of + * Evaluator. If there are multiple arguments, they must be + * separated by a comma (","). + * + * @return Returns The index at where the substring is found. If the + * substring is not found, then -1 is returned. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Integer result = null; + String exceptionMessage = "Two string arguments and one integer " + + "argument are required."; + + ArrayList values = FunctionHelper.getTwoStringsAndOneInteger(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (values.size() != 3) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(1), evaluator.getQuoteCharacter()); + int index = ((Integer) values.get(2)).intValue(); + result = new Integer(argumentOne.lastIndexOf(argumentTwo, index)); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Length.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Length.java new file mode 100644 index 0000000..07563b3 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Length.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +/** + * This class is a function that executes within Evaluator. The function returns + * the length of the source string. See the String.length() method in the JDK + * for a complete description of how this function works. + */ +public class Length implements Function { + /** + * Returns the name of the function - "length". + * + * @return The name of this function class. + */ + public String getName() { + return "length"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into one string + * argument. The string argument(s) HAS to be enclosed in quotes. + * White space that is not enclosed within quotes will be + * trimmed. Quote characters in the first and last positions of + * any string argument (after being trimmed) will be removed + * also. The quote characters used must be the same as the quote + * characters used by the current instance of Evaluator. If there + * are multiple arguments, they must be separated by a comma + * (","). + * + * @return The length of the source string. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + Integer result = null; + String exceptionMessage = "One string argument is required."; + + try { + String stringValue = arguments; + + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + stringValue, evaluator.getQuoteCharacter()); + + result = new Integer(argumentOne.length()); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result.toString(), + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotContains.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotContains.java new file mode 100644 index 0000000..8fa4f67 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotContains.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * the index within the source string of the first occurrence of the substring, + * starting at the specified index. See the String.indexOf(String, int) method + * in the JDK for a complete description of how this function works. + */ +public class NotContains implements Function { + /** + * Returns the name of the function - "indexOf". + * + * @return The name of this function class. + */ + public String getName() { + return "notContains"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments and one integer argument. The first argument is the + * source string, the second argument is the substring and the + * third argument is the index. The string argument(s) HAS to be + * enclosed in quotes. White space that is not enclosed within + * quotes will be trimmed. Quote characters in the first and last + * positions of any string argument (after being trimmed) will be + * removed also. The quote characters used must be the same as + * the quote characters used by the current instance of + * Evaluator. If there are multiple arguments, they must be + * separated by a comma (","). + * + * @return Returns The index at where the substring is found. If the + * substring is not found, then -1 is returned. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + + if (argumentOne.contains(argumentTwo)) { + result = EvaluationConstants.BOOLEAN_STRING_FALSE; + } else { + result = EvaluationConstants.BOOLEAN_STRING_TRUE; + } + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotEquals.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotEquals.java new file mode 100644 index 0000000..e47941a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/NotEquals.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function tests + * one string equals another. See the String.equals(String) method in the JDK + * for a complete description of how this function works. + */ +public class NotEquals implements Function { + /** + * Returns the name of the function - "equals". + * + * @return The name of this function class. + */ + public String getName() { + return "notEquals"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments. The first argument is a string that will be + * compared to the second argument / string. The string + * argument(s) HAS to be enclosed in quotes. White space that is + * not enclosed within quotes will be trimmed. Quote characters + * in the first and last positions of any string argument (after + * being trimmed) will be removed also. The quote characters used + * must be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return Returns "1.0" (true) if the string ends with the suffix, + * otherwise it returns "0.0" (false). The return value respresents + * a Boolean value that is compatible with the Boolean operators + * used by Evaluator. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments are required."; + + ArrayList strings = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (strings.size() != 2) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) strings.get(1), evaluator.getQuoteCharacter()); + + if (argumentOne.equals(argumentTwo)) { + result = EvaluationConstants.BOOLEAN_STRING_FALSE; + } else { + result = EvaluationConstants.BOOLEAN_STRING_TRUE; + } + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Replace.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Replace.java new file mode 100644 index 0000000..ca97f5a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Replace.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * a new string with all of the occurances of the old character in the source + * string replaced with the new character. See the String.replace(char, char) + * method in the JDK for a complete description of how this function works. + */ +public class Replace implements Function { + /** + * Returns the name of the function - "replace". + * + * @return The name of this function class. + */ + public String getName() { + return "replace"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into one string and + * two character arguments. The first argument is the source + * string to replace the charactes in. The second argument is the + * old character to replace in the source string. The third + * argument is the new character to replace the old character + * with in the source string. The string and character + * argument(s) HAS to be enclosed in quotes. White space that is + * not enclosed within quotes will be trimmed. Quote characters + * in the first and last positions of any string argument (after + * being trimmed) will be removed also. The quote characters used + * must be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return Returns a string with every occurence of the old character + * replaced with the new character. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "One string argument and two character " + + "arguments are required."; + + ArrayList values = FunctionHelper.getStrings(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (values.size() != 3) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(0), evaluator.getQuoteCharacter()); + + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(1), evaluator.getQuoteCharacter()); + + String argumentThree = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(2), evaluator.getQuoteCharacter()); + + char oldCharacter = ' '; + if (argumentTwo.length() == 1) { + oldCharacter = argumentTwo.charAt(0); + } else { + throw new FunctionException(exceptionMessage); + } + + char newCharacter = ' '; + if (argumentThree.length() == 1) { + newCharacter = argumentThree.charAt(0); + } else { + throw new FunctionException(exceptionMessage); + } + + result = argumentOne.replace(oldCharacter, newCharacter); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_STRING); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StartsWith.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StartsWith.java new file mode 100644 index 0000000..8afbefe --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StartsWith.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function tests + * if the string starts with a specified prefix beginning at a specified index. + * See the String.startsWith(String, int) method in the JDK for a complete + * description of how this function works. + */ +public class StartsWith implements Function { + /** + * Returns the name of the function - "startsWith". + * + * @return The name of this function class. + */ + public String getName() { + return "startsWith"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into two string + * arguments and one integer argument. The first argument is the + * string to test, the second argument is the prefix and the + * third argument is the index to start at. The string + * argument(s) HAS to be enclosed in quotes. White space that is + * not enclosed within quotes will be trimmed. Quote characters + * in the first and last positions of any string argument (after + * being trimmed) will be removed also. The quote characters used + * must be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return Returns "1.0" (true) if the string ends with the suffix, + * otherwise it returns "0.0" (false). The return value respresents + * a Boolean value that is compatible with the Boolean operators + * used by Evaluator. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "Two string arguments and one integer " + + "argument are required."; + + ArrayList values = FunctionHelper.getTwoStringsAndOneInteger(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (values.size() != 3) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(0), evaluator.getQuoteCharacter()); + String argumentTwo = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(1), evaluator.getQuoteCharacter()); + int index = ((Integer) values.get(2)).intValue(); + + if (argumentOne.startsWith(argumentTwo, index)) { + result = EvaluationConstants.BOOLEAN_STRING_TRUE; + } else { + result = EvaluationConstants.BOOLEAN_STRING_FALSE; + } + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_NUMERIC); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StringFunctions.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StringFunctions.java new file mode 100644 index 0000000..2424b55 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/StringFunctions.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.Function; +import com.baoying.enginex.executor.util.jeval.function.FunctionGroup; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A groups of functions that can loaded at one time into an instance of + * Evaluator. This group contains all of the functions located in the + * net.sourceforge.jeval.function.string package. + */ +public class StringFunctions implements FunctionGroup { + /** + * Used to store instances of all of the functions loaded by this class. + */ + private List functions = new ArrayList(); + + /** + * Default contructor for this class. The functions loaded by this class are + * instantiated in this constructor. + */ + public StringFunctions() { + functions.add(new CharAt()); + functions.add(new CompareTo()); + functions.add(new CompareToIgnoreCase()); + functions.add(new Concat()); + functions.add(new EndsWith()); + functions.add(new Equals()); + functions.add(new EqualsIgnoreCase()); + functions.add(new Eval()); + functions.add(new IndexOf()); + functions.add(new LastIndexOf()); + functions.add(new Length()); + functions.add(new Replace()); + functions.add(new StartsWith()); + functions.add(new Substring()); + functions.add(new ToLowerCase()); + functions.add(new ToUpperCase()); + functions.add(new Trim()); + functions.add(new Contains()); + functions.add(new NotContains()); + functions.add(new NotEquals()); + } + + /** + * Returns the name of the function group - "stringFunctions". + * + * @return The name of this function group class. + */ + public String getName() { + return "stringFunctions"; + } + + /** + * Returns a list of the functions that are loaded by this class. + * + * @return A list of the functions loaded by this class. + */ + public List getFunctions() { + return functions; + } + + /** + * Loads the functions in this function group into an instance of Evaluator. + * + * @param evaluator + * An instance of Evaluator to load the functions into. + */ + public void load(final Evaluator evaluator) { + Iterator functionIterator = functions.iterator(); + + while (functionIterator.hasNext()) { + evaluator.putFunction((Function) functionIterator.next()); + } + } + + /** + * Unloads the functions in this function group from an instance of + * Evaluator. + * + * @param evaluator + * An instance of Evaluator to unload the functions from. + */ + public void unload(final Evaluator evaluator) { + Iterator functionIterator = functions.iterator(); + + while (functionIterator.hasNext()) { + evaluator.removeFunction(((Function) functionIterator.next()) + .getName()); + } + } +} diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Substring.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Substring.java new file mode 100644 index 0000000..61485c1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Substring.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +import java.util.ArrayList; + +/** + * This class is a function that executes within Evaluator. The function returns + * a string that is a substring of the source string. See the + * String.substring(int, int) method in the JDK for a complete description of + * how this function works. + */ +public class Substring implements Function { + /** + * Returns the name of the function - "substring". + * + * @return The name of this function class. + */ + public String getName() { + return "substring"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into one string + * argument and two integer arguments. The first argument is the + * source string, the second argument is the beginning index and + * the third argument is the ending index. The string argument(s) + * HAS to be enclosed in quotes. White space that is not enclosed + * within quotes will be trimmed. Quote characters in the first + * and last positions of any string argument (after being + * trimmed) will be removed also. The quote characters used must + * be the same as the quote characters used by the current + * instance of Evaluator. If there are multiple arguments, they + * must be separated by a comma (","). + * + * @return Returns the specified substring. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "One string argument and two integer " + + "arguments are required."; + + ArrayList values = FunctionHelper.getOneStringAndTwoIntegers(arguments, + EvaluationConstants.FUNCTION_ARGUMENT_SEPARATOR); + + if (values.size() != 3) { + throw new FunctionException(exceptionMessage); + } + + try { + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + (String) values.get(0), evaluator.getQuoteCharacter()); + int beginningIndex = ((Integer) values.get(1)).intValue(); + int endingIndex = ((Integer) values.get(2)).intValue(); + result = argumentOne.substring(beginningIndex, endingIndex); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_STRING); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToLowerCase.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToLowerCase.java new file mode 100644 index 0000000..12eb7b5 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToLowerCase.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +/** + * This class is a function that executes within Evaluator. The function returns + * the source string in lower case. See the String.toLowerCase() method in the + * JDK for a complete description of how this function works. + */ +public class ToLowerCase implements Function { + /** + * Returns the name of the function - "toLowerCase". + * + * @return The name of this function class. + */ + public String getName() { + return "toLowerCase"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into one string + * argument. The string argument(s) HAS to be enclosed in quotes. + * White space that is not enclosed within quotes will be + * trimmed. Quote characters in the first and last positions of + * any string argument (after being trimmed) will be removed + * also. The quote characters used must be the same as the quote + * characters used by the current instance of Evaluator. If there + * are multiple arguments, they must be separated by a comma + * (","). + * + * @return The source string, converted to lowercase. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "One string argument is required."; + + try { + String stringValue = arguments; + + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + stringValue, evaluator.getQuoteCharacter()); + + result = argumentOne.toLowerCase(); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_STRING); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToUpperCase.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToUpperCase.java new file mode 100644 index 0000000..71f3160 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/ToUpperCase.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +/** + * This class is a function that executes within Evaluator. The function returns + * the source string in upper case. See the String.toUpperCase() method in the + * JDK for a complete description of how this function works. + */ +public class ToUpperCase implements Function { + /** + * Returns the name of the function - "toUpperCase". + * + * @return The name of this function class. + */ + public String getName() { + return "toUpperCase"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into one string + * argument. The string argument(s) HAS to be enclosed in quotes. + * White space that is not enclosed within quotes will be + * trimmed. Quote characters in the first and last positions of + * any string argument (after being trimmed) will be removed + * also. The quote characters used must be the same as the quote + * characters used by the current instance of Evaluator. If there + * are multiple arguments, they must be separated by a comma + * (","). + * + * @return The source string, converted to lowercase. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "One string argument is required."; + + try { + String stringValue = arguments; + + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + stringValue, evaluator.getQuoteCharacter()); + + result = argumentOne.toUpperCase(); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_STRING); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Trim.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Trim.java new file mode 100644 index 0000000..2a4fae7 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/function/string/Trim.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.function.string; + +import com.baoying.enginex.executor.util.jeval.Evaluator; +import com.baoying.enginex.executor.util.jeval.function.*; + +/** + * This class is a function that executes within Evaluator. The function returns + * the source string with white space removed from both ends. See the + * String.trim() method in the JDK for a complete description of how this + * function works. + */ +public class Trim implements Function { + /** + * Returns the name of the function - "trim". + * + * @return The name of this function class. + */ + public String getName() { + return "trim"; + } + + /** + * Executes the function for the specified argument. This method is called + * internally by Evaluator. + * + * @param evaluator + * An instance of Evaluator. + * @param arguments + * A string argument that will be converted into one string + * argument. The string argument(s) HAS to be enclosed in quotes. + * White space that is not enclosed within quotes will be + * trimmed. Quote characters in the first and last positions of + * any string argument (after being trimmed) will be removed + * also. The quote characters used must be the same as the quote + * characters used by the current instance of Evaluator. If there + * are multiple arguments, they must be separated by a comma + * (","). + * + * @return The source string, with white space removed from both ends. + * + * @exception FunctionException + * Thrown if the argument(s) are not valid for this function. + */ + public FunctionResult execute(final Evaluator evaluator, final String arguments) + throws FunctionException { + String result = null; + String exceptionMessage = "One string argument is required."; + + try { + String stringValue = arguments; + + String argumentOne = FunctionHelper.trimAndRemoveQuoteChars( + stringValue, evaluator.getQuoteCharacter()); + + result = argumentOne.trim(); + } catch (FunctionException fe) { + throw new FunctionException(fe.getMessage(), fe); + } catch (Exception e) { + throw new FunctionException(exceptionMessage, e); + } + + return new FunctionResult(result, + FunctionConstants.FUNCTION_RESULT_TYPE_STRING); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AbstractOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AbstractOperator.java new file mode 100644 index 0000000..412a0d6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AbstractOperator.java @@ -0,0 +1,182 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationException; + +/** + * This is the standard operator that is the parent to all operators found in + * expressions. + */ +public abstract class AbstractOperator implements Operator { + + private String symbol = null; + + private int precedence = 0; + + private boolean unary = false; + + /** + * A constructor that takes the operator symbol and precedence as input. + * + * @param symbol + * The character(s) that makes up the operator. + * @param precedence + * The precedence level given to this operator. + */ + public AbstractOperator(final String symbol, final int precedence) { + + this.symbol = symbol; + this.precedence = precedence; + } + + /** + * A constructor that takes the operator symbol, precedence, unary indicator + * and unary precedence as input. + * + * @param symbol + * The character(s) that makes up the operator. + * @param precedence + * The precedence level given to this operator. + * @param unary + * Indicates of the operator is a unary operator or not. + */ + public AbstractOperator( + + String symbol, int precedence, boolean unary) { + + this.symbol = symbol; + this.precedence = precedence; + this.unary = unary; + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + return 0; + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + * + * @return String The value of the evaluated operands. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. + */ + public String evaluate(final String leftOperand, final String rightOperand) + throws EvaluationException { + throw new EvaluationException("Invalid operation for a string."); + } + + /** + * Evaluate one double operand. + * + * @param operand + * The operand being evaluated. + */ + public double evaluate(double operand) { + return 0; + } + + /** + * Returns the character(s) that makes up the operator. + * + * @return The operator symbol. + */ + public String getSymbol() { + return symbol; + } + + /** + * Returns the precedence given to this operator. + * + * @return The precedecne given to this operator. + */ + public int getPrecedence() { + return precedence; + } + + /** + * Returns the length of the operator symbol. + * + * @return The length of the operator symbol. + */ + public int getLength() { + return symbol.length(); + } + + /** + * Returns an indicator of if the operator is unary or not. + * + * @return An indicator of if the operator is unary or not. + */ + public boolean isUnary() { + return unary; + } + + /** + * Determines if this operator is equal to another operator. Equality is + * determined by comparing the operator symbol of both operators. + * + * @param object + * The object to compare with this operator. + * + * @return True if the object is equal and false if not. + * + * @exception IllegalStateException + * Thrown if the input object is not of the Operator type. + */ + public boolean equals(final Object object) { + if (object == null) { + return false; + } + + if (!(object instanceof AbstractOperator)) { + throw new IllegalStateException("Invalid operator object."); + } + + AbstractOperator operator = (AbstractOperator) object; + + if (symbol.equals(operator.getSymbol())) { + return true; + } + + return false; + } + + /** + * Returns the String representation of this operator, which is the symbol. + * + * @return The operator symbol. + */ + public String toString() { + return getSymbol(); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AdditionOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AdditionOperator.java new file mode 100644 index 0000000..7e5bd77 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/AdditionOperator.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The addition operator. + */ +public class AdditionOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public AdditionOperator() { + super("+", 5, true); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + Double rtnValue = new Double(leftOperand + rightOperand); + + return rtnValue.doubleValue(); + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public String evaluate(final String leftOperand, final String rightOperand) { + String rtnValue = new String(leftOperand.substring(0, leftOperand + .length() - 1) + + rightOperand.substring(1, rightOperand.length())); + + return rtnValue; + } + + /** + * Evaluate one double operand. + * + * @param operand + * The operand being evaluated. + */ + public double evaluate(double operand) { + return operand; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanAndOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanAndOperator.java new file mode 100644 index 0000000..2c20c47 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanAndOperator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The boolean and operator. + */ +public class BooleanAndOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public BooleanAndOperator() { + super("&&", 2); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand == 1 && rightOperand == 1) { + return 1; + } + + return 0; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanNotOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanNotOperator.java new file mode 100644 index 0000000..d7e955a --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanNotOperator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The boolean not operator. + */ +public class BooleanNotOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public BooleanNotOperator() { + super("!", 0, true); + } + + /** + * Evaluate one double operand. + * + * @param operand + * The operand being evaluated. + */ + public double evaluate(final double operand) { + if (operand == 1) { + return 0; + } + + return 1; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanOrOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanOrOperator.java new file mode 100644 index 0000000..d7c0aa8 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/BooleanOrOperator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The boolean or operator. + */ +public class BooleanOrOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public BooleanOrOperator() { + super("||", 1); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand == 1 || rightOperand == 1) { + return 1; + } + + return 0; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ClosedParenthesesOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ClosedParenthesesOperator.java new file mode 100644 index 0000000..825faf6 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ClosedParenthesesOperator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The closed parentheses operator. + */ +public class ClosedParenthesesOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public ClosedParenthesesOperator() { + super(")", 0); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/DivisionOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/DivisionOperator.java new file mode 100644 index 0000000..89092be --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/DivisionOperator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The division operator. + */ +public class DivisionOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public DivisionOperator() { + super("/", 6); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + Double rtnValue = new Double(leftOperand / rightOperand); + + return rtnValue.doubleValue(); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/EqualOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/EqualOperator.java new file mode 100644 index 0000000..0fc50f2 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/EqualOperator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; + +/** + * The equal operator. + */ +public class EqualOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public EqualOperator() { + super("==", 3); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand == rightOperand) { + return 1; + } + + return 0; + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public String evaluate(String leftOperand, String rightOperand) { + if (leftOperand.compareTo(rightOperand) == 0) { + return EvaluationConstants.BOOLEAN_STRING_TRUE; + } + + return EvaluationConstants.BOOLEAN_STRING_FALSE; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOperator.java new file mode 100644 index 0000000..7b96ea9 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOperator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; + +/** + * The greater than operator. + */ +public class GreaterThanOperator extends AbstractOperator { + /** + * Default constructor. + */ + public GreaterThanOperator() { + super(">", 4); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand > rightOperand) { + return 1; + } + + return 0; + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public String evaluate(final String leftOperand, final String rightOperand) { + if (leftOperand.compareTo(rightOperand) > 0) { + return EvaluationConstants.BOOLEAN_STRING_TRUE; + } + + return EvaluationConstants.BOOLEAN_STRING_FALSE; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOrEqualOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOrEqualOperator.java new file mode 100644 index 0000000..c1a0360 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/GreaterThanOrEqualOperator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; + +/** + * The greater than or equal operator. + */ +public class GreaterThanOrEqualOperator extends AbstractOperator { + /** + * Default constructor. + */ + public GreaterThanOrEqualOperator() { + super(">=", 4); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand >= rightOperand) { + return 1; + } + + return 0; + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public String evaluate(final String leftOperand, final String rightOperand) { + if (leftOperand.compareTo(rightOperand) >= 0) { + return EvaluationConstants.BOOLEAN_STRING_TRUE; + } + + return EvaluationConstants.BOOLEAN_STRING_FALSE; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOperator.java new file mode 100644 index 0000000..304bd5c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOperator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; + +/** + * The less than operator. + */ +public class LessThanOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public LessThanOperator() { + super("<", 4); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand < rightOperand) { + return 1; + } + + return 0; + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public String evaluate(final String leftOperand, final String rightOperand) { + if (leftOperand.compareTo(rightOperand) < 0) { + return EvaluationConstants.BOOLEAN_STRING_TRUE; + } + + return EvaluationConstants.BOOLEAN_STRING_FALSE; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOrEqualOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOrEqualOperator.java new file mode 100644 index 0000000..44b287c --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/LessThanOrEqualOperator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; + +/** + * The less than or equal operator. + */ +public class LessThanOrEqualOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public LessThanOrEqualOperator() { + super("<=", 4); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand <= rightOperand) { + return 1; + } + + return 0; + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public String evaluate(String leftOperand, String rightOperand) { + if (leftOperand.compareTo(rightOperand) <= 0) { + return EvaluationConstants.BOOLEAN_STRING_TRUE; + } + + return EvaluationConstants.BOOLEAN_STRING_FALSE; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ModulusOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ModulusOperator.java new file mode 100644 index 0000000..4e8d5c1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/ModulusOperator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The modulus operator. + */ +public class ModulusOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public ModulusOperator() { + super("%", 6); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + Double rtnValue = new Double(leftOperand % rightOperand); + + return rtnValue.doubleValue(); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/MultiplicationOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/MultiplicationOperator.java new file mode 100644 index 0000000..71026a4 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/MultiplicationOperator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The multiplication operator. + */ +public class MultiplicationOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public MultiplicationOperator() { + super("*", 6); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + Double rtnValue = new Double(leftOperand * rightOperand); + + return rtnValue.doubleValue(); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/NotEqualOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/NotEqualOperator.java new file mode 100644 index 0000000..60af1db --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/NotEqualOperator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationConstants; + +/** + * The not equal operator. + */ +public class NotEqualOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public NotEqualOperator() { + super("!=", 3); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + if (leftOperand != rightOperand) { + return 1; + } + + return 0; + } + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public String evaluate(String leftOperand, String rightOperand) { + if (leftOperand.compareTo(rightOperand) != 0) { + return EvaluationConstants.BOOLEAN_STRING_TRUE; + } + + return EvaluationConstants.BOOLEAN_STRING_FALSE; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/OpenParenthesesOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/OpenParenthesesOperator.java new file mode 100644 index 0000000..b5eb5e1 --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/OpenParenthesesOperator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The open parentheses operator. + */ +public class OpenParenthesesOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public OpenParenthesesOperator() { + super("(", 0); + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/Operator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/Operator.java new file mode 100644 index 0000000..758091e --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/Operator.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +import com.baoying.enginex.executor.util.jeval.EvaluationException; + +/** + * An oerator than can specified in an expression. + */ +public interface Operator { + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public abstract double evaluate(double leftOperand, double rightOperand); + + /** + * Evaluates two string operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + * + * @return String The value of the evaluated operands. + * + * @exception EvaluateException + * Thrown when an error is found while evaluating the + * expression. + */ + public abstract String evaluate(final String leftOperand, + final String rightOperand) throws EvaluationException; + + /** + * Evaluate one double operand. + * + * @param operand + * The operand being evaluated. + */ + public abstract double evaluate(final double operand); + + /** + * Returns the character(s) that makes up the operator. + * + * @return The operator symbol. + */ + public abstract String getSymbol(); + + /** + * Returns the precedence given to this operator. + * + * @return The precedecne given to this operator. + */ + public abstract int getPrecedence(); + + /** + * Returns the length of the operator symbol. + * + * @return The length of the operator symbol. + */ + public abstract int getLength(); + + /** + * Returns an indicator of if the operator is unary or not. + * + * @return An indicator of if the operator is unary or not. + */ + public abstract boolean isUnary(); +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/SubtractionOperator.java b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/SubtractionOperator.java new file mode 100644 index 0000000..9dbca9b --- /dev/null +++ b/jar-enginex-runner/src/main/java/com/baoying/enginex/executor/util/jeval/operator/SubtractionOperator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2007 Robert Breidecker. + * + * 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. + */ + +package com.baoying.enginex.executor.util.jeval.operator; + +/** + * The subtraction operator. + */ +public class SubtractionOperator extends AbstractOperator { + + /** + * Default constructor. + */ + public SubtractionOperator() { + super("-", 5, true); + } + + /** + * Evaluates two double operands. + * + * @param leftOperand + * The left operand being evaluated. + * @param rightOperand + * The right operand being evaluated. + */ + public double evaluate(final double leftOperand, final double rightOperand) { + Double rtnValue = new Double(leftOperand - rightOperand); + + return rtnValue.doubleValue(); + } + + /** + * Evaluate one double operand. + * + * @param operand + * The operand being evaluated. + */ + public double evaluate(final double operand) { + return -operand; + } +} \ No newline at end of file diff --git a/jar-enginex-runner/src/main/resources/application-dev.properties b/jar-enginex-runner/src/main/resources/application-dev.properties new file mode 100644 index 0000000..6181b46 --- /dev/null +++ b/jar-enginex-runner/src/main/resources/application-dev.properties @@ -0,0 +1,38 @@ +server.port=8081 + +logging.config=classpath:logging-config.xml + +# mysql +spring.datasource.default.url = jdbc:mysql://localhost:3306/riskmanage?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true +spring.datasource.default.username = root +spring.datasource.default.password = enginex513 +spring.datasource.default.driver-class-name = com.mysql.cj.jdbc.Driver + +# redis +redis.host=localhost +redis.port=6379 +redis.db=1 +redis.password=localhost +redis.pool.maxTotal=3000 +redis.pool.maxIdle=100 +redis.pool.maxWait=1000 +redis.pool.timeout=100000 + +# mail +spring.mail.host=smtp.exmail.qq.com +spring.mail.username=xxx +spring.mail.password=xxx +spring.mail.port=465 +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.timeout=50000 +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.socketFactory.port=465 +spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory +spring.mail.properties.mail.smtp.socketFactory.fallback=false + +# canal +switch.use.cache=off +switch.canal.cache=off +canal.hostname=localhost +canal.port=11111 + diff --git a/jar-enginex-runner/src/main/resources/application-prod.properties b/jar-enginex-runner/src/main/resources/application-prod.properties new file mode 100644 index 0000000..2dbd1b7 --- /dev/null +++ b/jar-enginex-runner/src/main/resources/application-prod.properties @@ -0,0 +1,45 @@ +server.port=8081 + +logging.config=classpath:logging-config.xml + +# mysql +spring.datasource.default.url = jdbc:mysql://localhost:3306/riskmanage?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true +spring.datasource.default.username = root +spring.datasource.default.password = enginex513! +spring.datasource.default.driver-class-name = com.mysql.cj.jdbc.Driver + +# redis +redis.host=localhost +redis.port=6379 +redis.db=1 +redis.password=localhost +redis.pool.maxTotal=3000 +redis.pool.maxIdle=100 +redis.pool.maxWait=1000 +redis.pool.timeout=100000 + +# mail +spring.mail.host=smtp.exmail.qq.com +spring.mail.username=xxx +spring.mail.password=xxx +spring.mail.port=465 +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.timeout=50000 +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.socketFactory.port=465 +spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory +spring.mail.properties.mail.smtp.socketFactory.fallback=false + +# hbase +spring.data.hbase.quorum: localhost:2181 +spring.data.hbase.rootDir: /usr/local/hbase/datatest +spring.data.hbase.nodeParent: /hbase + +# canal +switch.use.cache=off +switch.canal.cache=off +canal.hostname=localhost +canal.port=11111 + +# \u76D1\u63A7\u4E2D\u5FC3 \u6570\u636E\u5B58\u50A8\u65B9\u5F0F mysql \u6216\u8005 hbase +monitor.data.storage.type=mysql \ No newline at end of file diff --git a/jar-enginex-runner/src/main/resources/application-test.properties b/jar-enginex-runner/src/main/resources/application-test.properties new file mode 100644 index 0000000..1cae7c9 --- /dev/null +++ b/jar-enginex-runner/src/main/resources/application-test.properties @@ -0,0 +1,45 @@ +server.port=8081 + +logging.config=classpath:logging-config.xml + +# mysql +spring.datasource.default.url = jdbc:mysql://localhost:3306/riskmanage?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true +spring.datasource.default.username = root +spring.datasource.default.password = enginex513 +spring.datasource.default.driver-class-name = com.mysql.cj.jdbc.Driver + +# redis +redis.host=localhost +redis.port=6379 +redis.db=1 +redis.password=enginex123 +redis.pool.maxTotal=3000 +redis.pool.maxIdle=100 +redis.pool.maxWait=1000 +redis.pool.timeout=100000 + +# mail +spring.mail.host=smtp.exmail.qq.com +spring.mail.username=xxx +spring.mail.password=xxx +spring.mail.port=465 +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.timeout=50000 +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.socketFactory.port=465 +spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory +spring.mail.properties.mail.smtp.socketFactory.fallback=false + +# hbase +spring.data.hbase.quorum: localhost:2181 +spring.data.hbase.rootDir: /usr/local/hbase/datatest +spring.data.hbase.nodeParent: /hbase + +# canal +switch.use.cache=off +switch.canal.cache=on +canal.hostname=localhost +canal.port=11111 + +# \u76D1\u63A7\u4E2D\u5FC3 \u6570\u636E\u5B58\u50A8\u65B9\u5F0F mysql \u6216\u8005 hbase +monitor.data.storage.type=mysql \ No newline at end of file diff --git a/jar-enginex-runner/src/main/resources/application.properties b/jar-enginex-runner/src/main/resources/application.properties new file mode 100644 index 0000000..257b306 --- /dev/null +++ b/jar-enginex-runner/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.profiles.active=dev \ No newline at end of file diff --git a/jar-enginex-runner/src/main/resources/logging-config.xml b/jar-enginex-runner/src/main/resources/logging-config.xml new file mode 100644 index 0000000..fc9a3ec --- /dev/null +++ b/jar-enginex-runner/src/main/resources/logging-config.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JarEnginexExecutorApplicationTests.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JarEnginexExecutorApplicationTests.java new file mode 100644 index 0000000..64d7062 --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JarEnginexExecutorApplicationTests.java @@ -0,0 +1,13 @@ +package com.baoying.enginex.executor; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class JarEnginexExecutorApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JevalTest.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JevalTest.java new file mode 100644 index 0000000..4b25ac9 --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/JevalTest.java @@ -0,0 +1,16 @@ +package com.baoying.enginex.executor; + +import com.baoying.enginex.executor.util.JevalUtil; +import com.baoying.enginex.executor.util.jeval.EvaluationException; +import com.baoying.enginex.executor.util.jeval.Evaluator; + +import java.util.HashMap; + +public class JevalTest { + public static void main(String[] args) throws EvaluationException { + HashMap map = new HashMap<>(); + map.put("ceshiyong","'a'"); + String expression = "(contains(#{ceshiyong},'a'))"; + System.out.println(JevalUtil.evaluateBoolean(expression.toString(), map)); + } +} diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/RegexTest.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/RegexTest.java new file mode 100644 index 0000000..629cac1 --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/RegexTest.java @@ -0,0 +1,36 @@ +package com.baoying.enginex.executor; + +import com.alibaba.fastjson.JSON; +import com.baoying.enginex.executor.util.ExecuteUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexTest { + public static void main(String[] args) { + Pattern pattern$ = Pattern.compile("\\$\\{[a-zA-Z0-9_\u4e00-\u9fa5()()-]+\\}"); + Matcher matcher$ = pattern$.matcher("${aa}"); + + while (matcher$.find()){ + System.out.println(matcher$.group()); + } +// System.out.println("".matches("大abcd")); +// Pattern pattern = Pattern.compile("(abcd)+"); +// Pattern pattern = Pattern.compile("(\\[(0|([1-9]([0-9])*))\\])+$"); +// Matcher matcher = pattern.matcher("addd[0][1][2][3][4][5]"); +// +// System.out.println(matcher.find()); +// System.out.println(matcher.group(0)); +// String fieldEnStr = "a.s.d.1.2.3."; +// System.out.println(JSON.toJSONString(fieldEnStr.split("\\."))); + } + private static void testRegex(){ + String formula = "@1@2@3@4@5@6@7@"; + Pattern pattern = Pattern.compile("@[a-zA-Z0-9_\u4e00-\u9fa5()()-]+@"); + Matcher matcher = pattern.matcher(formula); + while (matcher.find()) { + String fieldEn = matcher.group().replace("@", ""); + System.out.println(matcher.group()); + } + } +} diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/Auth.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/Auth.java new file mode 100644 index 0000000..da71fa2 --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/Auth.java @@ -0,0 +1,11 @@ +package com.baoying.enginex.executor.grouping; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class Auth { + private Integer id; + private String name; +} diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/GroupTest.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/GroupTest.java new file mode 100644 index 0000000..afb66ca --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/GroupTest.java @@ -0,0 +1,250 @@ +package com.baoying.enginex.executor.grouping; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.junit.platform.commons.util.StringUtils; + +import java.awt.*; +import java.util.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class GroupTest { + public static void main(String[] args) { +// test2(); +// test3(); +// test1(); + test4(); + + } + public static void test1(){ + List list = new ArrayList(); + for (int i = 0; i < 100; i++) { + list.add(new User(i,"用户 : "+i,i%10+18,new Role((int)(Math.random()*10+1),"角色 : "+i%10,new Auth((int)(Math.random()*10+1),"权限 : "+i%20)))); + } + Map> result = new HashMap<>(); + + Map> collect = list.stream().collect(Collectors.groupingBy(user -> user.getAge())); + for (Map.Entry> entry : collect.entrySet()) { + Integer key = entry.getKey(); + Map> collect1 = entry.getValue().stream().collect(Collectors.groupingByConcurrent(user -> user.getRole().getId())); + for (Map.Entry> childEntry : collect1.entrySet()) { + result.put(""+key+":"+childEntry.getKey(),childEntry.getValue()); + } + } +// System.out.println(collect); + } +// public static void handlerGroup(Map> param,List keys){ +// Map> result = new HashMap<>(); +// for (Map.Entry> listEntry : param.entrySet()) { +// //前一次分组时的key +// String parentKey = listEntry.getKey(); +// //第一次分组完成的一个数组 +// List list = listEntry.getValue(); +// //默认key为父级key +// String resultKey = parentKey; +// +// for (String key : keys) { +// Map> collect = list.stream().collect(Collectors.groupingBy(json -> json.get(key).toString())); +// for (Map.Entry> childEntry : collect.entrySet()) { +// resultKey += (":"+ childEntry.getKey()); +// result.put(resultKey,childEntry.getValue()); +// } +// +// } +// } +// } + + + public static void test2(){ + List> list = new ArrayList(); + Map map = null; + for (int i = 0; i < 100; i++) { + map = new HashMap(); + map.put("id",i); + map.put("name","用户 : "+i); + map.put("age",i%10); + list.add(map); + } + Map>> collect = list.stream().collect(Collectors.groupingBy(item -> item.get("age"))); + + for (Map.Entry>> entry : collect.entrySet()) { + System.out.println(entry.getKey()); + System.out.println(entry.getValue()); + System.out.println(entry.getValue().size()); + } + } + + public static void test3(){ + List> list = new ArrayList(); + JSONObject map = null; + for (int i = 0; i < 100; i++) { + map = new JSONObject(); + map.put("id",i); + map.put("name","用户 : "+i); + map.put("age",i%10); + list.add(map); + } + Map>> collect = list.stream().collect(Collectors.groupingBy(item -> item.get("age"))); + + for (Map.Entry>> entry : collect.entrySet()) { + System.out.println(entry.getKey()); + System.out.println(entry.getValue()); + System.out.println(entry.getValue().size()); + } + + } + + public static void test4(){ + List list = new ArrayList(); + JSONObject map = null; + List authList; + for (int i = 0; i < 10000; i++) { + map = new JSONObject(); + map.put("id",i%5); + map.put("name","用户"+i%3); + map.put("age",i%13); + authList = new ArrayList<>(); + authList.add(new Auth(i%7,"权限"+i%7)); + map.put("role",new Role(i%11,"角色"+i%7,null,authList)); + + list.add(map); + } + +// Map> stringListMap = handlerGroup(list, Arrays.asList("age", "name", "id","role.id")); + Map> stringListMap = recursionGroup(list, Arrays.asList("age", "name", "role.name","role.authList[0].name")); + System.out.println(stringListMap); + } + +// public static Map> handlerGroup(List param, List keys ){ +// //没有给分组的key则直接返回 +// if (keys==null||keys.isEmpty()){ +// return null; +// } +// Map> result = recursionGroup(param, keys,0,""); +// return result; +// } +// +// public static Map> recursionGroup(List param,List keys,int index,String parentKey){ +// +// if (StringUtils.isNotBlank(parentKey)){ +// parentKey+=":"; +// } +// final String preKey = parentKey; +// Map> result = new HashMap<>(); +// +// Map> temp = new HashMap<>(); +// if (keys.get(index)!=null){ +// //做本次分组 +// Map> collect = param.stream().collect(Collectors.groupingBy(json -> { +// String[] split = keys.get(index).split("\\."); +// if (split.length>1){ +// JSONObject jsonObject = json; +// for (int i = 0; i < split.length; i++) { +// if (iindex+1){ +// //执行分组 +// for (Map.Entry> entry : collect.entrySet()) { +// temp.putAll(recursionGroup(entry.getValue(),keys,index+1,entry.getKey())); +// } +// }else { +// temp = collect; +// } +// }else { +// //没有分组的key无法执行 +// } +// result = temp.entrySet().stream().collect(Collectors.toMap(item->preKey+item.getKey(),item->item.getValue())); +// return result; +// } + + public static Map> recursionGroup(List param,List keys){ + return param.stream().collect(Collectors.groupingBy(item->{ + String cond = ""; + for (String key : keys) { + if (StringUtils.isNotBlank(cond)){ + cond+=":"; + } + String[] split = key.split("\\."); + if (split.length>1){ + JSONObject jsonObject = item; + for (int i = 0; i < split.length; i++) { + if (i authList; + + public Role(Integer id, String name, Auth auth, List authList) { + this.id = id; + this.name = name; + this.auth = auth; + this.authList = authList; + } + + public Role(Integer id, String name, Auth auth) { + this.id = id; + this.name = name; + this.auth = auth; + } +} diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/User.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/User.java new file mode 100644 index 0000000..b7f2792 --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/grouping/User.java @@ -0,0 +1,13 @@ +package com.baoying.enginex.executor.grouping; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class User { + private Integer id; + private String name; + private Integer age; + private Role role; +} diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/RedisTest.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/RedisTest.java new file mode 100644 index 0000000..c2b6194 --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/RedisTest.java @@ -0,0 +1,75 @@ +package com.baoying.enginex.executor.sql; + +import redis.clients.jedis.*; + +import java.util.List; + +public class RedisTest { + public static void main(String[] args) { +// JedisPoolConfig config = new JedisPoolConfig(); +// config.setMaxTotal(1); +// config.setMaxIdle(1); +// config.setMaxWaitMillis(3000); +// config.setTestOnBorrow(true); +// config.setTestOnReturn(true); +// +// JedisPool pool = new JedisPool(config, +// "127.0.0.1", +// 6379, +// 3000, +// "root", +// 1); +// Jedis jedis = pool.getResource(); + JedisShardInfo jedisShardInfo = new JedisShardInfo("47.102.125.25",6379,3000,"root"); + jedisShardInfo.setPassword("enginex123"); + Jedis jedis = new Jedis(jedisShardInfo); + jedis.select(1); + if (jedis.isConnected()){ + + Object eval = jedis.eval("local result = 0;\n" + + "local flag = true;\n" + + "local cursor = '0';\n" + + "while(flag)\n" + + "do\n" + + "local scanResult = redis.call('hscan','THRESHOLD_MOBILE_DAY:20211116:手机号1',cursor ,'match','12*');\n" + + "cursor = scanResult[1];\n" + + " for k, v in pairs(scanResult[2]) do\n" + + " if k%2 == 0 then\n" + + " result = result+v;\n" + + " end\n" + + " end\n" + + " if scanResult == nil or cursor == '0'then\n" + + " flag = false;\n" + + " end\n" + + "end\n" + + "return result;"); + +// System.out.println(jedis.eval("return redis.call('get', 'test1')")); +// Object eval = jedis.eval("return redis.call('LRANGE', 'list1' , 0 , -1)"); + long value = 0; + if (eval instanceof List){ + System.out.println(eval); + List result = (List) eval; + + if (result.get(1) instanceof List){ + System.out.println(result.get(1)); + List kv = (List)result.get(1); + for (int i = 0; i < kv.size(); i++) { + if (i%2 == 0){ + continue; + } + value += Long.valueOf(kv.get(i)); + } + + } + + } + + System.out.println(value); +// String aaa = jedis.get("test1"); +// System.out.println(aaa); + } + + + } +} diff --git a/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/SqlForTest.java b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/SqlForTest.java new file mode 100644 index 0000000..ea73f20 --- /dev/null +++ b/jar-enginex-runner/src/test/java/com/baoying/enginex/executor/sql/SqlForTest.java @@ -0,0 +1,51 @@ +package com.baoying.enginex.executor.sql; + +import com.alibaba.fastjson.JSON; +import com.baoying.enginex.executor.datamanage.mapper.SimpleMapper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +//@SpringBootTest +//@RunWith(SpringRunner.class) +public class SqlForTest { + + @Autowired + private SimpleMapper simpleMapper; + @Test + public void testForSql(){ + + String sqlStr = " select * from t_list_db " + + " where id in " + + " \n" + + " #{item}\n" + + " "; + Map param = new HashMap<>(); + param.put("sqlStr",sqlStr); + List ids = new ArrayList<>(); + ids.add(112L); + ids.add(114L); + ids.add(115L); + param.put("list",ids); + List> test = simpleMapper.test(param); + + System.out.println(JSON.toJSONString(test)); + + } + + public static void main(String[] args) { + String sqlStr = " select * from t_list_db " + + " where id in ( #{aaaa} )"; + Pattern sqlnPattern = Pattern.compile("\\s*in\\s*\\(\\s*#\\{([a-zA-Z0-9_\u4e00-\u9fa5()()-]+)\\}\\s*\\)"); + Matcher matcher = sqlnPattern.matcher(sqlStr); + if ( matcher.find()){ + System.out.println(matcher.group(0)); + } + } +}