TSK-1942: optimized generation of DMN model during upload
This commit is contained in:
parent
3c2688daef
commit
f1705c2676
|
@ -1,6 +1,7 @@
|
|||
package pro.taskana.routing.dmn.service;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -10,7 +11,6 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.camunda.bpm.dmn.xlsx.AdvancedSpreadsheetAdapter;
|
||||
import org.camunda.bpm.dmn.xlsx.XlsxConverter;
|
||||
import org.camunda.bpm.model.dmn.Dmn;
|
||||
import org.camunda.bpm.model.dmn.DmnModelInstance;
|
||||
import org.camunda.bpm.model.dmn.instance.OutputEntry;
|
||||
|
@ -61,18 +61,22 @@ public class DmnConverterService {
|
|||
|
||||
taskanaEngine.checkRoleMembership(TaskanaRole.ADMIN, TaskanaRole.BUSINESS_ADMIN);
|
||||
|
||||
StringBuilder serializedRules = new StringBuilder();
|
||||
|
||||
try (InputStream inputStream = new BufferedInputStream(excelRoutingFile.getInputStream())) {
|
||||
|
||||
XlsxConverter converter = new XlsxConverter();
|
||||
converter.setIoDetectionStrategy(new AdvancedSpreadsheetAdapter());
|
||||
|
||||
DmnModelInstance dmnModelInstance = converter.convert(inputStream);
|
||||
validateOutputs(dmnModelInstance);
|
||||
DmnModelInstance dmnModelInstance = converter.convert(inputStream, serializedRules);
|
||||
DmnModelInstance patchedModel =
|
||||
mergeSerializedRulesIntoDmn(dmnModelInstance, serializedRules);
|
||||
validateOutputs(patchedModel);
|
||||
|
||||
InputEntriesSanitizer.sanitizeRegexInsideInputEntries(dmnModelInstance);
|
||||
InputEntriesSanitizer.sanitizeRegexInsideInputEntries(patchedModel);
|
||||
|
||||
if (DmnValidatorManager.isDmnUploadProviderEnabled()) {
|
||||
DmnValidatorManager.getInstance(taskanaEngine).validate(dmnModelInstance);
|
||||
DmnValidatorManager.getInstance(taskanaEngine).validate(patchedModel);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
@ -80,12 +84,26 @@ public class DmnConverterService {
|
|||
}
|
||||
|
||||
File uploadDestinationFile = new File(dmnUploadPath);
|
||||
Dmn.writeModelToFile(uploadDestinationFile, dmnModelInstance);
|
||||
Dmn.writeModelToFile(uploadDestinationFile, patchedModel);
|
||||
|
||||
return dmnModelInstance;
|
||||
return patchedModel;
|
||||
}
|
||||
}
|
||||
|
||||
private DmnModelInstance mergeSerializedRulesIntoDmn(
|
||||
DmnModelInstance originalInstance, StringBuilder serializedRules) {
|
||||
String dmnString = Dmn.convertToString(originalInstance);
|
||||
StringBuilder finalDmn = new StringBuilder();
|
||||
int splitPosition = dmnString.indexOf("</decisionTable>");
|
||||
finalDmn.append(dmnString.substring(0, splitPosition));
|
||||
finalDmn.append(serializedRules);
|
||||
finalDmn.append(dmnString.substring(splitPosition));
|
||||
DmnModelInstance patchedModel =
|
||||
Dmn.readModelFromStream(new ByteArrayInputStream(finalDmn.toString().getBytes()));
|
||||
|
||||
return patchedModel;
|
||||
}
|
||||
|
||||
private Set<KeyDomain> getOutputKeyDomains(DmnModelInstance dmnModel) {
|
||||
Set<KeyDomain> outputKeyDomains = new HashSet<>();
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Copied from the project https://github.com/camunda-community-hub/camunda-dmn-xlsx
|
||||
*/
|
||||
|
||||
package pro.taskana.routing.dmn.service;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.camunda.bpm.dmn.xlsx.SimpleInputOutputDetectionStrategy;
|
||||
import org.camunda.bpm.dmn.xlsx.XlsxWorksheetContext;
|
||||
import org.camunda.bpm.dmn.xlsx.api.SpreadsheetAdapter;
|
||||
import org.camunda.bpm.model.dmn.DmnModelInstance;
|
||||
import org.docx4j.openpackaging.exceptions.Docx4JException;
|
||||
import org.docx4j.openpackaging.packages.SpreadsheetMLPackage;
|
||||
import org.docx4j.openpackaging.parts.DocPropsExtendedPart;
|
||||
import org.docx4j.openpackaging.parts.SpreadsheetML.SharedStrings;
|
||||
import org.docx4j.openpackaging.parts.SpreadsheetML.WorkbookPart;
|
||||
import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart;
|
||||
|
||||
/**
|
||||
* Converter for XLSX files to DMN 1.1 decision tables.
|
||||
*
|
||||
* @author Thorben Lindhauer
|
||||
* @author Holger Hagen
|
||||
*/
|
||||
public class XlsxConverter {
|
||||
protected SpreadsheetAdapter ioDetectionStrategy = new SimpleInputOutputDetectionStrategy();
|
||||
|
||||
public XlsxConverter() {}
|
||||
|
||||
public DmnModelInstance convert(InputStream inputStream, StringBuilder serializedRules) {
|
||||
SpreadsheetMLPackage spreadSheetPackage;
|
||||
|
||||
try {
|
||||
spreadSheetPackage = SpreadsheetMLPackage.load(inputStream);
|
||||
} catch (Docx4JException e) {
|
||||
throw new RuntimeException("cannot load document", e);
|
||||
}
|
||||
|
||||
WorkbookPart workbookPart = spreadSheetPackage.getWorkbookPart();
|
||||
XlsxWorksheetContext worksheetContext;
|
||||
|
||||
try {
|
||||
DocPropsExtendedPart docPropsExtendedPart = spreadSheetPackage.getDocPropsExtendedPart();
|
||||
String worksheetName;
|
||||
if (docPropsExtendedPart != null
|
||||
&& (docPropsExtendedPart.getContents()).getTitlesOfParts() != null) {
|
||||
worksheetName =
|
||||
(String)
|
||||
((docPropsExtendedPart.getContents())
|
||||
.getTitlesOfParts()
|
||||
.getVector()
|
||||
.getVariantOrI1OrI2()
|
||||
.get(0))
|
||||
.getValue();
|
||||
} else {
|
||||
worksheetName = "default";
|
||||
}
|
||||
|
||||
WorksheetPart worksheetPart = workbookPart.getWorksheet(0);
|
||||
SharedStrings sharedStrings = workbookPart.getSharedStrings();
|
||||
worksheetContext =
|
||||
new XlsxWorksheetContext(
|
||||
sharedStrings.getContents(), worksheetPart.getContents(), worksheetName);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not determine worksheet", e);
|
||||
}
|
||||
|
||||
XlsxWorksheetConverter converter =
|
||||
new XlsxWorksheetConverter(worksheetContext, this.ioDetectionStrategy);
|
||||
return converter.convert(serializedRules);
|
||||
}
|
||||
|
||||
public void setIoDetectionStrategy(SpreadsheetAdapter ioDetectionStrategy) {
|
||||
this.ioDetectionStrategy = ioDetectionStrategy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
/* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Copied from the project https://github.com/camunda-community-hub/camunda-dmn-xlsx
|
||||
*/
|
||||
|
||||
package pro.taskana.routing.dmn.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.camunda.bpm.dmn.xlsx.CellContentHandler;
|
||||
import org.camunda.bpm.dmn.xlsx.DmnConversionContext;
|
||||
import org.camunda.bpm.dmn.xlsx.DmnValueNumberConverter;
|
||||
import org.camunda.bpm.dmn.xlsx.DmnValueRangeConverter;
|
||||
import org.camunda.bpm.dmn.xlsx.DmnValueStringConverter;
|
||||
import org.camunda.bpm.dmn.xlsx.FeelSimpleUnaryTestConverter;
|
||||
import org.camunda.bpm.dmn.xlsx.InputOutputColumns;
|
||||
import org.camunda.bpm.dmn.xlsx.XlsxWorksheetContext;
|
||||
import org.camunda.bpm.dmn.xlsx.api.SpreadsheetAdapter;
|
||||
import org.camunda.bpm.dmn.xlsx.api.SpreadsheetCell;
|
||||
import org.camunda.bpm.dmn.xlsx.api.SpreadsheetRow;
|
||||
import org.camunda.bpm.dmn.xlsx.elements.HeaderValuesContainer;
|
||||
import org.camunda.bpm.dmn.xlsx.elements.IndexedDmnColumns;
|
||||
import org.camunda.bpm.model.dmn.Dmn;
|
||||
import org.camunda.bpm.model.dmn.DmnModelInstance;
|
||||
import org.camunda.bpm.model.dmn.HitPolicy;
|
||||
import org.camunda.bpm.model.dmn.impl.DmnModelConstants;
|
||||
import org.camunda.bpm.model.dmn.instance.Decision;
|
||||
import org.camunda.bpm.model.dmn.instance.DecisionTable;
|
||||
import org.camunda.bpm.model.dmn.instance.Definitions;
|
||||
import org.camunda.bpm.model.dmn.instance.Description;
|
||||
import org.camunda.bpm.model.dmn.instance.DmnElement;
|
||||
import org.camunda.bpm.model.dmn.instance.Input;
|
||||
import org.camunda.bpm.model.dmn.instance.InputEntry;
|
||||
import org.camunda.bpm.model.dmn.instance.InputExpression;
|
||||
import org.camunda.bpm.model.dmn.instance.NamedElement;
|
||||
import org.camunda.bpm.model.dmn.instance.Output;
|
||||
import org.camunda.bpm.model.dmn.instance.OutputEntry;
|
||||
import org.camunda.bpm.model.dmn.instance.Rule;
|
||||
import org.camunda.bpm.model.dmn.instance.Text;
|
||||
|
||||
/**
|
||||
* Converter for XLSX worksheets to DMN 1.1 decision tables.
|
||||
*
|
||||
* @author Thorben Lindhauer
|
||||
* @author Holger Hagen
|
||||
*/
|
||||
public class XlsxWorksheetConverter {
|
||||
|
||||
static {
|
||||
CellContentHandler.DEFAULT_HANDLERS.add(new DmnValueRangeConverter());
|
||||
CellContentHandler.DEFAULT_HANDLERS.add(new FeelSimpleUnaryTestConverter());
|
||||
CellContentHandler.DEFAULT_HANDLERS.add(new DmnValueStringConverter());
|
||||
CellContentHandler.DEFAULT_HANDLERS.add(new DmnValueNumberConverter());
|
||||
}
|
||||
|
||||
protected XlsxWorksheetContext worksheetContext;
|
||||
protected DmnConversionContext dmnConversionContext;
|
||||
protected SpreadsheetAdapter spreadsheetAdapter;
|
||||
|
||||
public XlsxWorksheetConverter(
|
||||
XlsxWorksheetContext worksheetContext, SpreadsheetAdapter spreadsheetAdapter) {
|
||||
this.worksheetContext = worksheetContext;
|
||||
this.dmnConversionContext =
|
||||
new DmnConversionContext(
|
||||
worksheetContext, spreadsheetAdapter.getCellContentHandlers(worksheetContext));
|
||||
this.spreadsheetAdapter = spreadsheetAdapter;
|
||||
}
|
||||
|
||||
public DmnModelInstance convert(StringBuilder serializedRules) {
|
||||
|
||||
DmnModelInstance dmnModel = initializeEmptyDmnModel();
|
||||
|
||||
Decision decision = generateElement(dmnModel, Decision.class, worksheetContext.getName());
|
||||
decision.setName(spreadsheetAdapter.determineDecisionName(worksheetContext));
|
||||
dmnModel.getDefinitions().addChildElement(decision);
|
||||
|
||||
DecisionTable decisionTable = generateElement(dmnModel, DecisionTable.class, "decisionTable");
|
||||
decision.addChildElement(decisionTable);
|
||||
|
||||
setHitPolicy(decisionTable);
|
||||
convertInputsOutputs(dmnModel, decisionTable);
|
||||
convertRules(
|
||||
dmnModel,
|
||||
decisionTable,
|
||||
spreadsheetAdapter.determineRuleRows(worksheetContext),
|
||||
serializedRules);
|
||||
|
||||
return dmnModel;
|
||||
}
|
||||
|
||||
protected void setHitPolicy(DecisionTable decisionTable) {
|
||||
HitPolicy hitPolicy = spreadsheetAdapter.determineHitPolicy(worksheetContext);
|
||||
if (hitPolicy != null) {
|
||||
decisionTable.setHitPolicy(hitPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
protected void convertInputsOutputs(DmnModelInstance dmnModel, DecisionTable decisionTable) {
|
||||
|
||||
InputOutputColumns inputOutputColumns =
|
||||
spreadsheetAdapter.determineInputOutputs(worksheetContext);
|
||||
|
||||
// inputs
|
||||
for (HeaderValuesContainer hvc : inputOutputColumns.getInputHeaders()) {
|
||||
Input input = generateElement(dmnModel, Input.class, hvc.getId());
|
||||
decisionTable.addChildElement(input);
|
||||
|
||||
// mandatory
|
||||
InputExpression inputExpression = generateElement(dmnModel, InputExpression.class);
|
||||
Text text = generateText(dmnModel, hvc.getText());
|
||||
inputExpression.setText(text);
|
||||
input.setInputExpression(inputExpression);
|
||||
|
||||
// optionals
|
||||
if (hvc.getLabel() != null) {
|
||||
input.setLabel(hvc.getLabel());
|
||||
}
|
||||
if (hvc.getTypeRef() != null) {
|
||||
inputExpression.setTypeRef(hvc.getTypeRef());
|
||||
}
|
||||
if (hvc.getExpressionLanguage() != null) {
|
||||
inputExpression.setExpressionLanguage(hvc.getExpressionLanguage());
|
||||
}
|
||||
|
||||
dmnConversionContext.getIndexedDmnColumns().addInput(hvc.getColumn(), input);
|
||||
}
|
||||
|
||||
// outputs
|
||||
for (HeaderValuesContainer hvc : inputOutputColumns.getOutputHeaders()) {
|
||||
Output output = generateElement(dmnModel, Output.class, hvc.getId());
|
||||
decisionTable.addChildElement(output);
|
||||
|
||||
// mandatory
|
||||
output.setName(hvc.getText());
|
||||
|
||||
// optionals
|
||||
if (hvc.getLabel() != null) {
|
||||
output.setLabel(hvc.getLabel());
|
||||
}
|
||||
if (hvc.getTypeRef() != null) {
|
||||
output.setTypeRef(hvc.getTypeRef());
|
||||
}
|
||||
|
||||
dmnConversionContext.getIndexedDmnColumns().addOutput(hvc.getColumn(), output);
|
||||
}
|
||||
}
|
||||
|
||||
protected void convertRules(
|
||||
DmnModelInstance dmnModel,
|
||||
DecisionTable decisionTable,
|
||||
List<SpreadsheetRow> rulesRows,
|
||||
StringBuilder serializedRules) {
|
||||
for (SpreadsheetRow rule : rulesRows) {
|
||||
convertRule(dmnModel, decisionTable, rule, serializedRules);
|
||||
}
|
||||
}
|
||||
|
||||
protected void convertRule(
|
||||
DmnModelInstance dmnModel,
|
||||
DecisionTable decisionTable,
|
||||
SpreadsheetRow ruleRow,
|
||||
StringBuilder serializedRules) {
|
||||
Rule rule = generateElement(dmnModel, Rule.class, "excelRow" + ruleRow.getRaw().getR());
|
||||
// decisionTable.addChildElement(rule);
|
||||
|
||||
IndexedDmnColumns dmnColumns = dmnConversionContext.getIndexedDmnColumns();
|
||||
|
||||
for (Input input : dmnColumns.getOrderedInputs()) {
|
||||
String xlsxColumn = dmnColumns.getSpreadsheetColumn(input);
|
||||
SpreadsheetCell cell = ruleRow.getCell(xlsxColumn);
|
||||
String coordinate = xlsxColumn + ruleRow.getRaw().getR();
|
||||
|
||||
InputEntry inputEntry = generateElement(dmnModel, InputEntry.class, coordinate);
|
||||
String textValue =
|
||||
cell != null ? dmnConversionContext.resolveCellValue(cell) : getDefaultCellContent();
|
||||
Text text = generateText(dmnModel, textValue);
|
||||
inputEntry.setText(text);
|
||||
rule.addChildElement(inputEntry);
|
||||
}
|
||||
|
||||
for (Output output : dmnColumns.getOrderedOutputs()) {
|
||||
String xlsxColumn = dmnColumns.getSpreadsheetColumn(output);
|
||||
SpreadsheetCell cell = ruleRow.getCell(xlsxColumn);
|
||||
String coordinate = xlsxColumn + ruleRow.getRaw().getR();
|
||||
|
||||
OutputEntry outputEntry = generateElement(dmnModel, OutputEntry.class, coordinate);
|
||||
String textValue =
|
||||
cell != null ? dmnConversionContext.resolveCellValue(cell) : getDefaultCellContent();
|
||||
Text text = generateText(dmnModel, textValue);
|
||||
outputEntry.setText(text);
|
||||
rule.addChildElement(outputEntry);
|
||||
}
|
||||
|
||||
SpreadsheetCell annotationCell = ruleRow.getCells().get(ruleRow.getCells().size() - 1);
|
||||
Description description =
|
||||
generateDescription(dmnModel, worksheetContext.resolveCellContent(annotationCell));
|
||||
rule.setDescription(description);
|
||||
|
||||
serializeRuleToStringBuilder(rule, ruleRow, description, serializedRules);
|
||||
}
|
||||
|
||||
protected void serializeRuleToStringBuilder(
|
||||
Rule rule, SpreadsheetRow ruleRow, Description description, StringBuilder serializedRules) {
|
||||
serializedRules
|
||||
.append("<rule id=\"excelRow")
|
||||
.append(ruleRow.getRaw().getR())
|
||||
.append("\">\n<description>")
|
||||
.append(description.getTextContent())
|
||||
.append("</description>\n");
|
||||
for (InputEntry inputEntry : rule.getInputEntries()) {
|
||||
serializedRules
|
||||
.append("<inputEntry id=\"")
|
||||
.append(inputEntry.getId())
|
||||
.append("\">\n<text>")
|
||||
.append(inputEntry.getText().getTextContent())
|
||||
.append("</text>\n</inputEntry>\n");
|
||||
}
|
||||
for (OutputEntry outputEntry : rule.getOutputEntries()) {
|
||||
serializedRules
|
||||
.append("<outputEntry id=\"")
|
||||
.append(outputEntry.getId())
|
||||
.append("\">\n<text>")
|
||||
.append(outputEntry.getText().getTextContent())
|
||||
.append("</text>\n</outputEntry>\n");
|
||||
}
|
||||
serializedRules.append("</rule>\n");
|
||||
}
|
||||
|
||||
protected String getDefaultCellContent() {
|
||||
return "-";
|
||||
}
|
||||
|
||||
protected DmnModelInstance initializeEmptyDmnModel() {
|
||||
DmnModelInstance dmnModel = Dmn.createEmptyModel();
|
||||
Definitions definitions = generateNamedElement(dmnModel, Definitions.class, "definitions");
|
||||
definitions.setNamespace(DmnModelConstants.CAMUNDA_NS);
|
||||
dmnModel.setDefinitions(definitions);
|
||||
|
||||
return dmnModel;
|
||||
}
|
||||
|
||||
public <E extends NamedElement> E generateNamedElement(
|
||||
DmnModelInstance modelInstance, Class<E> elementClass, String name) {
|
||||
E element = generateElement(modelInstance, elementClass, name);
|
||||
element.setName(name);
|
||||
return element;
|
||||
}
|
||||
|
||||
public <E extends DmnElement> E generateElement(
|
||||
DmnModelInstance modelInstance, Class<E> elementClass, String id) {
|
||||
E element = modelInstance.newInstance(elementClass);
|
||||
element.setId(id);
|
||||
return element;
|
||||
}
|
||||
|
||||
public <E extends DmnElement> E generateElement(
|
||||
DmnModelInstance modelInstance, Class<E> elementClass) {
|
||||
// TODO: use a proper generator for random IDs
|
||||
String generatedId = elementClass.getSimpleName() + (int) (Integer.MAX_VALUE * Math.random());
|
||||
return generateElement(modelInstance, elementClass, generatedId);
|
||||
}
|
||||
|
||||
protected Text generateText(DmnModelInstance dmnModel, String content) {
|
||||
Text text = dmnModel.newInstance(Text.class);
|
||||
text.setTextContent(content);
|
||||
return text;
|
||||
}
|
||||
|
||||
protected Description generateDescription(DmnModelInstance dmnModel, String content) {
|
||||
Description description = dmnModel.newInstance(Description.class);
|
||||
description.setTextContent(content);
|
||||
return description;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue