/**
* Koya is an alfresco module that provides a corporate orientated dataroom.
*
* Copyright (C) Itl Developpement 2014
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see `<http://www.gnu.org/licenses/>`.
*/
package org.alfresco.repo.invitation;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.jbpm.JBPMEngine;
import org.alfresco.service.cmr.invitation.Invitation;
import org.alfresco.service.cmr.invitation.InvitationException;
import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden;
import org.alfresco.service.cmr.invitation.InvitationExceptionNotFound;
import org.alfresco.service.cmr.invitation.InvitationExceptionUserError;
import org.alfresco.service.cmr.invitation.ModeratedInvitation;
import org.alfresco.service.cmr.invitation.NominatedInvitation;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.workflow.WorkflowAdminService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import fr.itldev.koya.alfservice.UserService;
import fr.itldev.koya.exception.KoyaServiceException;
import fr.itldev.koya.model.impl.User;
import fr.itldev.koya.model.permissions.SitePermission;
/**
* @see org.alfresco.repo.invitation.InvitationServiceImpl
*
*
* This service overrides classic invitation service. User search is now only on
* email field ()
*
*
*
* startNominatedInvite method
*
*
*
*
*
*/
public class InvitationKoyaServiceImpl extends InvitationServiceImpl {
private static final Log logger = LogFactory.getLog(InvitationServiceImpl.class);
private WorkflowAdminService workflowAdminService;
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
private final int maxUserNameGenRetries = MAX_NUM_INVITEE_USER_NAME_GEN_TRIES;
/**
* Start the invitation process for a NominatedInvitation
*
* @param inviteeUserName Alfresco user name of the invitee
* @param resourceType
* @param resourceName
* @param inviteeRole
* @param serverPath
* @param acceptUrl
* @param rejectUrl
* @return the nominated invitation which will contain the invitationId and
* ticket which will uniqely identify this invitation for the rest of the
* workflow.
* @throws InvitationException
* @throws InvitationExceptionUserError
* @throws InvitationExceptionForbidden
*/
@Override
public NominatedInvitation inviteNominated(String inviteeUserName, Invitation.ResourceType resourceType,
String resourceName, String inviteeRole, String serverPath, String acceptUrl, String rejectUrl) {
// inviteeUserName was specified
NodeRef person = getPersonService().getPerson(inviteeUserName);
Serializable firstNameVal = this.getNodeService().getProperty(person, ContentModel.PROP_FIRSTNAME);
Serializable lastNameVal = this.getNodeService().getProperty(person, ContentModel.PROP_LASTNAME);
Serializable emailVal = this.getNodeService().getProperty(person, ContentModel.PROP_EMAIL);
String firstName = DefaultTypeConverter.INSTANCE.convert(String.class, firstNameVal);
String lastName = DefaultTypeConverter.INSTANCE.convert(String.class, lastNameVal);
String email = DefaultTypeConverter.INSTANCE.convert(String.class, emailVal);
return inviteNominated(firstName, lastName, email, inviteeUserName, resourceType, resourceName, inviteeRole,
serverPath, acceptUrl, rejectUrl);
}
/**
* Start the invitation process for a NominatedInvitation
*
* @param inviteeFirstName
* @param inviteeLastName
* @param inviteeEmail
* @param resourceType
* @param resourceName
* @param inviteeRole
* @param serverPath
* @param acceptUrl
* @param rejectUrl
* @return the nominated invitation which will contain the invitationId and
* ticket which will uniqely identify this invitation for the rest of the
* workflow.
* @throws InvitationException
* @throws InvitationExceptionUserError
* @throws InvitationExceptionForbidden
*/
@Override
public NominatedInvitation inviteNominated(String inviteeFirstName, String inviteeLastName, String inviteeEmail,
Invitation.ResourceType resourceType, String resourceName, String inviteeRole, String serverPath,
String acceptUrl, String rejectUrl) {
return inviteNominated(inviteeFirstName, inviteeLastName, inviteeEmail, null, resourceType, resourceName,
inviteeRole, serverPath, acceptUrl, rejectUrl);
}
// Temporary method
private NominatedInvitation inviteNominated(String inviteeFirstName, String inviteeLastName, String inviteeEmail,
String inviteeUserName, Invitation.ResourceType resourceType, String resourceName, String inviteeRole,
String serverPath, String acceptUrl, String rejectUrl) {
// Validate the request
// Check resource exists
if (resourceType == Invitation.ResourceType.WEB_SITE) {
return startNominatedInvite(inviteeFirstName, inviteeLastName, inviteeEmail, inviteeUserName, resourceType,
resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl);
}
throw new InvitationException("unknown resource type");
}
/**
* Get an invitation from its invitation id <BR />
* Invitations are returned which may be in progress or completed.
*
* @throws InvitationExceptionNotFound the invitation does not exist.
* @throws InvitationExceptionUserError
* @return the invitation.
*/
private Invitation getInvitation(WorkflowTask startTask) {
Invitation invitation = getNominatedInvitation(startTask);
if (invitation == null) {
invitation = getModeratedInvitation(startTask.getPath().getId());
}
return invitation;
}
private WorkflowTask getModeratedInvitationReviewTask(String invitationId) {
WorkflowTask reviewTask = null;
// since the invitation may have been updated e.g. invitee comments (and therefore modified date)
// we need to get the properties from the review task (which should be the only active
// task)
List<WorkflowTask> tasks = getWorkflowService().getTasksForWorkflowPath(invitationId);
for (WorkflowTask task : tasks) {
if (taskTypeMatches(task, WorkflowModelModeratedInvitation.WF_ACTIVITI_REVIEW_TASK)) {
reviewTask = task;
break;
}
}
return reviewTask;
}
private ModeratedInvitation getModeratedInvitation(String invitationId) {
WorkflowTask reviewTask = getModeratedInvitationReviewTask(invitationId);
ModeratedInvitation invitation = getModeratedInvitation(invitationId, reviewTask);
return invitation;
}
private ModeratedInvitation getModeratedInvitation(String invitationId, WorkflowTask reviewTask) {
ModeratedInvitation invitation = null;
if (reviewTask != null) {
Map<QName, Serializable> properties = reviewTask.getProperties();
invitation = new ModeratedInvitationImpl(invitationId, properties);
}
return invitation;
}
private NominatedInvitation getNominatedInvitation(WorkflowTask startTask) {
NominatedInvitation invitation = null;
if (taskTypeMatches(startTask, WorkflowModelNominatedInvitation.WF_TASK_INVITE_TO_SITE)) {
Date inviteDate = startTask.getPath().getInstance().getStartDate();
String invitationId = startTask.getPath().getInstance().getId();
invitation = new NominatedInvitationImpl(invitationId, inviteDate, startTask.getProperties());
}
return invitation;
}
private boolean taskTypeMatches(WorkflowTask task, QName... types) {
QName taskDefName = task.getDefinition().getMetadata().getName();
return Arrays.asList(types).contains(taskDefName);
}
/**
* @param workflowAdminService the workflowAdminService to set
*/
@Override
public void setWorkflowAdminService(WorkflowAdminService workflowAdminService) {
this.workflowAdminService = workflowAdminService;
super.setWorkflowAdminService(workflowAdminService);
}
/**
* Creates a person for the invitee with a generated user name.
*
* @param inviteeFirstName first name of invitee
* @param inviteeLastName last name of invitee
* @param inviteeEmail email address of invitee
* @return invitee user name
*/
private String createInviteePerson(String inviteeFirstName, String inviteeLastName, String inviteeEmail) {
// Attempt to generate user name for invitee
// which does not belong to an existing person
// Tries up to MAX_NUM_INVITEE_USER_NAME_GEN_TRIES
// at which point a web script exception is thrown
String inviteeUserName = null;
int i = 0;
do {
inviteeUserName = getUserNameGenerator().generateUserName(inviteeFirstName, inviteeLastName, inviteeEmail, i);
i++;
} while (getPersonService().personExists(inviteeUserName) && (i < getMaxUserNameGenRetries()));
// if after 10 tries is not able to generate a user name for a
// person who doesn't already exist, then throw a web script exception
if (getPersonService().personExists(inviteeUserName)) {
logger.debug("Failed - unable to generate username for invitee.");
Object[] objs = {inviteeFirstName, inviteeLastName, inviteeEmail};
throw new InvitationException("invitation.invite.unable_generate_id", objs);
}
// create a person node for the invitee with generated invitee user name
// and other provided person property values
final Map<QName, Serializable> properties = new HashMap<>();
properties.put(ContentModel.PROP_USERNAME, inviteeUserName);
properties.put(ContentModel.PROP_FIRSTNAME, inviteeFirstName);
properties.put(ContentModel.PROP_LASTNAME, inviteeLastName);
properties.put(ContentModel.PROP_EMAIL, inviteeEmail);
final String finalUserName = inviteeUserName;
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>() {
@SuppressWarnings("synthetic-access")
@Override
public Object doWork() throws Exception {
NodeRef person = getPersonService().createPerson(properties);
//MNT-9101 Share: Cancelling an invitation for a disabled user, the user gets deleted in the process.
getNodeService().addAspect(person, ContentModel.ASPECT_ANULLABLE, null);
getPermissionService().setPermission(person, finalUserName, PermissionService.ALL_PERMISSIONS, true);
return null;
}
}, AuthenticationUtil.getSystemUserName());
return inviteeUserName;
}
/**
* Creates a disabled user account for the given invitee user name with a
* generated password
*
* @param inviteeUserName
* @return password generated for invitee user account
*/
private String createInviteeDisabledAccount(String inviteeUserName) {
// generate password using password generator
char[] generatedPassword = getPasswordGenerator().generatePassword().toCharArray();
// create disabled user account for invitee user name with generated
// password
getAuthenticationService().createAuthentication(inviteeUserName, generatedPassword);
getAuthenticationService().setAuthenticationEnabled(inviteeUserName, false);
return String.valueOf(generatedPassword);
}
/**
* Starts the Invite workflow
*
* @param inviteeFirstName first name of invitee
* @param inviteeLastNamme last name of invitee
* @param inviteeEmail email address of invitee
* @param siteShortName short name of site that the invitee is being invited
* to by the inviter
* @param inviteeSiteRole role under which invitee is being invited to the
* site by the inviter
* @param serverPath externally accessible server address of server hosting
* invite web scripts
*/
private NominatedInvitation startNominatedInvite(String inviteeFirstName, String inviteeLastName,
String inviteeEmail, String inviteeUserName, Invitation.ResourceType resourceType,
String siteShortName, String inviteeSiteRole, String serverPath, String acceptUrl, String rejectUrl) {
// get the inviter user name (the name of user web script is executed
// under)
String inviterUserName = getAuthenticationService().getCurrentUserName();
boolean created = false;
checkManagerRole(inviterUserName, resourceType, siteShortName);
if (logger.isInfoEnabled()) {
logger.info("startInvite() inviterUserName=" + inviterUserName + " inviteeUserName=" + inviteeUserName
+ " inviteeFirstName=" + inviteeFirstName + " inviteeLastName=" + inviteeLastName
+ " inviteeEmail=" + inviteeEmail + " siteShortName=" + siteShortName + " inviteeSiteRole="
+ inviteeSiteRole);
}
//
// if we have not explicitly been passed an existing user's user name
// then ....
//
// if a person already exists with the given invitee email address
//
// 1) obtain invitee user name from first person found having the
// invitee email address
// 2) handle error conditions -
// (invitee already has an invitation in progress for the given site,
// or he/she is already a member of the given site
//
if (inviteeUserName == null || inviteeUserName.trim().length() == 0) {
inviteeUserName = null;
/*
* Try de to find user by email
*/
User u = null;
try {
u = userService.getUserByEmail(inviteeEmail);
} catch (KoyaServiceException kse) {
}
if (u !=null) {
// get person already existing who has the given
// invitee email address
NodeRef personRef = u.getNodeRef();
// got a match on email
// get invitee user name of that person
Serializable userNamePropertyVal = this.getNodeService().getProperty(personRef, ContentModel.PROP_USERNAME);
inviteeUserName = DefaultTypeConverter.INSTANCE.convert(String.class, userNamePropertyVal);
if (logger.isDebugEnabled()) {
logger.debug("not explictly passed username - found matching email, resolved inviteeUserName="
+ inviteeUserName);
}
}
if (inviteeUserName == null) {
// This shouldn't normally happen. Due to the fix for ETHREEOH-3268, the link to invite external users
// should be disabled when the authentication chain does not allow it.
if (!getAuthenticationService().isAuthenticationCreationAllowed()) {
throw new InvitationException("invitation.invite.authentication_chain");
}
// else there are no existing people who have the given invitee
// email address so create new person
inviteeUserName = createInviteePerson(inviteeFirstName, inviteeLastName, inviteeEmail);
created = true;
if (logger.isDebugEnabled()) {
logger.debug("not explictly passed username - created new person, inviteeUserName="
+ inviteeUserName);
}
}
}
/**
* throw exception if person is already a member of the given site
*/
if (getSiteService().isMember(siteShortName, inviteeUserName)) {
if (logger.isDebugEnabled()) {
logger.debug("Failed - invitee user is already a member of the site.");
}
Object objs[] = {inviteeUserName, inviteeEmail, siteShortName};
throw new InvitationExceptionUserError("invitation.invite.already_member", objs);
}
//
// If a user account does not already exist for invitee user name
// then create a disabled user account for the invitee.
// Hold a local reference to generated password if disabled invitee
// account
// is created, otherwise if a user account already exists for invitee
// user name, then local reference to invitee password will be "null"
//
final String initeeUserNameFinal = inviteeUserName;
String inviteePassword = created ? AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<String>() {
@SuppressWarnings("synthetic-access")
@Override
public String doWork() {
return createInviteeDisabledAccount(initeeUserNameFinal);
}
}, AuthenticationUtil.getSystemUserName()) : null;
// create a ticket for the invite - this is used
String inviteTicket = GUID.generate();
//
// Start the invite workflow with inviter, invitee and site properties
//
WorkflowDefinition wfDefinition = getWorkflowDefinition(true);
// Get invitee person NodeRef to add as assignee
NodeRef inviteeNodeRef = getPersonService().getPerson(inviteeUserName);
SiteInfo siteInfo = getSiteService().getSite(siteShortName);
String siteDescription = siteInfo.getDescription();
if (siteDescription == null) {
siteDescription = "";
} else if (siteDescription.length() > 255) {
siteDescription = siteDescription.substring(0, 255);
}
// get the workflow description
String workflowDescription = generateWorkflowDescription(siteInfo, "invitation.nominated.workflow.description");
// create workflow properties
Map<QName, Serializable> workflowProps = new HashMap<>(32);
workflowProps.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, workflowDescription);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITER_USER_NAME, inviterUserName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_USER_NAME, inviteeUserName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_EMAIL, inviteeEmail);
workflowProps.put(WorkflowModel.ASSOC_ASSIGNEE, inviteeNodeRef);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_FIRSTNAME, inviteeFirstName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_LASTNAME, inviteeLastName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_GEN_PASSWORD, inviteePassword);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_NAME, siteShortName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TITLE, siteInfo.getTitle());
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_DESCRIPTION, siteDescription);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TYPE, resourceType.toString());
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_ROLE, inviteeSiteRole);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_SERVER_PATH, serverPath);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_ACCEPT_URL, acceptUrl);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_REJECT_URL, rejectUrl);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITE_TICKET, inviteTicket);
return (NominatedInvitation) startWorkflow(wfDefinition, workflowProps);
}
private Invitation startWorkflow(WorkflowDefinition wfDefinition, Map<QName, Serializable> workflowProps) {
NodeRef wfPackage = getWorkflowService().createPackage(null);
workflowProps.put(WorkflowModel.ASSOC_PACKAGE, wfPackage);
// start the workflow
WorkflowPath wfPath = getWorkflowService().startWorkflow(wfDefinition.getId(), workflowProps);
//
// complete invite workflow start task to send out the invite email
//
// get the workflow tasks
String workflowId = wfPath.getInstance().getId();
WorkflowTask startTask = getWorkflowService().getStartTask(workflowId);
// attach empty package to start task, end it and follow with transition
// that sends out the invite
if (logger.isDebugEnabled()) {
logger.debug("Starting Invite workflow task by attaching empty package...");
}
if (logger.isDebugEnabled()) {
logger.debug("Transitioning Invite workflow task...");
}
try {
getWorkflowService().endTask(startTask.getId(), null);
} catch (RuntimeException err) {
if (logger.isDebugEnabled()) {
logger.debug("Failed - caught error during Invite workflow transition: " + err.getMessage());
}
throw err;
}
Invitation invitation = getInvitation(startTask);
return invitation;
}
/**
* Return Activiti workflow definition unless Activiti engine is disabled.
*
* @param isNominated TODO
* @return
*/
private WorkflowDefinition getWorkflowDefinition(boolean isNominated) {
String workflowName = isNominated ? getNominatedDefinitionName() : getModeratedDefinitionName();
WorkflowDefinition definition = getWorkflowService().getDefinitionByName(workflowName);
if (definition == null) {
// handle workflow definition does not exist
Object objs[] = {workflowName};
throw new InvitationException("invitation.error.noworkflow", objs);
}
return definition;
}
private String getNominatedDefinitionName() {
if (workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) {
return WorkflowModelNominatedInvitation.WORKFLOW_DEFINITION_NAME_ACTIVITI;
} else if (workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID)) {
return WorkflowModelNominatedInvitation.WORKFLOW_DEFINITION_NAME;
}
throw new IllegalStateException("None of the Workflow engines supported by teh InvitationService are currently enabled!");
}
private String getModeratedDefinitionName() {
if (workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID)) {
return WorkflowModelModeratedInvitation.WORKFLOW_DEFINITION_NAME_ACTIVITI;
} else if (workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID)) {
return WorkflowModelModeratedInvitation.WORKFLOW_DEFINITION_NAME;
}
throw new IllegalStateException("None of the Workflow engines supported by teh InvitationService are currently enabled!");
}
/**
* Check that the specified user has manager role over the resource.
*
* @param userId
* @throws InvitationException
*/
private void checkManagerRole(String userId, Invitation.ResourceType resourceType, String siteShortName) {
// if inviter is not the site manager then throw web script exception
String inviterRole = getSiteService().getMembersRole(siteShortName, userId);
/**
* Koya modification : allow Collaborators to send invitation
*/
if ((inviterRole != null)
&& (inviterRole.equals(SitePermission.MANAGER.toString())
|| inviterRole.equals(SitePermission.COLLABORATOR.toString()))) {
} else {
Object objs[] = {userId, siteShortName};
throw new InvitationExceptionForbidden("invitation.invite.not_site_manager", objs);
}
}
private int getMaxUserNameGenRetries() {
return maxUserNameGenRetries;
}
/**
* Validator for invitationId
*
* @param invitationId
*/
private void validateInvitationId(String invitationId) {
final String ID_SEPERATOR_REGEX = "\\$";
String[] parts = invitationId.split(ID_SEPERATOR_REGEX);
if (parts.length != 2) {
Object objs[] = {invitationId};
throw new InvitationExceptionUserError("invitation.error.invalid_inviteId_format", objs);
}
}
private WorkflowTask getStartTask(String invitationId) {
validateInvitationId(invitationId);
WorkflowTask startTask = null;
try {
startTask = getWorkflowService().getStartTask(invitationId);
} catch (WorkflowException we) {
// Do nothing
}
if (startTask == null) {
Object objs[] = {invitationId};
throw new InvitationExceptionNotFound("invitation.error.not_found", objs);
}
return startTask;
}
@Override
public Invitation accept(String invitationId, String ticket) {
WorkflowTask startTask = getStartTask(invitationId);
NominatedInvitation invitation = getNominatedInvitation(startTask);
if (invitation == null) {
throw new InvitationException("State error, accept may only be called on a nominated invitation.");
}
// Check invitationId and ticket match
if (invitation.getTicket().equals(ticket) == false) {
//TODO localise msg
String msg = "Response to invite has supplied an invalid ticket. The response to the invitation could thus not be processed";
throw new InvitationException(msg);
}
endInvitation(startTask,
WorkflowModelNominatedInvitation.WF_TRANSITION_ACCEPT, null,
WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING, WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING);
//MNT-9101 Share: Cancelling an invitation for a disabled user, the user gets deleted in the process.
/*
KOYA removed Code : fixes invitation by Manager bug
NodeRef person = personService.getPersonOrNull(invitation.getInviterUserName());
if (person != null && nodeService.hasAspect(person, ContentModel.ASPECT_ANULLABLE)) {
nodeService.removeAspect(person, ContentModel.ASPECT_ANULLABLE);
}*/
return invitation;
}
private void endInvitation(WorkflowTask startTask, String transition, Map<QName, Serializable> properties, QName... taskTypes) {
// Deleting a person can cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping here
if (TransactionalResourceHelper.getSet(getClass().getName()).add(startTask.getPath().getInstance().getId())) {
List<WorkflowTask> tasks = getWorkflowService().getTasksForWorkflowPath(startTask.getPath().getId());
if (tasks.size() == 1) {
WorkflowTask task = tasks.get(0);
if (taskTypeMatches(task, taskTypes)) {
if (properties != null) {
getWorkflowService().updateTask(task.getId(), properties, null, null);
}
getWorkflowService().endTask(task.getId(), transition);
return;
}
}
// Throw exception if the task not found.
Object objs[] = {startTask.getPath().getInstance().getId()};
throw new InvitationExceptionUserError("invitation.invite.already_finished", objs);
}
}
}