
com.xlrit.gears.engine.form.GearsFormEngineImpl Maven / Gradle / Ivy
package com.xlrit.gears.engine.form;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import com.xlrit.gears.base.content.ContentStore;
import com.xlrit.gears.base.execution.Execution;
import com.xlrit.gears.base.execution.ExecutionFactory;
import com.xlrit.gears.base.choice.Choices;
import com.xlrit.gears.base.form.Form;
import com.xlrit.gears.base.meta.Attributes;
import com.xlrit.gears.base.meta.Input;
import com.xlrit.gears.base.meta.Multiple;
import com.xlrit.gears.engine.meta.MetaManager;
import com.xlrit.gears.engine.meta.PrintOptions;
import com.xlrit.gears.engine.meta.Property;
import com.xlrit.gears.engine.meta.TypeInfo;
import com.xlrit.gears.engine.util.EngineUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.annotations.VisibleForTesting;
import lombok.RequiredArgsConstructor;
import org.flowable.engine.form.FormData;
import org.flowable.engine.form.StartFormData;
import org.flowable.engine.form.TaskFormData;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import static com.google.common.base.Preconditions.checkNotNull;
@Component
@RequiredArgsConstructor
public class GearsFormEngineImpl implements GearsFormEngine {
private static final Logger LOG = LoggerFactory.getLogger(GearsFormEngineImpl.class);
private static final Map NO_VARS = Collections.emptyMap();
private final MetaManager metaManager;
private final CompanionManager companionManager;
private final ExecutionFactory executionFactory;
private final ContentStore contentStore;
private final ObjectMapper objectMapper;
@Override
public ExecutionFactory getExecutionFactory() {
return executionFactory;
}
@Override
public ObjectNode renderStartForm(StartFormData startForm) {
Execution execution = createStartExecution(startForm.getProcessDefinition());
return renderForm(startForm, RenderOptions.DEFAULT, execution, null);
}
@Override
public ObjectNode renderTaskForm(TaskFormData taskForm) {
TaskEntity taskEntity = (TaskEntity) taskForm.getTask();
Execution execution = createTaskExecution(taskEntity);
return renderForm(taskForm, RenderOptions.DEFAULT, execution, taskEntity.getVariablesLocal());
}
@Override
public Execution createStartExecution(ProcessDefinition processDefinition) {
Objects.requireNonNull(processDefinition, "processDefinition is required");
Execution execution = executionFactory.basic();
String actorName = EngineUtils.getStarterActorName((ProcessDefinitionEntity) processDefinition);
EngineUtils.initializeProcessStarter(execution, actorName);
return execution;
}
@Override
public Execution createTaskExecution(TaskEntity taskEntity) {
Objects.requireNonNull(taskEntity, "task is required");
ExecutionEntity executionEntity = CommandContextUtil.getExecutionEntityManager().findById(taskEntity.getExecutionId());
String loggerName = "process." + executionEntity.getProcessDefinitionKey() + "." + taskEntity.getName();
return executionFactory.wrap(executionEntity, loggerName);
}
@Override
public ObjectNode renderForm(FormData formData, RenderOptions options, Execution execution, Map properties) {
checkNotNull(formData, "formData required");
checkNotNull(execution, "execution required");
return renderForm(getFormInfo(formData.getFormKey()), options, execution, properties);
}
@Override
public Form submitForm(String deploymentId, String formKey, Execution execution, Map properties) {
checkNotNull(formKey, "formKey required");
checkNotNull(execution, "execution required");
return submitForm(getFormInfo(formKey), execution, properties);
}
// === rendering === //
ObjectNode renderForm(FormInfo formInfo, RenderOptions renderOptions, Execution execution, Map vars) {
JsonNode savedValues = getFormValuesNode(vars);
LOG.debug("renderForm: formInfo={}, savedValues={}", formInfo, savedValues);
RenderContext ctx = new RenderContext(objectMapper, metaManager, renderOptions, PrintOptions.DEFAULT);
Object basedOnValue = formInfo.getBasedOnValue(execution);
ObjectNode formNode = objectMapper.createObjectNode();
formNode.set("fields", ctx.renderView(formInfo.getFields(), execution));
formNode.set("values", ctx.renderModel(formInfo.getFields(), execution, basedOnValue));
formNode.set("saved", savedValues);
formNode.set("aux", ctx.renderAux(formInfo.getFields(), execution));
return formNode;
}
@Override
public Choices getChoices(String formKey, Execution initialExecution, String path) {
return getChoices(getFormInfo(formKey), initialExecution, path);
}
@VisibleForTesting
public Choices getChoices(FormInfo formInfo, Execution initialExecution, String path) {
LOG.debug("getChoices: formInfo={}, path={}", formInfo, path);
return formInfo.evaluate(path, initialExecution, (execution, field) -> ((InputFormField) field).getChoices(execution));
}
// === submission === //
Form submitForm(FormInfo formInfo, Execution execution, Map properties) {
SubmitContext ctx = createSubmitContext(properties);
Form form = formInfo.convert(ctx, ctx.getFormValues());
execution.setForm(form);
Validation validation = formInfo.validate(ctx, execution, form);
if (validation.isFailure()) {
throw validation.toInputException();
}
return form;
}
// === (de)serialization === //
public byte[] serializeForm(Form form) {
try {
FormInfo formInfo = getFormInfo(form.getClass());
JsonNode json = serialize(formInfo, form);
return objectMapper.writer().writeValueAsBytes(json);
}
catch (JsonProcessingException e) {
throw new RuntimeException("Unable to serialize form " + form, e);
}
}
@VisibleForTesting
JsonNode serialize(FormInfo formInfo, Form form) {
return formInfo.serialize(form, objectMapper, PrintOptions.NONE);
}
public Form deserializeForm(String formKey, byte[] bytes) {
return deserializeForm(getFormClass(formKey), bytes);
}
@VisibleForTesting
T deserializeForm(Class formClass, byte[] bytes) {
try {
FormInfo formInfo = getFormInfo(formClass);
JsonNode formNode = objectMapper.reader().readTree(bytes);
Form form = deserialize(formInfo, formNode);
return formClass.cast(form);
}
catch (IOException e) {
throw new RuntimeException("Unable to deserialize form " + formClass, e);
}
}
@VisibleForTesting
Form deserialize(FormInfo formInfo, JsonNode formNode) {
return (Form) formInfo.deserialize(formNode, objectMapper);
}
// === form info === //
private final ConcurrentMap formInfos = new ConcurrentHashMap<>(16);
@Override
public FormInfo getFormInfo(String formKey) { return getFormInfo(getFormClass(formKey)); }
protected FormInfo getFormInfo(Class formClass) {
return formInfos.computeIfAbsent(formClass, this::createFormInfo);
}
private FormInfo createFormInfo(Class formClass) {
CompanionAdapter ca = companionManager.getAdapter(formClass);
ValueRef basedOnRef = ca.getBasedOnRef();
return new FormInfo(formClass, createFieldInfos(formClass, ca, FormPath.ROOT), basedOnRef);
}
private List extends FormField> createFieldInfos(Class> formClass, CompanionAdapter ca, FormPath parentPath) {
Attributes attributesAnno = formClass.getAnnotation(Attributes.class);
checkNotNull(attributesAnno, "%s must have an @Attributes annotation", formClass);
String[] fieldNames = attributesAnno.value();
return Arrays.stream(fieldNames)
.map(name -> createFieldInfo(formClass, ca, parentPath, name))
.collect(Collectors.toList());
}
@SuppressWarnings({"rawtypes", "unchecked"})
FormField createFieldInfo(Class> formClass, CompanionAdapter ca, FormPath parentPath, String name) {
Property property = Property.byName(formClass, name);
Input inputAnno = property.getAnnotation(Input.class);
if (inputAnno != null) {
TypeInfo typeInfo = metaManager.genericTypeToInfo(property.getGenericType());
FormPath path = parentPath.input(name);
ValueRef defaultRef = ca.getDefaultRef(path);
ValueRef chosenFromRef = ca.getChoicesRef(path);
return new InputFormField(path, typeInfo, property, defaultRef, chosenFromRef);
}
Multiple multipleAnno = property.getAnnotation(Multiple.class);
if (multipleAnno != null) {
FormPath path = parentPath.multiple(name);
ValueRef basedOnRef = ca.getBasedOnRef(path);
ValueRef collectionRef = ca.getCollectionRef(path);
Class elementType = property.getElementType();
LineInfo lineInfo = new LineInfo(elementType, createFieldInfos(elementType, ca, path.line(multipleAnno.elementName())));
return new MultipleFormField(path, property, basedOnRef, collectionRef, lineInfo);
}
// fall through: neither @Input nor @Multiple is present
throw new RuntimeException("Getter for '" + name + "' in " + formClass + " must be annotated with either @Input or @Multiple");
}
// === ugly stuff === //
@SuppressWarnings("unchecked")
private SubmitContext createSubmitContext(Map properties) {
if (properties == null) properties = NO_VARS;
JsonNode formValues = (JsonNode) properties.get("values");
if (formValues == null) formValues = objectMapper.createObjectNode();
List files = (List) properties.get("files");
return new SubmitContext(objectMapper, contentStore, formValues, files);
}
private JsonNode getFormValuesNode(Map properties) {
if (properties == null) properties = NO_VARS;
JsonNode result = (JsonNode) properties.get("values");
return result != null ? result : objectMapper.createObjectNode();
}
@SuppressWarnings("unchecked")
private Class extends Form> getFormClass(String formKey) {
try {
return (Class extends Form>) Thread.currentThread().getContextClassLoader().loadClass(formKey);
}
catch (Exception e) {
throw new RuntimeException("Unable to get form class with key '" + formKey + "'", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy