├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── dmnmgr-test-driver ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── lv1871 │ │ └── oss │ │ └── dmnmgr │ │ └── test │ │ └── driver │ │ ├── DecisionRunner.java │ │ ├── DmnTestCreator.java │ │ ├── DmnTestExecutor.java │ │ ├── DmnTester.java │ │ ├── VariableMapperService.java │ │ └── model │ │ ├── DecisionSimulationResponse.java │ │ ├── DmnProject.java │ │ ├── DmnProjectTestContainer.java │ │ ├── DmnProjectTestDefinition.java │ │ ├── DmnTest.java │ │ ├── DmnTestSuite.java │ │ └── TestSuiteTestResult.java │ └── test │ ├── java │ └── de │ │ └── lv1871 │ │ └── oss │ │ └── dmnmgr │ │ └── test │ │ └── driver │ │ ├── IntegrationTest.java │ │ └── VariableMapperServiceTest.java │ └── resources │ ├── a_test.dmn │ └── a_test.dmnapp.json ├── dmnmgr-tester ├── pom.xml └── src │ ├── main │ └── java │ │ └── de │ │ └── lv1871 │ │ └── oss │ │ └── tester │ │ └── test │ │ ├── dmnassert │ │ ├── DmnAssert.java │ │ ├── DmnTest.java │ │ ├── DmnVariableAssert.java │ │ └── model │ │ │ └── DecisionSimulationResponse.java │ │ ├── domain │ │ └── DecisionEngine.java │ │ └── function │ │ ├── ExtendedBiFunction.java │ │ ├── ExtendedBiPredicate.java │ │ └── LambdaExtension.java │ └── test │ ├── java │ └── de │ │ └── lv1871 │ │ └── oss │ │ └── tester │ │ └── test │ │ ├── dmnassert │ │ ├── DmnAssertTest.java │ │ ├── DmnTestTest.java │ │ └── DmnVariableAssertTest.java │ │ ├── domain │ │ └── DecisionEngineTest.java │ │ └── function │ │ └── LambdaExtensionTest.java │ └── resources │ ├── testDrgTwoDecisions.dmn │ └── testDrgTwoDecisions.dmnapp.json ├── dmnmgr ├── pom.xml └── src │ ├── main │ ├── java │ │ └── de │ │ │ └── lv1871 │ │ │ └── oss │ │ │ └── dmnmgr │ │ │ ├── App.java │ │ │ ├── api │ │ │ ├── DecisionResource.java │ │ │ ├── UtilityResource.java │ │ │ ├── documentation │ │ │ │ └── DecisionApi.java │ │ │ └── model │ │ │ │ ├── DecisionRequest.java │ │ │ │ ├── DecisionSimulationRequest.java │ │ │ │ ├── DecisionTestRequest.java │ │ │ │ ├── DecisionTestResponse.java │ │ │ │ ├── DmnValidationResponse.java │ │ │ │ ├── DmnValidationResult.java │ │ │ │ ├── ErrorSeverity.java │ │ │ │ ├── OpenApiDefinitionResponse.java │ │ │ │ └── ServiceResult.java │ │ │ ├── controller │ │ │ └── HomeController.java │ │ │ ├── domain │ │ │ ├── CheckerFunctions.java │ │ │ ├── InputEntryValidator.java │ │ │ ├── InputExpressionRequiredValidator.java │ │ │ ├── OutputEntryValidator.java │ │ │ └── OutputNameRequiredValidator.java │ │ │ └── service │ │ │ ├── AdvancedDmnCheckService.java │ │ │ ├── DecisionTestService.java │ │ │ ├── DownloadService.java │ │ │ ├── GenericDecisionService.java │ │ │ └── VariableMapperService.java │ └── resources │ │ ├── META-INF │ │ ├── processes.xml │ │ └── services │ │ │ └── javax.ws.rs.ext.RuntimeDelegate │ │ └── application.yml │ └── test │ ├── java │ └── de │ │ └── lv1871 │ │ └── oss │ │ └── dmnmgr │ │ ├── domain │ │ └── CheckerFunctionsTest.java │ │ └── service │ │ └── AdvancedDmnCheckServiceTest.java │ └── resources │ ├── juelfeel.dmn │ ├── juelfeel.dmnapp.json │ ├── meineTestDecision.dmn │ ├── meineTestDecision.dmnapp.json │ ├── requiredInputExpressionTest.dmn │ ├── requiredInputExpressionTest.dmnapp.json │ ├── requiredOutputNameTest.dmn │ ├── requiredOutputNameTest.dmnapp.json │ ├── testTokenizedExpression.dmn │ ├── testTokenizedExpression.dmnapp.json │ ├── testTokenizedWithErrorExpression.dmn │ └── testTokenizedWithErrorExpression.dmnapp.json ├── pom.xml └── readme_assets └── general_architecture.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/maven,eclipse 3 | # Edit at https://www.gitignore.io/?templates=maven,eclipse 4 | 5 | ### Eclipse ### 6 | 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .settings/ 16 | .loadpath 17 | .recommenders 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # PyDev specific (Python IDE for Eclipse) 26 | *.pydevproject 27 | 28 | # CDT-specific (C/C++ Development Tooling) 29 | .cproject 30 | 31 | # CDT- autotools 32 | .autotools 33 | 34 | # Java annotation processor (APT) 35 | .factorypath 36 | 37 | # PDT-specific (PHP Development Tools) 38 | .buildpath 39 | 40 | # sbteclipse plugin 41 | .target 42 | 43 | # Tern plugin 44 | .tern-project 45 | 46 | # TeXlipse plugin 47 | .texlipse 48 | 49 | # STS (Spring Tool Suite) 50 | .springBeans 51 | 52 | # Code Recommenders 53 | .recommenders/ 54 | 55 | # Annotation Processing 56 | .apt_generated/ 57 | 58 | # Scala IDE specific (Scala & Java development for Eclipse) 59 | .cache-main 60 | .scala_dependencies 61 | .worksheet 62 | 63 | ### Eclipse Patch ### 64 | # Eclipse Core 65 | .project 66 | 67 | # JDT-specific (Eclipse Java Development Tools) 68 | .classpath 69 | 70 | # Annotation Processing 71 | .apt_generated 72 | 73 | .sts4-cache/ 74 | 75 | ### Maven ### 76 | target/ 77 | pom.xml.tag 78 | pom.xml.releaseBackup 79 | pom.xml.versionsBackup 80 | pom.xml.next 81 | release.properties 82 | dependency-reduced-pom.xml 83 | buildNumber.properties 84 | .mvn/timing.properties 85 | .mvn/wrapper/maven-wrapper.jar 86 | 87 | *.log 88 | 89 | db 90 | 91 | settings.json 92 | .vscode/ 93 | 94 | log* 95 | 96 | # End of https://www.gitignore.io/api/maven,eclipse -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2020 David Ibl 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2020 David Ibl 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dmnmgr-server 2 | 3 | The DMN Manager is a toolkit incoperating the Camunda DMN implementation and providing 4 | tools to develop DMN based applications in cross functional teams. 5 | 6 | ## General architecture 7 | 8 | ![alt text](readme_assets/general_architecture.png "general architecture") 9 | 10 | - The client application is used to visually define business rules, simulate decision models and write tests. (https://github.com/davidibl/dmnmgr-client) 11 | - The server-side application provides web services to simulate decisions, run tests and validate DMN definitions. (This repository) 12 | - The test driver is a java library to run tests defined in the DMN Manager client during CI/CD processes automatically. (This repository) 13 | 14 | ## Use 15 | 16 | The DMN Manager server-side component is a java spring boot based webservice. To run this service, just build the project and run the jar file inside the target folder of the child project 'dmnmgr'. 17 | 18 | To use the test driver, provision any artifactory or any java project with the jar library from the target folder of the 'dmnmgr-test-driver' child project. 19 | 20 | ## Automatic dmn test execution 21 | 22 | To enable automatic test execution during build process of any java project, import a dmn file in the DMN Manager client tool, define tests and save the project into the resources folder of the java project. 23 | 24 | After saving a DMN Manager project a new file with the extension *.dmnapp.json will appear next to the original dmn file. This file contains all the testcases and test expectations. 25 | 26 | Now, just create a Java class with the following annotation: 27 | 28 | ` 29 | @RunWith(DmnTester.class) 30 | ` 31 | 32 | All testcases defined in the dmnapp.json file will now be executed during normal test phase of the build process. 33 | 34 | ## Customization 35 | 36 | The web service to validate, simulate and test decisions uses a specific version of the Camunda feel-scala engine to fulfill all requests. To ensure production near behaviour during development time it is recommended to build the webservice with a decision engine as near as possible to the production scenario. 37 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | de.lv1871.oss 6 | dmnmgr-project 7 | 1.3.0-SNAPSHOT 8 | 9 | 10 | dmnmgr-test-driver 11 | jar 12 | 13 | dmnmgr-test-driver 14 | 15 | 16 | 17 | de.lv1871.oss 18 | dmnmgr-tester 19 | ${project.version} 20 | compile 21 | 22 | 23 | org.camunda.bpm.extension.feel.scala 24 | feel-engine-plugin 25 | provided 26 | 27 | 28 | org.camunda.bpm.extension.feel.scala 29 | feel-engine-factory 30 | ${feel.scala.version} 31 | 32 | 33 | org.camunda.bpm.extension.feel.scala 34 | feel-engine 35 | ${feel.scala.version} 36 | 37 | 38 | junit 39 | junit 40 | 41 | 42 | com.fasterxml.jackson.core 43 | jackson-annotations 44 | provided 45 | 46 | 47 | com.fasterxml.jackson.core 48 | jackson-core 49 | provided 50 | 51 | 52 | com.fasterxml.jackson.core 53 | jackson-databind 54 | provided 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-assembly-plugin 63 | 3.1.1 64 | 65 | 66 | 67 | jar-with-dependencies 68 | 69 | 70 | 71 | 72 | 73 | make-assembly 74 | package 75 | 76 | single 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/DecisionRunner.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | 5 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse.DecisionTestCaseResponseBuilder; 6 | import de.lv1871.oss.tester.test.domain.DecisionEngine; 7 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 8 | 9 | public class DecisionRunner { 10 | 11 | private static VariableMapperService MAPPER = new VariableMapperService(); 12 | 13 | public static DecisionSimulationResponse decide(DecisionEngine engine, String decisionKey, 14 | ObjectNode variablesNode) { 15 | 16 | try { 17 | var variables = MAPPER.getVariablesFromJsonAsMap(variablesNode); 18 | 19 | var decisionResult = engine.evaluateDecisionByKey(decisionKey, variables); 20 | 21 | if (decisionResult 22 | .getResultList() 23 | .stream() 24 | .filter(result -> result.get(null) != null) 25 | .count() > 0) { 26 | return DecisionTestCaseResponseBuilder.create() 27 | .withMessage("Ein oder meherere Output-Felder haben keinen Namen.").build(); 28 | } 29 | 30 | return DecisionTestCaseResponseBuilder 31 | .create() 32 | .withResult(decisionResult.getResultList()) 33 | .build(); 34 | 35 | } catch (Exception exception) { 36 | if (exception.getCause() != null) { 37 | return DecisionTestCaseResponseBuilder 38 | .create() 39 | .withMessage(exception.getCause().getMessage()) 40 | .build(); 41 | } 42 | return DecisionTestCaseResponseBuilder 43 | .create() 44 | .withMessage(exception.getMessage()) 45 | .build(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/DmnTestCreator.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Map.Entry; 13 | import java.util.UUID; 14 | import java.util.stream.Collectors; 15 | 16 | import com.fasterxml.jackson.databind.ObjectMapper; 17 | 18 | import de.lv1871.oss.dmnmgr.test.driver.model.DmnProject; 19 | import de.lv1871.oss.dmnmgr.test.driver.model.DmnProjectTestContainer; 20 | import de.lv1871.oss.dmnmgr.test.driver.model.DmnProjectTestDefinition; 21 | import de.lv1871.oss.dmnmgr.test.driver.model.DmnTest; 22 | import de.lv1871.oss.dmnmgr.test.driver.model.DmnTestSuite; 23 | import de.lv1871.oss.tester.test.domain.DecisionEngine; 24 | import junit.framework.Test; 25 | import junit.framework.TestSuite; 26 | 27 | public class DmnTestCreator { 28 | 29 | private static String PROJECT_FILE_EXTENSION = "dmnapp.json"; 30 | private static ObjectMapper MAPPER = new ObjectMapper(); 31 | 32 | public static Test suite() { 33 | var suite = new TestSuite(); 34 | new DmnTestCreator().createDmnTests(null).forEach(suite::addTest); 35 | return suite; 36 | } 37 | 38 | public static Test getSuiteParameterized(Path basePath) { 39 | var suite = new TestSuite(); 40 | new DmnTestCreator().createDmnTests(basePath).forEach(suite::addTest); 41 | return suite; 42 | } 43 | 44 | public List createDmnTests(Path basePath) { 45 | return findTests(basePath) 46 | .stream() 47 | .map(this::toJunitTestSuite) 48 | .collect(Collectors.toList()); 49 | } 50 | 51 | public List findTests(Path basePath) { 52 | return getResourceFiles(basePath) 53 | .stream() 54 | .filter(file -> file.endsWith(PROJECT_FILE_EXTENSION)) 55 | .map(file -> Paths.get(file).toString()) 56 | .map(this::loadTests) 57 | .collect(Collectors.toList()); 58 | } 59 | 60 | private DmnTestSuite loadTests(String projectFilePath) { 61 | try { 62 | var projectFile = new File(projectFilePath); 63 | 64 | var dmnProject = MAPPER.readValue(new File(projectFilePath), DmnProject.class); 65 | var suite = new DmnTestSuite(); 66 | 67 | suite.setTest(dmnProject.getTestsuite() 68 | .entrySet() 69 | .stream() 70 | .map(this::toDmnTest) 71 | .flatMap(List::stream) 72 | .collect(Collectors.toList())); 73 | 74 | suite.setXml(readXmlFile(projectFile, dmnProject.getDmnPath())); 75 | suite.setName(dmnProject.getDmnPath()); 76 | return suite; 77 | } catch (Exception e) { 78 | throw new RuntimeException(e); 79 | } 80 | } 81 | 82 | private String readXmlFile(File projectFile, String dmnPath) throws IOException { 83 | 84 | var dmnFilePath = getResourceRelativToBasePath(projectFile.getParentFile(), dmnPath); 85 | 86 | var xmlFile = Paths.get(dmnFilePath); 87 | return new String(Files.readAllBytes(xmlFile), StandardCharsets.UTF_8); 88 | } 89 | 90 | private List toDmnTest(Entry testsRaw) { 91 | return testsRaw 92 | .getValue() 93 | .getTests() 94 | .stream() 95 | .map(testRaw -> this.testRawToTest(testRaw, testsRaw.getKey())) 96 | .collect(Collectors.toList()); 97 | } 98 | 99 | private DmnTest testRawToTest(DmnProjectTestDefinition testRaw, String tableId) { 100 | var test = new DmnTest(); 101 | test.setData(testRaw.getData()); 102 | test.setExpectedData(testRaw.getExpectedData()); 103 | var testName = (testRaw.getName() != null && !testRaw.getName().trim().equals("")) ? testRaw.getName() 104 | : UUID.randomUUID().toString(); 105 | test.setName(testName); 106 | test.setTableId(tableId); 107 | return test; 108 | } 109 | 110 | private TestSuite toJunitTestSuite(DmnTestSuite dmnSuite) { 111 | var suite = new TestSuite(dmnSuite.getName()); 112 | 113 | var processEngine = DecisionEngine.createEngine().parseDecision(dmnSuite.getXml()); 114 | 115 | dmnSuite 116 | .getTest() 117 | .stream() 118 | .collect(Collectors.groupingBy(DmnTest::getTableId)) 119 | .entrySet() 120 | .stream() 121 | .map(entry -> this.entrySetToJunitTestSuite(entry, processEngine)) 122 | .forEach(suite::addTest); 123 | return suite; 124 | } 125 | 126 | private TestSuite entrySetToJunitTestSuite(Entry> entry, DecisionEngine engine) { 127 | TestSuite suite = new TestSuite(entry.getKey()); 128 | entry 129 | .getValue() 130 | .stream() 131 | .map(dmnTest -> new DmnTestExecutor(dmnTest, engine)) 132 | .forEach(suite::addTest); 133 | return suite; 134 | } 135 | 136 | private String getResourceRelativToBasePath(File baseDirectory, String filename) { 137 | return getResources(baseDirectory) 138 | .stream() 139 | .filter(file -> file.endsWith(filename)) 140 | .findFirst() 141 | .orElseThrow(() -> new RuntimeException(String.format("DMN File \"%s\" not found", filename))); 142 | } 143 | 144 | private List getResourceFiles(Path basePath) { 145 | if (basePath != null) { 146 | return getDmnProjectFilesByPath(basePath); 147 | } 148 | 149 | var files = new ArrayList(); 150 | files.addAll(getResources(Paths.get("src", "main", "resources").toFile())); 151 | files.addAll(getResources(Paths.get("src", "test", "resources").toFile())); 152 | return files; 153 | } 154 | 155 | private List getDmnProjectFilesByPath(Path basePath) { 156 | if (basePath.toFile().isFile()) { 157 | return Arrays.asList(basePath.toFile().getAbsolutePath()); 158 | } 159 | 160 | return getResourceFiles(basePath); 161 | } 162 | 163 | private List getResources(File base) { 164 | if (base == null || !base.exists() || !base.isDirectory()) { 165 | return new ArrayList<>(); 166 | } 167 | 168 | var files = Arrays.asList(base.list()) 169 | .stream() 170 | .map(file -> Paths.get(base.getPath(), file).toString()) 171 | .collect(Collectors.toList()); 172 | 173 | files.addAll(files.stream() 174 | .filter(path -> Files.isDirectory(Paths.get(path))) 175 | .map(File::new) 176 | .map(this::getResources) 177 | .flatMap(List::stream) 178 | .collect(Collectors.toList())); 179 | 180 | return files; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/DmnTestExecutor.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map.Entry; 7 | 8 | import org.junit.Assert; 9 | 10 | import com.fasterxml.jackson.databind.node.ObjectNode; 11 | 12 | import de.lv1871.oss.dmnmgr.test.driver.model.DmnTest; 13 | import junit.framework.TestCase; 14 | import static de.lv1871.oss.tester.test.dmnassert.DmnTest.testExpectation; 15 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 16 | import de.lv1871.oss.tester.test.domain.DecisionEngine; 17 | 18 | public class DmnTestExecutor extends TestCase { 19 | 20 | private static String ASSERTION_ERROR_MESSAGE_TEMPLATE = 21 | "\n--------------------------------------------\n" + 22 | "Expected Data:\n" + 23 | "%s\n\n" + 24 | "--------------------------------------------\n" + 25 | "Result:\n" + 26 | "%s"; 27 | 28 | private static VariableMapperService MAPPER = new VariableMapperService(); 29 | 30 | private final DmnTest test; 31 | private DecisionEngine dmnEngine; 32 | 33 | public DmnTestExecutor(DmnTest test, DecisionEngine engine) { 34 | super(test.getName()); 35 | this.test = test; 36 | this.dmnEngine = engine; 37 | } 38 | 39 | protected void runTest() throws Throwable { 40 | testDecision(); 41 | } 42 | 43 | public void testDecision() { 44 | var decisionSimulationResponse = DecisionRunner.decide( 45 | dmnEngine, 46 | this.test.getTableId(), 47 | this.test.getData()); 48 | 49 | if (decisionSimulationResponse.getResult() == null) { 50 | fail(decisionSimulationResponse.getMessage()); 51 | } 52 | 53 | try { 54 | 55 | List> expectedDataAssertionFailed = new ArrayList<>(); 56 | 57 | for (ObjectNode expectedNode : this.test.getExpectedData()) { 58 | HashMap expectedObjectMap = MAPPER.getVariablesFromJsonAsMap(expectedNode); 59 | expectedDataAssertionFailed.addAll(testExpectation(decisionSimulationResponse, expectedObjectMap)); 60 | } 61 | 62 | var testFailed = expectedDataAssertionFailed.size() > 0; 63 | 64 | var resultMessage = testFailed ? getFailureMessage(decisionSimulationResponse) : ""; 65 | 66 | Assert.assertFalse(resultMessage, testFailed); 67 | 68 | } catch (Exception e) { 69 | fail(e.getMessage()); 70 | } 71 | } 72 | 73 | private String getFailureMessage(DecisionSimulationResponse decisionSimulationResponse) { 74 | var expectedData = MAPPER.writeJson(this.test.getExpectedData()); 75 | var result = MAPPER.writeJson(decisionSimulationResponse.getResult()); 76 | 77 | return decisionSimulationResponse.getMessage() == null 78 | ? String.format(ASSERTION_ERROR_MESSAGE_TEMPLATE, expectedData, result) 79 | : decisionSimulationResponse.getMessage(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/DmnTester.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver; 2 | 3 | import org.junit.runners.AllTests; 4 | 5 | public class DmnTester extends AllTests { 6 | 7 | public DmnTester(Class klass) throws Throwable { 8 | super(DmnTestCreator.class); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/VariableMapperService.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.SerializationFeature; 9 | import com.fasterxml.jackson.databind.node.ObjectNode; 10 | 11 | public class VariableMapperService { 12 | 13 | private static ObjectMapper MAPPER; 14 | 15 | public VariableMapperService() { 16 | this.initMapper(); 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | public HashMap getVariablesFromJsonAsMap(ObjectNode json) { 21 | var valueAsString = json.toString(); 22 | try { 23 | return MAPPER.readValue(valueAsString, HashMap.class); 24 | } catch (IOException e) { 25 | throw new RuntimeException(e.getMessage()); 26 | } 27 | } 28 | 29 | public String writeJson(Object value) { 30 | try { 31 | return MAPPER.writeValueAsString(value); 32 | } catch (JsonProcessingException e) { 33 | throw new RuntimeException(e); 34 | } 35 | } 36 | 37 | private void initMapper() { 38 | MAPPER = new ObjectMapper(); 39 | MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/model/DecisionSimulationResponse.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver.model; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class DecisionSimulationResponse { 7 | 8 | private final String message; 9 | private final List> result; 10 | 11 | private DecisionSimulationResponse(String message, List> result) { 12 | this.message = message; 13 | this.result = result; 14 | } 15 | 16 | public List> getResult() { 17 | return result; 18 | } 19 | 20 | public String getMessage() { 21 | return message; 22 | } 23 | 24 | public static class DecisionTestCaseResponseBuilder { 25 | 26 | private String message = null; 27 | private List> result = null; 28 | 29 | public static DecisionTestCaseResponseBuilder create() { 30 | return new DecisionTestCaseResponseBuilder(); 31 | } 32 | 33 | public DecisionTestCaseResponseBuilder withMessage(String message) { 34 | this.message = message; 35 | return this; 36 | } 37 | 38 | public DecisionTestCaseResponseBuilder withResult(List> result) { 39 | this.result = result; 40 | return this; 41 | } 42 | 43 | public DecisionSimulationResponse build() { 44 | return new DecisionSimulationResponse(message, result); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/model/DmnProject.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver.model; 2 | 3 | import java.util.Map; 4 | 5 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public class DmnProject { 9 | 10 | private String dmnPath; 11 | private Map testsuite; 12 | 13 | public String getDmnPath() { 14 | return dmnPath; 15 | } 16 | 17 | public void setDmnPath(String dmnPath) { 18 | this.dmnPath = dmnPath; 19 | } 20 | 21 | public Map getTestsuite() { 22 | return testsuite; 23 | } 24 | 25 | public void setTestsuite(Map testsuite) { 26 | this.testsuite = testsuite; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/model/DmnProjectTestContainer.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver.model; 2 | 3 | import java.util.List; 4 | 5 | public class DmnProjectTestContainer { 6 | 7 | private List tests; 8 | 9 | public List getTests() { 10 | return tests; 11 | } 12 | 13 | public void setTests(List tests) { 14 | this.tests = tests; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/model/DmnProjectTestDefinition.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver.model; 2 | 3 | import java.util.List; 4 | 5 | import com.fasterxml.jackson.databind.node.ObjectNode; 6 | 7 | public class DmnProjectTestDefinition { 8 | 9 | private String name; 10 | private ObjectNode data; 11 | private List expectedData; 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | public ObjectNode getData() { 22 | return data; 23 | } 24 | 25 | public void setData(ObjectNode data) { 26 | this.data = data; 27 | } 28 | 29 | public List getExpectedData() { 30 | return expectedData; 31 | } 32 | 33 | public void setExpectedData(List expectedData) { 34 | this.expectedData = expectedData; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/model/DmnTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver.model; 2 | 3 | import java.util.List; 4 | 5 | import com.fasterxml.jackson.databind.node.ObjectNode; 6 | 7 | public class DmnTest { 8 | 9 | private String name; 10 | private String tableId; 11 | private ObjectNode data; 12 | private List expectedData; 13 | 14 | public List getExpectedData() { 15 | return expectedData; 16 | } 17 | 18 | public void setExpectedData(List expectedData) { 19 | this.expectedData = expectedData; 20 | } 21 | 22 | public ObjectNode getData() { 23 | return data; 24 | } 25 | 26 | public void setData(ObjectNode data) { 27 | this.data = data; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public String getTableId() { 39 | return tableId; 40 | } 41 | 42 | public void setTableId(String tableId) { 43 | this.tableId = tableId; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/model/DmnTestSuite.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver.model; 2 | 3 | import java.util.List; 4 | 5 | public class DmnTestSuite { 6 | 7 | private String xml; 8 | private String name; 9 | private List test; 10 | 11 | public List getTest() { 12 | return test; 13 | } 14 | 15 | public void setTest(List test) { 16 | this.test = test; 17 | } 18 | 19 | public String getXml() { 20 | return xml; 21 | } 22 | 23 | public void setXml(String xml) { 24 | this.xml = xml; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/main/java/de/lv1871/oss/dmnmgr/test/driver/model/TestSuiteTestResult.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver.model; 2 | 3 | public class TestSuiteTestResult { 4 | 5 | private Boolean testFailed; 6 | 7 | public Boolean getTestFailed() { 8 | return testFailed; 9 | } 10 | 11 | public void setTestFailed(Boolean testFailed) { 12 | this.testFailed = testFailed; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/test/java/de/lv1871/oss/dmnmgr/test/driver/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.nio.file.Paths; 7 | 8 | import org.junit.Test; 9 | 10 | import junit.framework.TestResult; 11 | 12 | public class IntegrationTest { 13 | 14 | // @formatter:off 15 | private static final String ASSERTION_ERROR_TEST_A = 16 | "--------------------------------------------\n" + 17 | "Expected Data:\n" + 18 | "[{\"res\":\"We\"}]\n" + 19 | "\n" + 20 | "--------------------------------------------\n" + 21 | "Result:\n" + 22 | "[{\"res\":\"Welt\"}]"; 23 | // @formatter:on 24 | 25 | @Test 26 | public void testAssertFailesWhenDataNotEqual() { 27 | junit.framework.Test testsuite = DmnTestCreator 28 | .getSuiteParameterized(Paths.get("src", "test", "resources", "a_test.dmnapp.json")); 29 | TestResult result = new TestResult(); 30 | testsuite.run(result); 31 | 32 | assertEquals(1, result.errorCount()); 33 | assertTrue(result.errors().nextElement().exceptionMessage().contains(ASSERTION_ERROR_TEST_A)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/test/java/de/lv1871/oss/dmnmgr/test/driver/VariableMapperServiceTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.test.driver; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.math.BigDecimal; 7 | import java.text.ParseException; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Date; 10 | import java.util.HashMap; 11 | 12 | import org.junit.Test; 13 | 14 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 15 | import com.fasterxml.jackson.databind.node.ObjectNode; 16 | 17 | public class VariableMapperServiceTest { 18 | 19 | private VariableMapperService cut = new VariableMapperService(); 20 | 21 | @Test 22 | public void testDeserializesObjectNodeToMapCorrect() { 23 | ObjectNode node = new ObjectNode(JsonNodeFactory.instance); 24 | node.put("testDatum", "2019-12-20T12:12:00Z"); 25 | node.put("Hallo", "Welt"); 26 | node.put("zahl", 1); 27 | node.put("bool", true); 28 | 29 | HashMap jsonAsMap = cut.getVariablesFromJsonAsMap(node); 30 | 31 | assertEquals("Welt", jsonAsMap.get("Hallo")); 32 | assertEquals("2019-12-20T12:12:00Z", jsonAsMap.get("testDatum")); 33 | assertEquals(1, jsonAsMap.get("zahl")); 34 | assertTrue((boolean) jsonAsMap.get("bool")); 35 | } 36 | 37 | @Test 38 | public void testSerializesDatesAsJavascriptDates() { 39 | TestObject testObject = new TestObject(createDateFromString("2012-12-20:12"), BigDecimal.valueOf(12.12)); 40 | 41 | String jsonString = cut.writeJson(testObject); 42 | 43 | assertTrue("Big Decimal fehlerhaft " + jsonString, jsonString.contains("\"zahl\":12.12")); 44 | assertTrue("Datum fehlerhaft " + jsonString, jsonString.contains("\"datum\":\"2012-12-20T11:00:00.000+0000\"")); 45 | } 46 | 47 | private Date createDateFromString(String dateString) { 48 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd:HH"); 49 | try { 50 | return format.parse(dateString); 51 | } catch (ParseException e) { 52 | throw new RuntimeException(e); 53 | } 54 | } 55 | 56 | public static class TestObject { 57 | 58 | private Date datum; 59 | private BigDecimal zahl; 60 | 61 | public TestObject(Date datum, BigDecimal zahl) { 62 | this.setDatum(datum); 63 | this.setZahl(zahl); 64 | } 65 | 66 | public Date getDatum() { 67 | return datum; 68 | } 69 | 70 | public void setDatum(Date datum) { 71 | this.datum = datum; 72 | } 73 | 74 | public BigDecimal getZahl() { 75 | return zahl; 76 | } 77 | 78 | public void setZahl(BigDecimal zahl) { 79 | this.zahl = zahl; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/test/resources/a_test.dmn: -------------------------------------------------------------------------------- 1 | 2 | test"Hallo""Welt" -------------------------------------------------------------------------------- /dmnmgr-test-driver/src/test/resources/a_test.dmnapp.json: -------------------------------------------------------------------------------- 1 | {"dmnPath":"a_test.dmn","testsuite":{"decision":{"tests":[{"name":"Test A","data":{"test":"Hallo"},"expectedData":[{"res":"Welt"}]},{"name":"Test B","data":{"test":"Hallo"},"expectedData":[{"res":"We"}]}]}},"definitions":{"decision":{"requestModel":{"type":"object","properties":[{"type":"string","name":"test"}]}}}} -------------------------------------------------------------------------------- /dmnmgr-tester/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | de.lv1871.oss 6 | dmnmgr-project 7 | 1.3.0-SNAPSHOT 8 | 9 | 10 | dmnmgr-tester 11 | jar 12 | 13 | dmnmgr-tester 14 | 15 | 16 | 17 | org.camunda.bpm.dmn 18 | camunda-engine-dmn 19 | 20 | 21 | org.camunda.bpm.extension.feel.scala 22 | feel-engine-plugin 23 | 24 | 25 | junit 26 | junit 27 | 28 | 29 | com.fasterxml.jackson.core 30 | jackson-annotations 31 | 32 | 33 | com.fasterxml.jackson.core 34 | jackson-core 35 | 36 | 37 | com.fasterxml.jackson.core 38 | jackson-databind 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/dmnassert/DmnAssert.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.dmnassert; 2 | 3 | import java.util.Map.Entry; 4 | 5 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 6 | import static de.lv1871.oss.tester.test.dmnassert.DmnVariableAssert.equal; 7 | 8 | public class DmnAssert { 9 | 10 | public static > boolean assertEqual( 11 | final DecisionSimulationResponse decisionSimulationResponse, 12 | final T expectedEntry 13 | ) { 14 | 15 | if (expectedEntry.getValue() == null) { 16 | return decisionSimulationResponse 17 | .getResult() 18 | .stream() 19 | .flatMap(map -> map.entrySet().stream()) 20 | .filter(entry -> entry.getKey() == expectedEntry.getKey() && entry.getValue() != null) 21 | .count() < 1; 22 | } 23 | 24 | return decisionSimulationResponse 25 | .getResult() 26 | .stream() 27 | .flatMap(map -> map.entrySet().stream()) 28 | .filter(resultEntry -> equal(resultEntry, expectedEntry)) 29 | .count() >= 1; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/dmnassert/DmnTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.dmnassert; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | import java.util.stream.Collectors; 7 | 8 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 9 | import de.lv1871.oss.tester.test.function.ExtendedBiPredicate; 10 | 11 | public class DmnTest { 12 | 13 | public static List> testExpectation( 14 | DecisionSimulationResponse decisionSimulationResponse, 15 | Map expectedMap 16 | ) { 17 | 18 | return expectedMap.entrySet() 19 | .stream() 20 | .filter(assertEntry.curryWith(decisionSimulationResponse).negate()) 21 | .collect(Collectors.toList()); 22 | } 23 | 24 | private static ExtendedBiPredicate> assertEntry = 25 | DmnAssert::assertEqual; 26 | } -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/dmnassert/DmnVariableAssert.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.dmnassert; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Map.Entry; 5 | 6 | public class DmnVariableAssert { 7 | 8 | public static boolean equal(Entry resultEntry, Entry expectedEntry) { 9 | if (!resultEntry.getKey().equals(expectedEntry.getKey())) { 10 | return false; 11 | } 12 | return equalValue(resultEntry.getValue(), expectedEntry.getValue()); 13 | } 14 | 15 | public static boolean equalValue(Object result, Object expected) { 16 | if (result == null && expected == null) { 17 | return true; 18 | } 19 | if (result == null || expected == null) { 20 | return false; 21 | } 22 | if (result instanceof Number && expected instanceof Number) { 23 | return numbersEqual((Number) result, (Number) expected); 24 | } 25 | return result.equals(expected); 26 | } 27 | 28 | public static boolean numbersEqual(Number n1, Number n2) { 29 | var b1 = new BigDecimal(n1.doubleValue()); 30 | var b2 = new BigDecimal(n2.doubleValue()); 31 | return b1.compareTo(b2) == 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/dmnassert/model/DecisionSimulationResponse.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.dmnassert.model; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class DecisionSimulationResponse { 7 | 8 | private final String message; 9 | private final List> result; 10 | private final List resultRuleIds; 11 | private final Map> resultTableRuleIds; 12 | 13 | private DecisionSimulationResponse(String message, List> result, List resultRuleIds, 14 | Map> resultTableRuleIds) { 15 | this.message = message; 16 | this.result = result; 17 | this.resultRuleIds = resultRuleIds; 18 | this.resultTableRuleIds = resultTableRuleIds; 19 | } 20 | 21 | public Map> getResultTableRuleIds() { 22 | return resultTableRuleIds; 23 | } 24 | 25 | public List> getResult() { 26 | return result; 27 | } 28 | 29 | public String getMessage() { 30 | return message; 31 | } 32 | 33 | public List getResultRuleIds() { 34 | return resultRuleIds; 35 | } 36 | 37 | public static class DecisionTestCaseResponseBuilder { 38 | 39 | private String message = null; 40 | private List> result = null; 41 | private List resultRuleIds = null; 42 | private Map> resultTableRuleIds = null; 43 | 44 | public static DecisionTestCaseResponseBuilder create() { 45 | return new DecisionTestCaseResponseBuilder(); 46 | } 47 | 48 | public DecisionTestCaseResponseBuilder withMessage(String message) { 49 | this.message = message; 50 | return this; 51 | } 52 | 53 | public DecisionTestCaseResponseBuilder withResult(List> result) { 54 | this.result = result; 55 | return this; 56 | } 57 | 58 | public DecisionSimulationResponse build() { 59 | return new DecisionSimulationResponse(message, result, resultRuleIds, resultTableRuleIds); 60 | } 61 | 62 | public DecisionTestCaseResponseBuilder withResultRuleIds(List resultRuleIds) { 63 | this.resultRuleIds = resultRuleIds; 64 | return this; 65 | } 66 | 67 | public DecisionTestCaseResponseBuilder withResultTableRuleIds(Map> resultTableRuleIds) { 68 | this.resultTableRuleIds = resultTableRuleIds; 69 | return this; 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/domain/DecisionEngine.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.domain; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Map.Entry; 11 | import java.util.stream.Collectors; 12 | 13 | import org.camunda.bpm.dmn.engine.DmnDecision; 14 | import org.camunda.bpm.dmn.engine.DmnDecisionRequirementsGraph; 15 | import org.camunda.bpm.dmn.engine.DmnDecisionTableResult; 16 | import org.camunda.bpm.dmn.engine.DmnEngine; 17 | import org.camunda.bpm.dmn.engine.DmnEngineConfiguration; 18 | import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationEvent; 19 | import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationListener; 20 | import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedDecisionRule; 21 | import org.camunda.bpm.dmn.engine.impl.DefaultDmnEngineConfiguration; 22 | import org.camunda.feel.integration.CamundaFeelEngineFactory; 23 | 24 | public class DecisionEngine { 25 | 26 | private DmnEngine dmnEngine; 27 | private Map decisions; 28 | private Map decisionRequirementsGraphs; 29 | private MyTableEvaluationListener tableEvaluationListener = new MyTableEvaluationListener(); 30 | 31 | public static DecisionEngine createEngine() { 32 | var engine = new DecisionEngine(); 33 | engine.init(); 34 | return engine; 35 | } 36 | 37 | private DecisionEngine() {} 38 | 39 | public DecisionEngine parseDecision(String dmnXml) { 40 | var drg = dmnEngine 41 | .parseDecisionRequirementsGraph(new ByteArrayInputStream(dmnXml.getBytes(StandardCharsets.UTF_8))); 42 | decisionRequirementsGraphs.put(drg.getKey(), drg); 43 | drg.getDecisionKeys().stream().forEach(key -> { 44 | this.decisions.put(key, drg.getDecision(key)); 45 | }); 46 | return this; 47 | } 48 | 49 | public DmnDecisionTableResult evaluateDecisionByKey(String key, Map variables) { 50 | return dmnEngine.evaluateDecisionTable(getDecisionByKey(key), variables); 51 | } 52 | 53 | public List getDecisionKeyByDrgKey(String key) { 54 | return decisionRequirementsGraphs.get(key).getDecisionKeys().stream().collect(Collectors.toList()); 55 | } 56 | 57 | public List getResultRules(String dmnTableId) { 58 | return tableEvaluationListener.getLatestMatchedRules(dmnTableId); 59 | } 60 | 61 | public Map> getResultRulesAllTables() { 62 | return tableEvaluationListener.getLatestMatchedRulesToAllMatchedTables(); 63 | } 64 | 65 | private void init() { 66 | 67 | decisions = new HashMap<>(); 68 | decisionRequirementsGraphs = new HashMap<>(); 69 | 70 | var dmnEngineConfig = (DefaultDmnEngineConfiguration) DmnEngineConfiguration 71 | .createDefaultDmnEngineConfiguration(); 72 | dmnEngineConfig.setDefaultInputEntryExpressionLanguage("feel-scala-unary-tests"); 73 | dmnEngineConfig.setDefaultOutputEntryExpressionLanguage("feel-scala"); 74 | dmnEngineConfig.setDefaultLiteralExpressionLanguage("feel-scala"); 75 | 76 | dmnEngineConfig.customPostDecisionTableEvaluationListeners( 77 | Arrays.asList(tableEvaluationListener) 78 | ); 79 | 80 | dmnEngine = dmnEngineConfig.feelEngineFactory(new CamundaFeelEngineFactory()).buildEngine(); 81 | } 82 | 83 | private static class MyTableEvaluationListener implements DmnDecisionTableEvaluationListener { 84 | 85 | private Map> latestMatchedRules = new HashMap<>(); 86 | 87 | @Override 88 | public void notify(DmnDecisionTableEvaluationEvent evaluationEvent) { 89 | latestMatchedRules.put( 90 | evaluationEvent.getDecisionTable().getKey(), 91 | evaluationEvent.getMatchingRules() 92 | .stream() 93 | .map(DmnEvaluatedDecisionRule::getId) 94 | .collect(Collectors.toList()) 95 | ); 96 | } 97 | 98 | public List getLatestMatchedRules(String tableId) { 99 | if (!latestMatchedRules.containsKey(tableId)) { 100 | return new ArrayList<>(); 101 | } 102 | return latestMatchedRules.get(tableId); 103 | } 104 | 105 | public Map> getLatestMatchedRulesToAllMatchedTables() { 106 | return latestMatchedRules 107 | .entrySet() 108 | .stream() 109 | .filter(matchedTable -> matchedTable.getValue().size() > 0) 110 | .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); 111 | } 112 | 113 | } 114 | 115 | public DmnDecision getDecisionByKey(String key) { 116 | assertDecisionAvailable(key); 117 | return decisions.get(key); 118 | } 119 | 120 | private void assertDecisionAvailable(String key) { 121 | if (!decisions.containsKey(key)) { 122 | throw new IllegalArgumentException(String.format("Decision with key %s not found.", key)); 123 | } 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/function/ExtendedBiFunction.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.function; 2 | 3 | import java.util.function.BiFunction; 4 | import java.util.function.Function; 5 | 6 | @FunctionalInterface 7 | public interface ExtendedBiFunction extends BiFunction { 8 | 9 | default Function> curry() { 10 | return a -> b -> this.apply(a, b); 11 | } 12 | 13 | default Function curryWith(T value) { 14 | return curry().apply(value); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/function/ExtendedBiPredicate.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.function; 2 | 3 | import java.util.function.BiPredicate; 4 | import java.util.function.Function; 5 | import java.util.function.Predicate; 6 | 7 | @FunctionalInterface 8 | public interface ExtendedBiPredicate extends BiPredicate { 9 | 10 | default Function> curry() { 11 | return a -> b -> this.test(a, b); 12 | } 13 | 14 | default Predicate curryWith(T value) { 15 | return curry().apply(value); 16 | } 17 | } -------------------------------------------------------------------------------- /dmnmgr-tester/src/main/java/de/lv1871/oss/tester/test/function/LambdaExtension.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.function; 2 | 3 | import java.util.function.Function; 4 | import java.util.function.Predicate; 5 | 6 | public class LambdaExtension { 7 | 8 | public static Predicate notNull(Function supplier) { 9 | return (value) -> notNull().test(supplier.apply(value)); 10 | } 11 | 12 | public static Predicate notNull() { 13 | return (value) -> value != null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/test/java/de/lv1871/oss/tester/test/dmnassert/DmnAssertTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.dmnassert; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.AbstractMap; 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | 13 | import org.junit.Test; 14 | 15 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 16 | 17 | public class DmnAssertTest { 18 | 19 | private static final int VALUE_3 = 42; 20 | private static final double VALUE_2 = 2.23; 21 | private static final int VALUE_1 = 1; 22 | private static final String FIELDNAME = "result"; 23 | 24 | @Test 25 | public void assertsFoundEntryAsEqual() { 26 | DecisionSimulationResponse decisionSimulationResponse = createDecisionResult(Arrays.asList( 27 | createResult(FIELDNAME, VALUE_1), 28 | createResult(FIELDNAME, VALUE_2))); 29 | 30 | boolean assertionResult = DmnAssert.assertEqual(decisionSimulationResponse, getEntry(FIELDNAME, VALUE_1)); 31 | 32 | assertTrue(assertionResult); 33 | } 34 | 35 | @Test 36 | public void assertsNullNotEqualSomeExpectedNumber() { 37 | DecisionSimulationResponse decisionSimulationResponse = createDecisionResult(Arrays.asList( 38 | createResult(FIELDNAME, null))); 39 | 40 | boolean assertionResult = DmnAssert.assertEqual(decisionSimulationResponse, getEntry(FIELDNAME, VALUE_1)); 41 | 42 | assertFalse(assertionResult); 43 | } 44 | 45 | @Test 46 | public void assertsNullNotEqualSomeFoundNumber() { 47 | DecisionSimulationResponse decisionSimulationResponse = createDecisionResult(Arrays.asList( 48 | createResult(FIELDNAME, VALUE_1))); 49 | 50 | boolean assertionResult = DmnAssert.assertEqual(decisionSimulationResponse, getEntry(FIELDNAME, null)); 51 | 52 | assertFalse(assertionResult); 53 | } 54 | 55 | @Test 56 | public void assertsNullValueEqualNoFoundNumber() { 57 | DecisionSimulationResponse decisionSimulationResponse = createDecisionResult(Arrays.asList()); 58 | 59 | boolean assertionResult = DmnAssert.assertEqual(decisionSimulationResponse, getEntry(FIELDNAME, null)); 60 | 61 | assertTrue(assertionResult); 62 | } 63 | 64 | @Test 65 | public void assertsNumberValueNotEqualNoFoundNumber() { 66 | DecisionSimulationResponse decisionSimulationResponse = createDecisionResult(Arrays.asList()); 67 | 68 | boolean assertionResult = DmnAssert.assertEqual(decisionSimulationResponse, getEntry(FIELDNAME, VALUE_1)); 69 | 70 | assertFalse(assertionResult); 71 | } 72 | 73 | @Test 74 | public void assertsEntryNotFoundAsNotEqual() { 75 | DecisionSimulationResponse decisionSimulationResponse = createDecisionResult(Arrays.asList( 76 | createResult(FIELDNAME, VALUE_1), 77 | createResult(FIELDNAME, VALUE_2))); 78 | 79 | boolean assertionResult = DmnAssert.assertEqual(decisionSimulationResponse, getEntry(FIELDNAME, VALUE_3)); 80 | 81 | assertFalse(assertionResult); 82 | } 83 | 84 | @Test 85 | public void assertsEntryFoundTwiceAsEqual() { 86 | DecisionSimulationResponse decisionSimulationResponse = createDecisionResult(Arrays.asList( 87 | createResult(FIELDNAME, VALUE_1), 88 | createResult(FIELDNAME, VALUE_2), 89 | createResult(FIELDNAME, VALUE_2))); 90 | 91 | boolean assertionResult = DmnAssert.assertEqual(decisionSimulationResponse, getEntry(FIELDNAME, VALUE_2)); 92 | 93 | assertTrue(assertionResult); 94 | } 95 | 96 | private DecisionSimulationResponse createDecisionResult(List> result) { 97 | return DecisionSimulationResponse.DecisionTestCaseResponseBuilder 98 | .create() 99 | .withResult(result) 100 | .build(); 101 | } 102 | 103 | private Map createResult(String key, Object value) { 104 | Map row = new HashMap<>(); 105 | row.put(key, value); 106 | return row; 107 | } 108 | 109 | private Entry getEntry(String key, Object value) { 110 | return new AbstractMap.SimpleEntry(key, value); 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /dmnmgr-tester/src/test/java/de/lv1871/oss/tester/test/dmnassert/DmnTestTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.dmnassert; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Map.Entry; 10 | 11 | import org.junit.Test; 12 | 13 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 14 | 15 | public class DmnTestTest { 16 | 17 | @Test 18 | public void testTestExpactationAllEqual() { 19 | DecisionSimulationResponse decisionSimulationResponse = createResponse(Arrays.asList( 20 | createResponseEntry("test", 1), 21 | createResponseEntry("zweitesAttribut", 3) 22 | )); 23 | Map expectedMap = createResponseEntry("test", 1); 24 | 25 | List> testExpectation = DmnTest.testExpectation(decisionSimulationResponse, expectedMap); 26 | 27 | assertEquals(0, testExpectation.size()); 28 | } 29 | 30 | @Test 31 | public void testTestFailsWhenValueNotContained() { 32 | DecisionSimulationResponse decisionSimulationResponse = createResponse(Arrays.asList( 33 | createResponseEntry("test", 1), 34 | createResponseEntry("zweitesAttribut", 3) 35 | )); 36 | Map expectedMap = createResponseEntry("test", 2); 37 | 38 | List> testExpectation = DmnTest.testExpectation(decisionSimulationResponse, expectedMap); 39 | 40 | assertEquals(1, testExpectation.size()); 41 | assertEquals("test", testExpectation.get(0).getKey()); 42 | assertEquals(2, testExpectation.get(0).getValue()); 43 | } 44 | 45 | @Test 46 | public void testTestWorksWhenExpectedValueIsNullAndNotContained() { 47 | DecisionSimulationResponse decisionSimulationResponse = createResponse(Arrays.asList( 48 | createResponseEntry("test", 1) 49 | )); 50 | Map expectedMap = createResponseEntry("nullValue", null); 51 | 52 | List> testExpectation = DmnTest.testExpectation(decisionSimulationResponse, expectedMap); 53 | 54 | assertEquals(0, testExpectation.size()); 55 | } 56 | 57 | @Test 58 | public void testFailesWhenNotAllExpectedValuesContained() { 59 | DecisionSimulationResponse decisionSimulationResponse = createResponse(Arrays.asList( 60 | createResponseEntry("test", 1), 61 | createResponseEntry("test2", "Hallo"), 62 | createResponseEntry("test3", "Welt") 63 | )); 64 | Map expectedMap = new HashMap<>(); 65 | expectedMap.put("test", 1); 66 | expectedMap.put("test2", "Hallo"); 67 | expectedMap.put("test3", "Welt"); 68 | expectedMap.put("test4", 1); 69 | 70 | List> testExpectation = DmnTest.testExpectation(decisionSimulationResponse, expectedMap); 71 | 72 | assertEquals(1, testExpectation.size()); 73 | assertEquals("test4", testExpectation.get(0).getKey()); 74 | } 75 | 76 | @Test 77 | public void testRunsWhenAllExpectedValuesContained() { 78 | DecisionSimulationResponse decisionSimulationResponse = createResponse(Arrays.asList( 79 | createResponseEntry("test", 1), 80 | createResponseEntry("test2", "Hallo"), 81 | createResponseEntry("test3", "Welt"), 82 | createResponseEntry("test4", 1) 83 | )); 84 | Map expectedMap = new HashMap<>(); 85 | expectedMap.put("test", 1); 86 | expectedMap.put("test2", "Hallo"); 87 | expectedMap.put("test3", "Welt"); 88 | expectedMap.put("test4", 1); 89 | 90 | List> testExpectation = DmnTest.testExpectation(decisionSimulationResponse, expectedMap); 91 | 92 | assertEquals(0, testExpectation.size()); 93 | } 94 | 95 | private DecisionSimulationResponse createResponse(List> results) { 96 | return DecisionSimulationResponse.DecisionTestCaseResponseBuilder 97 | .create() 98 | .withResult(results) 99 | .build(); 100 | } 101 | 102 | private Map createResponseEntry(String key, Object value) { 103 | Map entry = new HashMap<>(); 104 | entry.put(key, value); 105 | return entry; 106 | } 107 | } -------------------------------------------------------------------------------- /dmnmgr-tester/src/test/java/de/lv1871/oss/tester/test/dmnassert/DmnVariableAssertTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.dmnassert; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.math.BigDecimal; 7 | 8 | import org.junit.Test; 9 | 10 | public class DmnVariableAssertTest { 11 | 12 | @Test 13 | public void testIntegerCompareCorrectNotEqual() { 14 | assertFalse(DmnVariableAssert.numbersEqual(1, 2)); 15 | } 16 | 17 | @Test 18 | public void testIntegerCompareCorrectEqual() { 19 | assertTrue(DmnVariableAssert.numbersEqual(1, 1)); 20 | } 21 | 22 | @Test 23 | public void testDoubleCompareCorrectNotEqual() { 24 | assertFalse(DmnVariableAssert.numbersEqual(1.3, 2.5)); 25 | } 26 | 27 | @Test 28 | public void testDoubleCompareCorrectEqual() { 29 | assertTrue(DmnVariableAssert.numbersEqual(1.5, 1.5)); 30 | } 31 | 32 | @Test 33 | public void testDoubleCompareCorrectToIntegerValueEqual() { 34 | assertTrue(DmnVariableAssert.numbersEqual(1.0, 1)); 35 | } 36 | 37 | @Test 38 | public void testDoubleCompareCorrectToIntegerValueNotEqual() { 39 | assertFalse(DmnVariableAssert.numbersEqual(1.0, 2)); 40 | } 41 | 42 | @Test 43 | public void testNullValueComparisonBothNull() { 44 | assertTrue(DmnVariableAssert.equalValue(null, null)); 45 | } 46 | 47 | @Test 48 | public void testNullValueComparisonLeftNull() { 49 | assertFalse(DmnVariableAssert.equalValue(null, new Object())); 50 | } 51 | 52 | @Test 53 | public void testNullValueComparisonRightNull() { 54 | assertFalse(DmnVariableAssert.equalValue(new Object(), null)); 55 | } 56 | 57 | @Test 58 | public void testNumberComparisonPassToNumberComparison() { 59 | assertTrue(DmnVariableAssert.equalValue(new BigDecimal(12.0), 12L)); 60 | } 61 | 62 | @Test 63 | public void testStringsEqual() { 64 | assertTrue(DmnVariableAssert.equalValue(new String("Hallo".getBytes()), new String("Hallo".getBytes()))); 65 | } 66 | 67 | @Test 68 | public void testObjectComparisonWithEqualIsFalseWhenUnequal() { 69 | TestObject a = new TestObject(1, 3); 70 | TestObject b = new TestObject(1, 7); 71 | assertFalse(DmnVariableAssert.equalValue(a, b)); 72 | } 73 | 74 | @Test 75 | public void testObjectComparisonWithEqualIsTrueWhenEqual() { 76 | TestObject a = new TestObject(1, 3); 77 | TestObject b = new TestObject(1, 3); 78 | assertTrue(DmnVariableAssert.equalValue(a, b)); 79 | } 80 | 81 | private class TestObject { 82 | 83 | private int a; 84 | private int b; 85 | 86 | public TestObject(int a, int b) { 87 | this.a = a; 88 | this.b = b; 89 | } 90 | 91 | public int getB() { 92 | return b; 93 | } 94 | 95 | public int getA() { 96 | return a; 97 | } 98 | 99 | @Override 100 | public boolean equals(Object obj) { 101 | if (this == obj) 102 | return true; 103 | if (obj == null) 104 | return false; 105 | if (getClass() != obj.getClass()) 106 | return false; 107 | TestObject other = (TestObject) obj; 108 | return this.getA() == other.getA() && this.getB() == other.getB(); 109 | } 110 | 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/test/java/de/lv1871/oss/tester/test/domain/DecisionEngineTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.domain; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.io.IOException; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import org.camunda.bpm.dmn.engine.DmnDecisionTableResult; 11 | import org.camunda.bpm.model.xml.impl.util.IoUtil; 12 | import org.junit.Test; 13 | 14 | public class DecisionEngineTest { 15 | 16 | @Test 17 | public void testReadsAllDecisionsWithKeys() throws IOException { 18 | DecisionEngine engine = DecisionEngine.createEngine() 19 | .parseDecision(getDmnXml("testDrgTwoDecisions.dmn")); 20 | 21 | assertNotNull(engine.getDecisionByKey("decisionA")); 22 | assertNotNull(engine.getDecisionByKey("decisionB")); 23 | } 24 | 25 | @Test(expected = IllegalArgumentException.class) 26 | public void testReadsUnknownKeyCausesIllegalArgumentException() throws IOException { 27 | DecisionEngine engine = DecisionEngine.createEngine() 28 | .parseDecision(getDmnXml("testDrgTwoDecisions.dmn")); 29 | 30 | engine.getDecisionByKey("decisionC"); 31 | } 32 | 33 | @Test() 34 | public void testExecutesDecisionAndReturnsResult() throws IOException { 35 | DecisionEngine engine = DecisionEngine.createEngine() 36 | .parseDecision(getDmnXml("testDrgTwoDecisions.dmn")); 37 | 38 | DmnDecisionTableResult result = engine.evaluateDecisionByKey("decisionA", getVariables("input", "a")); 39 | 40 | assertEquals("b", result.get(0).get("result")); 41 | } 42 | 43 | private String getDmnXml(String filename) throws IOException { 44 | return IoUtil.getStringFromInputStream(this.getClass().getResourceAsStream("/" + filename)); 45 | } 46 | 47 | private Map getVariables(String key, Object value) { 48 | Map variables = new HashMap<>(); 49 | variables.put(key, value); 50 | return variables; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /dmnmgr-tester/src/test/java/de/lv1871/oss/tester/test/function/LambdaExtensionTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.tester.test.function; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | 5 | import org.junit.Test; 6 | 7 | public class LambdaExtensionTest { 8 | 9 | @Test 10 | public void testNotNullWithSupplierWhenNull() { 11 | TestObject testObject = new TestObject(null); 12 | 13 | assertFalse(LambdaExtension.notNull(TestObject::getValue).test(testObject)); 14 | } 15 | 16 | @Test 17 | public void testNotNullWithSupplierWhenNotNull() { 18 | TestObject testObject = new TestObject(null); 19 | 20 | assertFalse(LambdaExtension.notNull(TestObject::getValue).test(testObject)); 21 | } 22 | 23 | private static class TestObject { 24 | 25 | public TestObject(String value) { 26 | this.value = value; 27 | } 28 | 29 | private String value; 30 | 31 | public String getValue() { 32 | return value; 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/test/resources/testDrgTwoDecisions.dmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | input 11 | 12 | 13 | 14 | 15 | 16 | "a" 17 | 18 | 19 | "b" 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /dmnmgr-tester/src/test/resources/testDrgTwoDecisions.dmnapp.json: -------------------------------------------------------------------------------- 1 | {"dmnPath":"testDrgTwoDecisions.dmn","testsuite":{"decisionA":{"tests":[]},"definitions_09waf08":{"tests":[]},"decisionB":{"tests":[]}},"definitions":{},"plugins":[]} -------------------------------------------------------------------------------- /dmnmgr/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | de.lv1871.oss 6 | dmnmgr-project 7 | 1.3.0-SNAPSHOT 8 | 9 | 10 | dmnmgr 11 | jar 12 | 13 | dmnmgr 14 | http://maven.apache.org 15 | 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-dependencies 21 | ${springframework.boot.version} 22 | pom 23 | import 24 | 25 | 26 | 27 | 28 | 29 | 30 | de.lv1871.oss 31 | dmnmgr-tester 32 | ${project.version} 33 | compile 34 | 35 | 36 | org.camunda.bpm 37 | camunda-engine 38 | 39 | 40 | org.camunda.bpm.dmn 41 | camunda-engine-dmn 42 | 43 | 44 | org.camunda.bpm.extension.dmn.scala 45 | dmn-engine-camunda-plugin 46 | 47 | 48 | org.camunda.bpm.extension.feel.scala 49 | feel-engine-plugin 50 | 51 | 52 | org.camunda.bpm.extension.feel.scala 53 | feel-engine-factory 54 | ${feel.scala.version} 55 | 56 | 57 | org.camunda.bpm.extension.feel.scala 58 | feel-engine 59 | ${feel.scala.version} 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-web 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-logging 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-configuration-processor 72 | true 73 | 74 | 75 | de.redsix 76 | dmn-check-core 77 | 78 | 79 | de.redsix 80 | dmn-check-validators 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-starter-actuator 85 | 86 | 87 | io.springfox 88 | springfox-swagger-ui 89 | 90 | 91 | io.springfox 92 | springfox-swagger2 93 | 94 | 95 | com.fasterxml.jackson.datatype 96 | jackson-datatype-jsr310 97 | 98 | 99 | 100 | 101 | 102 | junit 103 | junit 104 | test 105 | 106 | 107 | 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-maven-plugin 112 | ${spring.boot.maven.plugin} 113 | 114 | 115 | 116 | repackage 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/App.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.bind.annotation.CrossOrigin; 9 | 10 | import de.lv1871.oss.dmnmgr.api.documentation.DecisionApi; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 13 | 14 | @SpringBootApplication 15 | @ComponentScan(value = { "de.lv1871.oss" }) 16 | @CrossOrigin 17 | @EnableSwagger2 18 | @Configuration 19 | public class App { 20 | public static void main(String[] args) { 21 | SpringApplication.run(App.class, args); 22 | } 23 | 24 | @Bean 25 | public Docket api() { 26 | return DecisionApi.createApiInfo(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/DecisionResource.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.CrossOrigin; 6 | import org.springframework.web.bind.annotation.RequestBody; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | import org.springframework.web.bind.annotation.ResponseBody; 10 | 11 | import de.lv1871.oss.dmnmgr.api.model.DecisionRequest; 12 | import de.lv1871.oss.dmnmgr.api.model.DecisionSimulationRequest; 13 | import de.lv1871.oss.dmnmgr.api.model.DecisionTestRequest; 14 | import de.lv1871.oss.dmnmgr.api.model.DecisionTestResponse; 15 | import de.lv1871.oss.dmnmgr.api.model.DmnValidationResponse; 16 | import de.lv1871.oss.dmnmgr.service.AdvancedDmnCheckService; 17 | import de.lv1871.oss.dmnmgr.service.DecisionTestService; 18 | import de.lv1871.oss.dmnmgr.service.GenericDecisionService; 19 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 20 | import io.swagger.annotations.ApiResponse; 21 | import io.swagger.annotations.ApiResponses; 22 | 23 | @Controller 24 | @CrossOrigin 25 | public class DecisionResource { 26 | 27 | @Autowired 28 | private GenericDecisionService decisionService; 29 | 30 | @Autowired 31 | private DecisionTestService testService; 32 | 33 | @Autowired 34 | private AdvancedDmnCheckService advancedDmnCheckService; 35 | 36 | @RequestMapping(value = "/api/decision/simulation", method = RequestMethod.POST) 37 | @ApiResponses(value = { @ApiResponse(code = 500, message = "Internal Server Error"), 38 | @ApiResponse(code = 406, message = "Data Not Acceptable"), 39 | @ApiResponse(code = 403, message = "Not Authorized") }) 40 | public @ResponseBody DecisionSimulationResponse evaluateTestCase( 41 | @RequestBody DecisionSimulationRequest decisionRequest) { 42 | return decisionService.decide(decisionRequest); 43 | } 44 | 45 | @RequestMapping(value = "/api/decision/test", method = RequestMethod.POST) 46 | @ApiResponses(value = { @ApiResponse(code = 500, message = "Internal Server Error"), 47 | @ApiResponse(code = 406, message = "Data Not Acceptable"), 48 | @ApiResponse(code = 403, message = "Not Authorized") }) 49 | public @ResponseBody DecisionTestResponse test(@RequestBody DecisionTestRequest decisionRequest) { 50 | return testService.testDecision(decisionRequest); 51 | } 52 | 53 | @RequestMapping(value = "/api/decision/validate", method = RequestMethod.POST) 54 | @ApiResponses(value = { @ApiResponse(code = 500, message = "Internal Server Error"), 55 | @ApiResponse(code = 406, message = "Data Not Acceptable"), 56 | @ApiResponse(code = 403, message = "Not Authorized") }) 57 | public @ResponseBody DmnValidationResponse validate(@RequestBody DecisionRequest decisionRequest) { 58 | return advancedDmnCheckService.validateDecision(decisionRequest); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/UtilityResource.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.CrossOrigin; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.ResponseBody; 10 | 11 | import de.lv1871.oss.dmnmgr.api.model.OpenApiDefinitionResponse; 12 | import de.lv1871.oss.dmnmgr.service.DownloadService; 13 | import io.swagger.annotations.ApiResponse; 14 | import io.swagger.annotations.ApiResponses; 15 | 16 | @Controller 17 | @CrossOrigin 18 | public class UtilityResource { 19 | 20 | @Autowired 21 | private DownloadService downloadService; 22 | 23 | @RequestMapping(value = "/api/open-api-definition", method = RequestMethod.GET) 24 | @ApiResponses(value = { @ApiResponse(code = 500, message = "Internal Server Error"), 25 | @ApiResponse(code = 406, message = "Data Not Acceptable"), 26 | @ApiResponse(code = 403, message = "Not Authorized") }) 27 | public @ResponseBody OpenApiDefinitionResponse deploy(@RequestParam("api-url") String url) { 28 | return downloadService.downloadOpenApiDefinition(url); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/documentation/DecisionApi.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.documentation; 2 | 3 | import static springfox.documentation.builders.PathSelectors.regex; 4 | 5 | import com.google.common.base.Predicate; 6 | 7 | import springfox.documentation.builders.ApiInfoBuilder; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.spi.DocumentationType; 10 | import springfox.documentation.spring.web.plugins.Docket; 11 | 12 | public class DecisionApi { 13 | 14 | private static final String API_TITLE = "DMN API"; 15 | private static final String API_NAME = "dmn-api"; 16 | private static final String API_DESCRIPTION = "Dieses API dient dazu Entscheidungen zu triggern"; 17 | 18 | private static ApiInfo apiInfo() { 19 | return new ApiInfoBuilder().title(API_TITLE).description(API_DESCRIPTION).build(); 20 | } 21 | 22 | public static Docket createApiInfo() { 23 | // @formatter:off 24 | return new Docket(DocumentationType.SWAGGER_2) 25 | .groupName(API_NAME) 26 | .useDefaultResponseMessages(false) 27 | .apiInfo(apiInfo()) 28 | .select() 29 | .paths(dokumentDecisionPath()) 30 | .build(); 31 | // @formatter:on 32 | } 33 | 34 | private static Predicate dokumentDecisionPath() { 35 | return regex("/api/.*"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/DecisionRequest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | public class DecisionRequest { 4 | 5 | private String xml; 6 | 7 | public String getXml() { 8 | return xml; 9 | } 10 | 11 | public void setXml(String xml) { 12 | this.xml = xml; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/DecisionSimulationRequest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | 5 | public class DecisionSimulationRequest extends DecisionRequest { 6 | 7 | private String dmnTableId; 8 | private ObjectNode variables; 9 | 10 | public ObjectNode getVariables() { 11 | return variables; 12 | } 13 | 14 | public void setVariables(ObjectNode variables) { 15 | this.variables = variables; 16 | } 17 | 18 | public String getDmnTableId() { 19 | return dmnTableId; 20 | } 21 | 22 | public void setDmnTableId(String dmnTableId) { 23 | this.dmnTableId = dmnTableId; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/DecisionTestRequest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | import java.util.List; 4 | 5 | import com.fasterxml.jackson.databind.node.ObjectNode; 6 | 7 | public class DecisionTestRequest extends DecisionSimulationRequest { 8 | 9 | private String dmnTableId; 10 | private List expectedData; 11 | 12 | public List getExpectedData() { 13 | return expectedData; 14 | } 15 | 16 | public void setExpectedData(List expectedData) { 17 | this.expectedData = expectedData; 18 | } 19 | 20 | public String getDmnTableId() { 21 | return dmnTableId; 22 | } 23 | 24 | public void setDmnTableId(String dmnTableId) { 25 | this.dmnTableId = dmnTableId; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/DecisionTestResponse.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | 7 | public class DecisionTestResponse { 8 | 9 | private final String message; 10 | private final List> result; 11 | private final List resultRuleIds; 12 | private final Boolean testSucceded; 13 | private final List> expectedDataAssertionFailed; 14 | 15 | private DecisionTestResponse(String message, List> result, List resultRuleIds, 16 | Boolean testSucceded, List> expectedDataAssertionFailed) { 17 | this.message = message; 18 | this.result = result; 19 | this.resultRuleIds = resultRuleIds; 20 | this.testSucceded = testSucceded; 21 | this.expectedDataAssertionFailed = expectedDataAssertionFailed; 22 | } 23 | 24 | public String getMessage() { 25 | return message; 26 | } 27 | 28 | public List> getResult() { 29 | return result; 30 | } 31 | 32 | public List getResultRuleIds() { 33 | return resultRuleIds; 34 | } 35 | 36 | public Boolean getTestSucceded() { 37 | return testSucceded; 38 | } 39 | 40 | public List> getExpectedDataAssertionFailed() { 41 | return expectedDataAssertionFailed; 42 | } 43 | 44 | public static class DecisionTestResponseBuilder { 45 | 46 | private String message = null; 47 | private List> result = null; 48 | private List resultRuleIds = null; 49 | private List> expectedDataAssertionFailed; 50 | private Boolean testSucceded = false; 51 | 52 | public static DecisionTestResponseBuilder create() { 53 | return new DecisionTestResponseBuilder(); 54 | } 55 | 56 | public DecisionTestResponseBuilder withMessage(String message) { 57 | this.message = message; 58 | return this; 59 | } 60 | 61 | public DecisionTestResponseBuilder withResultRuleIds(List resultRuleIds) { 62 | this.resultRuleIds = resultRuleIds; 63 | return this; 64 | } 65 | 66 | public DecisionTestResponseBuilder withResult(List> result) { 67 | this.result = result; 68 | return this; 69 | } 70 | 71 | public DecisionTestResponseBuilder withExpectedDataAssertionFailed( 72 | List> expectedDataAssertionFailed) { 73 | this.expectedDataAssertionFailed = expectedDataAssertionFailed; 74 | return this; 75 | } 76 | 77 | public DecisionTestResponseBuilder withTestSucceeded(Boolean testSucceded) { 78 | this.testSucceded = testSucceded; 79 | return this; 80 | } 81 | 82 | public DecisionTestResponse build() { 83 | return new DecisionTestResponse(message, result, resultRuleIds, testSucceded, expectedDataAssertionFailed); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/DmnValidationResponse.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | import java.util.List; 4 | 5 | public class DmnValidationResponse { 6 | 7 | private List errors; 8 | private List warnings; 9 | 10 | public List getWarnings() { 11 | return warnings; 12 | } 13 | 14 | public List getErrors() { 15 | return errors; 16 | } 17 | 18 | public void setErrors(List errors) { 19 | this.errors = errors; 20 | } 21 | 22 | public void setWarnings(List warnings) { 23 | this.warnings = warnings; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/DmnValidationResult.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | public class DmnValidationResult { 4 | 5 | private final String tableId; 6 | private final String ruleId; 7 | private final String cellId; 8 | private final String counterRuleId; 9 | private final String message; 10 | private final ErrorSeverity severity; 11 | 12 | private DmnValidationResult(String tableId, String ruleId, String cellId, String counterRuleId, String message, 13 | ErrorSeverity severety) { 14 | this.tableId = tableId; 15 | this.ruleId = ruleId; 16 | this.message = message; 17 | this.severity = severety; 18 | this.counterRuleId = counterRuleId; 19 | this.cellId = cellId; 20 | } 21 | 22 | public String getCellId() { 23 | return cellId; 24 | } 25 | 26 | public String getCounterRuleId() { 27 | return counterRuleId; 28 | } 29 | 30 | public String getTableId() { 31 | return tableId; 32 | } 33 | 34 | public String getRuleId() { 35 | return ruleId; 36 | } 37 | 38 | public String getMessage() { 39 | return message; 40 | } 41 | 42 | public ErrorSeverity getSeverity() { 43 | return severity; 44 | } 45 | 46 | public static class DmnValidationResultBuilder { 47 | 48 | private String tableId; 49 | private String ruleId; 50 | private String cellId; 51 | private String counterRuleId; 52 | private String message; 53 | private ErrorSeverity severity; 54 | 55 | public static DmnValidationResultBuilder create() { 56 | return new DmnValidationResultBuilder(); 57 | } 58 | 59 | public DmnValidationResultBuilder withTableId(String tableId) { 60 | this.tableId = tableId; 61 | return this; 62 | } 63 | 64 | public DmnValidationResultBuilder withRuleId(String ruleId) { 65 | this.ruleId = ruleId; 66 | return this; 67 | } 68 | 69 | public DmnValidationResultBuilder withCounterRuleId(String counterRuleId) { 70 | this.counterRuleId = counterRuleId; 71 | return this; 72 | } 73 | 74 | public DmnValidationResultBuilder withMessage(String message) { 75 | this.message = message; 76 | return this; 77 | } 78 | 79 | public DmnValidationResultBuilder withSeverity(ErrorSeverity severity) { 80 | this.severity = severity; 81 | return this; 82 | } 83 | 84 | public DmnValidationResultBuilder withCellId(String cellId) { 85 | this.cellId = cellId; 86 | return this; 87 | } 88 | 89 | public DmnValidationResult build() { 90 | return new DmnValidationResult(tableId, ruleId, cellId, counterRuleId, message, severity); 91 | } 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/ErrorSeverity.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | import de.redsix.dmncheck.result.Severity; 4 | 5 | public enum ErrorSeverity { 6 | ERROR, WARNING; 7 | 8 | public static ErrorSeverity ofSeverity(Severity severity) { 9 | switch(severity) { 10 | case ERROR: 11 | return ErrorSeverity.ERROR; 12 | case WARNING: 13 | return ErrorSeverity.WARNING; 14 | default: 15 | return ErrorSeverity.ERROR; 16 | 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/OpenApiDefinitionResponse.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | public class OpenApiDefinitionResponse { 4 | 5 | private String apiUrl; 6 | private String swaggerDefinition; 7 | 8 | public String getApiUrl() { 9 | return apiUrl; 10 | } 11 | 12 | public void setApiUrl(String apiUrl) { 13 | this.apiUrl = apiUrl; 14 | } 15 | 16 | public String getSwaggerDefinition() { 17 | return swaggerDefinition; 18 | } 19 | 20 | public void setSwaggerDefinition(String swaggerDefinition) { 21 | this.swaggerDefinition = swaggerDefinition; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/api/model/ServiceResult.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.api.model; 2 | 3 | public class ServiceResult { 4 | 5 | private boolean success; 6 | 7 | public boolean isSuccess() { 8 | return success; 9 | } 10 | 11 | public void setSuccess(boolean success) { 12 | this.success = success; 13 | } 14 | 15 | public static class ServiceResultBuilder { 16 | private boolean success; 17 | 18 | public static ServiceResultBuilder create() { 19 | return new ServiceResultBuilder(); 20 | } 21 | 22 | public ServiceResultBuilder withResult(boolean success) { 23 | this.success = success; 24 | return this; 25 | } 26 | 27 | public ServiceResult build() { 28 | ServiceResult result = new ServiceResult(); 29 | result.setSuccess(success); 30 | return result; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | @Controller 7 | public class HomeController { 8 | 9 | @RequestMapping("/api") 10 | public String home() { 11 | return "redirect:swagger-ui.html"; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/domain/CheckerFunctions.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.domain; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.camunda.commons.utils.StringUtil; 6 | 7 | public class CheckerFunctions { 8 | 9 | public static boolean containesUnquotedtQuotationMark(String trimmedText) { 10 | if (trimmedText == null || trimmedText.length() < 1) { 11 | return false; 12 | } 13 | String textWithoutMarks = trimmedText; 14 | if (trimmedText.startsWith("\"")) { 15 | textWithoutMarks = trimmedText.substring(1); 16 | } 17 | if (textWithoutMarks.endsWith("\"")) { 18 | textWithoutMarks = textWithoutMarks.substring(0, textWithoutMarks.length() - 1); 19 | } 20 | 21 | return Arrays.asList(textWithoutMarks.split("\\\\")) 22 | .stream() 23 | .map(value -> value.substring(1)) 24 | .filter(value -> value.indexOf("\"") > -1) 25 | .count() > 0; 26 | } 27 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/domain/InputEntryValidator.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.domain; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.Scanner; 9 | 10 | import org.camunda.bpm.engine.impl.el.ExpressionManager; 11 | import org.camunda.bpm.model.dmn.instance.InputEntry; 12 | 13 | import de.redsix.dmncheck.result.Severity; 14 | import de.redsix.dmncheck.result.ValidationResult; 15 | import de.redsix.dmncheck.validators.core.SimpleValidator; 16 | import de.redsix.dmncheck.validators.core.ValidationContext; 17 | 18 | import de.lv1871.oss.tester.test.function.ExtendedBiFunction; 19 | import static de.lv1871.oss.dmnmgr.domain.CheckerFunctions.containesUnquotedtQuotationMark; 20 | 21 | public class InputEntryValidator extends SimpleValidator { 22 | 23 | @Override 24 | protected boolean isApplicable(InputEntry arg0, ValidationContext arg1) { 25 | return true; 26 | } 27 | 28 | @Override 29 | protected List validate(InputEntry element, ValidationContext arg1) { 30 | var expressionLanguage = Optional.ofNullable(element).map(InputEntry::getExpressionLanguage).orElse("feel") 31 | .toLowerCase(); 32 | switch (expressionLanguage) { 33 | case "feel": 34 | return checkExpression(element, this::checkFeelExpression); 35 | case "juel": 36 | return checkExpression(element, this::checkJuelExpression); 37 | default: 38 | return null; 39 | } 40 | } 41 | 42 | private List checkExpression(InputEntry element, 43 | ExtendedBiFunction> validator) { 44 | return Optional.ofNullable(element) 45 | .map(InputEntry::getTextContent) 46 | .map(validator.curryWith(element)) 47 | .orElse(Collections.emptyList()); 48 | } 49 | 50 | private List checkJuelExpression(InputEntry entry, String text) { 51 | if (text == null) { 52 | return Collections.emptyList(); 53 | } 54 | 55 | if (text.startsWith("${") && !text.endsWith("}")) { 56 | return Arrays.asList(ValidationResult.init 57 | .message(String.format("Error in Juel Expression: (%s) Must start with '${'", text)) 58 | .severity(Severity.ERROR).element(entry).build()); 59 | } 60 | 61 | if (!text.startsWith("${") && text.endsWith("}")) { 62 | return Arrays.asList(ValidationResult.init 63 | .message(String.format("Error in Juel Expression: (%s) Must end with '}'", text)) 64 | .severity(Severity.ERROR).element(entry).build()); 65 | } 66 | 67 | List result = new ArrayList<>(); 68 | try { 69 | if (!text.startsWith("$") && text.length() > 0) { 70 | text = "${" + text + "}"; 71 | } 72 | new ExpressionManager().createExpression(text); 73 | } catch (Exception exception) { 74 | result.add(ValidationResult.init.message("Error in Juel Expression: " + exception.getMessage()) 75 | .severity(Severity.ERROR).element(entry).build()); 76 | } 77 | return result; 78 | } 79 | 80 | private List checkFeelExpression(InputEntry entry, String text) { 81 | if (text == null) { 82 | return Collections.emptyList(); 83 | } 84 | 85 | List result = new ArrayList<>(); 86 | var scanner = new Scanner(text); 87 | try { 88 | scanner.useDelimiter(","); 89 | while(scanner.hasNext()) { 90 | var value = scanner.next(); 91 | if (value == null) { 92 | continue; 93 | } 94 | var valueTrimmed = value.trim(); 95 | if (valueTrimmed.startsWith("\"") && !valueTrimmed.endsWith("\"")) { 96 | return Arrays.asList(ValidationResult.init 97 | .message(String.format("Error in Feel Expression: (%s) Must end with '\"'", text)) 98 | .severity(Severity.ERROR).element(entry).build()); 99 | } 100 | if (!valueTrimmed.startsWith("\"") && valueTrimmed.endsWith("\"")) { 101 | return Arrays.asList(ValidationResult.init 102 | .message(String.format("Error in Feel Expression: (%s) Must start with '\"'", text)) 103 | .severity(Severity.ERROR).element(entry).build()); 104 | } 105 | 106 | if (containesUnquotedtQuotationMark(valueTrimmed)) { 107 | result.add(ValidationResult.init 108 | .message(String.format("Error in Feel Expression: (%s) Must not contain a '\"'", valueTrimmed)) 109 | .severity(Severity.ERROR).element(entry).build()); 110 | } 111 | } 112 | } catch (Exception exception) { 113 | result.add(ValidationResult.init.message("Error in Juel Expression: " + exception.getMessage()) 114 | .severity(Severity.ERROR).element(entry).build()); 115 | } finally { 116 | scanner.close(); 117 | } 118 | return result; 119 | } 120 | 121 | @Override 122 | public Class getClassUnderValidation() { 123 | return InputEntry.class; 124 | } 125 | 126 | 127 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/domain/InputExpressionRequiredValidator.java: -------------------------------------------------------------------------------- 1 | 2 | package de.lv1871.oss.dmnmgr.domain; 3 | 4 | import de.redsix.dmncheck.result.Severity; 5 | import de.redsix.dmncheck.result.ValidationResult; 6 | import de.redsix.dmncheck.validators.core.SimpleValidator; 7 | import de.redsix.dmncheck.validators.core.ValidationContext; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | import org.camunda.bpm.model.dmn.instance.InputExpression; 14 | import org.camunda.bpm.model.dmn.instance.Text; 15 | import org.springframework.util.StringUtils; 16 | 17 | public class InputExpressionRequiredValidator extends SimpleValidator { 18 | 19 | @Override 20 | protected boolean isApplicable(InputExpression arg0, ValidationContext arg1) { 21 | return true; 22 | } 23 | 24 | @Override 25 | protected List validate(InputExpression expression, ValidationContext arg1) { 26 | try { 27 | final var expressionText = Optional 28 | .ofNullable(expression) 29 | .map(InputExpression::getText) 30 | .map(Text::getTextContent) 31 | .orElse(null); 32 | 33 | if (StringUtils.isEmpty(expressionText)) { 34 | return Collections.singletonList(ValidationResult.init 35 | .message(getClassUnderValidation().getSimpleName() + " has no expressiontext") 36 | .severity(Severity.ERROR) 37 | .element(expression) 38 | .build()); 39 | } 40 | return Collections.emptyList(); 41 | } catch (Exception ex) { 42 | return Collections.emptyList(); 43 | } 44 | } 45 | 46 | @Override 47 | public Class getClassUnderValidation() { 48 | return InputExpression.class; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/domain/OutputEntryValidator.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.domain; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import org.camunda.bpm.engine.impl.el.ExpressionManager; 10 | import org.camunda.bpm.model.dmn.instance.OutputEntry; 11 | 12 | import de.redsix.dmncheck.result.Severity; 13 | import de.redsix.dmncheck.result.ValidationResult; 14 | import de.redsix.dmncheck.validators.core.SimpleValidator; 15 | import de.redsix.dmncheck.validators.core.ValidationContext; 16 | 17 | import de.lv1871.oss.tester.test.function.ExtendedBiFunction; 18 | import static de.lv1871.oss.dmnmgr.domain.CheckerFunctions.containesUnquotedtQuotationMark; 19 | 20 | public class OutputEntryValidator extends SimpleValidator { 21 | 22 | @Override 23 | protected boolean isApplicable(OutputEntry arg0, ValidationContext arg1) { 24 | return true; 25 | } 26 | 27 | @Override 28 | protected List validate(OutputEntry element, ValidationContext arg1) { 29 | var expressionLanguage = Optional.ofNullable(element).map(OutputEntry::getExpressionLanguage).orElse("feel") 30 | .toLowerCase(); 31 | switch (expressionLanguage) { 32 | case "feel": 33 | return checkExpression(element, this::checkFeelExpression); 34 | case "juel": 35 | return checkExpression(element, this::checkJuelExpression); 36 | default: 37 | return null; 38 | } 39 | } 40 | 41 | private List checkExpression(OutputEntry element, 42 | ExtendedBiFunction> validator) { 43 | return Optional.ofNullable(element) 44 | .map(OutputEntry::getTextContent) 45 | .map(validator.curryWith(element)) 46 | .orElse(Collections.emptyList()); 47 | } 48 | 49 | private List checkJuelExpression(OutputEntry entry, String text) { 50 | if (text == null) { 51 | return Collections.emptyList(); 52 | } 53 | 54 | if (text.startsWith("${") && !text.endsWith("}")) { 55 | return Arrays.asList(ValidationResult.init 56 | .message(String.format("Error in Juel Expression: (%s) Must start with '${'", text)) 57 | .severity(Severity.ERROR).element(entry).build()); 58 | } 59 | 60 | if (!text.startsWith("${") && text.endsWith("}")) { 61 | return Arrays.asList(ValidationResult.init 62 | .message(String.format("Error in Juel Expression: (%s) Must end with '}'", text)) 63 | .severity(Severity.ERROR).element(entry).build()); 64 | } 65 | 66 | List result = new ArrayList<>(); 67 | try { 68 | if (!text.startsWith("$") && text.length() > 0) { 69 | text = "${" + text + "}"; 70 | } 71 | new ExpressionManager().createExpression(text); 72 | } catch (Exception exception) { 73 | result.add(ValidationResult.init.message("Error in Juel Expression: " + exception.getMessage()) 74 | .severity(Severity.ERROR).element(entry).build()); 75 | } 76 | return result; 77 | } 78 | 79 | private List checkFeelExpression(OutputEntry entry, String text) { 80 | if (text == null) { 81 | return Collections.emptyList(); 82 | } 83 | 84 | List result = new ArrayList<>(); 85 | String trimmedText = text.trim(); 86 | try { 87 | if (trimmedText.startsWith("\"") && !trimmedText.endsWith("\"")) { 88 | return Arrays.asList(ValidationResult.init 89 | .message(String.format("Error in Feel Expression: (%s) Must end with '\"'", trimmedText)) 90 | .severity(Severity.ERROR).element(entry).build()); 91 | } 92 | if (!trimmedText.startsWith("\"") && trimmedText.endsWith("\"")) { 93 | return Arrays.asList(ValidationResult.init 94 | .message(String.format("Error in Feel Expression: (%s) Must start with '\"'", trimmedText)) 95 | .severity(Severity.ERROR).element(entry).build()); 96 | } 97 | 98 | if (containesUnquotedtQuotationMark(trimmedText)) { 99 | result.add(ValidationResult.init 100 | .message(String.format("Error in Feel Expression: (%s) Must not contain a '\"'", trimmedText)) 101 | .severity(Severity.ERROR).element(entry).build()); 102 | } 103 | } catch (Exception exception) { 104 | result.add(ValidationResult.init.message("Error in Feel Expression: " + exception.getMessage()) 105 | .severity(Severity.ERROR).element(entry).build()); 106 | } 107 | return result; 108 | } 109 | 110 | @Override 111 | public Class getClassUnderValidation() { 112 | return OutputEntry.class; 113 | } 114 | 115 | 116 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/domain/OutputNameRequiredValidator.java: -------------------------------------------------------------------------------- 1 | 2 | package de.lv1871.oss.dmnmgr.domain; 3 | 4 | import de.redsix.dmncheck.result.Severity; 5 | import de.redsix.dmncheck.result.ValidationResult; 6 | import de.redsix.dmncheck.validators.core.SimpleValidator; 7 | import de.redsix.dmncheck.validators.core.ValidationContext; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | import org.camunda.bpm.model.dmn.instance.Output; 13 | import org.springframework.util.StringUtils; 14 | 15 | public class OutputNameRequiredValidator extends SimpleValidator { 16 | 17 | @Override 18 | protected boolean isApplicable(Output arg0, ValidationContext arg1) { 19 | return true; 20 | } 21 | 22 | @Override 23 | protected List validate(Output expression, ValidationContext arg1) { 24 | final var outputName = expression.getName(); 25 | if (StringUtils.isEmpty(outputName)) { 26 | return Collections.singletonList( 27 | ValidationResult 28 | .init 29 | .message(getClassUnderValidation().getSimpleName() + " has no name") 30 | .severity(Severity.ERROR) 31 | .element(expression) 32 | .build()); 33 | } 34 | return Collections.emptyList(); 35 | } 36 | 37 | @Override 38 | public Class getClassUnderValidation() { 39 | return Output.class; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/service/AdvancedDmnCheckService.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.service; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.InputStream; 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.lang.reflect.Modifier; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.stream.Collectors; 13 | 14 | import javax.annotation.PostConstruct; 15 | 16 | import org.camunda.bpm.model.dmn.Dmn; 17 | import org.camunda.bpm.model.dmn.DmnModelInstance; 18 | import org.camunda.bpm.model.xml.instance.ModelElementInstance; 19 | import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 20 | import org.springframework.stereotype.Service; 21 | 22 | import de.lv1871.oss.dmnmgr.api.model.DecisionRequest; 23 | import de.lv1871.oss.dmnmgr.api.model.DmnValidationResponse; 24 | import de.lv1871.oss.dmnmgr.api.model.DmnValidationResult; 25 | import de.lv1871.oss.dmnmgr.api.model.ErrorSeverity; 26 | import de.lv1871.oss.dmnmgr.api.model.DmnValidationResult.DmnValidationResultBuilder; 27 | import de.redsix.dmncheck.result.ValidationResult; 28 | import de.redsix.dmncheck.validators.core.Validator; 29 | import io.github.classgraph.ClassGraph; 30 | import io.github.classgraph.ClassInfoList; 31 | import io.github.classgraph.ScanResult; 32 | 33 | @Service 34 | public class AdvancedDmnCheckService { 35 | 36 | private static final String MESSAGE_NOT_CONNECTED_TABLE = "Element is not connected to requirement graph"; 37 | private static final String ATTRIBUTE_NAME_RULE_ID = "id"; 38 | private static final String SEQUEL_CORRESPONDING_RULE = "by rule "; 39 | private static final String VALIDATOR_PACKAGE = "de.redsix.dmncheck.validators"; 40 | private static final String VALIDATOR_CORE_PACKAGE = "de.redsix.dmncheck.validators.core"; 41 | 42 | private @MonotonicNonNull List validators; 43 | 44 | private String[] validatorPackages = new String[0]; 45 | 46 | private String[] validatorClasses = { 47 | "de.redsix.dmncheck.validators.ConflictingRuleValidator", 48 | "de.redsix.dmncheck.validators.ShadowedRuleValidator", 49 | "de.redsix.dmncheck.validators.AggregationOutputTypeValidator", 50 | "de.redsix.dmncheck.validators.ConnectedRequirementGraphValidator", 51 | "de.redsix.dmncheck.validators.DuplicateRuleValidator", 52 | "de.redsix.dmncheck.validators.InputTypeDeclarationValidator", 53 | "de.redsix.dmncheck.validators.OutputTypeDeclarationValidator", 54 | "de.lv1871.oss.dmnmgr.domain.InputExpressionRequiredValidator", 55 | "de.lv1871.oss.dmnmgr.domain.OutputNameRequiredValidator", 56 | "de.lv1871.oss.dmnmgr.domain.InputEntryValidator", 57 | "de.lv1871.oss.dmnmgr.domain.OutputEntryValidator" 58 | }; 59 | 60 | @PostConstruct 61 | public void init() { 62 | initValidators(); 63 | } 64 | 65 | public DmnValidationResponse validateDecision(DecisionRequest decisionRequest) { 66 | var stream = new ByteArrayInputStream(decisionRequest.getXml().getBytes(StandardCharsets.UTF_8)); 67 | return validateDecision(stream); 68 | } 69 | 70 | public DmnValidationResponse validateDecision(final InputStream file) { 71 | var response = new DmnValidationResponse(); 72 | response.setErrors(new ArrayList<>()); 73 | response.setWarnings(new ArrayList<>()); 74 | try { 75 | final var dmnModelInstance = Dmn.readModelFromStream(file); 76 | final var validationResults = runValidators(dmnModelInstance); 77 | 78 | var results = validationResults 79 | .stream() 80 | .filter(result -> !result.getMessage().contains("parse")) 81 | .map(this::mapModel) 82 | .collect(Collectors.toList()); 83 | 84 | response.setErrors(results 85 | .stream() 86 | .filter(result -> result.getSeverity() == ErrorSeverity.ERROR) 87 | .collect(Collectors.toList())); 88 | 89 | response.setWarnings(results 90 | .stream() 91 | .filter(result -> result.getSeverity() == ErrorSeverity.WARNING) 92 | .collect(Collectors.toList())); 93 | 94 | return response; 95 | } 96 | catch (Exception e) { 97 | var errors = Arrays.asList( 98 | DmnValidationResultBuilder 99 | .create() 100 | .withMessage(e.getMessage()) 101 | .withSeverity(ErrorSeverity.ERROR) 102 | .build() 103 | ); 104 | response.setErrors(errors); 105 | return response; 106 | } 107 | } 108 | 109 | private DmnValidationResult mapModel(ValidationResult result) { 110 | return DmnValidationResultBuilder 111 | .create() 112 | .withTableId(getTableId(result.getElement())) 113 | .withRuleId(getRuleId(result.getElement())) 114 | .withCellId(getCellId(result.getElement())) 115 | .withCounterRuleId(tryFindCounter(result.getMessage())) 116 | .withMessage(result.getMessage()) 117 | .withSeverity(correctSeverity(result)) 118 | .build(); 119 | } 120 | 121 | public ErrorSeverity correctSeverity(ValidationResult result) { 122 | if (MESSAGE_NOT_CONNECTED_TABLE.equals(result.getMessage())) { 123 | return ErrorSeverity.WARNING; 124 | } 125 | return ErrorSeverity.ofSeverity(result.getSeverity()); 126 | } 127 | 128 | private String tryFindCounter(String message) { 129 | if (message.contains(SEQUEL_CORRESPONDING_RULE)) { 130 | return Optional 131 | .ofNullable(message.split(SEQUEL_CORRESPONDING_RULE)) 132 | .filter(arr -> arr.length > 1) 133 | .map(arr -> arr[arr.length - 1]) 134 | .map(value -> value.trim()) 135 | .orElse(null); 136 | } 137 | return null; 138 | } 139 | 140 | private String getTableId(ModelElementInstance instance) { 141 | if (isDecisionRule(instance)) { 142 | return Optional.ofNullable(instance) 143 | .map(ModelElementInstance::getParentElement) 144 | .map(ModelElementInstance::getParentElement) 145 | .map(element -> element.getAttributeValue(ATTRIBUTE_NAME_RULE_ID)) 146 | .orElse(null); 147 | } else if(isLiteralExpression(instance)) { 148 | return Optional.ofNullable(instance) 149 | .map(ModelElementInstance::getParentElement) 150 | .map(ModelElementInstance::getParentElement) 151 | .map(ModelElementInstance::getParentElement) 152 | .map(element -> element.getAttributeValue(ATTRIBUTE_NAME_RULE_ID)) 153 | .orElse(null); 154 | } else if(isOutputClause(instance)) { 155 | return Optional.ofNullable(instance) 156 | .map(ModelElementInstance::getParentElement) 157 | .map(ModelElementInstance::getParentElement) 158 | .map(element -> element.getAttributeValue(ATTRIBUTE_NAME_RULE_ID)) 159 | .orElse(null); 160 | } else if(isInputEntry(instance) || isOutputEntry(instance)) { 161 | return Optional.ofNullable(instance) 162 | .map(ModelElementInstance::getParentElement) 163 | .map(ModelElementInstance::getParentElement) 164 | .map(ModelElementInstance::getParentElement) 165 | .map(element -> element.getAttributeValue(ATTRIBUTE_NAME_RULE_ID)) 166 | .orElse(null); 167 | } 168 | 169 | return Optional.ofNullable(instance) 170 | .map(element -> element.getAttributeValue(ATTRIBUTE_NAME_RULE_ID)) 171 | .orElse(null); 172 | } 173 | 174 | private boolean isInputEntry(ModelElementInstance instance) { 175 | return "inputEntry".equals(instance.getElementType().getTypeName()); 176 | } 177 | 178 | private boolean isOutputEntry(ModelElementInstance instance) { 179 | return "outputEntry".equals(instance.getElementType().getTypeName()); 180 | } 181 | 182 | private String getRuleId(ModelElementInstance instance) { 183 | if (isDecisionRule(instance)) { 184 | return instance.getAttributeValue(ATTRIBUTE_NAME_RULE_ID); 185 | } else if (isInputEntry(instance) || isOutputEntry(instance)) { 186 | return instance.getParentElement().getAttributeValue(ATTRIBUTE_NAME_RULE_ID); 187 | } 188 | 189 | return null; 190 | } 191 | 192 | private String getCellId(ModelElementInstance instance) { 193 | if (isInputEntry(instance) || isOutputEntry(instance)) { 194 | return instance.getAttributeValue(ATTRIBUTE_NAME_RULE_ID); 195 | } 196 | return null; 197 | } 198 | 199 | private boolean isDecisionRule(ModelElementInstance instance) { 200 | return "decisionRule".equals(instance.getElementType().getBaseType().getTypeName()); 201 | } 202 | 203 | private boolean isLiteralExpression(ModelElementInstance instance) { 204 | return "literalExpression".equals(instance.getElementType().getBaseType().getTypeName()); 205 | } 206 | 207 | private boolean isOutputClause(ModelElementInstance instance) { 208 | return "outputClause".equals(instance.getElementType().getBaseType().getTypeName()); 209 | } 210 | 211 | private List runValidators(final DmnModelInstance dmnModelInstance) { 212 | return getValidators().stream() 213 | .flatMap(validator -> validator.apply(dmnModelInstance).stream()) 214 | .collect(Collectors.toList()); 215 | } 216 | 217 | private List getValidators() { 218 | return validators; 219 | } 220 | 221 | private void initValidators() { 222 | 223 | if (validatorPackages == null) { 224 | validatorPackages = new String[] {VALIDATOR_PACKAGE, VALIDATOR_PACKAGE + ".core"}; 225 | } 226 | 227 | if (validatorClasses == null) { 228 | validatorClasses = new String[] { }; 229 | } 230 | 231 | final ScanResult scanResult = new ClassGraph() 232 | .whitelistClasses(Validator.class.getName()) 233 | .whitelistPackages(VALIDATOR_CORE_PACKAGE) 234 | .whitelistPackagesNonRecursive(validatorPackages) 235 | .whitelistClasses(validatorClasses) 236 | .scan(); 237 | 238 | final ClassInfoList allValidatorClasses = scanResult.getClassesImplementing(Validator.class.getName()); 239 | 240 | validators = allValidatorClasses.loadClasses(Validator.class).stream() 241 | .filter(validatorClass -> !Modifier.isAbstract(validatorClass.getModifiers())) 242 | .filter(validatorClass -> !Modifier.isInterface(validatorClass.getModifiers())) 243 | .map(this::instantiateValidator) 244 | .collect(Collectors.toList()); 245 | } 246 | 247 | private Validator instantiateValidator(final Class validator) { 248 | try { 249 | return validator.getDeclaredConstructor().newInstance(); 250 | } 251 | catch (IllegalAccessException | InstantiationException | 252 | NoSuchMethodException | InvocationTargetException e) { 253 | throw new RuntimeException("Failed to load validator " + validator, e); 254 | } 255 | } 256 | 257 | } -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/service/DecisionTestService.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.Map.Entry; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import com.fasterxml.jackson.databind.node.ObjectNode; 12 | 13 | import de.lv1871.oss.dmnmgr.api.model.DecisionTestRequest; 14 | import de.lv1871.oss.dmnmgr.api.model.DecisionTestResponse; 15 | import de.lv1871.oss.dmnmgr.api.model.DecisionTestResponse.DecisionTestResponseBuilder; 16 | 17 | import static de.lv1871.oss.tester.test.dmnassert.DmnTest.testExpectation; 18 | 19 | @Service 20 | public class DecisionTestService { 21 | 22 | private static final String ERROR_SIZE_NOT_CORRECT = "Die Anzahl der Treffer weicht von der Erwartung ab."; 23 | 24 | @Autowired 25 | private GenericDecisionService decisionService; 26 | 27 | @Autowired 28 | private VariableMapperService mapperService; 29 | 30 | public DecisionTestResponse testDecision(DecisionTestRequest request) { 31 | 32 | var engine = decisionService.deployAndCreateEngine(request.getXml()); 33 | var decisionSimulationResponse = decisionService 34 | .decide(engine, request.getDmnTableId(), request.getVariables()); 35 | 36 | if (decisionSimulationResponse.getResult() == null) { 37 | // @formatter:off 38 | return DecisionTestResponseBuilder 39 | .create() 40 | .withMessage(decisionSimulationResponse.getMessage()) 41 | .build(); 42 | // @formatter:on 43 | } 44 | 45 | try { 46 | 47 | List> expectedDataAssertionFailed = new ArrayList<>(); 48 | var message = decisionSimulationResponse.getMessage(); 49 | 50 | for (ObjectNode expectedNode : request.getExpectedData()) { 51 | var expectedObjectMap = mapperService.getVariablesFromJsonAsMap(expectedNode); 52 | expectedDataAssertionFailed.addAll(testExpectation(decisionSimulationResponse, expectedObjectMap)); 53 | } 54 | 55 | if (request.getExpectedData().size() != decisionSimulationResponse.getResult().size()) { 56 | message = Optional 57 | .ofNullable(message) 58 | .map(value -> String.format("%s; %s", value, ERROR_SIZE_NOT_CORRECT)) 59 | .orElse(ERROR_SIZE_NOT_CORRECT); 60 | } 61 | // @formatter:off 62 | return DecisionTestResponseBuilder 63 | .create() 64 | .withResult(decisionSimulationResponse.getResult()) 65 | .withResultRuleIds(decisionSimulationResponse.getResultRuleIds()) 66 | .withExpectedDataAssertionFailed(expectedDataAssertionFailed) 67 | .withTestSucceeded(expectedDataAssertionFailed.size() < 1 && 68 | !Optional.ofNullable(message).isPresent()) 69 | .withMessage(message) 70 | .build(); 71 | // @formatter:on 72 | } catch (Exception e) { 73 | return DecisionTestResponseBuilder.create().withMessage(e.getMessage()).build(); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/service/DownloadService.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.service; 2 | 3 | import java.io.InputStreamReader; 4 | import java.net.URL; 5 | 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.google.common.base.Charsets; 9 | import com.google.common.io.CharStreams; 10 | 11 | import de.lv1871.oss.dmnmgr.api.model.OpenApiDefinitionResponse; 12 | 13 | @Service 14 | public class DownloadService { 15 | 16 | public OpenApiDefinitionResponse downloadOpenApiDefinition(String urlString) { 17 | try { 18 | var url = new URL(urlString); 19 | 20 | String readUrlContents; 21 | try (InputStreamReader reader = new InputStreamReader(url.openStream(), Charsets.UTF_8)) { 22 | readUrlContents = CharStreams.toString(reader); 23 | } 24 | 25 | var response = new OpenApiDefinitionResponse(); 26 | response.setApiUrl(urlString); 27 | response.setSwaggerDefinition(readUrlContents); 28 | 29 | return response; 30 | } catch (Exception exception) { 31 | throw new RuntimeException(exception); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/service/GenericDecisionService.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | 6 | import com.fasterxml.jackson.databind.node.ObjectNode; 7 | 8 | import de.lv1871.oss.dmnmgr.api.model.DecisionSimulationRequest; 9 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse; 10 | import de.lv1871.oss.tester.test.dmnassert.model.DecisionSimulationResponse.DecisionTestCaseResponseBuilder; 11 | import de.lv1871.oss.tester.test.domain.DecisionEngine; 12 | 13 | @Service 14 | public class GenericDecisionService { 15 | 16 | @Autowired 17 | private VariableMapperService mapperService; 18 | 19 | public DecisionSimulationResponse decide(DecisionSimulationRequest decisionRequest) { 20 | try { 21 | var engine = deployAndCreateEngine(decisionRequest.getXml()); 22 | return decide(engine, decisionRequest.getDmnTableId(), decisionRequest.getVariables()); 23 | } catch (Exception exception) { 24 | exception.printStackTrace(); 25 | if (exception.getCause() != null) { 26 | return DecisionTestCaseResponseBuilder.create().withMessage(exception.getCause().getMessage()).build(); 27 | } 28 | return DecisionTestCaseResponseBuilder.create().withMessage(exception.getMessage()).build(); 29 | } 30 | } 31 | 32 | public DecisionSimulationResponse decide(DecisionEngine engine, String dmnTableId, ObjectNode variablesNode) { 33 | 34 | try { 35 | var variables = mapperService.getVariablesFromJsonAsMap(variablesNode); 36 | var decisionResult = engine.evaluateDecisionByKey(dmnTableId, variables); 37 | 38 | if (decisionResult.getResultList().stream().filter(result -> result.get(null) != null).count() > 0) { 39 | return DecisionTestCaseResponseBuilder.create() 40 | .withMessage("Ein oder meherere Output-Felder haben keinen Namen.").build(); 41 | } 42 | 43 | return DecisionTestCaseResponseBuilder.create().withResultRuleIds(engine.getResultRules(dmnTableId)) 44 | .withResultTableRuleIds(engine.getResultRulesAllTables()).withResult(decisionResult.getResultList()) 45 | .build(); 46 | } catch (Exception exception) { 47 | exception.printStackTrace(); 48 | if (exception.getCause() != null) { 49 | return DecisionTestCaseResponseBuilder.create().withMessage(exception.getCause().getMessage()).build(); 50 | } 51 | return DecisionTestCaseResponseBuilder.create().withMessage(exception.getMessage()).build(); 52 | } 53 | } 54 | 55 | public DecisionEngine deployAndCreateEngine(String dmnXml) { 56 | return DecisionEngine 57 | .createEngine() 58 | .parseDecision(dmnXml); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /dmnmgr/src/main/java/de/lv1871/oss/dmnmgr/service/VariableMapperService.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.service; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | 6 | import javax.annotation.PostConstruct; 7 | 8 | import org.springframework.stereotype.Service; 9 | 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.fasterxml.jackson.databind.SerializationFeature; 12 | import com.fasterxml.jackson.databind.node.ObjectNode; 13 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 14 | 15 | @Service 16 | public class VariableMapperService { 17 | 18 | private static ObjectMapper mapper; 19 | 20 | @SuppressWarnings("unchecked") 21 | public HashMap getVariablesFromJsonAsMap(ObjectNode json) { 22 | var valueAsString = json.toString(); 23 | try { 24 | return mapper.readValue(valueAsString, HashMap.class); 25 | } catch (IOException e) { 26 | throw new RuntimeException(e.getMessage()); 27 | } 28 | } 29 | 30 | @PostConstruct 31 | private static void initMapper() { 32 | mapper = new ObjectMapper(); 33 | mapper.registerModule(new JavaTimeModule()); 34 | mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dmnmgr/src/main/resources/META-INF/processes.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidibl/dmnmgr-server/1e4871a09d23f8e1d9fae6261bf4ad98ee941c9e/dmnmgr/src/main/resources/META-INF/processes.xml -------------------------------------------------------------------------------- /dmnmgr/src/main/resources/META-INF/services/javax.ws.rs.ext.RuntimeDelegate: -------------------------------------------------------------------------------- 1 | org.jboss.resteasy.spi.ResteasyProviderFactory -------------------------------------------------------------------------------- /dmnmgr/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: development 4 | 5 | --- 6 | 7 | spring: 8 | profiles: development 9 | server: 10 | port: 11401 11 | logging: 12 | file: log.log 13 | level: 14 | org: 15 | springframework: 16 | web: INFO 17 | 18 | --- 19 | 20 | spring: 21 | profiles: test 22 | server: 23 | port: 11401 24 | logging: 25 | file: log.log 26 | level: 27 | org: 28 | springframework: 29 | web: INFO 30 | 31 | --- 32 | 33 | spring: 34 | profiles: consolidation 35 | server: 36 | port: 11401 37 | logging: 38 | file: log.log 39 | level: 40 | org: 41 | springframework: 42 | web: INFO 43 | 44 | --- 45 | 46 | spring: 47 | profiles: production 48 | server: 49 | port: 11401 50 | logging: 51 | file: log.log 52 | level: 53 | org: 54 | springframework: 55 | web: INFO -------------------------------------------------------------------------------- /dmnmgr/src/test/java/de/lv1871/oss/dmnmgr/domain/CheckerFunctionsTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.domain; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | public class CheckerFunctionsTest { 9 | 10 | @Test 11 | public void testFindsUnescapedQuotationMark() { 12 | String value = "Hallo\" Welt"; 13 | Boolean result = CheckerFunctions.containesUnquotedtQuotationMark(value); 14 | assertTrue(result); 15 | } 16 | 17 | @Test 18 | public void testFindsUnescapedQuotationMarkWithOneCorrect() { 19 | String value = "Hal\\\"lo\" Welt"; 20 | Boolean result = CheckerFunctions.containesUnquotedtQuotationMark(value); 21 | assertTrue(result); 22 | } 23 | 24 | @Test 25 | public void testFindsEscapedQuotationMark() { 26 | String value = "Hallo\\\" Welt"; 27 | Boolean result = CheckerFunctions.containesUnquotedtQuotationMark(value); 28 | assertFalse(result); 29 | } 30 | } -------------------------------------------------------------------------------- /dmnmgr/src/test/java/de/lv1871/oss/dmnmgr/service/AdvancedDmnCheckServiceTest.java: -------------------------------------------------------------------------------- 1 | package de.lv1871.oss.dmnmgr.service; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import de.lv1871.oss.dmnmgr.api.model.DmnValidationResponse; 15 | import de.lv1871.oss.dmnmgr.api.model.DmnValidationResult; 16 | 17 | public class AdvancedDmnCheckServiceTest { 18 | 19 | private AdvancedDmnCheckService cut; 20 | 21 | @Before 22 | public void init() { 23 | cut = new AdvancedDmnCheckService(); 24 | cut.init(); 25 | } 26 | 27 | @Test 28 | public void testCheckHiddenRule() { 29 | DmnValidationResponse results = cut.validateDecision(this.getClass().getResourceAsStream("/meineTestDecision.dmn")); 30 | 31 | List decisionTable = results.getErrors().stream().map(DmnValidationResult::getTableId).distinct().collect(Collectors.toList()); 32 | List decisionRules = results.getErrors().stream().map(DmnValidationResult::getRuleId).distinct().collect(Collectors.toList()); 33 | 34 | assertNotNull(results); 35 | 36 | assertEquals(1, decisionTable.size()); 37 | assertEquals("decision", decisionTable.get(0)); 38 | 39 | assertEquals(2, decisionRules.size()); 40 | assertTrue(allContained(decisionRules, Arrays.asList("DecisionRule_0fuuqy7", "DecisionRule_1op4sed"))); 41 | } 42 | 43 | @Test 44 | public void testInputExpressionRequired() { 45 | DmnValidationResponse results = cut.validateDecision(this.getClass().getResourceAsStream("/requiredInputExpressionTest.dmn")); 46 | 47 | List decisionTable = results.getErrors().stream().map(DmnValidationResult::getTableId).distinct().collect(Collectors.toList()); 48 | 49 | assertNotNull(results); 50 | assertEquals(0, results.getWarnings().size()); 51 | assertEquals(1, results.getErrors().size()); 52 | assertEquals("decision", decisionTable.get(0)); 53 | assertEquals("InputExpression has no expressiontext", results.getErrors().get(0).getMessage()); 54 | 55 | } 56 | 57 | @Test 58 | public void testInputExpressionTokenizedHasnoError() { 59 | DmnValidationResponse results = cut.validateDecision(this.getClass().getResourceAsStream("/testTokenizedExpression.dmn")); 60 | 61 | assertNotNull(results); 62 | assertEquals(0, results.getWarnings().size()); 63 | assertEquals(0, results.getErrors().size()); 64 | } 65 | 66 | @Test 67 | public void testInputExpressionTokenizedWithError() { 68 | DmnValidationResponse results = cut.validateDecision(this.getClass().getResourceAsStream("/testTokenizedWithErrorExpression.dmn")); 69 | 70 | assertNotNull(results); 71 | assertEquals(0, results.getWarnings().size()); 72 | assertEquals(1, results.getErrors().size()); 73 | 74 | } 75 | 76 | @Test 77 | public void testOutputNameRequired() { 78 | DmnValidationResponse results = cut.validateDecision(this.getClass().getResourceAsStream("/requiredOutputNameTest.dmn")); 79 | 80 | List decisionTable = results.getErrors().stream().map(DmnValidationResult::getTableId).distinct().collect(Collectors.toList()); 81 | 82 | assertNotNull(results); 83 | assertEquals(0, results.getWarnings().size()); 84 | assertEquals(1, results.getErrors().size()); 85 | assertEquals("decision", decisionTable.get(0)); 86 | assertEquals("Output has no name", results.getErrors().get(0).getMessage()); 87 | 88 | } 89 | 90 | @Test 91 | public void testJuelGetsBasicValidation() { 92 | DmnValidationResponse results = cut.validateDecision(this.getClass().getResourceAsStream("/juelfeel.dmn")); 93 | 94 | List decisionTable = results.getErrors().stream().map(DmnValidationResult::getTableId).distinct().collect(Collectors.toList()); 95 | 96 | assertNotNull(results); 97 | assertEquals(0, results.getWarnings().size()); 98 | assertEquals(3, results.getErrors().size()); 99 | assertEquals("decision", decisionTable.get(0)); 100 | } 101 | 102 | private boolean allContained(List decisionRules, List expectedToBeContained) { 103 | return expectedToBeContained 104 | .stream() 105 | .filter(value -> !decisionRules.contains(value)) 106 | .count() < 1; 107 | } 108 | } -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/juelfeel.dmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | test 8 | 9 | 10 | 11 | 12 | 13 | test = "hallo" 14 | 15 | 16 | "Welt" 17 | 18 | 19 | 20 | 21 | "hallo 22 | 23 | 24 | "Huhu" 25 | 26 | 27 | 28 | 29 | Welt" 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | "hui", "bui" 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | "krass" 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/juelfeel.dmnapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "dmnPath": "juelfeel.dmn", 3 | "testsuite": { 4 | "decision": { 5 | "tests": [] 6 | }, 7 | "null": { 8 | "tests": [] 9 | } 10 | }, 11 | "definitions": { 12 | "decision": { 13 | "requestModel": { 14 | "type": "object", 15 | "properties": [ 16 | { 17 | "type": "string", 18 | "name": "test" 19 | } 20 | ] 21 | } 22 | } 23 | }, 24 | "plugins": [] 25 | } -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/meineTestDecision.dmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | input 11 | 12 | 13 | 14 | 15 | 16 | "Hallo" 17 | 18 | 19 | "Welt" 20 | 21 | 22 | 23 | 24 | "Wow" 25 | 26 | 27 | "Cool" 28 | 29 | 30 | 31 | 32 | "Hallo" 33 | 34 | 35 | "zwei" 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/meineTestDecision.dmnapp.json: -------------------------------------------------------------------------------- 1 | {"dmnPath":"meineTestDecision.dmn","testsuite":{"null":{"tests":[]},"definitions_09waf08":{"tests":[]},"decision":{"tests":[]}},"definitions":{},"plugins":[]} -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/requiredInputExpressionTest.dmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | "huch" 14 | 15 | 16 | "Welt" 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/requiredInputExpressionTest.dmnapp.json: -------------------------------------------------------------------------------- 1 | {"dmnPath":"requiredInputExpressionTest.dmn","testsuite":{"null":{"tests":[]},"decision":{"tests":[]}},"definitions":{},"plugins":[]} -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/requiredOutputNameTest.dmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | "eins" 8 | 9 | 10 | 11 | 12 | 13 | "Hallo" 14 | 15 | 16 | "Welt" 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/requiredOutputNameTest.dmnapp.json: -------------------------------------------------------------------------------- 1 | {"dmnPath":"requiredOutputNameTest.dmn","testsuite":{"null":{"tests":[]},"decision":{"tests":[]}},"definitions":{},"plugins":[]} -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/testTokenizedExpression.dmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | test 8 | 9 | 10 | 11 | 12 | 13 | "Hallo", null 14 | 15 | 16 | "Welt" 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/testTokenizedExpression.dmnapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "dmnPath": "testTokenizedExpression.dmn", 3 | "testsuite": { 4 | "decision": { 5 | "tests": [] 6 | } 7 | }, 8 | "definitions": {}, 9 | "plugins": [] 10 | } -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/testTokenizedWithErrorExpression.dmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | input 8 | 9 | 10 | 11 | 12 | 13 | "Hallo, "Hier" 14 | 15 | 16 | "Welt" 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /dmnmgr/src/test/resources/testTokenizedWithErrorExpression.dmnapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "dmnPath": "testTokenizedWithErrorExpression.dmn", 3 | "testsuite": { 4 | "null": { 5 | "tests": [] 6 | }, 7 | "decision": { 8 | "tests": [] 9 | } 10 | }, 11 | "definitions": {}, 12 | "plugins": [] 13 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | de.lv1871.oss 5 | dmnmgr-project 6 | 1.3.0-SNAPSHOT 7 | pom 8 | 9 | dmnmgr-project 10 | http://maven.apache.org 11 | 12 | 13 | dmnmgr-tester 14 | dmnmgr-test-driver 15 | dmnmgr 16 | 17 | 18 | 19 | UTF-8 20 | 3.1 21 | 11 22 | 11 23 | 2.5.3 24 | 7.11.0 25 | 1.3.0 26 | 2.2.5.RELEASE 27 | 1.3.1 28 | 2.1 29 | 1.2 30 | 2.2.0.RELEASE 31 | 4.12 32 | 2.10.0 33 | 2.5.0 34 | 1.4.193 35 | 1.10.1 36 | 1.1.4 37 | 38 | 39 | 40 | https://github.com/davidibl/dmnmgr-server 41 | scm:git:git://github.com:davidibl/dmnmgr-server.git 42 | scm:git:git@github.com:davidibl/dmnmgr-server.git 43 | HEAD 44 | 45 | 46 | 47 | LV 1871 48 | https://lv1871.de/ 49 | 50 | 51 | 52 | 53 | David Ibl 54 | 55 | 56 | 57 | 58 | 59 | The Apache Software License, Version 2.0 60 | http://www.apache.org/licenses/LICENSE-2.0.txt 61 | repo 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.camunda.bpm.dmn 71 | camunda-engine-dmn 72 | ${camunda.version} 73 | 74 | 75 | org.camunda.bpm 76 | camunda-engine 77 | ${camunda.version} 78 | 79 | 80 | org.camunda.bpm.extension.dmn.scala 81 | dmn-engine-camunda-plugin 82 | ${camunda.dmn.plugin.version} 83 | 84 | 85 | org.camunda.bpm.extension.feel.scala 86 | feel-engine-plugin 87 | ${feel.scala.version} 88 | 89 | 90 | org.camunda.bpm.extension.feel.scala 91 | feel-engine-factory 92 | ${feel.scala.version} 93 | 94 | 95 | org.camunda.bpm.extension.feel.scala 96 | feel-engine 97 | ${feel.scala.version} 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-configuration-processor 104 | true 105 | ${springframework.boot.version} 106 | 107 | 108 | org.springframework.boot 109 | spring-boot-starter-web 110 | ${springframework.boot.version} 111 | 112 | 113 | org.springframework.boot 114 | spring-boot-starter-logging 115 | ${springframework.boot.version} 116 | 117 | 118 | org.springframework.boot 119 | spring-boot-starter-actuator 120 | ${springframework.boot.version} 121 | 122 | 123 | 124 | 125 | com.fasterxml.jackson.datatype 126 | jackson-datatype-jsr310 127 | ${jackson.version} 128 | 129 | 130 | com.fasterxml.jackson.core 131 | jackson-annotations 132 | ${jackson.version} 133 | 134 | 135 | com.fasterxml.jackson.core 136 | jackson-core 137 | ${jackson.version} 138 | 139 | 140 | com.fasterxml.jackson.core 141 | jackson-databind 142 | ${jackson.version} 143 | 144 | 145 | 146 | 147 | com.h2database 148 | h2 149 | ${h2.version} 150 | 151 | 152 | io.springfox 153 | springfox-swagger-ui 154 | ${swagger.version} 155 | 156 | 157 | io.springfox 158 | springfox-swagger2 159 | ${swagger.version} 160 | 161 | 162 | de.redsix 163 | dmn-check 164 | ${redsix.dmn-check.version} 165 | 166 | 167 | de.redsix 168 | dmn-check-core 169 | ${redsix.dmn-check.version} 170 | 171 | 172 | de.redsix 173 | dmn-check-validators 174 | ${redsix.dmn-check.version} 175 | 176 | 177 | 178 | 179 | junit 180 | junit 181 | ${junit.version} 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /readme_assets/general_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidibl/dmnmgr-server/1e4871a09d23f8e1d9fae6261bf4ad98ee941c9e/readme_assets/general_architecture.png --------------------------------------------------------------------------------