/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2012-2015 ForgeRock AS. */ package org.forgerock.openidm.workflow.activiti.impl; import static org.forgerock.json.resource.Responses.newQueryResponse; import static org.forgerock.json.resource.Responses.newResourceResponse; import static org.forgerock.openidm.util.ResourceUtil.notSupportedOnCollection; import static org.forgerock.openidm.util.ResourceUtil.notSupportedOnInstance; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.forgerock.services.context.Context; import org.forgerock.json.resource.ActionRequest; import org.forgerock.json.resource.ActionResponse; import org.forgerock.json.resource.BadRequestException; import org.forgerock.json.resource.CollectionResourceProvider; import org.forgerock.json.resource.ConflictException; import org.forgerock.json.resource.CreateRequest; import org.forgerock.json.resource.DeleteRequest; import org.forgerock.json.resource.InternalServerErrorException; import org.forgerock.json.resource.NotFoundException; import org.forgerock.json.resource.PatchRequest; import org.forgerock.json.resource.QueryRequest; import org.forgerock.json.resource.QueryResourceHandler; import org.forgerock.json.resource.QueryResponse; import org.forgerock.json.resource.ReadRequest; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResourceResponse; import org.forgerock.services.context.SecurityContext; import org.forgerock.json.resource.UpdateRequest; import org.forgerock.openidm.workflow.activiti.ActivitiConstants; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import org.activiti.engine.ActivitiObjectNotFoundException; import org.activiti.engine.FormService; import org.activiti.engine.ProcessEngine; import org.activiti.engine.form.StartFormData; import org.activiti.engine.impl.RepositoryServiceImpl; import org.activiti.engine.impl.form.DateFormType; import org.activiti.engine.impl.form.DefaultStartFormHandler; import org.activiti.engine.impl.form.EnumFormType; import org.activiti.engine.impl.form.FormPropertyHandler; import org.activiti.engine.impl.identity.Authentication; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.repository.ProcessDefinitionQuery; import org.apache.ibatis.exceptions.PersistenceException; import org.forgerock.json.JsonPointer; import org.forgerock.json.JsonValue; import org.forgerock.openidm.workflow.activiti.impl.mixin.DateFormTypeMixIn; import org.forgerock.openidm.workflow.activiti.impl.mixin.EnumFormTypeMixIn; import org.forgerock.openidm.workflow.activiti.impl.mixin.ProcessDefinitionMixIn; import org.forgerock.util.encode.Base64; import org.forgerock.util.promise.Promise; /** * Resource implementation of ProcessDefinition related Activiti operations * */ public class ProcessDefinitionResource implements CollectionResourceProvider { private final static ObjectMapper mapper; private ProcessEngine processEngine; static { mapper = new ObjectMapper(); mapper.addMixIn(ProcessDefinitionEntity.class, ProcessDefinitionMixIn.class); mapper.addMixIn(EnumFormType.class, EnumFormTypeMixIn.class); mapper.addMixIn(DateFormType.class, DateFormTypeMixIn.class); mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true); } public ProcessDefinitionResource(ProcessEngine processEngine) { this.processEngine = processEngine; } @Override public Promise<ActionResponse, ResourceException> actionCollection(Context context, ActionRequest request) { return notSupportedOnCollection(request).asPromise(); } @Override public Promise<ActionResponse, ResourceException> actionInstance( Context context, String resourceId, ActionRequest request) { return notSupportedOnInstance(request).asPromise(); } @Override public Promise<ResourceResponse, ResourceException> createInstance(Context context, CreateRequest request) { return notSupportedOnInstance(request).asPromise(); } @Override public Promise<ResourceResponse, ResourceException> deleteInstance( Context context, String resourceId, DeleteRequest request) { try { Authentication.setAuthenticatedUserId(context.asContext(SecurityContext.class).getAuthenticationId()); ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) processEngine.getRepositoryService().getProcessDefinition(resourceId); if (processDefinition != null) { ResourceResponse r = convertInstance(processDefinition, request.getFields()); processEngine.getRepositoryService().deleteDeployment(processDefinition.getDeploymentId(), false); return r.asPromise(); } else { throw new NotFoundException(); } } catch (ActivitiObjectNotFoundException ex) { return new NotFoundException(ex.getMessage()).asPromise(); } catch (PersistenceException ex) { return new ConflictException("The process definition has running instances, can not be deleted") .asPromise(); } catch (ResourceException e) { return e.asPromise(); } catch (Exception ex) { return new InternalServerErrorException(ex.getMessage()).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> patchInstance( Context context, String resourceId, PatchRequest request) { return notSupportedOnInstance(request).asPromise(); } @Override public Promise<QueryResponse, ResourceException> queryCollection (Context context, QueryRequest request, QueryResourceHandler handler) { try { Authentication.setAuthenticatedUserId(context.asContext(SecurityContext.class).getAuthenticationId()); if (ActivitiConstants.QUERY_ALL_IDS.equals(request.getQueryId())) { List<ProcessDefinition> definitionList = processEngine.getRepositoryService().createProcessDefinitionQuery().list(); if (definitionList != null && definitionList.size() > 0) { for (ProcessDefinition processDefinition : definitionList) { Map value = mapper.convertValue(processDefinition, HashMap.class); ResourceResponse r = newResourceResponse(processDefinition.getId(), null, new JsonValue(value)); handler.handleResource(r); } } return newQueryResponse().asPromise(); } else if (ActivitiConstants.QUERY_FILTERED.equals(request.getQueryId())) { ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery(); setProcessDefinitionParams(query, request); List<ProcessDefinition> list = query.list(); for (ProcessDefinition processDefinition : list) { Map value = mapper.convertValue(processDefinition, HashMap.class); ResourceResponse r = newResourceResponse(processDefinition.getId(), null, new JsonValue(value)); handler.handleResource(r); } return newQueryResponse().asPromise(); } else { throw new BadRequestException("Unknown query-id"); } } catch (ResourceException e) { return e.asPromise(); } catch (Exception ex) { return new InternalServerErrorException(ex.getMessage(), ex).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> readInstance( Context context, String resourceId, ReadRequest request) { try { Authentication.setAuthenticatedUserId(context.asContext(SecurityContext.class).getAuthenticationId()); ProcessDefinitionEntity def = (ProcessDefinitionEntity) ((RepositoryServiceImpl) processEngine.getRepositoryService()) .getDeployedProcessDefinition(resourceId); return convertInstance(def, request.getFields()).asPromise(); } catch (ActivitiObjectNotFoundException ex) { return new NotFoundException(ex.getMessage()).asPromise(); } catch (Exception ex) { return new InternalServerErrorException(ex.getMessage(), ex).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> updateInstance( Context context, String resourceId, UpdateRequest request) { return notSupportedOnInstance(request).asPromise(); } /** * Process the query parameters of the request and set it on the * ProcessDefinitionQuery * * @param query Query to update * @param request incoming request */ private void setProcessDefinitionParams(ProcessDefinitionQuery query, QueryRequest request) { String deploymentId = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_DEPLOYMENTID); query = deploymentId == null ? query : query.deploymentId(deploymentId); String category = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_CATEGORY); query = category == null ? query : query.processDefinitionCategory(category); String categoryLike = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_CATEGORY + ActivitiConstants.LIKE); query = categoryLike == null ? query : query.processDefinitionCategoryLike(categoryLike); String processDefinitionId = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ID); query = processDefinitionId == null ? query : query.processDefinitionId(processDefinitionId); String processDefinitionKey = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_KEY); query = processDefinitionKey == null ? query : query.processDefinitionKey(processDefinitionKey); String processDefinitionKeyLike = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_KEY + ActivitiConstants.LIKE); query = processDefinitionKeyLike == null ? query : query.processDefinitionKeyLike(processDefinitionKeyLike); String processDefinitionName = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_NAME); query = processDefinitionName == null ? query : query.processDefinitionName(processDefinitionName); String processDefinitionNameLike = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_NAME + ActivitiConstants.LIKE); query = processDefinitionNameLike == null ? query : query.processDefinitionNameLike(processDefinitionNameLike); String processDefinitionResourceName = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_PROCESSDEFINITIONRESOURCENAME); query = processDefinitionResourceName == null ? query : query.processDefinitionResourceName(processDefinitionResourceName); String processDefinitionResourceNameLike = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_PROCESSDEFINITIONRESOURCENAME + ActivitiConstants.LIKE); query = processDefinitionResourceNameLike == null ? query : query.processDefinitionResourceNameLike(processDefinitionResourceNameLike); String processDefinitionVersion = ActivitiUtil.getParamFromRequest(request, ActivitiConstants.ACTIVITI_VERSION); query = processDefinitionVersion == null ? query : query.processDefinitionVersion(Integer.getInteger(processDefinitionVersion)); } /** * Return the list of FormProperty-related data * * @param handlers list of handlers to process * @return propertyList list of form properties */ private List<Map<String, Object>> getFormHandlerData(List<FormPropertyHandler> handlers) { final List<Map<String, Object>> propertyList = new ArrayList<>(); for (FormPropertyHandler h : handlers) { Map<String, Object> entry = new HashMap<>(); entry.put(ActivitiConstants.ID, h.getId()); entry.put(ActivitiConstants.FORMPROPERTY_DEFAULTEXPRESSION, h.getDefaultExpression()); entry.put(ActivitiConstants.FORMPROPERTY_VARIABLEEXPRESSION, h.getVariableExpression()); entry.put(ActivitiConstants.FORMPROPERTY_VARIABLENAME, h.getVariableName()); entry.put(ActivitiConstants.ACTIVITI_NAME, h.getName()); Map<String, Object> type = new HashMap<>(3); if (h.getType() != null) { type.put(ActivitiConstants.ACTIVITI_NAME, h.getType().getName()); type.put(ActivitiConstants.ENUM_VALUES, h.getType().getInformation("values")); type.put(ActivitiConstants.DATE_PATTERN, h.getType().getInformation("datePattern")); } entry.put(ActivitiConstants.FORMPROPERTY_TYPE, type); entry.put(ActivitiConstants.FORMPROPERTY_READABLE, h.isReadable()); entry.put(ActivitiConstants.FORMPROPERTY_REQUIRED, h.isRequired()); entry.put(ActivitiConstants.FORMPROPERTY_WRITABLE, h.isWritable()); propertyList.add(entry); } return propertyList; } /** * Converts a ProcessDefinitionEntity to Resource object * * @param processDefinition entity to be converted * @param fields the list of requested fields * @return converted process definition * @throws IOException */ private ResourceResponse convertInstance(ProcessDefinitionEntity processDefinition, List<JsonPointer> fields) throws IOException { final String deploymentId = processDefinition.getDeploymentId(); final JsonValue content = new JsonValue(mapper.convertValue(processDefinition, Map.class)); // add form data if (processDefinition.hasStartFormKey()) { FormService formService = processEngine.getFormService(); StartFormData startFormData = formService.getStartFormData(processDefinition.getId()); content.put(ActivitiConstants.ACTIVITI_FORMRESOURCEKEY, startFormData.getFormKey()); try (final InputStream startForm = processEngine.getRepositoryService().getResourceAsStream( deploymentId, startFormData.getFormKey()); final Reader reader = new InputStreamReader(startForm)) { Scanner s = new Scanner(reader).useDelimiter("\\A"); String formTemplate = s.hasNext() ? s.next() : ""; content.put(ActivitiConstants.ACTIVITI_FORMGENERATIONTEMPLATE, formTemplate); } } // add diagram if requested and exists if (fields.contains(ActivitiConstants.ACTIVITI_DIAGRAM) && processDefinition.getDiagramResourceName() != null) { try (final InputStream is = processEngine.getRepositoryService().getResourceAsStream( deploymentId, processDefinition.getDiagramResourceName())) { final byte[] data = new byte[is.available()]; is.read(data); content.put(ActivitiConstants.ACTIVITI_DIAGRAM, Base64.encode(data)); } } DefaultStartFormHandler startFormHandler = (DefaultStartFormHandler) processDefinition.getStartFormHandler(); content.put(ActivitiConstants.FORMPROPERTIES, getFormHandlerData(startFormHandler.getFormPropertyHandlers())); return newResourceResponse(processDefinition.getId(), null, content); } }