/* 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); } }