/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.workflow.simple.alfresco.conversion;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import org.activiti.bpmn.model.EventDefinition;
import org.activiti.bpmn.model.FieldExtension;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.IntermediateCatchEvent;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.ServiceTask;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.bpmn.model.TimerEventDefinition;
import org.activiti.bpmn.model.UserTask;
import org.activiti.workflow.simple.alfresco.conversion.form.AlfrescoFormCreator;
import org.activiti.workflow.simple.alfresco.conversion.script.PropertyReference;
import org.activiti.workflow.simple.alfresco.model.M2Aspect;
import org.activiti.workflow.simple.alfresco.model.M2Model;
import org.activiti.workflow.simple.alfresco.model.M2Namespace;
import org.activiti.workflow.simple.alfresco.model.M2Type;
import org.activiti.workflow.simple.alfresco.model.config.Configuration;
import org.activiti.workflow.simple.alfresco.model.config.Extension;
import org.activiti.workflow.simple.alfresco.model.config.Form;
import org.activiti.workflow.simple.alfresco.model.config.FormField;
import org.activiti.workflow.simple.alfresco.model.config.FormFieldControl;
import org.activiti.workflow.simple.alfresco.model.config.FormFieldControlParameter;
import org.activiti.workflow.simple.alfresco.model.config.Module;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.converter.listener.WorkflowDefinitionConversionListener;
import org.activiti.workflow.simple.definition.AbstractConditionStepListContainer;
import org.activiti.workflow.simple.definition.AbstractStepDefinitionContainer;
import org.activiti.workflow.simple.definition.AbstractStepListContainer;
import org.activiti.workflow.simple.definition.FormStepDefinition;
import org.activiti.workflow.simple.definition.ListConditionStepDefinition;
import org.activiti.workflow.simple.definition.ListStepDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import org.activiti.workflow.simple.definition.form.FormDefinition;
import org.activiti.workflow.simple.definition.form.FormPropertyDefinition;
import org.activiti.workflow.simple.definition.form.FormPropertyGroup;
import org.activiti.workflow.simple.definition.form.ReferencePropertyDefinition;
/**
* A {@link WorkflowDefinitionConversionListener} that creates a {@link M2Model} and a {@link Configuration}
* before conversion, that can be used to add any models and configuration needed throughout the conversion.
*
* @author Frederik Heremans
* @author Joram Barrez
*/
public class InitializeAlfrescoModelsConversionListener implements WorkflowDefinitionConversionListener, AlfrescoConversionConstants {
private static final long serialVersionUID = 1L;
protected AlfrescoFormCreator formCreator;
// Types of ReferencePropertyDefinition that should be ignore for reuse
protected static final Set<String> IGNORED_REFERENCE_TYPES_REUSE = new HashSet<String>(Arrays.asList(
AlfrescoConversionConstants.FORM_REFERENCE_DUEDATE,
AlfrescoConversionConstants.FORM_REFERENCE_PRIORITY,
AlfrescoConversionConstants.FORM_REFERENCE_PACKAGE_ITEMS,
AlfrescoConversionConstants.FORM_REFERENCE_WORKFLOW_DESCRIPTION
));
public InitializeAlfrescoModelsConversionListener() {
formCreator = new AlfrescoFormCreator();
}
@Override
public void beforeStepsConversion(WorkflowDefinitionConversion conversion) {
String processId = null;
if(conversion.getWorkflowDefinition().getId() != null) {
processId = AlfrescoConversionUtil.getValidIdString(conversion.getWorkflowDefinition().getId());
} else {
processId = generateUniqueProcessId(conversion);
}
M2Model model = addContentModel(conversion, processId);
addExtension(conversion, processId);
// In case the same property definitions are used across multiple forms, we need to identify this
// up-front and create an aspect for this that can be shared due to the fact that you cannot define the same
// property twice in a the same content-model namespace
addAspectsForReusedProperties(conversion.getWorkflowDefinition(), model, processId);
// Add list of property references
conversion.setArtifact(ARTIFACT_PROPERTY_REFERENCES, new ArrayList<PropertyReference>());
}
@Override
public void afterStepsConversion(WorkflowDefinitionConversion conversion) {
M2Model model = AlfrescoConversionUtil.getContentModel(conversion);
M2Namespace modelNamespace = model.getNamespaces().get(0);
for(FlowElement flowElement : conversion.getProcess().getFlowElements()) {
if(flowElement instanceof StartEvent) {
StartEvent startEvent = (StartEvent) flowElement;
if(startEvent.getFormKey() == null) {
Module module = AlfrescoConversionUtil.getExtension(conversion).getModules().get(0);
Configuration detailsForm = module.addConfiguration(EVALUATOR_STRING_COMPARE,
MessageFormat.format(EVALUATOR_CONDITION_ACTIVITI, conversion.getProcess().getId()));
// No form-key is set, either use the default or generate of start-form if this
// is available
if(conversion.getWorkflowDefinition().getStartFormDefinition() != null
&& !conversion.getWorkflowDefinition().getStartFormDefinition().getFormGroups().isEmpty()) {
// Create the content model for the start-task
M2Type type = new M2Type();
model.getTypes().add(type);
type.setName(AlfrescoConversionUtil.getQualifiedName(modelNamespace.getPrefix(),
AlfrescoConversionConstants.START_TASK_SIMPLE_NAME));
type.setParentName(AlfrescoConversionConstants.DEFAULT_START_FORM_TYPE);
// Create a form-config for the start-task
Module shareModule = AlfrescoConversionUtil.getExtension(conversion).getModules().get(0);
Configuration configuration = shareModule.addConfiguration(AlfrescoConversionConstants.EVALUATOR_TASK_TYPE
, type.getName());
Form formConfig = configuration.createForm();
formConfig.setStartForm(true);
// Populate model and form based on FormDefinition
formCreator.createForm(type, formConfig, conversion.getWorkflowDefinition().getStartFormDefinition(), conversion);
// Use the same form-config for the workflow details
detailsForm.addForm(formConfig);
// Set formKey on start-event, referencing type
startEvent.setFormKey(type.getName());
} else {
// Revert to the default start-form
startEvent.setFormKey(DEFAULT_START_FORM_TYPE);
// Also add form-config to the share-module for workflow detail screen, based on the default form
populateDefaultDetailFormConfig(detailsForm);
}
}
}
}
// Check all elements that can contain PropertyReferences or need additional builders invoked
List<PropertyReference> references = AlfrescoConversionUtil.getPropertyReferences(conversion);
for(FlowElement element : conversion.getProcess().getFlowElements()) {
if(element instanceof SequenceFlow) {
resolvePropertyRefrencesInSequenceFlow((SequenceFlow) element, modelNamespace, references);
} else if(element instanceof IntermediateCatchEvent) {
resolvePropertyRefrencesInCatchEvent((IntermediateCatchEvent) element, modelNamespace, references);
} else if(element instanceof ServiceTask) {
resolvePropertyRefrencesInServiceTask((ServiceTask) element, modelNamespace, references);
} else if(element instanceof UserTask) {
addScriptListenersToUserTask((UserTask) element, conversion);
}
}
// Check if all property-references reference a valid property
if(references != null && !references.isEmpty()) {
for(PropertyReference reference : references) {
reference.validate(model);
}
}
}
protected void addScriptListenersToUserTask(UserTask userTask, WorkflowDefinitionConversion conversion) {
// Add create-script-listener if it has been used in this conversion
if(AlfrescoConversionUtil.hasTaskScriptTaskListenerBuilder(conversion, userTask.getId(),
AlfrescoConversionConstants.TASK_LISTENER_EVENT_CREATE)) {
userTask.getTaskListeners().add(AlfrescoConversionUtil.getScriptTaskListenerBuilder(conversion, userTask.getId(),
AlfrescoConversionConstants.TASK_LISTENER_EVENT_CREATE).build());
}
// Add complete-script-listener if it has been used in this conversion
if(AlfrescoConversionUtil.hasTaskScriptTaskListenerBuilder(conversion, userTask.getId(),
AlfrescoConversionConstants.TASK_LISTENER_EVENT_COMPLETE)) {
userTask.getTaskListeners().add(AlfrescoConversionUtil.getScriptTaskListenerBuilder(conversion, userTask.getId(),
AlfrescoConversionConstants.TASK_LISTENER_EVENT_COMPLETE).build());
}
}
protected void resolvePropertyRefrencesInSequenceFlow(SequenceFlow sequenceFlow, M2Namespace modelNamespace, List<PropertyReference> references) {
if(sequenceFlow.getConditionExpression() != null && PropertyReference.containsPropertyReference(sequenceFlow.getConditionExpression())) {
sequenceFlow.setConditionExpression(PropertyReference.replaceAllPropertyReferencesInString(sequenceFlow.getConditionExpression(), modelNamespace.getPrefix(), references, false));
}
}
protected void resolvePropertyRefrencesInCatchEvent(IntermediateCatchEvent event, M2Namespace modelNamespace, List<PropertyReference> references) {
if(event.getEventDefinitions() != null && !event.getEventDefinitions().isEmpty()) {
for(EventDefinition def : event.getEventDefinitions()) {
if(def instanceof TimerEventDefinition) {
TimerEventDefinition timer = (TimerEventDefinition) def;
if(timer.getTimeDate() != null && PropertyReference.isPropertyReference(timer.getTimeDate())) {
timer.setTimeDate(PropertyReference.createReference(timer.getTimeDate()).getPropertyReferenceExpression(modelNamespace.getPrefix()));
}
}
}
}
}
protected void resolvePropertyRefrencesInServiceTask(ServiceTask serviceTask, M2Namespace modelNamespace, List<PropertyReference> references) {
if(serviceTask.getFieldExtensions() != null && !serviceTask.getFieldExtensions().isEmpty()) {
for(FieldExtension extension : serviceTask.getFieldExtensions()) {
String value = extension.getExpression();
if(value != null && !value.isEmpty() && PropertyReference.containsPropertyReference(value)) {
value = PropertyReference.replaceAllPropertyReferencesInString(value, modelNamespace.getPrefix(), references, true);
extension.setExpression(value);
}
}
}
}
protected String generateUniqueProcessId(WorkflowDefinitionConversion conversion) {
String processId = AlfrescoConversionUtil.getValidIdString(
PROCESS_ID_PREFIX + UUID.randomUUID().toString());
conversion.getProcess().setId(processId);
return processId;
}
protected void addAspectsForReusedProperties(WorkflowDefinition workflowDefinition, M2Model model, String processId) {
Map<String, FormPropertyDefinition> definitionMap = new HashMap<String, FormPropertyDefinition>();
// Add start-form properties
addDefinitionsToMap(workflowDefinition.getStartFormDefinition(), definitionMap);
// Run through steps recursivelye, looking for properties
addAspectsForReusedProperties(workflowDefinition.getSteps(), model, processId, definitionMap);
// Check if the map contains values other than null, this indicates duplicate properties are found
for(Entry<String, FormPropertyDefinition> entry : definitionMap.entrySet()) {
if(entry.getValue() != null) {
// Create an aspect for this property. The aspect itself will be populated when the first
// property is converted with that name
M2Aspect aspect = new M2Aspect();
aspect.setName(AlfrescoConversionUtil.getQualifiedName(processId, entry.getKey()));
model.getAspects().add(aspect);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void addAspectsForReusedProperties(List<StepDefinition> steps, M2Model model, String processId, Map<String, FormPropertyDefinition> definitionMap) {
for (StepDefinition step : steps) {
if (step instanceof FormStepDefinition) {
addDefinitionsToMap(((FormStepDefinition) step).getForm(), definitionMap);
} else if(step instanceof AbstractStepListContainer<?>) {
List<ListStepDefinition<?>> stepList = ((AbstractStepListContainer) step).getStepList();
for(ListStepDefinition<?> list : stepList) {
addAspectsForReusedProperties(list.getSteps(), model, processId, definitionMap);
}
} else if(step instanceof AbstractConditionStepListContainer<?>) {
List<ListConditionStepDefinition<?>> stepList = ((AbstractConditionStepListContainer) step).getStepList();
for(ListConditionStepDefinition<?> list : stepList) {
addAspectsForReusedProperties(list.getSteps(), model, processId, definitionMap);
}
} else if(step instanceof AbstractStepDefinitionContainer<?>) {
addAspectsForReusedProperties(((AbstractStepDefinitionContainer<WorkflowDefinition>) step).getSteps(), model, processId, definitionMap);
}
}
}
protected void addDefinitionsToMap(FormDefinition formDefinition, Map<String, FormPropertyDefinition> definitionMap) {
if(formDefinition != null && formDefinition.getFormGroups() != null) {
String finalPropertyName = null;
for(FormPropertyGroup group : formDefinition.getFormGroups()) {
if(group.getFormPropertyDefinitions() != null) {
for(FormPropertyDefinition def : group.getFormPropertyDefinitions()) {
if(isPropertyReuseCandidate(def)) {
finalPropertyName = AlfrescoConversionUtil.getValidIdString(def.getName());
if(definitionMap.containsKey(finalPropertyName)) {
definitionMap.put(finalPropertyName, def);
} else {
definitionMap.put(finalPropertyName, null);
}
}
}
}
}
}
}
protected boolean isPropertyReuseCandidate(FormPropertyDefinition def) {
boolean valid = !(def instanceof ReferencePropertyDefinition);
if(!valid) {
ReferencePropertyDefinition reference = (ReferencePropertyDefinition) def;
valid = ! IGNORED_REFERENCE_TYPES_REUSE.contains(reference.getType());
}
return valid;
}
protected M2Model addContentModel(WorkflowDefinitionConversion conversion, String processId) {
// The process ID is used as namespace prefix, to guarantee uniqueness
// Set general model properties
M2Model model = new M2Model();
model.setName(AlfrescoConversionUtil.getQualifiedName(processId,
CONTENT_MODEL_UNQUALIFIED_NAME));
M2Namespace namespace = AlfrescoConversionUtil.createNamespace(processId);
model.getNamespaces().add(namespace);
// Import required alfresco models
model.getImports().add(DICTIONARY_NAMESPACE);
model.getImports().add(CONTENT_NAMESPACE);
model.getImports().add(BPM_NAMESPACE);
// Store model in the conversion artifacts to be accessed later
AlfrescoConversionUtil.storeContentModel(model, conversion);
AlfrescoConversionUtil.storeModelNamespacePrefix(namespace.getPrefix(), conversion);
return model;
}
protected void addExtension(WorkflowDefinitionConversion conversion, String processId) {
// Create form-configuration
Extension extension = new Extension();
Module module = new Module();
extension.addModule(module);
module.setId(MessageFormat.format(MODULE_ID, processId));
AlfrescoConversionUtil.storeExtension(extension, conversion);
}
protected void populateDefaultDetailFormConfig(Configuration configuration) {
Form form = configuration.createForm();
// Add visibility of fields
form.getFormFieldVisibility().addShowFieldElement(PROPERTY_WORKFLOW_DESCRIPTION);
form.getFormFieldVisibility().addShowFieldElement(PROPERTY_WORKFLOW_DUE_DATE);
form.getFormFieldVisibility().addShowFieldElement(PROPERTY_WORKFLOW_PRIORITY);
form.getFormFieldVisibility().addShowFieldElement(PROPERTY_PACKAGEITEMS);
form.getFormFieldVisibility().addShowFieldElement(PROPERTY_SEND_EMAIL_NOTIFICATIONS);
// Add all sets to the appearance
form.getFormAppearance().addFormSet(FORM_SET_GENERAL, FORM_SET_APPEARANCE_TITLE, FORM_SET_GENERAL_LABEL, null);
form.getFormAppearance().addFormSet(FORM_SET_INFO, null, null, FORM_SET_TEMPLATE_2_COLUMN);
form.getFormAppearance().addFormSet(FORM_SET_ASSIGNEE, FORM_SET_APPEARANCE_TITLE, FORM_SET_ASSIGNEE_LABEL, null);
form.getFormAppearance().addFormSet(FORM_SET_ITEMS, FORM_SET_APPEARANCE_TITLE, FORM_SET_ITEMS_LABEL, null);
form.getFormAppearance().addFormSet(FORM_SET_OTHER, FORM_SET_APPEARANCE_TITLE, FORM_SET_OTHER_LABEL, null);
// Finally, add the individual fields
FormField descriptionField = new FormField();
descriptionField.setId(PROPERTY_WORKFLOW_DESCRIPTION);
descriptionField.setControl(new FormFieldControl(FORM_MULTILINE_TEXT_TEMPLATE));
descriptionField.setLabelId(FORM_WORKFLOW_DESCRIPTION_LABEL);
form.getFormAppearance().addFormAppearanceElement(descriptionField);
FormField dueDateField = new FormField();
dueDateField.setId(PROPERTY_WORKFLOW_DUE_DATE);
dueDateField.setSet(FORM_SET_INFO);
dueDateField.setLabelId(FORM_WORKFLOW_DUE_DATE_LABEL);
dueDateField.setControl(new FormFieldControl(FORM_DATE_TEMPLATE));
dueDateField.getControl().getControlParameters().add(new FormFieldControlParameter(FORM_DATE_PARAM_SHOW_TIME, Boolean.FALSE.toString()));
dueDateField.getControl().getControlParameters().add(new FormFieldControlParameter(FORM_DATE_PARAM_SUBMIT_TIME, Boolean.FALSE.toString()));
form.getFormAppearance().addFormAppearanceElement(dueDateField);
FormField priorityField = new FormField();
priorityField.setSet(FORM_SET_INFO);
priorityField.setLabelId(FORM_WORKFLOW_PRIORITY_LABEL);
priorityField.setId(PROPERTY_WORKFLOW_PRIORITY);
priorityField.setControl(new FormFieldControl(FORM_PRIORITY_TEMPLATE));
form.getFormAppearance().addFormAppearanceElement(priorityField);
form.getFormAppearance().addFormField(PROPERTY_PACKAGEITEMS, null, FORM_SET_ITEMS);
FormField emailNotificationsField = new FormField();
emailNotificationsField.setSet(FORM_SET_OTHER);
emailNotificationsField.setId(PROPERTY_SEND_EMAIL_NOTIFICATIONS);
emailNotificationsField.setControl(new FormFieldControl(FORM_EMAIL_NOTIFICATION_TEMPLATE));
form.getFormAppearance().addFormAppearanceElement(emailNotificationsField);
}
}