/**
* Copyright (C) 2012 BonitaSoft S.A.
* BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 of the License, or
* (at your option) any later version.
* <p/>
* 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 General Public License for more details.
* <p/>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.test.toolkit.bpm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bonitasoft.engine.api.ProcessAPI;
import org.bonitasoft.engine.api.TenantAPIAccessor;
import org.bonitasoft.engine.bpm.actor.ActorCriterion;
import org.bonitasoft.engine.bpm.actor.ActorInstance;
import org.bonitasoft.engine.bpm.bar.BusinessArchiveBuilder;
import org.bonitasoft.engine.bpm.bar.form.model.FormMappingModel;
import org.bonitasoft.engine.bpm.category.Category;
import org.bonitasoft.engine.bpm.category.CategoryCriterion;
import org.bonitasoft.engine.bpm.flownode.ActivityDefinition;
import org.bonitasoft.engine.bpm.flownode.UserTaskDefinition;
import org.bonitasoft.engine.bpm.form.FormMappingDefinitionBuilder;
import org.bonitasoft.engine.bpm.process.DesignProcessDefinition;
import org.bonitasoft.engine.bpm.process.InvalidProcessDefinitionException;
import org.bonitasoft.engine.bpm.process.ProcessDefinition;
import org.bonitasoft.engine.bpm.process.ProcessDefinitionNotFoundException;
import org.bonitasoft.engine.bpm.process.ProcessDeploymentInfo;
import org.bonitasoft.engine.bpm.process.ProcessInstance;
import org.bonitasoft.engine.bpm.process.ProcessInstanceSearchDescriptor;
import org.bonitasoft.engine.bpm.process.impl.ProcessDefinitionBuilder;
import org.bonitasoft.engine.bpm.supervisor.ProcessSupervisor;
import org.bonitasoft.engine.exception.BonitaHomeNotSetException;
import org.bonitasoft.engine.exception.DeletionException;
import org.bonitasoft.engine.exception.SearchException;
import org.bonitasoft.engine.exception.ServerAPIException;
import org.bonitasoft.engine.exception.UnknownAPITypeException;
import org.bonitasoft.engine.form.FormMappingTarget;
import org.bonitasoft.engine.form.FormMappingType;
import org.bonitasoft.engine.search.Order;
import org.bonitasoft.engine.search.SearchOptions;
import org.bonitasoft.engine.search.SearchOptionsBuilder;
import org.bonitasoft.engine.session.APISession;
import org.bonitasoft.engine.session.InvalidSessionException;
import org.bonitasoft.test.toolkit.exception.TestToolkitException;
import org.bonitasoft.test.toolkit.organization.TestGroup;
import org.bonitasoft.test.toolkit.organization.TestRole;
import org.bonitasoft.test.toolkit.organization.TestToolkitCtx;
import org.bonitasoft.test.toolkit.organization.TestUser;
/**
* @author Vincent Elcrin
*/
public class TestProcess {
private final ProcessDefinition processDefinition;
private boolean enabled = false;
public static final String COMMAND_ADD_USER_TO_SUPERVISOR = "createSupervisor";
public static final String KEY_PROCESS_DEFINITION_ID = "processDefinitionId";
public static final String KEY_MEMBER_TYPE = "memberType";
public static final String KEY_USER_ID = "userId";
public static final String KEY_GROUP_ID = "groupId";
public static final String KEY_ROLE_ID = "roleId";
private ProcessSupervisor processSupervisor;
private final List<ActorInstance> actors = new ArrayList<>();
/**
* Default Constructor.
*
* @throws Exception
*/
public TestProcess(final APISession apiSession, final ProcessDefinitionBuilder processDefinitionBuilder) {
this.processDefinition = createProcessDefinition(apiSession, processDefinitionBuilder);
/*
System.err.println("\n\n");
System.err.println("Building process: " + processDefinition.getName() + " - " + processDefinition.getId());
Thread.dumpStack();
System.err.println("\n\n");
*/
}
public TestProcess(final ProcessDefinitionBuilder processDefinitionBuilder) {
this(getSession(), processDefinitionBuilder);
}
public TestProcess(final APISession apiSession, final BusinessArchiveBuilder businessArchiveBuilder) {
this.processDefinition = deployProcessDefinition(apiSession, businessArchiveBuilder);
/*
System.err.println("\n\n");
System.err.println("Building process: " + processDefinition.getName() + " - " + processDefinition.getId());
Thread.dumpStack();
System.err.println("\n\n");
*/
}
public TestProcess(final BusinessArchiveBuilder businessArchiveBuilder) {
this(getSession(), businessArchiveBuilder);
}
/**
* @return
*/
private static APISession getSession() {
return TestToolkitCtx.getInstance().getInitiator().getSession();
}
/**
* Create an archive and deploy process
*
* @param apiSession
* @param processDefinitionBuilder
* @return
*/
private ProcessDefinition createProcessDefinition(final APISession apiSession, final ProcessDefinitionBuilder processDefinitionBuilder) {
try {
return deployProcessDefinition(apiSession, new BusinessArchiveBuilder().createNewBusinessArchive()
.setFormMappings(createDefaultProcessFormMapping(processDefinitionBuilder.getProcess()))
.setProcessDefinition(processDefinitionBuilder.done()));
} catch (final InvalidProcessDefinitionException e) {
throw new TestToolkitException("Invalid process definition", e);
}
}
public static FormMappingModel createDefaultProcessFormMapping(DesignProcessDefinition designProcessDefinition) {
FormMappingModel formMappingModel = new FormMappingModel();
formMappingModel.addFormMapping(FormMappingDefinitionBuilder.buildFormMapping("http://url.com", FormMappingType.PROCESS_START, FormMappingTarget.URL).build());
formMappingModel.addFormMapping(FormMappingDefinitionBuilder.buildFormMapping("http://url.com", FormMappingType.PROCESS_OVERVIEW, FormMappingTarget.URL).build());
for (ActivityDefinition activityDefinition : designProcessDefinition.getFlowElementContainer().getActivities()) {
if (activityDefinition instanceof UserTaskDefinition) {
formMappingModel.addFormMapping(FormMappingDefinitionBuilder.buildFormMapping("http://url.com", FormMappingType.TASK, FormMappingTarget.URL).withTaskname(activityDefinition.getName()).build());
}
}
return formMappingModel;
}
/**
* Deploy process from the archive
*
* @param apiSession
* @param businessArchiveBuilder
* @return
*/
private ProcessDefinition deployProcessDefinition(final APISession apiSession, final BusinessArchiveBuilder businessArchiveBuilder) {
try {
return getProcessAPI(apiSession).deploy(businessArchiveBuilder.done());
} catch (final Exception e) {
throw new TestToolkitException("Can't deploy business archive.", e);
}
}
protected static ProcessAPI getProcessAPI(final APISession apiSession) {
ProcessAPI processAPI = null;
try {
processAPI = TenantAPIAccessor.getProcessAPI(apiSession);
} catch (final InvalidSessionException e) {
throw new TestToolkitException("Can't get process API. Invalid session", e);
} catch (final BonitaHomeNotSetException e) {
throw new TestToolkitException("Can't get process API. Bonita home not set", e);
} catch (final ServerAPIException e) {
throw new TestToolkitException("Can't get process API. Server API exception", e);
} catch (final UnknownAPITypeException e) {
throw new TestToolkitException("Can't get process API. Unknown API type", e);
}
return processAPI;
}
public ProcessDefinition getProcessDefinition() {
return this.processDefinition;
}
public long getId() {
return this.processDefinition.getId();
}
public ProcessDeploymentInfo getProcessDeploymentInfo() {
try {
return getProcessAPI(getSession()).getProcessDeploymentInfo(getId());
} catch (ProcessDefinitionNotFoundException e) {
throw new TestToolkitException("Unable to get process deployement info", e);
}
}
/**
* Set process enablement
*
* @param apiSession
* @param enabled
* @return
*/
protected TestProcess setEnable(final APISession apiSession, final boolean enabled) {
if (enabled && !this.enabled) {
enableProcess(apiSession);
} else if (!enabled && this.enabled) {
disableProcess(apiSession);
}
this.enabled = enabled;
return this;
}
protected void delete(final APISession apiSession) {
try {
// Delete all process instances
long nbDeletedProcessInstances;
do {
nbDeletedProcessInstances = getProcessAPI(apiSession).deleteProcessInstances(processDefinition.getId(), 0, 100);
} while (nbDeletedProcessInstances > 0);
// Delete all archived process instances
long nbDeletedArchivedProcessInstances;
do {
nbDeletedArchivedProcessInstances = getProcessAPI(apiSession).deleteArchivedProcessInstances(processDefinition.getId(), 0, 100);
} while (nbDeletedArchivedProcessInstances > 0);
getProcessAPI(apiSession).deleteProcessDefinition(processDefinition.getId());
} catch (DeletionException e) {
throw new TestToolkitException("Can't delete process <" + this.processDefinition.getId() + "> with name " + this.processDefinition.getName(), e);
}
}
private void enableProcess(APISession apiSession) {
try {
getProcessAPI(apiSession).enableProcess(this.processDefinition.getId());
} catch (Exception e) {
throw new TestToolkitException("Can't enable process <" + this.processDefinition.getId() + ">", e);
}
}
private void disableProcess(APISession apiSession) {
try {
getProcessAPI(apiSession).disableProcess(this.processDefinition.getId());
} catch (Exception e) {
throw new TestToolkitException("Can't disable process <" + this.processDefinition.getId() + ">", e);
}
}
public TestProcess setEnable(final TestUser initiator, final boolean enabled) {
return setEnable(initiator.getSession(), enabled);
}
public void delete(final TestUser initiator) {
delete(initiator.getSession());
}
/**
* Deprecated, use {@link #enable()} or {@link #disable()}
*/
@Deprecated
public TestProcess setEnable(final boolean enabled) {
return setEnable(TestToolkitCtx.getInstance().getInitiator(), enabled);
}
public TestProcess enable() {
return setEnable(TestToolkitCtx.getInstance().getInitiator(), true);
}
public TestProcess disable() {
return setEnable(TestToolkitCtx.getInstance().getInitiator(), false);
}
public void delete() {
delete(TestToolkitCtx.getInstance().getInitiator());
}
/**
* Add actors to enable process
* <p/>
* TODO: Need to evolve to choose on which Actors category the actor will be added
*
* @param apiSession
* @param actor
* @return
* @throws Exception
*/
private TestProcess addActor(final APISession apiSession, final TestUser actor) {
final ProcessAPI processAPI = getProcessAPI(apiSession);
ActorInstance processActor = null;
try {
processActor = processAPI.getActors(this.processDefinition.getId(), 0, Integer.MAX_VALUE, ActorCriterion.NAME_ASC).get(this.actors.size());
processAPI.addUserToActor(processActor.getId(), actor.getUser().getId());
this.actors.add(processActor);
} catch (final Exception e) {
throw new TestToolkitException("Can't get actors for <" + this.processDefinition.getId() + ">.", e);
}
return this;
}
public TestProcess addActor(final TestGroup actor) {
return addActor(TestToolkitCtx.getInstance().getInitiator().getSession(), actor);
}
private TestProcess addActor(final APISession apiSession, final TestGroup actor) {
try {
final ProcessAPI processAPI = getProcessAPI(apiSession);
final ActorInstance processActor = processAPI.getActors(this.processDefinition.getId(), 0, Integer.MAX_VALUE, ActorCriterion.NAME_ASC).get(
this.actors.size());
processAPI.addGroupToActor(processActor.getId(), actor.getId());
this.actors.add(processActor);
} catch (final IndexOutOfBoundsException e) {
final String message = "can't add actor to process " + this.processDefinition.getId()
+ " process definition has only " + this.actors.size() + " actors";
throw new TestToolkitException(message, e);
} catch (final Exception e) {
throw new TestToolkitException("can't add actor to process " + this.processDefinition.getId(), e);
}
return this;
}
public TestProcess addActor(final TestRole actor) {
return addActor(TestToolkitCtx.getInstance().getInitiator().getSession(), actor);
}
private TestProcess addActor(final APISession apiSession, final TestRole actor) {
try {
final ProcessAPI processAPI = getProcessAPI(apiSession);
final ActorInstance processActor = processAPI.getActors(this.processDefinition.getId(), 0, Integer.MAX_VALUE, ActorCriterion.NAME_ASC).get(
this.actors.size());
processAPI.addRoleToActor(processActor.getId(), actor.getId());
this.actors.add(processActor);
} catch (final IndexOutOfBoundsException e) {
final String message = "can't add actor to process " + this.processDefinition.getId()
+ " process definition has only " + this.actors.size() + " actors";
throw new TestToolkitException(message, e);
} catch (final Exception e) {
throw new TestToolkitException("can't add actor to process " + this.processDefinition.getId(), e);
}
return this;
}
public TestProcess addActor(final TestUser initiator, final TestUser actor) {
addActor(initiator.getSession(), actor);
return this;
}
public TestProcess addActor(final TestUser actor) {
return addActor(TestToolkitCtx.getInstance().getInitiator(), actor);
}
private TestCase startCase(final APISession apiSession) {
setEnable(apiSession, true);
TestCase testCase = new TestCase(createProcesInstance(apiSession));
testCase.waitProcessState(apiSession, TestCase.READY_STATE);
return testCase;
}
protected ProcessInstance createProcesInstance(final APISession apiSession) {
try {
return getProcessAPI(apiSession).startProcess(apiSession.getUserId(), processDefinition.getId());
} catch (final Exception e) {
throw new TestToolkitException("Can't start process <" + processDefinition.getId() + ">", e);
}
}
public TestCase startCase(final TestUser initiator) {
return startCase(initiator.getSession());
}
public TestCase startCase() {
return startCase(TestToolkitCtx.getInstance().getInitiator());
}
/**
* Start several cases and return them as a list
*
* @param number
* @return
*/
public List<TestCase> startCases(final int number) {
final List<TestCase> result = new ArrayList<TestCase>();
for (int i = 0; i < number; i++) {
result.add(startCase());
}
return result;
}
// /////////////////////////////////////////////////////////////////////////////////////////
// / SUPERVISOR
// /////////////////////////////////////////////////////////////////////////////////////////
/**
* Add a user as process supervisor
*
* @param apiSession
* @param user
* @return
*/
private TestProcess addSupervisor(final APISession apiSession, final TestUser user) {
try {
this.processSupervisor = getProcessAPI(apiSession).createProcessSupervisorForUser(this.processDefinition.getId(), user.getId());
} catch (final Exception e) {
throw new TestToolkitException("Unable to add supervisor", e);
}
return this;
}
public TestProcess addSupervisor(final TestUser initiator, final TestUser user) {
return addSupervisor(initiator.getSession(), user);
}
public TestProcess addSupervisor(final TestUser user) {
return addSupervisor(TestToolkitCtx.getInstance().getInitiator(), user);
}
/**
* Add a role as process supervisor
*
* @param apiSession
* @param role
* @return
*/
private TestProcess addSupervisor(final APISession apiSession, final TestRole role) {
try {
getProcessAPI(apiSession).createProcessSupervisorForRole(this.processDefinition.getId(), role.getId());
} catch (final Exception e) {
throw new TestToolkitException("Unable to add supervisor", e);
}
return this;
}
public TestProcess addSupervisor(final TestUser initiator, final TestRole role) {
return addSupervisor(initiator.getSession(), role);
}
public TestProcess addSupervisor(final TestRole role) {
return addSupervisor(TestToolkitCtx.getInstance().getInitiator(), role);
}
/**
* Add a group as process supervisor
*
* @param apiSession
* @param group
* @return
*/
private TestProcess addSupervisor(final APISession apiSession, final TestGroup group) {
try {
getProcessAPI(apiSession).createProcessSupervisorForGroup(this.processDefinition.getId(), group.getId());
} catch (final Exception e) {
throw new TestToolkitException("Unable to add supervisor", e);
}
return this;
}
public TestProcess addSupervisor(final TestUser initiator, final TestGroup group) {
return addSupervisor(initiator.getSession(), group);
}
public TestProcess addSupervisor(final TestGroup group) {
return addSupervisor(TestToolkitCtx.getInstance().getInitiator(), group);
}
/**
* Add a memebership as process supervisor
*
* @param apiSession
* @param group
* @param role
* @return
*/
private TestProcess addSupervisor(final APISession apiSession, final TestGroup group, final TestRole role) {
try {
getProcessAPI(apiSession).createProcessSupervisorForMembership(this.processDefinition.getId(), group.getId(), role.getId());
} catch (final Exception e) {
throw new TestToolkitException("Unable to add supervisor", e);
}
return this;
}
public TestProcess addSupervisor(final TestUser initiator, final TestGroup group, final TestRole role) {
return addSupervisor(initiator.getSession(), group, role);
}
public TestProcess addSupervisor(final TestGroup group, final TestRole role) {
return addSupervisor(TestToolkitCtx.getInstance().getInitiator(), group, role);
}
public TestProcess addCategory(final TestCategory category) {
return addCategory(category.getId());
}
public TestProcess addCategory(final long categoryId) {
try {
TenantAPIAccessor.getProcessAPI(getSession()).addCategoriesToProcess(getId(), Arrays.asList(categoryId));
return this;
} catch (final Exception e) {
throw new TestToolkitException("Can't add this process to this category. " + e.getMessage(), e);
}
}
public List<TestCategory> getCategories() {
try {
final List<Category> categories = TenantAPIAccessor.getProcessAPI(getSession())
.getCategoriesOfProcessDefinition(getId(), 0, 100, CategoryCriterion.NAME_ASC);
final List<TestCategory> results = new ArrayList<TestCategory>(categories.size());
for (final Category category : categories) {
results.add(new TestCategory(category));
}
return results;
} catch (final Exception e) {
throw new TestToolkitException("Can't get categories", e);
}
}
public ProcessSupervisor getProcessSupervisor() {
return this.processSupervisor;
}
public List<ActorInstance> getActors() {
return this.actors;
}
public List<TestCase> listOpenCases() throws SearchException {
List<ProcessInstance> processInstances = searchOpenedProcessInstances();
return convertToCasesList(processInstances);
}
private List<TestCase> convertToCasesList(List<ProcessInstance> processInstances) {
List<TestCase> cases = new ArrayList<TestCase>();
for (ProcessInstance instance : processInstances) {
cases.add(new TestCase(instance));
}
return cases;
}
private List<ProcessInstance> searchOpenedProcessInstances() throws SearchException {
final SearchOptionsBuilder builder = new SearchOptionsBuilder(0, 100);
builder.filter(ProcessInstanceSearchDescriptor.PROCESS_DEFINITION_ID, getProcessDefinition().getId());
return getProcessAPI(getSession()).searchOpenProcessInstances(builder.done()).getResult();
}
public void deleteCases() throws Exception {
final ProcessAPI processAPI = TenantAPIAccessor.getProcessAPI(getSession());
int repeatNb = 10;
boolean repeat;
long sleep = 500;
Exception latestException = null;
do {
repeat = false;
repeatNb--;
final SearchOptions searchOptions = new SearchOptionsBuilder(0, 100000)
.filter(ProcessInstanceSearchDescriptor.PROCESS_DEFINITION_ID, processDefinition.getId())
.sort(ProcessInstanceSearchDescriptor.ID, Order.ASC).done();
for (ProcessInstance pi : processAPI.searchProcessInstances(searchOptions).getResult()) {
try {
processAPI.deleteProcessInstance(pi.getId());
} catch (DeletionException e) {
//ignore as it may be due a process instance finishing its execution
repeat = true;
latestException = e;
}
}
try {
processAPI.deleteArchivedProcessInstances(processDefinition.getId(), 0, 10000);
} catch (DeletionException e) {
//ignore as it may be due a process instance finishing its execution
repeat = true;
latestException = e;
}
Thread.sleep(sleep);
} while (repeat && repeatNb > 0);
if (repeat && latestException != null) {
throw latestException;
}
}
}