diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/persistence/entity/IdentityLinkEntity.java b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/persistence/entity/IdentityLinkEntity.java index f9479172662684b53360c55982aef7a178b80eb3..72f8ad9ccdd792833b8d9a73e9fb7693e62c0f0c 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/impl/persistence/entity/IdentityLinkEntity.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/impl/persistence/entity/IdentityLinkEntity.java @@ -202,6 +202,11 @@ public class IdentityLinkEntity implements Serializable, IdentityLink, Persisten this.processDef = processDef; this.processDefId = processDef.getId(); } + + @Override + public String getProcessDefinitionId() { + return this.processDefId; + } @Override public String toString() { diff --git a/modules/activiti-engine/src/main/java/org/activiti/engine/task/IdentityLink.java b/modules/activiti-engine/src/main/java/org/activiti/engine/task/IdentityLink.java index 21b9f8becf41aaf37efc8e43a0fefc785b9c6b16..17f4476620c0441b954e0786400c4148cf7d9ecf 100644 --- a/modules/activiti-engine/src/main/java/org/activiti/engine/task/IdentityLink.java +++ b/modules/activiti-engine/src/main/java/org/activiti/engine/task/IdentityLink.java @@ -50,5 +50,15 @@ public interface IdentityLink { * The id of the task associated with this identity link. */ String getTaskId(); + + /** + * The process definition id associated with this identity link. + */ + String getProcessDefinitionId(); + + /** + * The process instance id associated with this identity link. + */ + String getProcessInstanceId(); } diff --git a/modules/activiti-engine/src/main/resources/org/activiti/db/mapping/entity/IdentityLink.xml b/modules/activiti-engine/src/main/resources/org/activiti/db/mapping/entity/IdentityLink.xml index bfd57e2ce1372ebe607f07a23295277e217bb36f..b0b9f90549b1c59fce4d3721d4e0095315d9abab 100644 --- a/modules/activiti-engine/src/main/resources/org/activiti/db/mapping/entity/IdentityLink.xml +++ b/modules/activiti-engine/src/main/resources/org/activiti/db/mapping/entity/IdentityLink.xml @@ -39,7 +39,7 @@ - + diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestResponseFactory.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestResponseFactory.java index b457c6ec72096a65905a19d039c219264680c492..d4654b7e5ad47f25de9e9fd509354be8f62231bd 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestResponseFactory.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestResponseFactory.java @@ -43,6 +43,7 @@ import org.activiti.engine.task.Task; import org.activiti.rest.api.engine.AttachmentResponse; import org.activiti.rest.api.engine.CommentResponse; import org.activiti.rest.api.engine.EventResponse; +import org.activiti.rest.api.engine.RestIdentityLink; import org.activiti.rest.api.engine.variable.BooleanRestVariableConverter; import org.activiti.rest.api.engine.variable.DateRestVariableConverter; import org.activiti.rest.api.engine.variable.DoubleRestVariableConverter; @@ -63,7 +64,6 @@ import org.activiti.rest.api.identity.GroupResponse; import org.activiti.rest.api.identity.MembershipResponse; import org.activiti.rest.api.identity.UserInfoResponse; import org.activiti.rest.api.identity.UserResponse; -import org.activiti.rest.api.legacy.identity.LegacyRestIdentityLink; import org.activiti.rest.api.management.JobResponse; import org.activiti.rest.api.management.TableResponse; import org.activiti.rest.api.repository.DeploymentResourceResponse; @@ -300,12 +300,12 @@ public class RestResponseFactory { return value; } - public LegacyRestIdentityLink createRestIdentityLink(SecuredResource securedResource, IdentityLink link) { - return createRestIdentityLink(securedResource, link.getType(), link.getUserId(), link.getGroupId(), link.getTaskId()); + public RestIdentityLink createRestIdentityLink(SecuredResource securedResource, IdentityLink link) { + return createRestIdentityLink(securedResource, link.getType(), link.getUserId(), link.getGroupId(), link.getTaskId(), link.getProcessDefinitionId()); } - public LegacyRestIdentityLink createRestIdentityLink(SecuredResource securedResource, String type, String userId, String groupId, String taskId) { - LegacyRestIdentityLink result = new LegacyRestIdentityLink(); + public RestIdentityLink createRestIdentityLink(SecuredResource securedResource, String type, String userId, String groupId, String taskId, String processDefinitionId) { + RestIdentityLink result = new RestIdentityLink(); result.setUser(userId); result.setGroup(groupId); result.setType(type); @@ -316,7 +316,11 @@ public class RestResponseFactory { } else { family = RestUrls.SEGMENT_IDENTITYLINKS_FAMILY_GROUPS; } - result.setUrl(securedResource.createFullResourceUrl(RestUrls.URL_TASK_IDENTITYLINK, taskId, family, (userId != null ? userId : groupId), type)); + if(processDefinitionId != null) { + result.setUrl(securedResource.createFullResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, processDefinitionId, family, (userId != null ? userId : groupId))); + } else { + result.setUrl(securedResource.createFullResourceUrl(RestUrls.URL_TASK_IDENTITYLINK, taskId, family, (userId != null ? userId : groupId), type)); + } return result; } diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestUrls.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestUrls.java index d3ecbe001d0dabb3f8156373f72ebeb755542009..72563fbac7e1953a6b540c5b5cd8bc5b59e45514 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestUrls.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/RestUrls.java @@ -103,6 +103,15 @@ public final class RestUrls { */ public static final String[] URL_PROCESS_DEFINITION = {SEGMENT_REPOSITORY_RESOURCES, SEGMENT_PROCESS_DEFINITION_RESOURCE, "{0}"}; + /** + * URL template for a process definition's identity links: repository/process-definitions/{0:processDefinitionId}/identitylinks + */ + public static final String[] URL_PROCESS_DEFINITION_IDENTITYLINKS_COLLECTION = {SEGMENT_REPOSITORY_RESOURCES, SEGMENT_PROCESS_DEFINITION_RESOURCE, "{0}", SEGMENT_IDENTITYLINKS}; + + /** + * URL template for an identitylink on a process definition: repository/process-definitions/{0:processDefinitionId}/identitylinks/{1:family}/{2:identityId} + */ + public static final String[] URL_PROCESS_DEFINITION_IDENTITYLINK = {SEGMENT_REPOSITORY_RESOURCES, SEGMENT_PROCESS_DEFINITION_RESOURCE, "{0}", SEGMENT_IDENTITYLINKS, "{1}", "{2}"}; /** * URL template for task collection: runtime/tasks/{0:taskId} diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/engine/RestIdentityLink.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/engine/RestIdentityLink.java new file mode 100644 index 0000000000000000000000000000000000000000..87c815252505c09cf3d2adc0a9e2d2932f11a793 --- /dev/null +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/engine/RestIdentityLink.java @@ -0,0 +1,51 @@ +/* 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.rest.api.engine; + + +/** + * @author Frederik Heremans + */ +public class RestIdentityLink { + + private String url; + private String user; + private String group; + private String type; + + public String getUrl() { + return url; + } + public void setUrl(String url) { + this.url = url; + } + public String getUser() { + return user; + } + public void setUser(String user) { + this.user = user; + } + public String getGroup() { + return group; + } + public void setGroup(String group) { + this.group = group; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } +} diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinkCollectionResource.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinkCollectionResource.java new file mode 100644 index 0000000000000000000000000000000000000000..e2458b3df234616e2df0cd779dfc2bfd00b41597 --- /dev/null +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinkCollectionResource.java @@ -0,0 +1,102 @@ +/* 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.rest.api.repository; + +import java.util.ArrayList; +import java.util.List; + +import org.activiti.engine.ActivitiIllegalArgumentException; +import org.activiti.engine.ActivitiObjectNotFoundException; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.task.IdentityLink; +import org.activiti.engine.task.IdentityLinkType; +import org.activiti.rest.api.ActivitiUtil; +import org.activiti.rest.api.RestResponseFactory; +import org.activiti.rest.api.SecuredResource; +import org.activiti.rest.api.engine.RestIdentityLink; +import org.activiti.rest.application.ActivitiRestServicesApplication; +import org.restlet.data.Status; +import org.restlet.resource.Get; +import org.restlet.resource.Post; + + +/** + * @author Frederik Heremans + */ +public class ProcessDefinitionIdentityLinkCollectionResource extends SecuredResource { + + @Get + public List getIdentityLinks() { + if(!authenticate()) + return null; + + List result = new ArrayList(); + ProcessDefinition processDefinition = getProcessDefinitionFromRequest(); + + List identityLinks = ActivitiUtil.getRepositoryService().getIdentityLinksForProcessDefinition(processDefinition.getId()); + RestResponseFactory responseFactory = getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory(); + for(IdentityLink link : identityLinks) { + result.add(responseFactory.createRestIdentityLink(this, link)); + } + return result; + } + + @Post + public RestIdentityLink createIdentityLink(RestIdentityLink identityLink) { + if(!authenticate()) + return null; + + ProcessDefinition processDefinition = getProcessDefinitionFromRequest(); + + if(identityLink.getGroup() == null && identityLink.getUser() == null) { + throw new ActivitiIllegalArgumentException("A group or a user is required to create an identity link."); + } + + if(identityLink.getGroup() != null && identityLink.getUser() != null) { + throw new ActivitiIllegalArgumentException("Only one of user or group can be used to create an identity link."); + } + + if(identityLink.getGroup() != null) { + ActivitiUtil.getRepositoryService().addCandidateStarterGroup(processDefinition.getId(), identityLink.getGroup()); + } else { + ActivitiUtil.getRepositoryService().addCandidateStarterUser(processDefinition.getId(), identityLink.getUser()); + } + + // Always candidate for process-definition. User-provided value is ignored + identityLink.setType(IdentityLinkType.CANDIDATE); + + setStatus(Status.SUCCESS_CREATED); + return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory() + .createRestIdentityLink(this, identityLink.getType(), identityLink.getUser(), identityLink.getGroup(), null, processDefinition.getId()); + } + + /** + * Returns the {@link ProcessDefinition} that is requested. Throws the right exceptions + * when bad request was made or definition is not found. + */ + protected ProcessDefinition getProcessDefinitionFromRequest() { + String processDefinitionId = getAttribute("processDefinitionId"); + if(processDefinitionId == null) { + throw new ActivitiIllegalArgumentException("The processDefinitionId cannot be null"); + } + + ProcessDefinition processDefinition = ActivitiUtil.getRepositoryService().createProcessDefinitionQuery() + .processDefinitionId(processDefinitionId).singleResult(); + + if(processDefinition == null) { + throw new ActivitiObjectNotFoundException("Could not find a process definition with id '" + processDefinitionId + "'.", ProcessDefinition.class); + } + return processDefinition; + } +} diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinkResource.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinkResource.java new file mode 100644 index 0000000000000000000000000000000000000000..9d05c3f83de1ac9d05fc20186ffa6259821c49cd --- /dev/null +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinkResource.java @@ -0,0 +1,125 @@ +/* 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.rest.api.repository; + +import java.util.List; + +import org.activiti.engine.ActivitiIllegalArgumentException; +import org.activiti.engine.ActivitiObjectNotFoundException; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.task.IdentityLink; +import org.activiti.engine.task.IdentityLinkType; +import org.activiti.rest.api.ActivitiUtil; +import org.activiti.rest.api.RestUrls; +import org.activiti.rest.api.SecuredResource; +import org.activiti.rest.api.engine.RestIdentityLink; +import org.activiti.rest.application.ActivitiRestServicesApplication; +import org.restlet.data.Status; +import org.restlet.resource.Delete; +import org.restlet.resource.Get; + +/** + * @author Frederik Heremans + */ +public class ProcessDefinitionIdentityLinkResource extends SecuredResource { + + @Get + public RestIdentityLink getIdentityLink() { + if (!authenticate()) + return null; + + ProcessDefinition processDefinition = getProcessDefinitionFromRequest(); + + // Extract and validate identity link from URL + String family = getAttribute("family"); + String identityId = getAttribute("identityId"); + validateIdentityLinkArguments(family, identityId); + + // Check if identitylink to get exists + IdentityLink link = getIdentityLink(family, identityId, processDefinition.getId()); + return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory().createRestIdentityLink(this, link); + } + + @Delete + public void deleteIdentityLink() { + if (!authenticate()) + return; + + ProcessDefinition processDefinition = getProcessDefinitionFromRequest(); + + // Extract and validate identity link from URL + String family = getAttribute("family"); + String identityId = getAttribute("identityId"); + validateIdentityLinkArguments(family, identityId); + + // Check if identitylink to delete exists + IdentityLink link = getIdentityLink(family, identityId, processDefinition.getId()); + if(link.getUserId() != null) { + ActivitiUtil.getRepositoryService().deleteCandidateStarterUser(processDefinition.getId(), link.getUserId()); + } else { + ActivitiUtil.getRepositoryService().deleteCandidateStarterGroup(processDefinition.getId(), link.getGroupId()); + } + + setStatus(Status.SUCCESS_NO_CONTENT); + } + + protected void validateIdentityLinkArguments(String family, String identityId) { + if (family == null || (!RestUrls.SEGMENT_IDENTITYLINKS_FAMILY_GROUPS.equals(family) && !RestUrls.SEGMENT_IDENTITYLINKS_FAMILY_USERS.equals(family))) { + throw new ActivitiIllegalArgumentException("Identity link family should be 'users' or 'groups'."); + } + if (identityId == null) { + throw new ActivitiIllegalArgumentException("IdentityId is required."); + } + } + + protected IdentityLink getIdentityLink(String family, String identityId, String processDefinitionId) { + boolean isUser = family.equals(RestUrls.SEGMENT_IDENTITYLINKS_FAMILY_USERS); + + // Perhaps it would be better to offer getting a single identitylink from + // the API + List allLinks = ActivitiUtil.getRepositoryService().getIdentityLinksForProcessDefinition(processDefinitionId); + for (IdentityLink link : allLinks) { + boolean rightIdentity = false; + if (isUser) { + rightIdentity = identityId.equals(link.getUserId()); + } else { + rightIdentity = identityId.equals(link.getGroupId()); + } + + if (rightIdentity && link.getType().equals(IdentityLinkType.CANDIDATE)) { + return link; + } + } + throw new ActivitiObjectNotFoundException("Could not find the requested identity link.", IdentityLink.class); + } + + /** + * Returns the {@link ProcessDefinition} that is requested. Throws the right + * exceptions when bad request was made or definition is not found. + */ + protected ProcessDefinition getProcessDefinitionFromRequest() { + String processDefinitionId = getAttribute("processDefinitionId"); + if (processDefinitionId == null) { + throw new ActivitiIllegalArgumentException("The processDefinitionId cannot be null"); + } + + ProcessDefinition processDefinition = ActivitiUtil.getRepositoryService().createProcessDefinitionQuery().processDefinitionId(processDefinitionId) + .singleResult(); + + if (processDefinition == null) { + throw new ActivitiObjectNotFoundException("Could not find a process definition with id '" + processDefinitionId + "'.", ProcessDefinition.class); + } + return processDefinition; + } +} diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkCollectionResource.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkCollectionResource.java index 90271dd3c55ad1faccebbb8366da41b34944a71c..57889279a3d9e079ece74bc3620e9a2aa4394de9 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkCollectionResource.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkCollectionResource.java @@ -21,7 +21,7 @@ import org.activiti.engine.task.IdentityLink; import org.activiti.engine.task.Task; import org.activiti.rest.api.ActivitiUtil; import org.activiti.rest.api.RestResponseFactory; -import org.activiti.rest.api.legacy.identity.LegacyRestIdentityLink; +import org.activiti.rest.api.engine.RestIdentityLink; import org.activiti.rest.application.ActivitiRestServicesApplication; import org.restlet.data.Status; import org.restlet.resource.Get; @@ -34,11 +34,11 @@ import org.restlet.resource.Post; public class TaskIdentityLinkCollectionResource extends TaskBaseResource { @Get - public List getIdentityLinks() { + public List getIdentityLinks() { if(!authenticate()) return null; - List result = new ArrayList(); + List result = new ArrayList(); Task task = getTaskFromRequest(); List identityLinks = ActivitiUtil.getTaskService().getIdentityLinksForTask(task.getId()); @@ -50,7 +50,7 @@ public class TaskIdentityLinkCollectionResource extends TaskBaseResource { } @Post - public LegacyRestIdentityLink createIdentityLink(LegacyRestIdentityLink identityLink) { + public RestIdentityLink createIdentityLink(RestIdentityLink identityLink) { if(!authenticate()) return null; @@ -76,6 +76,6 @@ public class TaskIdentityLinkCollectionResource extends TaskBaseResource { setStatus(Status.SUCCESS_CREATED); return getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory() - .createRestIdentityLink(this, identityLink.getType(), identityLink.getUser(), identityLink.getGroup(), task.getId()); + .createRestIdentityLink(this, identityLink.getType(), identityLink.getUser(), identityLink.getGroup(), task.getId(), null); } } diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkFamilyResource.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkFamilyResource.java index fc7ecba5bab0bb563bfde3354f0778f850f905bc..fdb03c7a77d1d8cf160a4eee3c13c3fdf49f32d9 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkFamilyResource.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkFamilyResource.java @@ -23,7 +23,7 @@ import org.activiti.engine.task.Task; import org.activiti.rest.api.ActivitiUtil; import org.activiti.rest.api.RestResponseFactory; import org.activiti.rest.api.RestUrls; -import org.activiti.rest.api.legacy.identity.LegacyRestIdentityLink; +import org.activiti.rest.api.engine.RestIdentityLink; import org.activiti.rest.application.ActivitiRestServicesApplication; import org.restlet.data.Status; import org.restlet.resource.Delete; @@ -36,7 +36,7 @@ import org.restlet.resource.Get; public class TaskIdentityLinkFamilyResource extends TaskBaseResource { @Get - public List getIdentityLinksForFamily() { + public List getIdentityLinksForFamily() { if(!authenticate()) return null; @@ -51,7 +51,7 @@ public class TaskIdentityLinkFamilyResource extends TaskBaseResource { } boolean isUser = family.equals(RestUrls.SEGMENT_IDENTITYLINKS_FAMILY_USERS); - List results = new ArrayList(); + List results = new ArrayList(); RestResponseFactory responseFactory = getApplication(ActivitiRestServicesApplication.class).getRestResponseFactory(); List allLinks = ActivitiUtil.getTaskService().getIdentityLinksForTask(task.getId()); diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkResource.java b/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkResource.java index bb9b2e7892d35b4925dff2527a9e660a4b46d440..6515908d854cd092f0c05085d0358e44e6fa074a 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkResource.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/api/runtime/task/TaskIdentityLinkResource.java @@ -21,7 +21,7 @@ import org.activiti.engine.task.IdentityLink; import org.activiti.engine.task.Task; import org.activiti.rest.api.ActivitiUtil; import org.activiti.rest.api.RestUrls; -import org.activiti.rest.api.legacy.identity.LegacyRestIdentityLink; +import org.activiti.rest.api.engine.RestIdentityLink; import org.activiti.rest.application.ActivitiRestServicesApplication; import org.restlet.data.Status; import org.restlet.resource.Delete; @@ -34,7 +34,7 @@ import org.restlet.resource.Get; public class TaskIdentityLinkResource extends TaskBaseResource { @Get - public LegacyRestIdentityLink getIdentityLink() { + public RestIdentityLink getIdentityLink() { if(!authenticate()) return null; diff --git a/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java b/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java index 01da98e414502cb7d9669190d124ad80687a19c9..5de8ce316e6e55c2dcdff8ae90b5539b9783d4d5 100644 --- a/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java +++ b/modules/activiti-rest/src/main/java/org/activiti/rest/application/RestServicesInit.java @@ -74,6 +74,8 @@ import org.activiti.rest.api.repository.DeploymentResourceCollectionResource; import org.activiti.rest.api.repository.DeploymentResourceDataResource; import org.activiti.rest.api.repository.DeploymentResourceResource; import org.activiti.rest.api.repository.ProcessDefinitionCollectionResource; +import org.activiti.rest.api.repository.ProcessDefinitionIdentityLinkCollectionResource; +import org.activiti.rest.api.repository.ProcessDefinitionIdentityLinkResource; import org.activiti.rest.api.repository.ProcessDefinitionResource; import org.activiti.rest.api.repository.SimpleWorkflowResource; import org.activiti.rest.api.runtime.process.ExecutionCollectionResource; @@ -128,6 +130,8 @@ public class RestServicesInit { router.attach("/repository/process-definitions", ProcessDefinitionCollectionResource.class); router.attach("/repository/process-definitions/{processDefinitionId}", ProcessDefinitionResource.class); + router.attach("/repository/process-definitions/{processDefinitionId}/identitylinks", ProcessDefinitionIdentityLinkCollectionResource.class); + router.attach("/repository/process-definitions/{processDefinitionId}/identitylinks/{family}/{identityId}", ProcessDefinitionIdentityLinkResource.class); router.attach("/runtime/tasks", TaskCollectionResource.class); router.attach("/runtime/tasks/{taskId}", TaskResource.class); diff --git a/modules/activiti-rest/src/test/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinksResourceTest.java b/modules/activiti-rest/src/test/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinksResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6bbf3cb24031abdd1ecb65a9e84a9ce0298de98c --- /dev/null +++ b/modules/activiti-rest/src/test/java/org/activiti/rest/api/repository/ProcessDefinitionIdentityLinksResourceTest.java @@ -0,0 +1,238 @@ +package org.activiti.rest.api.repository; + +import java.util.List; + +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.task.IdentityLink; +import org.activiti.engine.test.Deployment; +import org.activiti.rest.BaseRestTestCase; +import org.activiti.rest.api.RestUrls; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.node.ObjectNode; +import org.restlet.data.Status; +import org.restlet.representation.Representation; +import org.restlet.resource.ClientResource; +import org.restlet.resource.ResourceException; + +/** + * Test for all REST-operations related to single a Process Definition resource. + * + * @author Frederik Heremans + */ +public class ProcessDefinitionIdentityLinksResourceTest extends BaseRestTestCase { + + /** + * Test getting identitylinks for a process definition. + */ + @Deployment(resources={"org/activiti/rest/api/repository/oneTaskProcess.bpmn20.xml"}) + public void testGetIdentityLinksForProcessDefinition() throws Exception { + + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult(); + repositoryService.addCandidateStarterGroup(processDefinition.getId(), "admin"); + repositoryService.addCandidateStarterUser(processDefinition.getId(), "kermit"); + + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl( + RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINKS_COLLECTION, processDefinition.getId())); + Representation response = client.get(); + + // Check "OK" status + assertEquals(Status.SUCCESS_OK, client.getResponse().getStatus()); + + JsonNode responseNode = objectMapper.readTree(response.getStream()); + assertNotNull(responseNode); + assertTrue(responseNode.isArray()); + assertEquals(2, responseNode.size()); + + boolean groupCandidateFound = false; + boolean userCandidateFound = false; + + for(int i=0; i < responseNode.size(); i++) { + ObjectNode link = (ObjectNode) responseNode.get(i); + assertNotNull(link); + if(!link.get("user").isNull()) { + assertEquals("kermit", link.get("user").getTextValue()); + assertEquals("candidate", link.get("type").getTextValue()); + assertTrue(link.get("group").isNull()); + assertTrue(link.get("url").getTextValue().endsWith(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, + encode(processDefinition.getId()), "users", "kermit"))); + userCandidateFound = true; + } else if(!link.get("group").isNull()) { + assertEquals("admin", link.get("group").getTextValue()); + assertEquals("candidate", link.get("type").getTextValue()); + assertTrue(link.get("user").isNull()); + assertTrue(link.get("url").getTextValue().endsWith(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, + encode(processDefinition.getId()), "groups", "admin"))); + groupCandidateFound = true; + } + } + assertTrue(groupCandidateFound); + assertTrue(userCandidateFound); + } + + public void testGetIdentityLinksForUnexistingProcessDefinition() throws Exception { + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINKS_COLLECTION, "unexisting")); + try { + client.get(); + fail("404 expected, but was: " + client.getResponse().getStatus()); + } catch(ResourceException expected) { + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, client.getResponse().getStatus()); + assertEquals("Could not find a process definition with id 'unexisting'.", client.getResponse().getStatus().getDescription()); + } + } + + @Deployment(resources={"org/activiti/rest/api/repository/oneTaskProcess.bpmn20.xml"}) + public void testAddCandidateStarterToProcessDefinition() throws Exception { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult(); + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINKS_COLLECTION, processDefinition.getId())); + + // Create user candidate + ObjectNode requestNode = objectMapper.createObjectNode(); + requestNode.put("user", "kermit"); + + Representation response = client.post(requestNode); + + assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus()); + + JsonNode responseNode = objectMapper.readTree(response.getStream()); + assertNotNull(responseNode); + assertEquals("kermit", responseNode.get("user").getTextValue()); + assertEquals("candidate", responseNode.get("type").getTextValue()); + assertTrue(responseNode.get("group").isNull()); + assertTrue(responseNode.get("url").getTextValue().endsWith(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, + encode(processDefinition.getId()), "users", "kermit"))); + + List createdLinks = repositoryService.getIdentityLinksForProcessDefinition(processDefinition.getId()); + assertEquals(1, createdLinks.size()); + assertEquals("kermit", createdLinks.get(0).getUserId()); + assertEquals("candidate", createdLinks.get(0).getType()); + repositoryService.deleteCandidateStarterUser(processDefinition.getId(), "kermit"); + + // Create group candidate + requestNode = objectMapper.createObjectNode(); + requestNode.put("group", "admin"); + + response = client.post(requestNode); + + assertEquals(Status.SUCCESS_CREATED, client.getResponse().getStatus()); + + responseNode = objectMapper.readTree(response.getStream()); + assertNotNull(responseNode); + assertEquals("admin", responseNode.get("group").getTextValue()); + assertEquals("candidate", responseNode.get("type").getTextValue()); + assertTrue(responseNode.get("user").isNull()); + assertTrue(responseNode.get("url").getTextValue().endsWith(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, + encode(processDefinition.getId()), "groups", "admin"))); + + createdLinks = repositoryService.getIdentityLinksForProcessDefinition(processDefinition.getId()); + assertEquals(1, createdLinks.size()); + assertEquals("admin", createdLinks.get(0).getGroupId()); + assertEquals("candidate", createdLinks.get(0).getType()); + repositoryService.deleteCandidateStarterUser(processDefinition.getId(), "admin"); + } + + public void testAddCandidateStarterToUnexistingProcessDefinition() throws Exception { + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINKS_COLLECTION, "unexisting")); + + // Create user candidate + ObjectNode requestNode = objectMapper.createObjectNode(); + requestNode.put("user", "kermit"); + + try { + client.post(requestNode); + fail("404 expected, but was: " + client.getResponse().getStatus()); + } catch(ResourceException expected) { + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, client.getResponse().getStatus()); + assertEquals("Could not find a process definition with id 'unexisting'.", client.getResponse().getStatus().getDescription()); + } + } + + @Deployment(resources={"org/activiti/rest/api/repository/oneTaskProcess.bpmn20.xml"}) + public void testGetCandidateStarterFromProcessDefinition() throws Exception { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult(); + repositoryService.addCandidateStarterGroup(processDefinition.getId(), "admin"); + repositoryService.addCandidateStarterUser(processDefinition.getId(), "kermit"); + + // Get user candidate + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, processDefinition.getId(), "users", "kermit")); + Representation response = client.get(); + + assertEquals(Status.SUCCESS_OK, client.getResponse().getStatus()); + + JsonNode responseNode = objectMapper.readTree(response.getStream()); + assertNotNull(responseNode); + assertEquals("kermit", responseNode.get("user").getTextValue()); + assertEquals("candidate", responseNode.get("type").getTextValue()); + assertTrue(responseNode.get("group").isNull()); + assertTrue(responseNode.get("url").getTextValue().endsWith(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, + encode(processDefinition.getId()), "users", "kermit"))); + + // Get group candidate + client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, processDefinition.getId(), "groups", "admin")); + response = client.get(); + + assertEquals(Status.SUCCESS_OK, client.getResponse().getStatus()); + + responseNode = objectMapper.readTree(response.getStream()); + assertNotNull(responseNode); + assertEquals("admin", responseNode.get("group").getTextValue()); + assertEquals("candidate", responseNode.get("type").getTextValue()); + assertTrue(responseNode.get("user").isNull()); + assertTrue(responseNode.get("url").getTextValue().endsWith(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, + encode(processDefinition.getId()), "groups", "admin"))); + } + + @Deployment(resources={"org/activiti/rest/api/repository/oneTaskProcess.bpmn20.xml"}) + public void testDeleteCandidateStarterFromProcessDefinition() throws Exception { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult(); + repositoryService.addCandidateStarterGroup(processDefinition.getId(), "admin"); + repositoryService.addCandidateStarterUser(processDefinition.getId(), "kermit"); + + // Get user candidate + ClientResource client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, processDefinition.getId(), "users", "kermit")); + Representation response = client.delete(); + + assertEquals(Status.SUCCESS_NO_CONTENT, client.getResponse().getStatus()); + assertEquals(0, response.getSize()); + + // Check if group-link remains + List remainingLinks = repositoryService.getIdentityLinksForProcessDefinition(processDefinition.getId()); + assertEquals(1, remainingLinks.size()); + assertEquals("admin", remainingLinks.get(0).getGroupId()); + + + // Delete group candidate + client = getAuthenticatedClient(RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, processDefinition.getId(), "groups", "admin")); + response = client.delete(); + + assertEquals(Status.SUCCESS_NO_CONTENT, client.getResponse().getStatus()); + assertEquals(0, response.getSize()); + + // Check if all links are removed + remainingLinks = repositoryService.getIdentityLinksForProcessDefinition(processDefinition.getId()); + assertEquals(0, remainingLinks.size()); + } + + public void testDeleteCandidateStarterFromUnexistingProcessDefinition() throws Exception { + ClientResource client = getAuthenticatedClient( + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, "unexisting", "groups", "admin")); + try { + client.delete(); + fail("404 expected, but was: " + client.getResponse().getStatus()); + } catch(ResourceException expected) { + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, client.getResponse().getStatus()); + assertEquals("Could not find a process definition with id 'unexisting'.", client.getResponse().getStatus().getDescription()); + } + } + + public void testGetCandidateStarterFromUnexistingProcessDefinition() throws Exception { + ClientResource client = getAuthenticatedClient( + RestUrls.createRelativeResourceUrl(RestUrls.URL_PROCESS_DEFINITION_IDENTITYLINK, "unexisting", "groups", "admin")); + try { + client.get(); + fail("404 expected, but was: " + client.getResponse().getStatus()); + } catch(ResourceException expected) { + assertEquals(Status.CLIENT_ERROR_NOT_FOUND, client.getResponse().getStatus()); + assertEquals("Could not find a process definition with id 'unexisting'.", client.getResponse().getStatus().getDescription()); + } + } +} diff --git a/userguide/src/en/chapters/ch14-REST.xml b/userguide/src/en/chapters/ch14-REST.xml index c9643a15b03553b2848820d8d4dcc599bbc0bfa3..bd75bacd5c56b8368b15bfac2a549a2329ffaebb 100644 --- a/userguide/src/en/chapters/ch14-REST.xml +++ b/userguide/src/en/chapters/ch14-REST.xml @@ -1299,6 +1299,303 @@ +
+ Get all candidate starters for a process-definition + + GET repository/process-definitions/{processDefinitionId}/identitylinks + + + + URL parameters + + + + Parameter + Required + Value + Description + + + + + processDefinitionId + Yes + String + The id of the process definition to get the identity links for. + + + +
+
+ + + Response codes + + + + Response code + Description + + + + + 200 + Indicates the process definition was found and the requested identity links are returned. + + + 404 + Indicates the requested process definition was not found. + + + +
+
+ + Success response body: + +[ + { + "url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/groups/admin", + "user":null, + "group":"admin", + "type":"candidate" + }, + { + "url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit", + "user":"kermit", + "group":null, + "type":"candidate" + } +] + +
+
+ Add a candidate starter to a process definition + + POST repository/process-definitions/{processDefinitionId}/identitylinks + + + + URL parameters + + + + Parameter + Required + Value + Description + + + + + processDefinitionId + Yes + String + The id of the process definition. + + + +
+
+ + Request body (user): + +{ + "userId" : "kermit" +} + + + Request body (group): + +{ + "groupId" : "sales" +} + + + + Response codes + + + + Response code + Description + + + + + 201 + Indicates the process definition was found and the identity link was created. + + + 404 + Indicates the requested process definition was not found. + + + +
+
+ + Success response body: + +{ + "url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit", + "user":"kermit", + "group":null, + "type":"candidate" +} + +
+ +
+ Delete a candidate starter from a process definition + + DELETE repository/process-definitions/{processDefinitionId}/identitylinks/{family}/{identityId} + + + + URL parameters + + + + Parameter + Required + Value + Description + + + + + processDefinitionId + Yes + String + The id of the process definition. + + + family + Yes + String + Either users or groups, depending on the type of identity link. + + + identityId + Yes + String + Either the userId or groupId of the identity to remove as candidate starter. + + + +
+
+ + + Response codes + + + + Response code + Description + + + + + 204 + Indicates the process definition was found and the identity link was removed. The response body is intentionally empty. + + + 404 + Indicates the requested process definition was not found or the process definition doesn't have an identity-link that matches the url. + + + +
+
+ + Success response body: + +{ + "url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit", + "user":"kermit", + "group":null, + "type":"candidate" +} + +
+ +
+ Get a candidate starter from a process definition + + GET repository/process-definitions/{processDefinitionId}/identitylinks/{family}/{identityId} + + + + URL parameters + + + + Parameter + Required + Value + Description + + + + + processDefinitionId + Yes + String + The id of the process definition. + + + family + Yes + String + Either users or groups, depending on the type of identity link. + + + identityId + Yes + String + Either the userId or groupId of the identity to get as candidate starter. + + + +
+
+ + + Response codes + + + + Response code + Description + + + + + 200 + Indicates the process definition was found and the identity link was returned. + + + 404 + Indicates the requested process definition was not found or the process definition doesn't have an identity-link that matches the url. + + + +
+
+ + Success response body: + +{ + "url":"http://localhost:8182/repository/process-definitions/oneTaskProcess%3A1%3A4/identitylinks/users/kermit", + "user":"kermit", + "group":null, + "type":"candidate" +} + +
+
@@ -2785,6 +3082,85 @@ GET runtime/tasks/{taskId}/identitylinks/groups "groupId" : "sales", "type" : "candidate", "url" : "http://localhost:8081/activiti-rest/service/runtime/tasks/100/identitylinks/groups/sales/candidate" +} + +
+ +
+ Create a single identity link on a task + + POST runtime/tasks/{taskId}/identitylinks + + + + URL parameters + + + + Parameter + Required + Value + Description + + + + + taskId + Yes + String + The id of the task . + + + +
+
+ + Request body (user): + +{ + "userId" : "kermit", + "type" : "candidate", +} + + + Request body (group): + +{ + "groupId" : "sales", + "type" : "candidate", +} + + + + Response codes + + + + Response code + Description + + + + + 201 + Indicates the task was found and the identity link was created. + + + 404 + Indicates the requested task was not found or the task doesn't have the requested identityLink. The status contains additional information about this error. + + + +
+
+ + Success response body: + +{ + "userId" : null, + "groupId" : "sales", + "type" : "candidate", + "url" : "http://localhost:8081/activiti-rest/service/runtime/tasks/100/identitylinks/groups/sales/candidate" }