Java tutorial
/* 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.kickstart.service.alfresco; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.io.StringWriter; import java.net.URLEncoder; import java.text.MessageFormat; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.activiti.kickstart.diagram.ProcessDiagramGenerator; import org.activiti.kickstart.dto.KickstartFormProperty; import org.activiti.kickstart.dto.KickstartTask; import org.activiti.kickstart.dto.KickstartUserTask; import org.activiti.kickstart.dto.KickstartWorkflow; import org.activiti.kickstart.dto.KickstartWorkflowInfo; import org.activiti.kickstart.service.Bpmn20MarshallingService; import org.activiti.kickstart.service.KickstartService; import org.activiti.kickstart.service.MetaDataKeys; import org.apache.chemistry.opencmis.client.api.Document; import org.apache.chemistry.opencmis.client.api.Folder; import org.apache.chemistry.opencmis.client.api.ItemIterable; import org.apache.chemistry.opencmis.client.api.QueryResult; import org.apache.chemistry.opencmis.client.api.Repository; import org.apache.chemistry.opencmis.client.api.Session; import org.apache.chemistry.opencmis.client.api.SessionFactory; import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.SessionParameter; import org.apache.chemistry.opencmis.commons.data.ContentStream; import org.apache.chemistry.opencmis.commons.enums.BindingType; import org.apache.chemistry.opencmis.commons.enums.VersioningState; import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpState; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.DeleteMethod; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.io.IOUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; /** * @author Joram Barrez */ public class AlfrescoKickstartServiceImpl implements KickstartService { private static final Logger LOGGER = Logger.getLogger(AlfrescoKickstartServiceImpl.class.getName()); // Constants // ///////////////////////////////////////////////////////////////////// private static final String KICKSTART_PREFIX = "ks:"; // URLS // //////////////////////////////////////////// private static final String ALFRESCO_BASE_URL = "http://localhost:8080/alfresco/service/"; private static final String SHARE_BASE_URL = "http://localhost:8081/share/"; // Alfresco specific folders and urls // ////////////////////////////////////////// private static final String WORKFLOW_DEFINITION_FOLDER = "/Data Dictionary/Workflow Definitions"; private static final String DATA_DICTIONARY_FOLDER = "/Data Dictionary/Models"; private static final String FORM_CONFIG_UPLOAD_URL = SHARE_BASE_URL + "page/modules/module"; // Task Model templates // ///////////////////////////////////////////////////////// private static final String TEMPLATE_FOLDER = "/org/activiti/kickstart/service/alfresco/"; private static final String TASK_MODEL_TEMPLATE_FILE = TEMPLATE_FOLDER + "task-model-template.xml"; private static String TASK_MODEL_TEMPLATE; private static final String TASK_MODEL_TYPE_TEMPLATE_FILE = TEMPLATE_FOLDER + "task-model-type-template.xml"; private static String TASK_MODEL_TYPE_TEMPLATE; private static final String TASK_MODEL_PROPERTY_TEMPLATE_FILE = TEMPLATE_FOLDER + "task-model-property-template.xml"; private static String TASK_MODEL_PROPERTY_TEMPLATE; // Form Config templates // ///////////////////////////////////////////////////////// private static final String FORM_CONFIG_TEMPLATE_FILE = TEMPLATE_FOLDER + "form-config-template.xml"; private static String FORM_CONFIG_TEMPLATE; private static final String FORM_CONFIG_EVALUATOR_CONFIG_TEMPLATE_FILE = TEMPLATE_FOLDER + "form-config-evaluator-config-template.xml"; private static String FORM_CONFIG_EVALUATOR_CONFIG_TEMPLATE; private static final String FORM_CONFIG_FIELD_TEMPLATE_FILE = TEMPLATE_FOLDER + "form-config-field-template.xml"; private static String FORM_CONFIG_FIELD_TEMPLATE; private static final String FORM_CONFIG_FIELD_VISIBILITY_TEMPLATE_FILE = TEMPLATE_FOLDER + "form-config-field-visibility-template.xml"; private static String FORM_CONFIG_FIELD_VISIBILITY_TEMPLATE; private static final String FORM_CONFIG_FIELD_INFO_TEMPLATE_FILE = TEMPLATE_FOLDER + "form-config-field-info-control-template.xml"; private static String FORM_CONFIG_FIELD_INFO_TEMPLATE; // Service parameters // /////////////////////////////////////////////////////////// protected String cmisUser; protected String cmisPassword; protected String cmisAtompubUrl; // Service members // ///////////////////////////////////////////////////////////// protected Session cachedSession; protected Bpmn20MarshallingService marshallingService; public AlfrescoKickstartServiceImpl(String cmisUser, String cmisPassword, String cmisAtompubUrl) { this.cmisUser = cmisUser; this.cmisPassword = cmisPassword; this.cmisAtompubUrl = cmisAtompubUrl; } protected Session getCmisSession() { if (cachedSession == null) { synchronized (this) { if (cachedSession == null) { Map<String, String> parameters = new HashMap<String, String>(); parameters.put(SessionParameter.USER, cmisUser); parameters.put(SessionParameter.PASSWORD, cmisPassword); parameters.put(SessionParameter.ATOMPUB_URL, cmisAtompubUrl); parameters.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value()); // We're using the Alfresco extensions parameters.put(SessionParameter.OBJECT_FACTORY_CLASS, "org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl"); // First need to fetch the repository info to know the repo id SessionFactory sessionFactory = SessionFactoryImpl.newInstance(); List<Repository> repositories = sessionFactory.getRepositories(parameters); String repositoryId = repositories.get(0).getId(); parameters.put(SessionParameter.REPOSITORY_ID, repositoryId); cachedSession = sessionFactory.createSession(parameters); } } } return cachedSession; } public String deployWorkflow(KickstartWorkflow kickstartWorkflow, Map<String, String> metadata) { // Validate metadata String jsonSource = metadata.get(MetaDataKeys.WORKFLOW_JSON_SOURCE); if (jsonSource == null) { throw new RuntimeException("Missing metadata " + MetaDataKeys.WORKFLOW_JSON_SOURCE); } // Create base name for all files that will be stored String baseName = kickstartWorkflow.getId(); // Following stringbuilders will construct a valid content model and form config StringBuilder taskModelsString = new StringBuilder(); StringBuilder evaluatorConfigStringBuilder = new StringBuilder(); // Mapping to store {formProperty} -> {form property with prefix and unique-ified name} HashMap<String, String> formPropertyMapping = new HashMap<String, String>(); // Needs to go first, as the formkey will be filled in here for (KickstartTask task : kickstartWorkflow.getTasks()) { if (task instanceof KickstartUserTask) { // Only need to generte a form for user tasks generateTaskAndFormConfigForUserTask((KickstartUserTask) task, taskModelsString, evaluatorConfigStringBuilder, baseName, formPropertyMapping); } } // Upload results to Alfresco uploadTaskModel(taskModelsString, baseName); uploadFormConfig(evaluatorConfigStringBuilder, kickstartWorkflow, baseName); // Upload process deployProcess(kickstartWorkflow, baseName, jsonSource); // Can't get the deployment id, so returning process definition id return baseName; } protected void deployProcess(KickstartWorkflow kickstartWorkflow, String baseFileName, String jsonSource) { Session cmisSession = getCmisSession(); Folder workflowDefinitionFolder = (Folder) cmisSession.getObjectByPath(WORKFLOW_DEFINITION_FOLDER); uploadDiagramFile(kickstartWorkflow, baseFileName, workflowDefinitionFolder); // Process image (must go first, since it will add DI to the process xml) uploadJsonFile(baseFileName, jsonSource, workflowDefinitionFolder); uploadProcessFile(kickstartWorkflow, baseFileName, workflowDefinitionFolder); } private void uploadDiagramFile(KickstartWorkflow kickstartWorkflow, String baseFileName, Folder workflowDefinitionFolder) { LOGGER.info("Generating process image..."); ProcessDiagramGenerator diagramGenerator = new ProcessDiagramGenerator(kickstartWorkflow, marshallingService); InputStream diagramInputStream = diagramGenerator.execute(); // Diagram is deployed next to the process xml if (workflowDefinitionFolder == null) { throw new RuntimeException( "Cannot find workflow definition folder '" + WORKFLOW_DEFINITION_FOLDER + "'"); } String diagramFileName = baseFileName + ".png"; ContentStream diagramContentStream = new ContentStreamImpl(diagramFileName, null, "image/png", diagramInputStream); Document diagramDocument = getDocumentFromFolder(workflowDefinitionFolder.getPath(), diagramFileName); if (diagramDocument == null) { HashMap<String, Object> diagramProperties = new HashMap<String, Object>(); diagramProperties.put(PropertyIds.NAME, diagramFileName); diagramProperties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); workflowDefinitionFolder.createDocument(diagramProperties, diagramContentStream, VersioningState.MAJOR); } else { diagramDocument.setContentStream(diagramContentStream, true); } } private void uploadJsonFile(String baseFileName, String jsonSource, Folder workflowDefinitionFolder) { LOGGER.info("Upload json source..."); String jsonSrcFileName = baseFileName + ".json"; ContentStream jsonSrcContentStream = new ContentStreamImpl(jsonSrcFileName, null, "application/json", new ByteArrayInputStream(jsonSource.getBytes())); Document jsonDocument = getDocumentFromFolder(workflowDefinitionFolder.getPath(), jsonSrcFileName); if (jsonDocument == null) { HashMap<String, Object> jsonSrcProperties = new HashMap<String, Object>(); jsonSrcProperties.put(PropertyIds.NAME, jsonSrcFileName); jsonSrcProperties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); workflowDefinitionFolder.createDocument(jsonSrcProperties, jsonSrcContentStream, VersioningState.MAJOR); } else { jsonDocument.setContentStream(jsonSrcContentStream, true); } } private void uploadProcessFile(KickstartWorkflow kickstartWorkflow, String baseFileName, Folder workflowDefinitionFolder) { String processFileName = baseFileName + ".bpmn20.xml"; String workflowXML = marshallingService.marshallWorkflow(kickstartWorkflow); InputStream inputStream = new ByteArrayInputStream(workflowXML.getBytes()); LOGGER.info("Uploading process definition xml..."); prettyLogXml(workflowXML); ContentStream processContentStream = new ContentStreamImpl(processFileName, null, "application/xml", inputStream); Document processDocument = getDocumentFromFolder(workflowDefinitionFolder.getPath(), processFileName); if (processDocument == null) { HashMap<String, Object> properties = new HashMap<String, Object>(); properties.put("cmis:name", processFileName); properties.put("cmis:objectTypeId", "D:bpm:workflowDefinition,P:cm:titled"); // Important! Process won't be deployed otherwise properties.put("bpm:definitionDeployed", true); properties.put("bpm:engineId", "activiti"); // Also vital for correct deployment! properties.put("cm:description", kickstartWorkflow.getName()); processDocument = workflowDefinitionFolder.createDocument(properties, processContentStream, VersioningState.MAJOR); } else { processDocument.setContentStream(processContentStream, true); } LOGGER.info("Process definition uploaded to '" + processDocument.getPaths() + "'"); } protected String processNameToBaseName(String processName) { return processName.replace(".bpmn20.xml", ""); } protected void generateTaskAndFormConfigForUserTask(KickstartUserTask userTask, StringBuilder taskModelsString, StringBuilder formConfigString, String baseName, HashMap<String, String> formPropertyMapping) { if (userTask.getForm() != null) { String uniqueTaskName = baseName + "_" + userTask.getName().toLowerCase().replace(" ", "_"); String prefixedUniqueTaskName = KICKSTART_PREFIX + uniqueTaskName; userTask.getForm().setFormKey(prefixedUniqueTaskName); StringBuilder typeString = new StringBuilder(); StringBuilder formAppearanceString = new StringBuilder(); StringBuilder formVisibilityString = new StringBuilder(); String descriptionPropertyName = KICKSTART_PREFIX + "description_" + uniqueTaskName; if (userTask.getDescription() != null) { formVisibilityString.append( MessageFormat.format(getFormConfigFieldVisibilityTemplate(), descriptionPropertyName)); formAppearanceString.append( MessageFormat.format(getFormConfigInfoTemplate(), descriptionPropertyName, "Description")); } if (userTask.getForm().getFormProperties() != null && userTask.getForm().getFormProperties().size() > 0) { // Get form-propertes for (KickstartFormProperty formProperty : userTask.getForm().getFormProperties()) { String uniquePropertyName = KICKSTART_PREFIX + baseName + "_" + createFriendlyName(formProperty.getProperty()); formPropertyMapping.put(formProperty.getProperty(), uniquePropertyName); if (formProperty.getType().equals("documents")) { // Package items are part of the parent content model task, // hence we do not need to add it to the task model formVisibilityString.append( MessageFormat.format(getFormConfigFieldVisibilityTemplate(), "packageItems")); formAppearanceString.append(MessageFormat.format(getFormConfigFieldTemplate(), "packageItems", formProperty.getProperty())); } else { // Property in type-definition typeString.append(MessageFormat.format(getTaskModelPropertyTemplate(), uniquePropertyName, getAlfrescoModelType(formProperty.getType()), formProperty.isRequired())); // Visibility in form-config formVisibilityString.append( MessageFormat.format(getFormConfigFieldVisibilityTemplate(), uniquePropertyName)); // Appearance on screen in form-config formAppearanceString.append(MessageFormat.format(getFormConfigFieldTemplate(), uniquePropertyName, formProperty.getProperty())); } } } // Replace all expressions in the description with the calculated value if (userTask.getDescription() != null) { for (String formProperty : formPropertyMapping.keySet()) { String formPropertyExpression = "${" + formProperty + "}"; if (userTask.getDescription().contains(formPropertyExpression)) { // Update description userTask.setDescription(userTask.getDescription().replace(formPropertyExpression, "${" + formPropertyMapping.get(formProperty) + "}")); } } } // Add name and all form-properties to model XML taskModelsString.append(MessageFormat.format(getTaskModelTypeTemplate(), prefixedUniqueTaskName, descriptionPropertyName, userTask.getDescription(), typeString.toString())); // Add task-form-config formConfigString.append(MessageFormat.format(getFormConfigEvaluatorConfigTemplate(), prefixedUniqueTaskName, formVisibilityString.toString(), formAppearanceString.toString())); } } protected void uploadTaskModel(StringBuilder taskModelsString, String baseFileName) { Session session = getCmisSession(); Folder modelFolder = (Folder) session.getObjectByPath(DATA_DICTIONARY_FOLDER); String taskModelFileName = baseFileName + "-task-model.xml"; // Finally, wrap all taskdefinitions is right XML -> this is the FULL // model file, including generic start-task String taskModelId = UUID.randomUUID().toString(); String taskModelXML = MessageFormat.format(getTaskModelTemplate(), taskModelId, taskModelsString.toString()); LOGGER.info("Deploying task model XML:"); prettyLogXml(taskModelXML); ByteArrayInputStream inputStream = new ByteArrayInputStream(taskModelXML.getBytes()); ContentStream contentStream = new ContentStreamImpl(taskModelFileName, null, "application/xml", inputStream); // Verify whether it is an update or a new workflow Document taskModelDocument = getDocumentFromFolder(DATA_DICTIONARY_FOLDER, taskModelFileName); if (taskModelDocument == null) { HashMap<String, Object> properties = new HashMap<String, Object>(); properties.put("cmis:name", taskModelFileName); properties.put("cmis:objectTypeId", "D:cm:dictionaryModel"); properties.put("cm:modelActive", true); LOGGER.info("Task model file : " + taskModelFileName); modelFolder.createDocument(properties, contentStream, VersioningState.MAJOR); } else { LOGGER.info("Updating content of " + taskModelFileName); taskModelDocument.setContentStream(contentStream, true); } } protected void uploadFormConfig(StringBuilder formConfigStringBuilder, KickstartWorkflow workflow, String baseFileName) { int version = 0; String formConfig = generateFormConfig(formConfigStringBuilder, workflow, version, baseFileName); int result = executeFormConfigUpload(formConfig); // Okay, Okay, this is pretty hackish. But it was the fastest way to get it working. // In an ideal world, with plenty of time and pink unicorns, we would fetch the form config // xml, adapt it with the new forms and save that back. // Or even beter: we could query Share to know the actual latest version number while (result == 409) { LOGGER.info("Found deployed form config with version " + version + " Trying now with " + (version + 1)); version = version + 1; formConfig = generateFormConfig(formConfigStringBuilder, workflow, version, baseFileName); result = executeFormConfigUpload(formConfig); } // We're also uploading it to the workflow definition folder, for future use LOGGER.info("Uploading formconfig to " + WORKFLOW_DEFINITION_FOLDER); uploadStringToDocument(formConfig, WORKFLOW_DEFINITION_FOLDER, baseFileName + "-form-config.xml", "application/xml"); } protected int executeFormConfigUpload(String formConfig) { HttpState state = new HttpState(); state.setCredentials(new AuthScope(null, AuthScope.ANY_PORT), new UsernamePasswordCredentials(cmisUser, cmisPassword)); LOGGER.info("Deploying form config XML: "); prettyLogXml(formConfig); PostMethod postMethod = new PostMethod(FORM_CONFIG_UPLOAD_URL); try { postMethod.setRequestEntity(new StringRequestEntity(formConfig, "application/xml", "UTF-8")); HttpClient httpClient = new HttpClient(); int result = httpClient.executeMethod(null, postMethod, state); // Display status code LOGGER.info("Response status code: " + result); // Display response LOGGER.info("Response body: "); LOGGER.info(postMethod.getResponseBodyAsString()); return result; } catch (Throwable t) { System.err.println("Error: " + t.getMessage()); t.printStackTrace(); } finally { postMethod.releaseConnection(); } throw new RuntimeException("Programmatic error. You shouldn't be here."); } private String generateFormConfig(StringBuilder evaluatorConfigStringBuilder, KickstartWorkflow workflow, int version, String baseFileName) { String formId = "kickstart_form_" + baseFileName; if (version > 0) { formId += "_" + version; } String formConfig = MessageFormat.format(getFormConfigTemplate(), formId, workflow.getId(), evaluatorConfigStringBuilder.toString()); return formConfig; } protected Object getAlfrescoModelType(String type) { if (type.equals("text")) { return "d:text"; } else if (type.equals("date")) { return "d:date"; } else if (type.equals("number")) { return "d:long"; } return null; } protected String createFriendlyName(String property) { return property.toLowerCase().replace(" ", "_"); } public String getWorkflowMetaData(String processDefinitionId, String metadataKey) { String metadataFile = processDefinitionId; if (metadataKey.equals(MetaDataKeys.WORKFLOW_JSON_SOURCE)) { metadataFile = metadataFile + ".json"; } Document document = getDocumentFromFolder(WORKFLOW_DEFINITION_FOLDER, metadataFile); StringBuilder strb = new StringBuilder(); BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(document.getContentStream().getStream())); try { String line = bufferedReader.readLine(); while (line != null) { strb.append(line); line = bufferedReader.readLine(); } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Could not read metadata '" + metadataKey + "' : " + e.getMessage()); e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } return strb.toString(); } public List<KickstartWorkflowInfo> findWorkflowInformation(boolean includeCounts) { if (includeCounts) { // Not yet implemented, cause it would be an 1+n call ... throw new UnsupportedOperationException(); } // Fetch all BPMN 2.0 xml processes from the definitions folder Session cmisSession = getCmisSession(); Folder workflowDefinitionFolder = (Folder) cmisSession.getObjectByPath(WORKFLOW_DEFINITION_FOLDER); String query = "select t.cm:description, d." + PropertyIds.NAME + ", d." + PropertyIds.CREATION_DATE + " from cmis:document as d join cm:titled as t on d.cmis:objectId = t.cmis:objectId where in_folder(d, '" + workflowDefinitionFolder.getId() + "') and d.cmis:name LIKE '%.bpmn20.xml' order by d.cmis:name"; LOGGER.info("Executing CMIS query '" + query + "'"); ItemIterable<QueryResult> results = cmisSession.query(query, false); // Transmorph them into the correct KickstartWorkflowInfo object ArrayList<KickstartWorkflowInfo> workflowInfos = new ArrayList<KickstartWorkflowInfo>(); for (QueryResult result : results) { // We're using only a fraction of the KickstartWorkflowInfo objects KickstartWorkflowInfo kickstartWorkflowInfo = new KickstartWorkflowInfo(); kickstartWorkflowInfo.setName((String) result.getPropertyValueById("cm:description")); kickstartWorkflowInfo .setId(processNameToBaseName((String) result.getPropertyValueById(PropertyIds.NAME))); GregorianCalendar createDate = result.getPropertyValueById(PropertyIds.CREATION_DATE); kickstartWorkflowInfo.setCreateTime(createDate.getTime()); workflowInfos.add(kickstartWorkflowInfo); } return workflowInfos; } public KickstartWorkflowInfo findWorkflowInformation(String processDefinitionId, boolean includeCounts) { KickstartWorkflowInfo kickstartWorkflowInfo = new KickstartWorkflowInfo(); // Get general info Session cmisSession = getCmisSession(); Folder workflowDefinitionFolder = (Folder) cmisSession.getObjectByPath(WORKFLOW_DEFINITION_FOLDER); try { Document bpmn20Document = (Document) cmisSession.getObjectByPath( workflowDefinitionFolder.getPath() + "/" + generateBpmnResourceName(processDefinitionId)); if (bpmn20Document != null) { kickstartWorkflowInfo .setId(processNameToBaseName((String) bpmn20Document.getPropertyValue(PropertyIds.NAME))); kickstartWorkflowInfo.setName((String) bpmn20Document.getPropertyValue("cm:description")); kickstartWorkflowInfo.setCreateTime( ((GregorianCalendar) bpmn20Document.getPropertyValue(PropertyIds.CREATION_DATE)).getTime()); } else { return null; } } catch (CmisObjectNotFoundException e) { return null; } // Get counts if (includeCounts) { kickstartWorkflowInfo .setNrOfRuntimeInstances(retrieveWorkflowInstanceIds(kickstartWorkflowInfo.getName()).size()); } return kickstartWorkflowInfo; } public KickstartWorkflow findWorkflowById(String id) { throw new UnsupportedOperationException(); } public void deleteWorkflow(String processDefinitionId) { // Delete all workflow instances, as this will block the undeployment of the process otherwise deleteWorkflowInstances(processDefinitionId); // TODO: make constants for the files, both for use in creation/removal // Remove all files in the workflow definition folder deleteDocumentFromFolder(WORKFLOW_DEFINITION_FOLDER, processDefinitionId + ".png"); deleteDocumentFromFolder(WORKFLOW_DEFINITION_FOLDER, processDefinitionId + "_image.png"); deleteDocumentFromFolder(WORKFLOW_DEFINITION_FOLDER, processDefinitionId + ".json"); deleteDocumentFromFolder(WORKFLOW_DEFINITION_FOLDER, processDefinitionId + ".bpmn20.xml"); // Remove task model deleteDocumentFromFolder(DATA_DICTIONARY_FOLDER, processDefinitionId + "-task-model.xml"); // Remove form config deleteFormConfig(processDefinitionId); deleteDocumentFromFolder(WORKFLOW_DEFINITION_FOLDER, processDefinitionId + "-form-config.xml"); } public InputStream getProcessImage(String processDefinitionId) { Session cmisSession = getCmisSession(); Folder workflowDefinitionFolder = (Folder) cmisSession.getObjectByPath(WORKFLOW_DEFINITION_FOLDER); Document imageDocument = (Document) cmisSession.getObjectByPath( workflowDefinitionFolder.getPath() + "/" + processDefinitionIdToProcessImage(processDefinitionId)); return imageDocument.getContentStream().getStream(); } public void setProcessImage(String processDefinitionId, InputStream processImageStream) { Session cmisSession = getCmisSession(); Folder workflowDefinitionFolder = (Folder) cmisSession.getObjectByPath(WORKFLOW_DEFINITION_FOLDER); String fileName = processDefinitionIdToProcessImage(processDefinitionId); ContentStream contentStream = new ContentStreamImpl(fileName, null, "image/png", processImageStream); Document imageDocument = getDocumentFromFolder(workflowDefinitionFolder.getPath(), fileName); if (imageDocument == null) { HashMap<String, Object> properties = new HashMap<String, Object>(); properties.put("cmis:name", fileName); properties.put("cmis:objectTypeId", "cmis:document"); workflowDefinitionFolder.createDocument(properties, contentStream, VersioningState.MAJOR); } else { imageDocument.setContentStream(contentStream, true); } } // Helpets ////////////////////// /** * Generates a valid bpmn 2.0 file name for the given process name. */ protected String generateBpmnResourceName(String string) { return string.replace(" ", "_") + ".bpmn20.xml"; } protected String processDefinitionIdToProcessImage(String processDefinitionId) { return processDefinitionId + "_image.png"; } public InputStream getBpmnXml(String processDefinitionId) { throw new UnsupportedOperationException(); } // CMIS helper methods ////////////////////////////////////////////////////////////////////////////////////////////// protected void deleteDocumentFromFolder(String folderPath, String documentName) { Document document = getDocumentFromFolder(folderPath, documentName); if (document != null) { document.delete(true); LOGGER.info("Removed document " + folderPath + "/" + documentName); } } protected Document getDocumentFromFolder(String folderPath, String documentName) { try { Session cmisSession = getCmisSession(); Folder workflowDefinitionFolder = (Folder) cmisSession.getObjectByPath(folderPath); String path = workflowDefinitionFolder.getPath() + "/" + documentName; return (Document) cmisSession.getObjectByPath(path); } catch (CmisObjectNotFoundException e) { return null; } } protected void uploadStringToDocument(String string, String folderPath, String documentName, String mimetype) { Session cmisSession = getCmisSession(); Folder folder = (Folder) cmisSession.getObjectByPath(folderPath); ContentStream contentStream = new ContentStreamImpl(documentName, null, mimetype, new ByteArrayInputStream(string.getBytes())); Document document = getDocumentFromFolder(folder.getPath(), documentName); if (document == null) { HashMap<String, Object> properties = new HashMap<String, Object>(); properties.put("cmis:name", documentName); properties.put("cmis:objectTypeId", "cmis:document"); folder.createDocument(properties, contentStream, VersioningState.MAJOR); } else { document.setContentStream(contentStream, true); } } // Helper methods for REST calls ///////////////////////////////////////////////////////////////////////////////////// protected List<String> retrieveWorkflowInstanceIds(String workflowName) { String url = ALFRESCO_BASE_URL + "api/workflow-instances?state=active"; JsonNode json = executeGet(url); ArrayNode data = (ArrayNode) json.get("data"); ArrayList<String> workflowInstanceIds = new ArrayList<String>(); for (int i = 0; i < data.size(); i++) { String title = data.get(i).get("title").asText(); if (title.equalsIgnoreCase(workflowName)) { workflowInstanceIds.add(data.get(i).get("id").asText()); } } return workflowInstanceIds; } protected JsonNode executeGet(String url) { HttpState state = new HttpState(); state.setCredentials(new AuthScope(null, AuthScope.ANY_PORT), new UsernamePasswordCredentials(cmisUser, cmisPassword)); GetMethod getMethod = new GetMethod(url); LOGGER.info("Executing GET '" + url + "'"); try { HttpClient httpClient = new HttpClient(); int result = httpClient.executeMethod(null, getMethod, state); // Display Response String responseJson = getMethod.getResponseBodyAsString(); LOGGER.info("Response status code: " + result); LOGGER.info("Response body: " + responseJson); ObjectMapper mapper = new ObjectMapper(); JsonNode json = mapper.readTree(responseJson); return json; } catch (Throwable t) { System.err.println("Error: " + t.getMessage()); t.printStackTrace(); } finally { getMethod.releaseConnection(); } return null; } protected void deleteWorkflowInstances(String workflowName) { List<String> workflowInstanceIds = retrieveWorkflowInstanceIds(workflowName); for (String workflowInstanceId : workflowInstanceIds) { deleteWorkflowInstance(workflowInstanceId); LOGGER.info("Deleted workflow instance '" + workflowInstanceId + "'"); } } protected void deleteWorkflowInstance(String workflowInstanceId) { HttpState state = new HttpState(); state.setCredentials(new AuthScope(null, AuthScope.ANY_PORT), new UsernamePasswordCredentials(cmisUser, cmisPassword)); // Only fetching one, we're only interested in the paging information, after all String url = ALFRESCO_BASE_URL + "api/workflow-instances/" + workflowInstanceId + "?forced=true"; DeleteMethod deleteMethod = new DeleteMethod(url); LOGGER.info("Executing DELETE '" + url + "'"); try { HttpClient httpClient = new HttpClient(); int result = httpClient.executeMethod(null, deleteMethod, state); // Display Response String responseJson = deleteMethod.getResponseBodyAsString(); LOGGER.info("Response status code: " + result); LOGGER.info("Response body: " + responseJson); } catch (Throwable t) { System.err.println("Error: " + t.getMessage()); t.printStackTrace(); } finally { deleteMethod.releaseConnection(); } } protected void deleteFormConfig(String workflowId) { String url = SHARE_BASE_URL + "page/modules/module/delete?moduleId=" + URLEncoder.encode("kickstart_form_" + workflowId); int statusCode = executeDelete(url); // Cont. hackyness from the upload. Read all about it there. int version = 1; while (statusCode == 200) { statusCode = executeDelete(url + "_" + version); version++; } } private int executeDelete(String url) { LOGGER.info("Executed module delete: " + url); GetMethod method = new GetMethod(url); try { HttpState httpState = new HttpState(); httpState.setCredentials(new AuthScope(null, AuthScope.ANY_PORT), new UsernamePasswordCredentials("admin", "admin")); HttpClient httpClient = new HttpClient(); int statusCode = httpClient.executeMethod(null, method, httpState); LOGGER.info("Status code result for delete: " + statusCode); return statusCode; } catch (Throwable t) { LOGGER.log(Level.SEVERE, "Error: " + t.getMessage()); t.printStackTrace(); } finally { method.releaseConnection(); } throw new RuntimeException("Programmatic error. You shouldn't be here."); } // Getters & Setters public String getCmisUser() { return cmisUser; } public void setCmisUser(String cmisUser) { this.cmisUser = cmisUser; } public String getCmisPassword() { return cmisPassword; } public void setCmisPassword(String cmisPassword) { this.cmisPassword = cmisPassword; } public String getCmisAtompubUrl() { return cmisAtompubUrl; } public void setCmisAtompubUrl(String cmisAtompubUrl) { this.cmisAtompubUrl = cmisAtompubUrl; } public Bpmn20MarshallingService getMarshallingService() { return marshallingService; } public void setMarshallingService(Bpmn20MarshallingService marshallingService) { this.marshallingService = marshallingService; } // Helper methods for XML templates ///////////////////////////////////////////////////////////////// protected String getTaskModelTemplate() { if (TASK_MODEL_TEMPLATE == null) { TASK_MODEL_TEMPLATE = readTemplateFile(TASK_MODEL_TEMPLATE_FILE); } return TASK_MODEL_TEMPLATE; } protected String getTaskModelTypeTemplate() { if (TASK_MODEL_TYPE_TEMPLATE == null) { TASK_MODEL_TYPE_TEMPLATE = readTemplateFile(TASK_MODEL_TYPE_TEMPLATE_FILE); } return TASK_MODEL_TYPE_TEMPLATE; } protected String getTaskModelPropertyTemplate() { if (TASK_MODEL_PROPERTY_TEMPLATE == null) { TASK_MODEL_PROPERTY_TEMPLATE = readTemplateFile(TASK_MODEL_PROPERTY_TEMPLATE_FILE); } return TASK_MODEL_PROPERTY_TEMPLATE; } protected String getFormConfigTemplate() { if (FORM_CONFIG_TEMPLATE == null) { FORM_CONFIG_TEMPLATE = readTemplateFile(FORM_CONFIG_TEMPLATE_FILE); } return FORM_CONFIG_TEMPLATE; } protected String getFormConfigEvaluatorConfigTemplate() { if (FORM_CONFIG_EVALUATOR_CONFIG_TEMPLATE == null) { FORM_CONFIG_EVALUATOR_CONFIG_TEMPLATE = readTemplateFile(FORM_CONFIG_EVALUATOR_CONFIG_TEMPLATE_FILE); } return FORM_CONFIG_EVALUATOR_CONFIG_TEMPLATE; } protected String getFormConfigFieldTemplate() { if (FORM_CONFIG_FIELD_TEMPLATE == null) { FORM_CONFIG_FIELD_TEMPLATE = readTemplateFile(FORM_CONFIG_FIELD_TEMPLATE_FILE); } return FORM_CONFIG_FIELD_TEMPLATE; } protected String getFormConfigFieldVisibilityTemplate() { if (FORM_CONFIG_FIELD_VISIBILITY_TEMPLATE == null) { FORM_CONFIG_FIELD_VISIBILITY_TEMPLATE = readTemplateFile(FORM_CONFIG_FIELD_VISIBILITY_TEMPLATE_FILE); } return FORM_CONFIG_FIELD_VISIBILITY_TEMPLATE; } protected String getFormConfigInfoTemplate() { if (FORM_CONFIG_FIELD_INFO_TEMPLATE == null) { FORM_CONFIG_FIELD_INFO_TEMPLATE = readTemplateFile(FORM_CONFIG_FIELD_INFO_TEMPLATE_FILE); } return FORM_CONFIG_FIELD_INFO_TEMPLATE; } protected String readTemplateFile(String templateFile) { LOGGER.info("Reading template file '" + templateFile + "'"); InputStream inputStream = AlfrescoKickstartServiceImpl.class.getResourceAsStream(templateFile); if (inputStream == null) { LOGGER.warning("Could not read template file '" + templateFile + "'!"); } else { try { return IOUtils.toString(inputStream); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Error while reading '" + templateFile + "' : " + e.getMessage()); } } return null; } protected void prettyLogXml(String xml) { try { Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); Source xmlInput = new StreamSource(new StringReader(xml)); StreamResult xmlOutput = new StreamResult(new StringWriter()); transformer.transform(xmlInput, xmlOutput); LOGGER.info(xmlOutput.getWriter().toString()); } catch (Exception e) { e.printStackTrace(); } } }