Skip to content

Commit 77b2a3f

Browse files
Merge pull request #232 from CodeForPhilly/validate-form-paths-drawer
FormEditor Basic Validation
2 parents 127b1e2 + 0013de3 commit 77b2a3f

File tree

12 files changed

+522
-83
lines changed

12 files changed

+522
-83
lines changed

builder-api/src/main/java/org/acme/controller/EligibilityCheckResource.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import jakarta.ws.rs.core.Context;
88
import jakarta.ws.rs.core.MediaType;
99
import jakarta.ws.rs.core.Response;
10+
import com.fasterxml.jackson.databind.JsonNode;
1011
import org.acme.auth.AuthUtils;
1112
import org.acme.constants.CheckStatus;
1213
import org.acme.model.domain.EligibilityCheck;
@@ -243,6 +244,18 @@ public Response publishCustomCheck(@Context SecurityIdentity identity, @PathPara
243244
.build();
244245
}
245246

247+
// Extract input schema from DMN
248+
try {
249+
String workingDmn = workingDmnOpt.get();
250+
HashMap<String, String> dmnDependenciesMap = new HashMap<String, String>();
251+
JsonNode inputSchema = dmnService.extractInputSchema(workingDmn, dmnDependenciesMap, check.getName());
252+
check.setInputDefinition(inputSchema);
253+
} catch (Exception e) {
254+
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
255+
.entity(Map.of("error", "Failed to extract input schema for check " + check.getId()))
256+
.build();
257+
}
258+
246259
// Update workingCheck so that the incremented version number is saved
247260
check.setVersion(incrementMajorVersion(check.getVersion()));
248261
try {

builder-api/src/main/java/org/acme/service/DmnService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package org.acme.service;
2+
import com.fasterxml.jackson.databind.JsonNode;
23
import org.acme.enums.EvaluationResult;
34

45
import java.util.List;
@@ -17,4 +18,9 @@ public EvaluationResult evaluateDmn(
1718
Map<String, Object> inputs,
1819
Map<String, Object> parameters
1920
) throws Exception;
21+
public JsonNode extractInputSchema(
22+
String dmnXml,
23+
Map<String, String> dependenciesMap,
24+
String modelId
25+
) throws Exception;
2026
}

builder-api/src/main/java/org/acme/service/KieDmnService.java

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
import org.kie.api.runtime.KieSession;
1313
import org.kie.dmn.api.core.*;
1414
import org.kie.dmn.api.core.ast.DecisionNode;
15+
import org.kie.dmn.api.core.ast.InputDataNode;
16+
import com.fasterxml.jackson.databind.JsonNode;
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import com.fasterxml.jackson.databind.node.ArrayNode;
19+
import com.fasterxml.jackson.databind.node.ObjectNode;
1520

1621
import java.io.*;
1722
import java.util.*;
@@ -28,6 +33,16 @@ public DmnCompilationResult(byte[] dmnBytes, List<String> errors) {
2833
}
2934
}
3035

36+
class DmnModelResult {
37+
public DMNModel model;
38+
public DMNRuntime runtime;
39+
40+
public DmnModelResult(DMNModel model, DMNRuntime runtime) {
41+
this.model = model;
42+
this.runtime = runtime;
43+
}
44+
}
45+
3146
@ApplicationScoped
3247
public class KieDmnService implements DmnService {
3348
@Inject
@@ -43,6 +58,23 @@ private KieSession initializeKieSession(byte[] moduleBytes) throws IOException {
4358
return kieContainer.newKieSession();
4459
}
4560

61+
private DmnModelResult compileAndGetDmnModel(String dmnXml, Map<String, String> dependenciesMap, String modelId) throws Exception {
62+
DmnCompilationResult compilationResult = compileDmnModel(dmnXml, dependenciesMap, modelId);
63+
if (!compilationResult.errors.isEmpty()) {
64+
throw new IllegalStateException("DMN compilation failed: " + String.join(", ", compilationResult.errors));
65+
}
66+
67+
KieSession kieSession = initializeKieSession(compilationResult.dmnBytes);
68+
DMNRuntime dmnRuntime = kieSession.getKieRuntime(DMNRuntime.class);
69+
70+
List<DMNModel> dmnModels = dmnRuntime.getModels();
71+
if (dmnModels.size() != 1) {
72+
throw new RuntimeException("Expected exactly one DMN model, found: " + dmnModels.size());
73+
}
74+
75+
return new DmnModelResult(dmnModels.get(0), dmnRuntime);
76+
}
77+
4678
// Validates that the DMN XML can compile and contains the required decision.
4779
// Returns a list of error messages if any issues are found.
4880
public List<String> validateDmnXml (
@@ -148,27 +180,12 @@ public EvaluationResult evaluateDmn(
148180
String dmnXml = dmnXmlOpt.get();
149181

150182
HashMap<String, String> dmnDependenciesMap = new HashMap<String, String>();
151-
DmnCompilationResult compilationResult = compileDmnModel(dmnXml, dmnDependenciesMap, dmnModelName);
152-
if (!compilationResult.errors.isEmpty()) {
153-
Log.error("DMN Compilation errors for model " + dmnModelName + ":");
154-
for (String error : compilationResult.errors) {
155-
Log.error(error);
156-
}
157-
throw new IllegalStateException("DMN Model compilation failed for model: " + dmnModelName);
158-
}
183+
DmnModelResult modelResult = compileAndGetDmnModel(dmnXml, dmnDependenciesMap, dmnModelName);
184+
DMNModel dmnModel = modelResult.model;
185+
DMNRuntime dmnRuntime = modelResult.runtime;
159186

160-
KieSession kieSession = initializeKieSession(compilationResult.dmnBytes);
161-
DMNRuntime dmnRuntime = kieSession.getKieRuntime(DMNRuntime.class);
162-
163-
List<DMNModel> dmnModels = dmnRuntime.getModels();
164-
if (dmnModels.size() != 1) {
165-
throw new RuntimeException("Expected exactly one DMN model, found: " + dmnModels.size());
166-
}
187+
Log.info("DMN Model loaded: " + dmnModel.getName() + " Namespace: " + dmnModel.getNamespace());
167188

168-
Log.info("DMN Model loaded: " + dmnModels.get(0).getName() + " Namespace: " + dmnModels.get(0).getNamespace());
169-
170-
// Prepare model and context using inputs
171-
DMNModel dmnModel = dmnModels.get(0);
172189
DMNContext context = dmnRuntime.newContext();
173190

174191
for (Map.Entry<String, Object> input : inputs.entrySet()) {
@@ -202,4 +219,66 @@ else if (result instanceof Boolean && !(Boolean) result) {
202219
}
203220
throw new RuntimeException("Unexpected decision result type: " + result.getClass().getName());
204221
}
222+
223+
public JsonNode extractInputSchema(
224+
String dmnXml,
225+
Map<String, String> dependenciesMap,
226+
String modelId
227+
) throws Exception {
228+
Log.info("Extracting input schema from DMN model: " + modelId);
229+
230+
DmnModelResult modelResult = compileAndGetDmnModel(dmnXml, dependenciesMap, modelId);
231+
DMNModel dmnModel = modelResult.model;
232+
Set<InputDataNode> inputs = dmnModel.getInputs();
233+
234+
ObjectMapper mapper = new ObjectMapper();
235+
236+
// Create custom object for non-parameters inputs
237+
ObjectNode customObj = mapper.createObjectNode();
238+
customObj.put("type", "object");
239+
240+
ArrayList<String> requiredCustomProps = new ArrayList<String>();
241+
242+
// Fill out custom properties and check if parameters input exists
243+
ObjectNode customProperties = mapper.createObjectNode();
244+
boolean hasParametersInput = false;
245+
for (InputDataNode input : inputs) {
246+
String inputName = input.getName();
247+
if ("parameters".equals(inputName)) {
248+
hasParametersInput = true;
249+
continue;
250+
}
251+
// Add as empty object (no type constraints)
252+
customProperties.set(inputName, mapper.createObjectNode());
253+
requiredCustomProps.add(inputName);
254+
}
255+
ArrayNode requiredCustomPropsNode = mapper.valueToTree(requiredCustomProps);
256+
customObj.set("required", requiredCustomPropsNode);
257+
customObj.set("properties", customProperties);
258+
259+
ObjectNode properties = mapper.createObjectNode();
260+
properties.set("custom", customObj);
261+
262+
ArrayList<String> requiredTopLevelProps = new ArrayList<String>();
263+
requiredTopLevelProps.add("custom");
264+
265+
// Only add parameters to schema if it's defined as an input in the DMN
266+
if (hasParametersInput) {
267+
ObjectNode parametersObj = mapper.createObjectNode();
268+
ObjectNode parametersProperties = mapper.createObjectNode();
269+
parametersObj.put("type", "object");
270+
parametersObj.set("properties", parametersProperties);
271+
properties.set("parameters", parametersObj);
272+
requiredTopLevelProps.add("parameters");
273+
}
274+
275+
ObjectNode schema = mapper.createObjectNode();
276+
ArrayNode requiredTopLevelPropsNode = mapper.valueToTree(requiredTopLevelProps);
277+
schema.put("type", "object");
278+
schema.set("properties", properties);
279+
schema.set("required", requiredTopLevelPropsNode);
280+
281+
Log.info("Extracted " + inputs.size() + " inputs from DMN model: " + modelId);
282+
return schema;
283+
}
205284
}

builder-frontend/package-lock.json

Lines changed: 149 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)