Several bugfixes for workflow handling (for Content Items)

pull/20/head
Jens Pelzetter 2022-01-22 17:55:33 +01:00
parent ba96a3c280
commit 8a6797e3c9
7 changed files with 185 additions and 62 deletions

View File

@ -33,6 +33,7 @@ import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.core.CcmObject; import org.libreccm.core.CcmObject;
import org.libreccm.core.CcmObjectRepository; import org.libreccm.core.CcmObjectRepository;
import org.libreccm.core.UnexpectedErrorException; import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.security.Permission;
import org.libreccm.security.PermissionChecker; import org.libreccm.security.PermissionChecker;
import org.libreccm.security.Role; import org.libreccm.security.Role;
import org.libreccm.security.RoleManager; import org.libreccm.security.RoleManager;
@ -49,12 +50,17 @@ import javax.persistence.TypedQuery;
import org.libreccm.security.Shiro; import org.libreccm.security.Shiro;
import org.libreccm.security.User; import org.libreccm.security.User;
import org.libreccm.security.UserRepository; import org.libreccm.security.UserRepository;
import org.libreccm.workflow.AssignableTask;
import org.libreccm.workflow.AssignableTaskManager;
import org.libreccm.workflow.Task; import org.libreccm.workflow.Task;
import org.libreccm.workflow.TaskAssignment;
import org.libreccm.workflow.TaskManager; import org.libreccm.workflow.TaskManager;
import org.libreccm.workflow.Workflow; import org.libreccm.workflow.Workflow;
import org.libreccm.workflow.WorkflowRepository; import org.libreccm.workflow.WorkflowRepository;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.stream.Collectors;
import javax.transaction.Transactional; import javax.transaction.Transactional;
@ -69,6 +75,9 @@ public class ContentItemRepository
private static final long serialVersionUID = -145167586339461600L; private static final long serialVersionUID = -145167586339461600L;
@Inject
private AssignableTaskManager assignableTaskManager;
@Inject @Inject
private CategoryManager categoryManager; private CategoryManager categoryManager;
@ -691,12 +700,29 @@ public class ContentItemRepository
if (draft.getWorkflow() != null) { if (draft.getWorkflow() != null) {
final Workflow workflow = draft.getWorkflow(); final Workflow workflow = draft.getWorkflow();
for (final Task task : workflow.getTasks()) { final List<Task> tasks = new ArrayList<>(workflow.getTasks());
for (final Task task : tasks) {
if (task instanceof AssignableTask) {
final AssignableTask assignable = (AssignableTask) task;
final List<Role> assignedRoles = assignable
.getAssignments()
.stream()
.map(TaskAssignment::getRole)
.collect(Collectors.toList());
for (final Role role : assignedRoles) {
assignableTaskManager.retractTask(assignable, role);
}
}
taskManager.removeTask(workflow, task); taskManager.removeTask(workflow, task);
} }
workflowRepo.delete(workflow); workflowRepo.delete(workflow);
} }
final List<Permission> permissions = draft.getPermissions();
for (final Permission permission : permissions) {
getEntityManager().remove(permission);
}
super.delete(draft); super.delete(draft);
} }

View File

@ -197,7 +197,7 @@ public class ContentType extends CcmObject implements Serializable {
} }
protected void setDefaultWorkflow(final Workflow defaultWorkflow) { protected void setDefaultWorkflow(final Workflow defaultWorkflow) {
if (!defaultWorkflow.isAbstractWorkflow()) { if (defaultWorkflow != null && !defaultWorkflow.isAbstractWorkflow()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"The provided workflow is not an abstract workflow."); "The provided workflow is not an abstract workflow.");
} }

View File

@ -59,21 +59,31 @@ public class ContentTypeManager {
* of {@link ContentItem}. * of {@link ContentItem}.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Class<? extends ContentItem> classNameToClass(final String className) { public Class<? extends ContentItem> classNameToClass(
final String className
) {
final Class<?> clazz; final Class<?> clazz;
try { try {
clazz = Class.forName(className); clazz = Class.forName(className);
} catch (ClassNotFoundException ex) { } catch (ClassNotFoundException ex) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(
"No class with the name \"%s\" exists.", className), String.format(
ex); "No class with the name \"%s\" exists.",
className
),
ex
);
} }
if (ContentItem.class.isAssignableFrom(clazz)) { if (ContentItem.class.isAssignableFrom(clazz)) {
return (Class<? extends ContentItem>) clazz; return (Class<? extends ContentItem>) clazz;
} else { } else {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(
"Class \"%s\" is not a content type.", className)); String.format(
"Class \"%s\" is not a content type.",
className
)
);
} }
} }
@ -89,13 +99,39 @@ public class ContentTypeManager {
public void setDefaultLifecycle( public void setDefaultLifecycle(
@RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES) @RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES)
final ContentType type, final ContentType type,
final LifecycleDefinition definition) { final LifecycleDefinition definition
) {
Objects.requireNonNull(
type,
"Can't set default lifecycle for ContentType null."
);
Objects.requireNonNull(
definition,
"Can't use LifecycleDefinition null as default lifecycle."
);
type.setDefaultLifecycle(definition); type.setDefaultLifecycle(definition);
typeRepo.save(type); typeRepo.save(type);
} }
/**
* Removes the default lifecycle from a content type. This does not delete
* the lifecycle definition.
*
* @param type The type from which the default lifecycle is removed.
*/
@Transactional(Transactional.TxType.REQUIRED)
@AuthorizationRequired
public void removeDefaultLifecycle(final ContentType type) {
Objects
.requireNonNull(
type,
"Can't remove default lifecycle from ContentType null."
)
.setDefaultLifecycle(null);
typeRepo.save(type);
}
/** /**
* Sets the default workflow to use for new items of a content type. * Sets the default workflow to use for new items of a content type.
* *
@ -108,14 +144,22 @@ public class ContentTypeManager {
public void setDefaultWorkflow( public void setDefaultWorkflow(
@RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES) @RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES)
final ContentType type, final ContentType type,
final Workflow template) { final Workflow template
) {
Objects.requireNonNull(type); Objects.requireNonNull(
Objects.requireNonNull(template); type,
"Can't set default workflow for ContentType null."
);
Objects.requireNonNull(
template,
"Can't use Workflow template null as default workflow."
);
if (!template.isAbstractWorkflow()) { if (!template.isAbstractWorkflow()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"The provided workflow is not an abstract workflow."); "The provided workflow is not a workflow template "
+ "(abstract workflow)."
);
} }
type.setDefaultWorkflow(template); type.setDefaultWorkflow(template);
@ -123,6 +167,23 @@ public class ContentTypeManager {
typeRepo.save(type); typeRepo.save(type);
} }
/**
* Removes the default workflow from a {@link ContentType}. This does not
* delete the workflow template.
*
* @param type The type from which the default workflow is removed.
*/
public void removeDefaultWorkflow(final ContentType type) {
Objects
.requireNonNull(
type,
"Can't remove default workflow from ContentType null."
)
.setDefaultWorkflow(null);
typeRepo.save(type);
}
/** /**
* Creates a permission granting the {@link TypePrivileges#USE_TYPE} * Creates a permission granting the {@link TypePrivileges#USE_TYPE}
* privilege to a role. * privilege to a role.
@ -135,8 +196,8 @@ public class ContentTypeManager {
public void grantUsageOfType( public void grantUsageOfType(
@RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES) @RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES)
final ContentType type, final ContentType type,
final Role role) { final Role role
) {
permissionManager.grantPrivilege(TypePrivileges.USE_TYPE, role, type); permissionManager.grantPrivilege(TypePrivileges.USE_TYPE, role, type);
} }
@ -153,8 +214,8 @@ public class ContentTypeManager {
public void denyUsageOnType( public void denyUsageOnType(
@RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES) @RequiresPrivilege(AdminPrivileges.ADMINISTER_CONTENT_TYPES)
final ContentType type, final ContentType type,
final Role role) { final Role role
) {
permissionManager.revokePrivilege(TypePrivileges.USE_TYPE, role, type); permissionManager.revokePrivilege(TypePrivileges.USE_TYPE, role, type);
} }

View File

@ -497,7 +497,9 @@ public class FolderManager {
final List<String> tokens = new ArrayList<>(); final List<String> tokens = new ArrayList<>();
tokens.add(folder.getName()); if (folder.getParentFolder() != null) {
tokens.add(folder.getName());
}
Folder current = folder; Folder current = folder;
while (getParentFolder(current).isPresent()) { while (getParentFolder(current).isPresent()) {
current = getParentFolder(current).get(); current = getParentFolder(current).get();

View File

@ -36,6 +36,8 @@ import org.libreccm.workflow.WorkflowManager;
import org.libreccm.workflow.WorkflowRepository; import org.libreccm.workflow.WorkflowRepository;
import org.librecms.contentsection.ContentSection; import org.librecms.contentsection.ContentSection;
import org.librecms.contentsection.ContentSectionManager; import org.librecms.contentsection.ContentSectionManager;
import org.librecms.contentsection.ContentType;
import org.librecms.contentsection.ContentTypeManager;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -95,6 +97,12 @@ public class ConfigurationWorkflowController {
*/ */
@Inject @Inject
private ContentSectionsUi sectionsUi; private ContentSectionsUi sectionsUi;
/**
* Manager for content types.
*/
@Inject
private ContentTypeManager typeManager;
/** /**
* Used for globaliazation stuff. * Used for globaliazation stuff.
@ -381,6 +389,14 @@ public class ConfigurationWorkflowController {
sectionManager.removeWorkflowTemplateFromContentSection( sectionManager.removeWorkflowTemplateFromContentSection(
workflow, section workflow, section
); );
final List<ContentType> typesUsingWorkflow = section
.getContentTypes()
.stream()
.filter(type -> workflow.equals(type.getDefaultWorkflow()))
.collect(Collectors.toList());
for(final ContentType typeUsingWorkflow : typesUsingWorkflow) {
typeManager.removeDefaultWorkflow(typeUsingWorkflow);
}
workflowRepo.delete(workflow); workflowRepo.delete(workflow);
return String.format( return String.format(

View File

@ -37,6 +37,7 @@ import javax.transaction.Transactional;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
@ -48,6 +49,7 @@ import javax.persistence.TypedQuery;
*/ */
@RequestScoped @RequestScoped
public class TaskManager implements Serializable { public class TaskManager implements Serializable {
private static final long serialVersionUID = -5605541655413527137L; private static final long serialVersionUID = -5605541655413527137L;
private static final Logger LOGGER = LogManager.getLogger(TaskManager.class); private static final Logger LOGGER = LogManager.getLogger(TaskManager.class);
@ -91,6 +93,25 @@ public class TaskManager implements Serializable {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void removeTask(final Workflow workflow, final Task task) { public void removeTask(final Workflow workflow, final Task task) {
final List<Task> blockedTasks = task
.getBlockedTasks()
.stream()
.map(TaskDependency::getBlockedTask)
.collect(Collectors.toList());
for (final Task blockedTask : blockedTasks) {
removeDependentTask(task, blockedTask);
}
final List<Task> blockingTasks = task
.getBlockingTasks()
.stream()
.map(TaskDependency::getBlockingTask)
.collect(Collectors.toList());
for (final Task blockingTask : blockingTasks) {
removeDependentTask(blockingTask, task);
}
workflow.removeTask(task); workflow.removeTask(task);
task.setWorkflow(null); task.setWorkflow(null);
@ -110,17 +131,19 @@ public class TaskManager implements Serializable {
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void addDependentTask(final Task blockingTask, public void addDependentTask(
final Task blockedTask) final Task blockingTask,
throws CircularTaskDependencyException { final Task blockedTask
) throws CircularTaskDependencyException {
Objects.requireNonNull(blockedTask); Objects.requireNonNull(blockedTask);
Objects.requireNonNull(blockingTask); Objects.requireNonNull(blockingTask);
LOGGER.debug("Adding a dependency between task {} (blocking task) " LOGGER.debug(
+ "and task {} (blocked task)...", "Adding a dependency between task {} (blocking task) "
Objects.toString(blockingTask), + "and task {} (blocked task)...",
Objects.toString(blockedTask)); Objects.toString(blockingTask),
Objects.toString(blockedTask)
);
LOGGER.debug("Checking for circular dependencies..."); LOGGER.debug("Checking for circular dependencies...");
checkForCircularDependencies(blockedTask, blockingTask); checkForCircularDependencies(blockedTask, blockingTask);
@ -133,10 +156,12 @@ public class TaskManager implements Serializable {
final Boolean dependencyExists = query.getSingleResult(); final Boolean dependencyExists = query.getSingleResult();
if (dependencyExists) { if (dependencyExists) {
LOGGER.info("Dependency between task {} (blocking task) " LOGGER.info(
+ "and task {} (blocked task) already exists.", "Dependency between task {} (blocking task) "
Objects.toString(blockingTask), + "and task {} (blocked task) already exists.",
Objects.toString(blockedTask)); Objects.toString(blockingTask),
Objects.toString(blockedTask)
);
return; return;
} }
@ -144,13 +169,12 @@ public class TaskManager implements Serializable {
dependency.setUuid(UUID.randomUUID().toString()); dependency.setUuid(UUID.randomUUID().toString());
dependency.setBlockedTask(blockedTask); dependency.setBlockedTask(blockedTask);
dependency.setBlockingTask(blockingTask); dependency.setBlockingTask(blockingTask);
blockingTask.addBlockedTask(dependency); blockingTask.addBlockedTask(dependency);
blockedTask.addBlockingTask(dependency); blockedTask.addBlockingTask(dependency);
// blockingTask.addDependentTask(blockedTask); // blockingTask.addDependentTask(blockedTask);
// blockedTask.addDependsOn(blockingTask); // blockedTask.addDependsOn(blockingTask);
entityManager.persist(dependency); entityManager.persist(dependency);
taskRepo.save(blockedTask); taskRepo.save(blockedTask);
taskRepo.save(blockingTask); taskRepo.save(blockingTask);
@ -160,30 +184,29 @@ public class TaskManager implements Serializable {
* Removes a dependent task. * Removes a dependent task.
* *
* @param blockingTask The task which blocks the other task. * @param blockingTask The task which blocks the other task.
* @param blockedTask The task which is blocked by the other task. * @param blockedTask The task which is blocked by the other task.
*/ */
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void removeDependentTask(final Task blockingTask, public void removeDependentTask(final Task blockingTask,
final Task blockedTask) { final Task blockedTask) {
final TypedQuery<TaskDependency> query = entityManager final TypedQuery<TaskDependency> query = entityManager
.createNamedQuery("Task.findDependency", TaskDependency.class); .createNamedQuery("Task.findDependency", TaskDependency.class);
query.setParameter("blockedTask", blockedTask); query.setParameter("blockedTask", blockedTask);
query.setParameter("blockingTask", blockingTask); query.setParameter("blockingTask", blockingTask);
final List<TaskDependency> dependencies = query.getResultList(); final List<TaskDependency> dependencies = query.getResultList();
for(final TaskDependency dependency : dependencies) { for (final TaskDependency dependency : dependencies) {
entityManager.remove(dependency); entityManager.remove(dependency);
blockingTask.removeBlockedTask(dependency); blockingTask.removeBlockedTask(dependency);
blockedTask.removeBlockingTask(dependency); blockedTask.removeBlockingTask(dependency);
} }
// blockingTask.removeDependentTask(blockedTask); // blockingTask.removeDependentTask(blockedTask);
// blockedTask.removeDependsOn(blockingTask); // blockedTask.removeDependsOn(blockingTask);
taskRepo.save(blockedTask); taskRepo.save(blockedTask);
taskRepo.save(blockingTask); taskRepo.save(blockingTask);
} }
@ -206,14 +229,14 @@ public class TaskManager implements Serializable {
} }
private boolean dependsOn(final Task blockingTask, final Task blockedTask) { private boolean dependsOn(final Task blockingTask, final Task blockedTask) {
final TypedQuery<Boolean> query = entityManager final TypedQuery<Boolean> query = entityManager
.createNamedQuery("Task.existsDependency", Boolean.class); .createNamedQuery("Task.existsDependency", Boolean.class);
query.setParameter("blockingTask", blockingTask); query.setParameter("blockingTask", blockingTask);
query.setParameter("blockedTask", blockedTask); query.setParameter("blockedTask", blockedTask);
return query.getSingleResult(); return query.getSingleResult();
// for (final Task current : blockingTask.getDependsOn()) { // for (final Task current : blockingTask.getDependsOn()) {
// if (current.equals(blockedTask)) { // if (current.equals(blockedTask)) {
// return true; // return true;
@ -316,9 +339,9 @@ public class TaskManager implements Serializable {
* @param task The task to finish. * @param task The task to finish.
*/ */
public void finish(final Task task) { public void finish(final Task task) {
Objects.requireNonNull(task, "Can't finished null..."); Objects.requireNonNull(task, "Can't finished null...");
if (task.getTaskState() != TaskState.ENABLED) { if (task.getTaskState() != TaskState.ENABLED) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
"Task %s is not enabled.", "Task %s is not enabled.",
@ -343,9 +366,8 @@ public class TaskManager implements Serializable {
* @param task * @param task
*/ */
protected void updateState(final Task task) { protected void updateState(final Task task) {
Objects.requireNonNull(task); Objects.requireNonNull(task);
LOGGER.debug("Updating state for task {}...", LOGGER.debug("Updating state for task {}...",
Objects.toString(task)); Objects.toString(task));
@ -355,8 +377,9 @@ public class TaskManager implements Serializable {
return; return;
} }
for (final TaskDependency blockingTaskDependency : task.getBlockingTasks()) { for (final TaskDependency blockingTaskDependency : task
.getBlockingTasks()) {
final Task blockingTask = blockingTaskDependency.getBlockingTask(); final Task blockingTask = blockingTaskDependency.getBlockingTask();
LOGGER.debug("Checking dependency {}...", LOGGER.debug("Checking dependency {}...",
Objects.toString(blockingTask)); Objects.toString(blockingTask));

View File

@ -291,6 +291,9 @@ public class WorkflowManager implements Serializable {
) )
); );
} }
taskRepo.save(task);
workflowRepo.save(workflow);
} }
/** /**
@ -434,21 +437,14 @@ public class WorkflowManager implements Serializable {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public void start(final Workflow workflow) { public void start(final Workflow workflow) {
final WorkflowState oldState = workflow.getState();
workflow.setState(WorkflowState.STARTED);
if (oldState == WorkflowState.INIT) {
workflow.setActive(true); workflow.setActive(true);
updateState(workflow); updateState(workflow);
// for (final Task current : workflow.getTasks()) {
// current.setActive(true);
// taskManager.updateState(current);
// }
final List<Task> tasks = workflow.getTasks(); final List<Task> tasks = workflow.getTasks();
if (!tasks.isEmpty()) { if (!tasks.isEmpty()) {
final Task firstTask = tasks.get(0); final Task firstTask = tasks.get(0);
firstTask.setActive(true); firstTask.setActive(true);
firstTask.setTaskState(TaskState.ENABLED);
taskManager.updateState(firstTask); taskManager.updateState(firstTask);
if (firstTask instanceof AssignableTask) { if (firstTask instanceof AssignableTask) {
@ -465,7 +461,6 @@ public class WorkflowManager implements Serializable {
} }
} }
} }
}
workflowRepo.save(workflow); workflowRepo.save(workflow);
} }