CCM NG/ccm-core,ccm-cms: Some changes to Workflow, Task etc. Primarly some missing features/methods required by Workflow UI in CCM CMS.

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4441 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-11-16 18:37:08 +00:00
parent 7430901acc
commit 48dea0fb14
44 changed files with 1548 additions and 1112 deletions

View File

@ -436,7 +436,7 @@ public class ContentItemPage extends CMSPage implements ActionListener {
* @param itemId the id of the ContentItem object to display
* @param tab The index of the tab to display
*/
public static String getItemURL(BigDecimal itemId, int tab) {
public static String getItemURL(long itemId, int tab) {
final ContentItem item =
(ContentItem) DomainObjectFactory.newInstance(new OID(
ContentItem.BASE_DATA_OBJECT_TYPE, itemId));

View File

@ -57,7 +57,7 @@ import com.arsdigita.toolbox.ui.Section;
import com.arsdigita.util.Assert;
import com.arsdigita.util.SequentialMap;
import com.arsdigita.util.UncheckedWrapperException;
import org.apache.log4j.Logger;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@ -66,6 +66,9 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Collections;
import org.apache.logging.log4j.LogManager;
import org.librecms.contenttypes.AuthoringKitInfo;
import org.librecms.contenttypes.ContentTypeInfo;
/**
* This class represents a single authoring kit. The wizard accepts a
@ -83,12 +86,11 @@ import java.util.Collections;
* This constructor will be called when the component is automatically
* instantiated by the <code>AuthoringKitWizard</code>.</p>
*
* @version $Id: AuthoringKitWizard.java 2140 2011-01-16 12:04:20Z pboy $
*/
public class AuthoringKitWizard extends LayoutPanel implements Resettable {
/** Private Logger instance for this class */
private static final Logger s_log = Logger.getLogger(
private static final Logger LOGGER = LogManager.getLogger(
AuthoringKitWizard.class);
private static Class[] s_args = new Class[]{
ItemSelectionModel.class,
@ -103,8 +105,8 @@ public class AuthoringKitWizard extends LayoutPanel implements Resettable {
private static final java.util.List<AssetStepEntry> s_assets = new
ArrayList<AssetStepEntry>();
private final Object[] m_vals;
private final ContentType m_type;
private final AuthoringKit m_kit;
private final ContentTypeInfo m_type;
private final AuthoringKitInfo m_kit;
private final ItemSelectionModel m_sel;
private final WorkflowRequestLocal m_workflow;
private final AssignedTaskTable m_tasks;
@ -131,14 +133,12 @@ public class AuthoringKitWizard extends LayoutPanel implements Resettable {
*
* @param type The content type of the items that this wizard will
* handle
*
* @param itemModel The item selection model which will supply
* this wizard with the content item object
* @param model
*/
public AuthoringKitWizard(final ContentType type,
public AuthoringKitWizard(final ContentTypeInfo type,
final ItemSelectionModel model) {
if (s_log.isDebugEnabled()) {
s_log.debug("Authoring kit wizard for type " + type + " "
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Authoring kit wizard for type " + type + " "
+ "undergoing creation");
}
@ -262,9 +262,9 @@ public class AuthoringKitWizard extends LayoutPanel implements Resettable {
Collection skipSteps = ContentSection.getConfig().getAssetStepsToSkip(
type);
Iterator it = skipSteps.iterator();
if (s_log.isDebugEnabled()) {
if (LOGGER.isDebugEnabled()) {
while (it.hasNext()) {
s_log.debug("skip step " + it.next());
LOGGER.debug("skip step " + it.next());
}
}
//Iterator assets = s_assets.iterator();
@ -276,7 +276,7 @@ public class AuthoringKitWizard extends LayoutPanel implements Resettable {
final String baseObjectType = data.getBaseDataObjectType();
//Class step = (Class) data[1];
Class step = data.getStep();
s_log.debug("possibly adding asset step " + step.getName());
LOGGER.debug("possibly adding asset step " + step.getName());
if (!skipSteps.contains(step.getName())) {
//GlobalizedMessage label = (GlobalizedMessage) data[2];
GlobalizedMessage label = data.getLabel();
@ -423,7 +423,7 @@ public class AuthoringKitWizard extends LayoutPanel implements Resettable {
// registered, but I needed the image step to use a different step class if the specialised
// image step application was loaded. Solution is to ensure initialiser in new project
// runs after original ccm-ldn-image-step initializer and override the registered step here
s_log.debug(
LOGGER.debug(
"registering asset step - label: "
+ label.localize()
+ " step class: "
@ -451,7 +451,7 @@ public class AuthoringKitWizard extends LayoutPanel implements Resettable {
*/
if ((thisObjectType.equals(baseObjectType))
&& (thisLabel.localize().equals(label.localize()))) {
s_log.debug(
LOGGER.debug(
"registering authoring step with same label as previously registered step");
s_assets.remove(data);
break;
@ -570,8 +570,8 @@ public class AuthoringKitWizard extends LayoutPanel implements Resettable {
* @param className The Java class name of the step
*/
protected Component instantiateStep(String name) {
if (s_log.isDebugEnabled()) {
s_log.debug("Instantiating kit wizard '" + name + "' with "
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Instantiating kit wizard '" + name + "' with "
+ "arguments " + s_args);
}

View File

@ -18,7 +18,6 @@
*/
package com.arsdigita.cms.ui.authoring;
import com.arsdigita.bebop.Component;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Resettable;
@ -33,6 +32,7 @@ import org.librecms.contentsection.ContentType;
import com.arsdigita.cms.ItemSelectionModel;
import com.arsdigita.toolbox.ui.LayoutPanel;
import com.arsdigita.util.UncheckedWrapperException;
import com.arsdigita.xml.Element;
import oracle.jrockit.jfr.events.ContentTypeImpl;
@ -42,15 +42,14 @@ import org.librecms.contenttypes.ContentTypeInfo;
import java.math.BigDecimal;
/**
* An invisible component which contains all the possible authoring kits.
* The kits are loaded from the database at construction time. The selector
* chooses which kit to display at page rendering time based on the value
* of the content_type state parameter.
* An invisible component which contains all the possible authoring kits. The
* kits are loaded from the database at construction time. The selector chooses
* which kit to display at page rendering time based on the value of the
* content_type state parameter.
*
* Essentially, this component is a hack which is used to get around
* the fact that we cannot instantiate stateful components dynamically.
* Essentially, this component is a hack which is used to get around the fact
* that we cannot instantiate stateful components dynamically.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @author unknown
@ -61,14 +60,14 @@ public class WizardSelector extends AuthoringKitSelector
private ItemSelectionModel itemSelectionModel;
/**
* Construct a new WizardSelector. Load all the possible authoring kits
* from the database and construct wizards for them.
* Construct a new WizardSelector. Load all the possible authoring kits from
* the database and construct wizards for them.
*
* @param model the {@link ItemSelectionModel} which will
* supply the wizard with its item
* @param model the {@link ItemSelectionModel} which will supply the wizard
* with its item
*
* @param typeModel the {@link ACSObjectSelectionModel} which will
* supply the default content type
* @param typeModel the {@link ACSObjectSelectionModel} which will supply
* the default content type
*
* @pre itemModel != null
*/
@ -81,16 +80,17 @@ public class WizardSelector extends AuthoringKitSelector
/**
* Get the wizard for the given kit.
*
* @param kit
* @param type
* @return
*/
@Override
public Component instantiateKitComponent(final AuthoringKitInfo kit,
final ContentTypeInfo type) {
final ItemSelectionModel itemModel = new
ItemSelectionModel(type,
(LongParameter)itemSelectionModel.getStateParameter());
final ItemSelectionModel itemModel = new ItemSelectionModel(
type, (LongParameter) itemSelectionModel.getStateParameter());
final AuthoringKitWizard wizard = new AuthoringKitWizard(type, itemModel);
return wizard;
@ -107,54 +107,51 @@ public class WizardSelector extends AuthoringKitSelector
private Component getCurrentWizard(PageState state) {
// Get the current item and extract its content type
if(!itemSelectionModel.isSelected(state))
throw new RuntimeException( (String) GlobalizationUtil.globalize(
"cms.ui.authoring.missing_item_id")
.localize());
if (!itemSelectionModel.isSelected(state)) {
throw new UncheckedWrapperException("No item selected.");
}
ContentItem item =
(ContentItem)itemSelectionModel.getSelectedObject(state);
final ContentItem item = (ContentItem) itemSelectionModel
.getSelectedObject(state);
ContentType type = item.getContentType();
BigDecimal typeId;
final ContentType type = item.getContentType();
final String typeClass;
if(type == null) {
if (type == null) {
// Try to get the default content type
typeId = (BigDecimal)getComponentSelectionModel().getSelectedKey(state);
if(typeId == null) {
throw new RuntimeException((String) GlobalizationUtil.globalize(
"cms.ui.authoring.missing_content_type")
.localize());
typeClass = getComponentSelectionModel().getSelectedKey(state);
if (typeClass == null || typeClass.isEmpty()) {
throw new UncheckedWrapperException("Content type is missing");
}
} else {
typeId = type.getID();
typeClass = type.getContentItemClass();
}
// Return the selected wizard
return (Component)getComponent(typeId);
return (Component) getComponent(typeClass);
}
// Choose the right wizard and run it
public void generateXML(PageState state, Element parent) {
@Override
public void generateXML(final PageState state, final Element parent) {
Component c = getCurrentWizard(state);
final Component component = getCurrentWizard(state);
if(c == null) {
throw new RuntimeException( (String) GlobalizationUtil.globalize(
"cms.ui.authoring.no_current_wizard")
.localize());
if (component == null) {
throw new UncheckedWrapperException("No Wizard.");
}
c.generateXML(state, parent);
component.generateXML(state, parent);
}
/**
* Reset the state of the current wizard
*/
public void reset(PageState state) {
Resettable r = (Resettable)getCurrentWizard(state);
if(r != null) r.reset(state);
public void reset(final PageState state) {
final Resettable resettable = (Resettable) getCurrentWizard(state);
if (resettable != null) {
resettable.reset(state);
}
}
}

View File

@ -24,8 +24,8 @@ import com.arsdigita.kernel.KernelConfig;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.security.Shiro;
import org.libreccm.security.User;
import org.libreccm.workflow.UserTask;
import org.libreccm.workflow.UserTaskRepository;
import org.libreccm.workflow.AssignableTask;
import org.libreccm.workflow.AssignableTaskRepository;
import org.libreccm.workflow.Workflow;
import org.libreccm.workflow.WorkflowManager;
@ -50,7 +50,7 @@ public class AssignedTaskController {
private WorkflowManager workflowManager;
@Inject
private UserTaskRepository userTaskRepo;
private AssignableTaskRepository userTaskRepo;
@Inject
private Shiro shiro;
@ -71,7 +71,7 @@ public class AssignedTaskController {
@Transactional(Transactional.TxType.REQUIRED)
public List<RowData<Long>> getAssignedTasks(final Workflow workflow) {
final User user = shiro.getUser();
final List<UserTask> tasks = userTaskRepo.getAssignedTasks(user,
final List<AssignableTask> tasks = userTaskRepo.getAssignedTasks(user,
workflow);
return tasks
@ -81,7 +81,7 @@ public class AssignedTaskController {
}
private RowData<Long> createRowData(final UserTask task) {
private RowData<Long> createRowData(final AssignableTask task) {
final RowData<Long> rowData = new RowData<>(3);

View File

@ -39,14 +39,14 @@ import org.apache.log4j.Logger;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.security.Shiro;
import org.libreccm.workflow.Task;
import org.libreccm.workflow.UserTask;
import org.libreccm.workflow.UserTaskRepository;
import org.libreccm.workflow.AssignableTask;
import org.libreccm.workflow.AssignableTaskRepository;
import org.libreccm.workflow.WorkflowConstants;
import org.libreccm.workflow.WorkflowManager;
import org.libreccm.workflow.WorkflowRepository;
import org.librecms.CmsConstants;
import org.librecms.workflow.CmsTask;
import org.librecms.workflow.CmsTaskType;
import org.librecms.workflow.CmsTaskTypeOld;
import java.util.List;
@ -177,16 +177,15 @@ public final class AssignedTaskSection extends Section {
protected final Object initialValue(final PageState state) {
final Workflow workflow = m_flow.getWorkflow(state);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserTaskRepository userTaskRepo = cdiUtil.findBean(
UserTaskRepository.class);
final AssignableTaskRepository userTaskRepo = cdiUtil.findBean(AssignableTaskRepository.class);
final Shiro shiro = cdiUtil.findBean(Shiro.class);
return userTaskRepo.findEnabledTasksForWorkflow(shiro.getUser(),
workflow);
}
@SuppressWarnings("unchecked")
final List<UserTask> getTasks(final PageState state) {
return (ArrayList<UserTask>) get(state);
final List<AssignableTask> getTasks(final PageState state) {
return (ArrayList<AssignableTask>) get(state);
}
}
@ -208,7 +207,7 @@ public final class AssignedTaskSection extends Section {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final WorkflowManager workflowManager = cdiUtil.findBean(WorkflowManager.class);
for(final UserTask task : m_tasks.getTasks(state)) {
for(final AssignableTask task : m_tasks.getTasks(state)) {
if (relevant(task) && !task.isLocked()) {
workflowManager.lockTask(task);
}
@ -219,7 +218,7 @@ public final class AssignedTaskSection extends Section {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final WorkflowManager workflowManager = cdiUtil.findBean(WorkflowManager.class);
for(final UserTask task : m_tasks.getTasks(state)) {
for(final AssignableTask task : m_tasks.getTasks(state)) {
if (relevant(task) && task.isLocked()) {
workflowManager.unlockTask(task);
}
@ -227,7 +226,7 @@ public final class AssignedTaskSection extends Section {
}
final boolean tasksLocked(final PageState state) {
for(final UserTask task : m_tasks.getTasks(state)) {
for(final AssignableTask task : m_tasks.getTasks(state)) {
if (relevant(task) && !task.isLocked()) {
return false;
}
@ -248,7 +247,7 @@ public final class AssignedTaskSection extends Section {
return !m_tasks.getTasks(state).isEmpty();
}
private boolean relevant(final UserTask task) {
private boolean relevant(final AssignableTask task) {
return true;
// ToDo

View File

@ -40,8 +40,8 @@ import org.apache.log4j.Logger;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.security.Shiro;
import org.libreccm.security.User;
import org.libreccm.workflow.UserTask;
import org.libreccm.workflow.UserTaskRepository;
import org.libreccm.workflow.AssignableTask;
import org.libreccm.workflow.AssignableTaskRepository;
import org.libreccm.workflow.WorkflowManager;
import org.librecms.CmsConstants;
import org.librecms.workflow.CmsTaskTypeRepository;
@ -70,13 +70,12 @@ public final class AssignedTaskTable extends Table {
final int column = event.getColumn();
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final UserTaskRepository userTaskRepo = cdiUtil.findBean(
UserTaskRepository.class);
final AssignableTaskRepository userTaskRepo = cdiUtil.findBean(AssignableTaskRepository.class);
final WorkflowManager workflowManager = cdiUtil.findBean(WorkflowManager.class);
final Shiro shiro = cdiUtil.findBean(Shiro.class);
if (column == 1) {
final UserTask task = userTaskRepo.findById((Long) event
final AssignableTask task = userTaskRepo.findById((Long) event
.getRowKey());
final User currentUser = shiro.getUser();
final User lockingUser = task.getLockingUser();

View File

@ -27,7 +27,7 @@ import com.arsdigita.bebop.parameters.IntegerParameter;
import com.arsdigita.cms.ui.BaseForm;
import com.arsdigita.cms.ui.ListOptionPrintListener;
import org.librecms.workflow.CmsTaskType;
import org.librecms.workflow.CmsTaskTypeOld;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.kernel.KernelConfig;
@ -142,27 +142,27 @@ class BaseTaskForm extends BaseForm {
}
*/
// Fix this one too
private class TaskTypePrintListener extends ListOptionPrintListener<CmsTaskType> {
private class TaskTypePrintListener extends ListOptionPrintListener<CmsTaskTypeOld> {
@Override
protected List<CmsTaskType> getDataQuery(final PageState state) {
protected List<CmsTaskTypeOld> getDataQuery(final PageState state) {
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final CmsTaskTypeRepository taskTypeRepo = cdiUtil.findBean(
CmsTaskTypeRepository.class);
final List<CmsTaskType> taskTypes = taskTypeRepo.findAll();
final List<CmsTaskTypeOld> taskTypes = taskTypeRepo.findAll();
return taskTypes;
}
@Override
public String getKey(final CmsTaskType taskType) {
public String getKey(final CmsTaskTypeOld taskType) {
return Long.toString(taskType.getTaskTypeId());
}
@Override
public String getValue(final CmsTaskType taskType) {
public String getValue(final CmsTaskTypeOld taskType) {
final KernelConfig kernelConfig = KernelConfig.getConfig();
final Locale defaultLocale = kernelConfig.getDefaultLocale();
return taskType.getName().getValue(defaultLocale);

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.cms.ui.workflow;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.form.TextArea;
import com.arsdigita.cms.ui.BaseForm;
import org.librecms.workflow.CmsTask;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.workflow.TaskRepository;
/**
* @author Justin Ross
* @author <a href="mailto:jens.pelzetter">Jens Pelzetter</a>
*/
class CommentAddForm extends BaseForm {
private static final Logger LOGGER = LogManager.getLogger(CommentAddForm.class);
private final TaskRequestLocal selectedTask;
private final TextArea comment;
public CommentAddForm(final TaskRequestLocal task) {
super("addComment", gz("cms.ui.workflow.task.comment.add"));
this.selectedTask = task;
comment = new TextArea("Comment");
comment.setWrap(TextArea.SOFT);
comment.setRows(5);
comment.setCols(40);
addComponent(comment);
addAction(new Finish());
addAction(new Cancel());
addProcessListener(new ProcessListener());
}
private class ProcessListener implements FormProcessListener {
@Override
public final void process(final FormSectionEvent event)
throws FormProcessException {
LOGGER.debug("Processing comment add");
final PageState state = event.getPageState();
final CmsTask task = selectedTask.getTask(state);
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final TaskRepository taskRepo = cdiUtil.findBean(TaskRepository.class);
task.addComment((String)comment.getValue(state));
taskRepo.save(task);
}
}
}

View File

@ -30,7 +30,7 @@ import com.arsdigita.bebop.form.OptionGroup;
import com.arsdigita.kernel.KernelConfig;
import org.librecms.workflow.CmsTask;
import org.librecms.workflow.CmsTaskType;
import org.librecms.workflow.CmsTaskTypeOld;
import com.arsdigita.util.UncheckedWrapperException;
@ -120,7 +120,7 @@ class TaskAddForm extends BaseTaskForm {
defaultLocale,
((String) m_description.getValue(state)));
final CmsTaskType taskType = taskTypeRepo.findById((Long) m_type.getValue(state));
final CmsTaskTypeOld taskType = taskTypeRepo.findById((Long) m_type.getValue(state));
task.setTaskType(taskType);
task.setActive(true);

View File

@ -30,7 +30,7 @@ import com.arsdigita.cms.ui.UserSearchForm;
import com.arsdigita.globalization.GlobalizedMessage;
import org.libreccm.security.User;
import org.libreccm.workflow.UserTask;
import org.libreccm.workflow.AssignableTask;
import com.arsdigita.xml.Element;
@ -146,7 +146,7 @@ class TaskAddUser extends SimpleContainer {
WorkflowManager.class);
final UserRepository userRepo = cdiUtil.findBean(UserRepository.class);
final UserTask task = m_task.getTask(state);
final AssignableTask task = m_task.getTask(state);
User user;
for (int i = 0; i < users.length; i++) {

View File

@ -37,7 +37,7 @@ import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.workflow.Task;
import org.libreccm.workflow.TaskRepository;
import org.librecms.workflow.CmsTaskType;
import org.librecms.workflow.CmsTaskTypeOld;
import org.librecms.workflow.CmsTaskTypeRepository;
import java.util.ArrayList;
@ -144,7 +144,7 @@ class TaskEditForm extends BaseTaskForm {
defaultLocale,
(String) m_description.getValue(state));
final CmsTaskType taskType = taskTypeRepo.findById((Long) m_type
final CmsTaskTypeOld taskType = taskTypeRepo.findById((Long) m_type
.getValue(state));
task.setTaskType(taskType);

View File

@ -0,0 +1,253 @@
/*
* Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.cms.ui.workflow;
import com.arsdigita.bebop.FormProcessException;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.event.FormInitListener;
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.event.FormValidationListener;
import com.arsdigita.bebop.form.Option;
import com.arsdigita.bebop.form.RadioGroup;
import com.arsdigita.bebop.parameters.BooleanParameter;
import org.librecms.contentsection.ContentSection;
import com.arsdigita.cms.ContentCenter;
import com.arsdigita.cms.ui.ContentItemPage;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.util.UncheckedWrapperException;
import org.librecms.workflow.CmsTask;
import org.librecms.workflow.CmsTaskTypeOld;
import com.arsdigita.web.RedirectSignal;
import com.arsdigita.web.URL;
import com.arsdigita.web.Web;
import org.libreccm.workflow.Task;
import org.apache.logging.log4j.Logger;
import java.util.Iterator;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.libreccm.cdi.utils.CdiUtil;
import org.libreccm.security.PermissionChecker;
import org.libreccm.security.Shiro;
import org.libreccm.workflow.TaskRepository;
import org.libreccm.workflow.WorkflowManager;
import org.libreccm.workflow.WorkflowRepository;
import org.librecms.CmsConstants;
import org.librecms.contentsection.ContentItem;
import org.librecms.contentsection.ContentItemRepository;
/**
* <p>
* A form that prompts the user to comment on and approve tasks and then
* finishes the task if it was approved.</p>
*
* @author Justin Ross &lt;jross@redhat.com&gt;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public final class TaskFinishForm extends CommentAddForm {
private static final Logger LOGGER = LogManager.getLogger(
TaskFinishForm.class);
private final TaskRequestLocal m_task;
private final Label m_approvePrompt;
private final RadioGroup m_approve;
public TaskFinishForm(final TaskRequestLocal task) {
super(task);
m_task = task;
m_approve = new RadioGroup(new BooleanParameter("approve"));
m_approve.addOption(new Option("true",
lz("cms.ui.workflow.task.approve")));
m_approve.addOption(new Option("false",
lz("cms.ui.workflow.task.reject")));
m_approvePrompt = new Label(gz("cms.ui.workflow.task.approve_prompt"));
addComponent(m_approvePrompt);
addComponent(m_approve);
addInitListener(new InitListener());
addValidationListener(new ValidationListener());
addProcessListener(new ProcessListener());
}
private class InitListener implements FormInitListener {
@Override
public final void init(final FormSectionEvent e) {
LOGGER.debug("Initializing task finish");
final PageState state = e.getPageState();
if (isVisible(state)) {
final CmsTask task = m_task.getTask(state);
if (requiresApproval(task)) {
m_approvePrompt.setVisible(state, true);
m_approve.setVisible(state, true);
} else {
m_approvePrompt.setVisible(state, false);
m_approve.setVisible(state, false);
}
}
}
}
private class ValidationListener implements FormValidationListener {
@Override
public final void validate(final FormSectionEvent e)
throws FormProcessException {
LOGGER.debug("Validating task finish");
final PageState state = e.getPageState();
final CmsTask task = m_task.getTask(state);
if (requiresApproval(task) && m_approve.getValue(state) == null) {
throw new FormProcessException(new GlobalizedMessage(
"cms.ui.workflow.task.approval_or_reject_required",
CmsConstants.CMS_BUNDLE));
}
}
}
private class ProcessListener implements FormProcessListener {
@Override
public final void process(final FormSectionEvent event)
throws FormProcessException {
LOGGER.debug("Processing task finish");
final PageState state = event.getPageState();
final CmsTask task = m_task.getTask(state);
boolean finishedTask = false;
final CdiUtil cdiUtil = CdiUtil.createCdiUtil();
final PermissionChecker permissionChecker = cdiUtil.findBean(
PermissionChecker.class);
final ContentItemRepository itemRepo = cdiUtil.findBean(
ContentItemRepository.class);
final Optional<ContentItem> item = itemRepo.findItemWithWorkflow(
task.getWorkflow());
if (!item.isPresent()) {
throw new UncheckedWrapperException(
"Workflow not assigned to an item");
}
permissionChecker.checkPermission(task.getTaskType().getPrivilege(),
item.get());
final TaskRepository taskRepo = cdiUtil.findBean(
TaskRepository.class);
if (requiresApproval(task)) {
LOGGER.debug("The task requires approval; checking to see "
+ "if it's approved");
// XXX I think the fact that this returns a Boolean is
// the effect of broken parameter marshalling in
// Bebop.
final Boolean isApproved = (Boolean) m_approve.getValue(state);
if (isApproved.equals(Boolean.TRUE)) {
LOGGER.debug("The task is approved; finishing the task");
final Shiro shiro = cdiUtil.findBean(Shiro.class);
final WorkflowManager workflowManager = cdiUtil.findBean(
WorkflowManager.class);
task.setActive(false);
finishedTask = true;
} else {
LOGGER.debug("The task is rejected; reenabling dependent "
+ "tasks");
// Reenable the previous tasks.
final Iterator<Task> iter = task.getDependentTasks().
iterator();
while (iter.hasNext()) {
final Task dependent = (Task) iter.next();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Reenabling task " + dependent.
getLabel());
}
dependent.setActive(true);
taskRepo.save(dependent);
}
}
} else {
LOGGER.debug("The task does not require approval; finishing "
+ "it");
task.setActive(false);
taskRepo.save(task);
}
if (finishedTask) {
Iterator tasks = Engine.getInstance(CMSEngine.CMS_ENGINE_TYPE).
getEnabledTasks(Web.getWebContext().getUser(),
task.getParentID()).iterator();
if (tasks.hasNext()) {
CmsTask thisTask = (CmsTask) tasks.next();
PermissionDescriptor thisTaskAccess = new PermissionDescriptor(
thisTask.getTaskType().getPrivilege(), task.
getWorkflow().getObject(), user);
if (PermissionService.checkPermission(thisTaskAccess)) {
// Lock task for user
thisTask.lock((User) user);
int targetTab = (thisTask.getTaskType().getID().equals(CmsTaskTypeOld.DEPLOY)) ? ContentItemPage.PUBLISHING_TAB : ContentItemPage.AUTHORING_TAB;
throw new RedirectSignal(URL.there(state.getRequest(),
ContentItemPage.
getItemURL(
task.
getItem(),
targetTab)),
true);
}
}
// redirect to /content-center if streamlined creation mode is active.
if (ContentSection.getConfig().getUseStreamlinedCreation()) {
throw new RedirectSignal(URL.there(state.getRequest(),
ContentCenter.getURL()),
true);
}
}
}
}
private static boolean requiresApproval(final CmsTask task) {
return !task.getTaskType().getID().equals(CmsTaskTypeOld.AUTHOR);
}
}

View File

@ -49,7 +49,7 @@ import org.libreccm.security.Role;
import org.libreccm.security.RoleRepository;
import org.libreccm.security.Shiro;
import org.libreccm.workflow.Task;
import org.libreccm.workflow.UserTask;
import org.libreccm.workflow.AssignableTask;
import org.libreccm.workflow.WorkflowManager;
import org.librecms.CmsConstants;
import org.librecms.contentsection.privileges.AdminPrivileges;
@ -173,7 +173,7 @@ final class TaskItemPane extends BaseItemPane {
final User user = shiro.getUser();
final List<UserTask> tasks = workflowManager.lockedBy(user);
final List<AssignableTask> tasks = workflowManager.lockedBy(user);
return tasks.contains(task);
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.cms.workflow;
/**
* Class for generating a URL to the Authoring kit given the ID of the
* ContentItem and the Task.
*
* @author Uday Mathur (umathur@arsdigita.com)
* @author <a href="mail:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* */
public class AuthoringTaskURLGenerator implements TaskURLGenerator {
public AuthoringTaskURLGenerator() {}
/**
* Generates a Link to the Authoring Kit in the Item Management part
* of the CMS UI.
*
* @param itemId id of the item in question
* @param taskId this param is ignored.
* @return
* */
@Override
public String generateURL(final long itemId, final long taskId) {
throw new UnsupportedOperationException("ToDo");
// return ContentItemPage.getItemURL(itemId,
// ContentItemPage.AUTHORING_TAB);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.cms.workflow;
/**
* Generates a Link to the Deploy Task Panel under the Workflow Tab in the Item
* Management part of the CMS UI.
*
* @author Uday Mathur (umathur@arsdigita.com)
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*
*/
public class DeployTaskURLGenerator implements TaskURLGenerator {
public DeployTaskURLGenerator() {
}
/**
* Generates a Link to the Finish Task Panel under the Workflow Tab in the
* Item Management part of the CMS UI.
*
* @param itemId id of the item in question
* @param taskId id of the task to finish
* @return
*
*/
@Override
public String generateURL(final long itemId, final long taskId) {
// String url = ContentItemPage.getItemURL(itemId, ContentItemPage.PUBLISHING_TAB);
// return url;
throw new UnsupportedOperationException("ToDo");
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.cms.workflow;
/**
* Class for generating a URL to the Authoring kit given the ID of the
* ContentItem and the Task. Eventually we may have a separate kit for editors,
* hence this is a separate class and has its own TaskType
*
* @author Uday Mathur (umathur@arsdigita.com)
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*
*/
public class EditingTaskURLGenerator implements TaskURLGenerator {
public EditingTaskURLGenerator() {
}
/**
* Generates a Link to the Workflow Tab in the Item Management part of the
* CMS UI.
*
* @param itemId id of the item in question
* @param taskId this param is ignored.
*
* @return
*
*/
@Override
public String generateURL(final long itemId, final long taskId) {
// final StringBuffer url = new StringBuffer
// (ContentItemPage.getItemURL(itemId, ContentItemPage.WORKFLOW_TAB));
//
// // XXX task, approve, and action were constants; restore them
// url.append("&action=approve&task=").append(taskId.toString());
//
// return url.toString();
throw new UnsupportedOperationException("ToDo");
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.cms.workflow;
import java.math.BigDecimal;
/**
* Interface for generating a URL for a Task given the ID of the
* ContentItem and the Task.
*
* @author Uday Mathur (umathur@arsdigita.com)
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* */
public interface TaskURLGenerator {
String generateURL(long item_id, long task_id);
}

View File

@ -73,21 +73,24 @@ import static org.librecms.CmsConstants.*;
query = "SELECT i FROM ContentItem i "
+ "JOIN i.categories c "
+ "WHERE c.category = :folder "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER + "'")
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER
+ "'")
,
@NamedQuery(
name = "ContentItem.countItemsInFolder",
query = "SELECT count(i) FROM ContentItem i "
+ "JOIN i.categories c "
+ "WHERE c.category = :folder "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER + "'")
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER
+ "'")
,
@NamedQuery(
name = "ContentItem.countByNameInFolder",
query = "SELECT COUNT(i) FROM ContentItem i "
+ "JOIN i.categories c "
+ "WHERE c.category = :folder "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER + "' "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER
+ "' "
+ "AND i.displayName = :name")
,
@NamedQuery(
@ -95,7 +98,8 @@ import static org.librecms.CmsConstants.*;
query = "SELECT i FROM ContentItem i "
+ "JOIN i.categories c "
+ "WHERE c.category = :folder "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER + "' "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER
+ "' "
+ "AND LOWER(i.displayName) LIKE CONCAT(LOWER(:name), '%')")
,
@NamedQuery(
@ -103,7 +107,8 @@ import static org.librecms.CmsConstants.*;
query = "SELECT COUNT(i) FROM ContentItem i "
+ "JOIN i.categories c "
+ "WHERE c.category = :folder "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER + "' "
+ "AND c.type = '" + CATEGORIZATION_TYPE_FOLDER
+ "' "
+ "AND LOWER(i.displayName) LIKE CONCAT(LOWER(:name), '%')"
)
,
@ -125,7 +130,12 @@ import static org.librecms.CmsConstants.*;
query = "SELECT i FROM ContentItem i "
+ "WHERE i.itemUuid = :uuid "
+ "AND i.version = org.librecms.contentsection.ContentItemVersion.LIVE")
,
@NamedQuery(
name = "ContentItem.findItemWithWorkflow",
query = "SELECT i FROM ContentItem i "
+ "WHERE i.workflow = :workflow"
)
})
public class ContentItem extends CcmObject implements Serializable,
InheritsPermissions {

View File

@ -169,7 +169,8 @@ public class ContentItemManager {
*
* @param <T> The type of the content item.
* @param name The name (URL stub) of the new content item.
* @param section The content section in which the item is generated.
* @param section The content section in which the item is
* generated.
* @param folder The folder in which in the item is stored.
* @param workflowTemplate The template for the workflow to apply to the new
* item.
@ -225,7 +226,7 @@ public class ContentItemManager {
if (workflowTemplate != null) {
final Workflow workflow = workflowManager.createWorkflow(
workflowTemplate);
workflowTemplate, item);
item.setWorkflow(workflow);
}
@ -319,8 +320,9 @@ public class ContentItemManager {
*
* @param item The item to copy.
* @param targetFolder The folder in which the copy is created. If the
* target folder is the same folder as the folder of the original item an
* index is appended to the name of the item.
* target folder is the same folder as the folder of the
* original item an index is appended to the name of the
* item.
*
* @return The copy of the item
*/
@ -383,7 +385,7 @@ public class ContentItemManager {
final WorkflowTemplate template = draftItem.getWorkflow()
.getTemplate();
final Workflow copyWorkflow = workflowManager.createWorkflow(
template);
template, item);
copy.setWorkflow(copyWorkflow);
}
@ -562,7 +564,8 @@ public class ContentItemManager {
private void copyAttachmentList(final AttachmentList sourceList,
final ContentItem target) {
final AttachmentList targetList = new AttachmentList();
copyLocalizedString(sourceList.getDescription(), targetList.getDescription());
copyLocalizedString(sourceList.getDescription(), targetList
.getDescription());
targetList.setItem(target);
targetList.setName(sourceList.getName());
targetList.setOrder(sourceList.getOrder());
@ -844,8 +847,8 @@ public class ContentItemManager {
targetAsset = sourceAttachment.getAsset();
} else {
try {
targetAsset = sourceAttachment.getAsset().getClass().
newInstance();
targetAsset = sourceAttachment.getAsset().getClass()
.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new UncheckedWrapperException(ex);
}
@ -1143,9 +1146,9 @@ public class ContentItemManager {
* @param type Type of the content item.
*
* @return The live version of an item. If the item provided is already the
* live version the provided item is returned, otherwise the live version is
* returned. If there is no live version an empty {@link Optional} is
* returned.
* live version the provided item is returned, otherwise the live
* version is returned. If there is no live version an empty
* {@link Optional} is returned.
*/
@AuthorizationRequired
@Transactional(Transactional.TxType.REQUIRED)
@ -1203,10 +1206,10 @@ public class ContentItemManager {
* @param type Type of the item.
*
* @return The draft version of the provided content item. If the provided
* item is the draft version the provided item is simply returned. Otherwise
* the draft version is retrieved from the database and is returned. Each
* content item has a draft version (otherwise something is seriously wrong
* with the database) this method will
* item is the draft version the provided item is simply returned.
* Otherwise the draft version is retrieved from the database and is
* returned. Each content item has a draft version (otherwise
* something is seriously wrong with the database) this method will
* <b>never</b> return {@code null}.
*/
@AuthorizationRequired

View File

@ -29,7 +29,9 @@ import java.util.UUID;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
import org.libreccm.workflow.Workflow;
/**
* Repository for content items.
@ -251,4 +253,17 @@ public class ContentItemRepository
return query.getSingleResult();
}
public Optional<ContentItem> findItemWithWorkflow(final Workflow workflow) {
final TypedQuery<ContentItem> query = getEntityManager()
.createNamedQuery("ContentItem.findItemWithWorkflow",
ContentItem.class);
query.setParameter("workflow", workflow);
try {
return Optional.of(query.getSingleResult());
} catch(NoResultException ex) {
return Optional.empty();
}
}
}

View File

@ -18,14 +18,15 @@
*/
package org.librecms.workflow;
import org.libreccm.workflow.UserTask;
import org.libreccm.workflow.AssignableTask;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import static org.librecms.CmsConstants.*;
@ -36,12 +37,12 @@ import static org.librecms.CmsConstants.*;
*/
@Entity
@Table(name = "WORKFLOW_TASKS", schema = DB_SCHEMA)
public class CmsTask extends UserTask implements Serializable {
public class CmsTask extends AssignableTask implements Serializable {
private static final long serialVersionUID = -3988352366529930659L;
@OneToOne
@JoinColumn(name = "TASK_TYPE_ID")
@Column(name = "TASK_TYPE")
@Enumerated(EnumType.STRING)
private CmsTaskType taskType;
public CmsTaskType getTaskType() {
@ -79,7 +80,7 @@ public class CmsTask extends UserTask implements Serializable {
return false;
}
return Objects.equals(taskType, other.taskType);
return Objects.equals(taskType, other.getTaskType());
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2015 LibreCCM Foundation.
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -18,188 +18,38 @@
*/
package org.librecms.workflow;
import org.libreccm.l10n.LocalizedString;
import com.arsdigita.cms.workflow.AuthoringTaskURLGenerator;
import com.arsdigita.cms.workflow.DeployTaskURLGenerator;
import com.arsdigita.cms.workflow.EditingTaskURLGenerator;
import com.arsdigita.cms.workflow.TaskURLGenerator;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import javax.persistence.AssociationOverride;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import static org.librecms.CmsConstants.*;
import org.librecms.contentsection.privileges.ItemPrivileges;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Entity
@Table(name = "WORKFLOW_TASK_TYPES", schema = DB_SCHEMA)
public class CmsTaskType implements Serializable {
public enum CmsTaskType {
private static final long serialVersionUID = -4326031746212785970L;
AUTHOR(AuthoringTaskURLGenerator.class, ItemPrivileges.EDIT),
EDIT(EditingTaskURLGenerator.class, ItemPrivileges.APPROVE),
DEPLOY(DeployTaskURLGenerator.class, ItemPrivileges.PUBLISH);
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TASK_TYPE_ID")
private long taskTypeId;
private final Class<? extends TaskURLGenerator> urlGenerator;
private final String privilege;
@Embedded
@AssociationOverride(
name = "values",
joinTable = @JoinTable(name = "ARTICLE_LEADS",
schema = DB_SCHEMA,
joinColumns = {
@JoinColumn(name = "OBJECT_ID")}
))
private LocalizedString name;
@Column(name = "DEFAULT_URL_GENERATOR_CLASS", length = 1024)
private String defaultUrlGeneratorClass;
@Column(name = "PRIVILEGE", length = 256)
private String privilege;
@OneToMany
@JoinColumn(name = "TASK_TYPE_ID")
private Set<TaskEventUrlGenerator> generators;
public CmsTaskType() {
generators = new HashSet<>();
private CmsTaskType(final Class<? extends TaskURLGenerator> urlGenerator,
final String privilege) {
this.urlGenerator = urlGenerator;
this.privilege = privilege;
}
public long getTaskTypeId() {
return taskTypeId;
}
protected void setTaskTypeId(final long taskTypeId) {
this.taskTypeId = taskTypeId;
}
public LocalizedString getName() {
return name;
}
public void setName(final LocalizedString name) {
this.name = name;
}
public String getDefaultUrlGeneratorClass() {
return defaultUrlGeneratorClass;
}
public void setDefaultUrlGeneratorClass(
final String defaultUrlGeneratorClass) {
this.defaultUrlGeneratorClass = defaultUrlGeneratorClass;
public Class<? extends TaskURLGenerator> getUrlGenerator() {
return urlGenerator;
}
public String getPrivilege() {
return privilege;
}
public void setPrivilege(final String privilege) {
this.privilege = privilege;
}
public Set<TaskEventUrlGenerator> getGenerators() {
if (generators == null) {
return null;
} else {
return Collections.unmodifiableSet(generators);
}
}
protected void setGenerators(final Set<TaskEventUrlGenerator> generators) {
this.generators = generators;
}
public void addGenerator(final TaskEventUrlGenerator generator) {
generators.add(generator);
}
public void removeGenerator(final TaskEventUrlGenerator generator) {
generators.remove(generator);
}
@Override
public int hashCode() {
int hash = 5;
hash = 79 * hash + (int) (taskTypeId ^ (taskTypeId >>> 32));
hash = 79 * hash + Objects.hashCode(name);
hash = 79 * hash + Objects.hashCode(defaultUrlGeneratorClass);
hash = 79 * hash + Objects.hashCode(privilege);
hash = 79 * hash + Objects.hashCode(generators);
return hash;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof CmsTaskType) {
return false;
}
final CmsTaskType other = (CmsTaskType) obj;
if (!other.canEqual(this)) {
return false;
}
if (taskTypeId != other.getTaskTypeId()) {
return false;
}
if (!Objects.equals(defaultUrlGeneratorClass,
other.getDefaultUrlGeneratorClass())) {
return false;
}
if (!Objects.equals(privilege, other.getPrivilege())) {
return false;
}
if (!Objects.equals(name, other.getName())) {
return false;
}
return Objects.equals(generators, other.getGenerators());
}
public boolean canEqual(final Object obj) {
return obj instanceof CmsTaskType;
}
@Override
public final String toString() {
return toString("");
}
public String toString(final String data) {
return String.format("%s{ "
+ "taskTypeId = %d, "
+ "name = %s, "
+ "defaultUrlGeneratorClass = \"%s\", "
+ "privilege = \"%s\","
+ "generators = { %s }%s"
+ " }",
super.toString(),
taskTypeId,
Objects.toString(name),
defaultUrlGeneratorClass,
privilege,
Objects.toString(generators),
data);
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.workflow;
import org.libreccm.core.AbstractEntityRepository;
import javax.enterprise.context.RequestScoped;
/**
* A repository for the {@link CmsTaskType} entity.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class CmsTaskTypeRepository extends AbstractEntityRepository<Long, CmsTaskType>{
@Override
public Class<CmsTaskType> getEntityClass() {
return CmsTaskType.class;
}
@Override
public boolean isNew(final CmsTaskType taskType) {
return taskType.getTaskTypeId() == 0;
}
}

View File

@ -1,156 +0,0 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.librecms.workflow;
import org.librecms.contentsection.ContentType;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import static org.librecms.CmsConstants.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Entity
@Table(name = "TASK_EVENT_URL_GENERATOR", schema = DB_SCHEMA)
public class TaskEventUrlGenerator implements Serializable {
private static final long serialVersionUID = -1861545657474968084L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "GENERATOR_ID")
private long generatorId;
@Column(name = "EVENT", length = 256)
private String event;
@OneToOne
@JoinColumn(name = "CONTENT_TYPE_ID")
private ContentType contentType;
@Column(name = "URL_GENERATOR_CLASS", length = 1024)
private String urlGeneratorClass;
public long getGeneratorId() {
return generatorId;
}
public void setGeneratorId(final long generatorId) {
this.generatorId = generatorId;
}
public String getEvent() {
return event;
}
public void setEvent(final String event) {
this.event = event;
}
public ContentType getContentType() {
return contentType;
}
public void setContentType(final ContentType contentType) {
this.contentType = contentType;
}
public String getUrlGeneratorClass() {
return urlGeneratorClass;
}
public void setUrlGeneratorClass(final String urlGeneratorClass) {
this.urlGeneratorClass = urlGeneratorClass;
}
@Override
public int hashCode() {
int hash = 3;
hash = 47 * hash + (int) (generatorId ^ (generatorId >>> 32));
hash = 47 * hash + Objects.hashCode(event);
hash = 47 * hash + Objects.hashCode(contentType);
hash = 47 * hash + Objects.hashCode(urlGeneratorClass);
return hash;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof TaskEventUrlGenerator) {
return false;
}
final TaskEventUrlGenerator other = (TaskEventUrlGenerator) obj;
if (!(other.canEqual(this))) {
return false;
}
if (generatorId != other.getGeneratorId()) {
return false;
}
if (!Objects.equals(event, other.getEvent())) {
return false;
}
if (!Objects.equals(urlGeneratorClass, other.getUrlGeneratorClass())) {
return false;
}
return Objects.equals(contentType, other.getContentType());
}
public boolean canEqual(final Object obj) {
return obj instanceof TaskEventUrlGenerator;
}
@Override
public final String toString() {
return toString("");
}
public String toString(final String data) {
return String.format("%s{ "
+ "generatorId = %d, "
+ "event = \"%s\", "
+ "contentType = %s, "
+ "urlGeneratorClass = \"%s\"%s"
+ " }",
super.toString(),
generatorId,
event,
Objects.toString(contentType),
urlGeneratorClass,
data);
}
}

View File

@ -45,31 +45,37 @@ import javax.persistence.TemporalType;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Entity
@Table(name = "WORKFLOW_USER_TASKS", schema = DB_SCHEMA)
@Table(name = "WORKFLOW_ASSIGNABLE_TASKS", schema = DB_SCHEMA)
@NamedQueries({
@NamedQuery(
name = "UserTask.findLockedBy",
query = "SELECT t FROM UserTask t WHERE t.lockingUser = :user")
name = "AssignableTask.findLockedBy",
query = "SELECT t FROM AssignableTask t WHERE t.lockingUser = :user")
,
@NamedQuery(
name = "UserTask.findEnabledTasksForWorkflow",
query = "SELECT t FROM UserTask t "
name = "AssignableTask.findEnabledTasksForWorkflow",
query = "SELECT t FROM AssignableTask t "
+ "WHERE t.lockingUser = :user "
+ "AND t.workflow = :workflow"
)
,
@NamedQuery(
name = "UserTask.findAssignedTasks",
query = "SELECT t FROM UserTask t "
name = "AssignableTask.findAssignedTasks",
query = "SELECT t FROM AssignableTask t "
+ "WHERE t.assignments.role IN :roles "
+ "AND t.assignments.workflow = :workflow "
+ "AND t.active = true")
,
@NamedQuery(
name = "AssignableTask.findOverdueTasks",
query = "SELECT t FROM AssignableTask t "
+ "WHERE t.workflow = :workflow "
+ "AND t.dueDate < :now")
})
//Can't reduce complexity yet
@SuppressWarnings({"PMD.CyclomaticComplexity",
"PMD.StdCyclomaticComplexity",
"PMD.ModifiedCyclomaticComplexity"})
public class UserTask extends Task implements Serializable {
public class AssignableTask extends Task implements Serializable {
private static final long serialVersionUID = 4188064584389893019L;
@ -99,7 +105,7 @@ public class UserTask extends Task implements Serializable {
@OneToMany(mappedBy = "task")
private List<TaskAssignment> assignments;
public UserTask() {
public AssignableTask() {
super();
assignments = new ArrayList<>();
}
@ -217,10 +223,10 @@ public class UserTask extends Task implements Serializable {
return false;
}
if (!(obj instanceof UserTask)) {
if (!(obj instanceof AssignableTask)) {
return false;
}
final UserTask other = (UserTask) obj;
final AssignableTask other = (AssignableTask) obj;
if (!other.canEqual(this)) {
return false;
}
@ -245,7 +251,7 @@ public class UserTask extends Task implements Serializable {
@Override
public boolean canEqual(final Object obj) {
return obj instanceof UserTask;
return obj instanceof AssignableTask;
}
@Override

View File

@ -33,38 +33,41 @@ import javax.persistence.TypedQuery;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class UserTaskRepository extends AbstractEntityRepository<Long, UserTask> {
public class AssignableTaskRepository
extends AbstractEntityRepository<Long, AssignableTask> {
@Override
public Class<UserTask> getEntityClass() {
return UserTask.class;
public Class<AssignableTask> getEntityClass() {
return AssignableTask.class;
}
@Override
public boolean isNew(final UserTask task) {
public boolean isNew(final AssignableTask task) {
return task.getTaskId() == 0;
}
public List<UserTask> findEnabledTasksForWorkflow(final User user,
final Workflow workflow) {
final TypedQuery<UserTask> query = getEntityManager().createNamedQuery(
"UserTask.findEnabledTasksForWorkflow", UserTask.class);
public List<AssignableTask> findEnabledTasksForWorkflow(
final User user, final Workflow workflow) {
final TypedQuery<AssignableTask> query = getEntityManager()
.createNamedQuery(
"UserTask.findEnabledTasksForWorkflow", AssignableTask.class);
query.setParameter("user", user);
query.setParameter("workflow", workflow);
return query.getResultList();
}
public List<UserTask> getAssignedTasks(final User user,
public List<AssignableTask> getAssignedTasks(final User user,
final Workflow workflow) {
final TypedQuery<UserTask> query = getEntityManager().createNamedQuery(
"UserTask.findAssignedTasks", UserTask.class);
final TypedQuery<AssignableTask> query = getEntityManager()
.createNamedQuery(
"UserTask.findAssignedTasks", AssignableTask.class);
final List<Role> roles = user.getRoleMemberships()
.stream()
.map(membership -> membership.getRole())
.collect(Collectors.toList());
query.setParameter("roles", roles );
query.setParameter("roles", roles);
query.setParameter("workflow", workflow);
return query.getResultList();

View File

@ -30,9 +30,10 @@ import java.util.Objects;
import javax.persistence.AssociationOverride;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@ -40,9 +41,11 @@ import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
/**
@ -59,6 +62,35 @@ import javax.persistence.Table;
"PMD.ShortClassName",
"PMD.TooManyMethods",
"PMD.AvoidDuplicateLiterals"})
@NamedQueries({
@NamedQuery(
name = "Task.countUnfinishedAndActiveTasksForWorkflow",
query = "SELECT COUNT(t) FROM Task t "
+ "WHERE t.taskState != org.libreccm.workflow.TaskState.FINISHED "
+ "AND t.active = true "
+ "AND t.workflow = :workflow")
,
@NamedQuery(
name = "Task.countUnfinishedTasksForWorkflow",
query = "SELECT COUNT(t) FROM Task t "
+ "WHERE t.taskState != org.libreccm.workflow.TaskState.FINISHED "
+ "AND t.workflow = :workflow"
)
,
@NamedQuery(
name = "Task.findEnabledTasks",
query = "SELECT t FROM Task t "
+ "WHERE t.workflow = :workflow "
+ "AND t.taskState = org.libreccm.workflow.TaskState.ENABLED "
+ "AND t.active = true"
)
,
@NamedQuery(
name = "Task.findFinishedTasks",
query = "SELECT t FROM Task t "
+ "WHERE t.workflow = :workflow "
+ "AND t.taskState = org.libreccm.workflow.TaskState.FINISHED")
})
public class Task implements Serializable {
private static final long serialVersionUID = 8161343036908150426L;
@ -90,7 +122,8 @@ public class Task implements Serializable {
private boolean active;
@Column(name = "TASK_STATE", length = 512)
private String taskState;
@Enumerated(EnumType.STRING)
private TaskState taskState;
@ManyToOne
@JoinColumn(name = "WORKFLOW_ID")
@ -108,14 +141,9 @@ public class Task implements Serializable {
@JoinColumn(name = "DEPENDENT_TASK_ID")})
private List<Task> dependsOn;
@ElementCollection
@JoinTable(name = "WORKFLOW_TASK_COMMENTS",
schema = DB_SCHEMA,
joinColumns = {
@JoinColumn(name = "TASK_ID")})
@Column(name = "COMMENT")
@Lob
private List<String> comments;
@OneToMany
@JoinColumn(name = "TASK_ID")
private List<TaskComment> comments;
public Task() {
super();
@ -159,11 +187,11 @@ public class Task implements Serializable {
this.active = active;
}
public String getTaskState() {
public TaskState getTaskState() {
return taskState;
}
public void setTaskState(final String taskState) {
protected void setTaskState(final TaskState taskState) {
this.taskState = taskState;
}
@ -215,7 +243,7 @@ public class Task implements Serializable {
dependsOn.remove(task);
}
public List<String> getComments() {
public List<TaskComment> getComments() {
if (comments == null) {
return null;
} else {
@ -223,15 +251,15 @@ public class Task implements Serializable {
}
}
protected void setComments(final List<String> comments) {
protected void setComments(final List<TaskComment> comments) {
this.comments = comments;
}
public void addComment(final String comment) {
public void addComment(final TaskComment comment) {
comments.add(comment);
}
public void removeComment(final String comment) {
public void removeComment(final TaskComment comment) {
comments.remove(comment);
}

View File

@ -51,7 +51,7 @@ public class TaskAssignment implements Serializable {
@ManyToOne
@JoinColumn(name = "TASK_ID")
private UserTask task;
private AssignableTask task;
@ManyToOne
@JoinColumn(name = "ROLE_ID")
@ -65,11 +65,11 @@ public class TaskAssignment implements Serializable {
this.taskAssignmentId = taskAssignmentId;
}
public UserTask getTask() {
public AssignableTask getTask() {
return task;
}
protected void setTask(final UserTask task) {
protected void setTask(final AssignableTask task) {
this.task = task;
}

View File

@ -20,6 +20,7 @@ package org.libreccm.workflow;
import static org.libreccm.core.CoreConstants.*;
import org.libreccm.core.CcmObject;
import org.libreccm.l10n.LocalizedString;
import java.io.Serializable;
@ -32,6 +33,8 @@ import javax.persistence.AssociationOverride;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@ -40,7 +43,10 @@ import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
/**
@ -50,6 +56,12 @@ import javax.persistence.Table;
@Entity
@Table(name = "WORKFLOWS", schema = DB_SCHEMA)
@Inheritance(strategy = InheritanceType.JOINED)
@NamedQueries({
@NamedQuery(
name = "Workflow.findForObject",
query = "SELECT w FROM Workflow w "
+ "WHERE W.object = :object")
})
public class Workflow implements Serializable {
private static final long serialVersionUID = 4322500264543325829L;
@ -82,6 +94,21 @@ public class Workflow implements Serializable {
}))
private LocalizedString description;
@Column(name = "WORKFLOW_STATE")
@Enumerated(EnumType.STRING)
private WorkflowState state;
@Column(name = "ACTIVE")
private boolean active;
@Column(name = "TASKS_STATE")
@Enumerated(EnumType.STRING)
private TaskState tasksState;
@OneToOne
@JoinColumn(name = "OBJECT_ID")
private CcmObject object;
@OneToMany(mappedBy = "workflow")
private List<Task> tasks;
@ -125,6 +152,38 @@ public class Workflow implements Serializable {
this.description = description;
}
public WorkflowState getState() {
return state;
}
protected void setState(final WorkflowState state) {
this.state = state;
}
public boolean isActive() {
return active;
}
protected void setActive(final boolean active) {
this.active = active;
}
public TaskState getTasksState() {
return tasksState;
}
protected void setTasksState(final TaskState tasksState) {
this.tasksState = tasksState;
}
public CcmObject getObject() {
return object;
}
protected void setObject(final CcmObject object) {
this.object = object;
}
public List<Task> getTasks() {
if (tasks == null) {
return null;
@ -150,6 +209,11 @@ public class Workflow implements Serializable {
int hash = 5;
hash = 79 * hash + (int) (this.workflowId ^ (this.workflowId >>> 32));
hash = 79 * hash + Objects.hashCode(this.name);
hash = 79 * hash + Objects.hashCode(description);
hash = 79 * hash + Objects.hashCode(state);
hash = 79 * hash + (active ? 1 : 0);
hash = 79 * hash + Objects.hashCode(tasksState);
hash = 79 * hash + Objects.hashCode(object);
return hash;
}
@ -166,10 +230,31 @@ public class Workflow implements Serializable {
return false;
}
if (this.workflowId != other.getWorkflowId()) {
if (workflowId != other.getWorkflowId()) {
return false;
}
return Objects.equals(this.name, other.getName());
if (!Objects.equals(name, other.getName())) {
return false;
}
if (!Objects.equals(description, other.getDescription())) {
return false;
}
if (!Objects.equals(state, other.getState())) {
return false;
}
if (active != other.isActive()) {
return false;
}
if (!Objects.equals(tasksState, other.getTasksState())) {
return false;
}
return Objects.equals(object, other.getObject());
}
@ -185,11 +270,19 @@ public class Workflow implements Serializable {
public String toString(final String data) {
return String.format("%s{ "
+ "workflowId = %d, "
+ "name = \"%s\"%s"
+ "name = \"%s\", "
+ "description = \"%s\", "
+ "state = \"%s\", "
+ "active = %b"
+ "object = \"%s\"%s"
+ " }",
super.toString(),
workflowId,
name,
Objects.toString(name),
Objects.toString(description),
Objects.toString(state),
active,
Objects.toString(object),
data);
}

View File

@ -18,14 +18,17 @@
*/
package org.libreccm.workflow;
import com.arsdigita.kernel.KernelConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.core.CcmObject;
import org.libreccm.core.CoreConstants;
import org.libreccm.l10n.LocalizedString;
import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege;
import org.libreccm.security.Role;
import org.libreccm.security.RoleRepository;
import org.libreccm.security.Shiro;
import org.libreccm.security.User;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
@ -33,12 +36,14 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
@ -52,6 +57,9 @@ import javax.transaction.Transactional;
@RequestScoped
public class WorkflowManager {
private final static Logger LOGGER = LogManager.getLogger(
WorkflowManager.class);
@Inject
private EntityManager entityManager;
@ -62,15 +70,28 @@ public class WorkflowManager {
private TaskRepository taskRepo;
@Inject
private RoleRepository roleRepo;
private TaskManager taskManager;
@Inject
private Shiro shiro;
@Inject
private ConfigurationManager confManager;
private Locale defaultLocale;
@PostConstruct
private void init() {
final KernelConfig kernelConfig = confManager.findConfiguration(
KernelConfig.class);
defaultLocale = kernelConfig.getDefaultLocale();
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public Workflow createWorkflow(final WorkflowTemplate template) {
public Workflow createWorkflow(final WorkflowTemplate template,
final CcmObject object) {
final Workflow workflow = new Workflow();
final LocalizedString name = new LocalizedString();
@ -90,6 +111,9 @@ public class WorkflowManager {
template.getTasks().forEach(taskTemplate -> fixTaskDependencies(
taskTemplate, tasks.get(taskTemplate.getTaskId()), tasks));
workflow.setObject(object);
workflow.setState(WorkflowState.INIT);
tasks.values().forEach(task -> taskRepo.save(task));
workflowRepo.save(workflow);
@ -172,139 +196,161 @@ public class WorkflowManager {
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void addTask(final Workflow workflow, final Task task) {
workflow.addTask(task);
task.setWorkflow(workflow);
public List<Task> findEnabledTasks(final Workflow workflow) {
if (workflow.getState() == WorkflowState.DELETED
|| workflow.getState() == WorkflowState.STOPPED) {
LOGGER.debug(String.format("Workflow state is \"%s\". Workflow "
+ "has no enabled tasks.",
workflow.getState().toString()));
return Collections.emptyList();
}
workflowRepo.save(workflow);
taskRepo.save(task);
final TypedQuery<Task> query = entityManager.createNamedQuery(
"Task.findEnabledTasks", Task.class);
query.setParameter("workflow", workflow);
return Collections.unmodifiableList(query.getResultList());
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void removeTask(final Workflow workflow, final Task task) {
workflow.removeTask(task);
task.setWorkflow(null);
public List<Task> findFinishedTasks(final Workflow workflow) {
final TypedQuery<Task> query = entityManager.createNamedQuery(
"Task.findFinishedTasks", Task.class);
query.setParameter("workflow", workflow);
workflowRepo.save(workflow);
taskRepo.save(task);
return Collections.unmodifiableList(query.getResultList());
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void assignTask(final UserTask task, final Role role) {
final TaskAssignment assignment = new TaskAssignment();
assignment.setTask(task);
assignment.setRole(role);
public List<AssignableTask> findOverdueTasks(final Workflow workflow) {
final TypedQuery<AssignableTask> query = entityManager.createNamedQuery(
"AssignableTask.findOverdueTasks", AssignableTask.class);
query.setParameter("workflow", workflow);
query.setParameter("now", new Date());
task.addAssignment(assignment);
role.addAssignedTask(assignment);
entityManager.persist(assignment);
taskRepo.save(task);
roleRepo.save(role);
return Collections.unmodifiableList(query.getResultList());
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void retractTask(final UserTask task, final Role role) {
final List<TaskAssignment> result = task.getAssignments().stream()
.filter(assigned -> role.equals(assigned.getRole()))
.collect(Collectors.toList());
if (!result.isEmpty()) {
final TaskAssignment assignment = result.get(0);
task.removeAssignment(assignment);
role.removeAssignedTask(assignment);
entityManager.remove(assignment);
}
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void addDependentTask(final Task parent, final Task task) {
parent.addDependentTask(task);
task.addDependsOn(parent);
taskRepo.save(task);
taskRepo.save(parent);
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void removeDependentTask(final Task parent, final Task task) {
parent.removeDependentTask(task);
task.removeDependsOn(parent);
taskRepo.save(task);
taskRepo.save(parent);
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void lockTask(final UserTask task) {
task.setLocked(true);
task.setLockingUser(shiro.getUser());
taskRepo.save(task);
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void unlockTask(final UserTask task) {
task.setLocked(false);
task.setLockingUser(null);
taskRepo.save(task);
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public List<UserTask> lockedBy(final User user) {
final TypedQuery<UserTask> query = entityManager.createNamedQuery(
"UserTask.findLockedBy", UserTask.class);
query.setParameter("user", user);
return query.getResultList();
}
public void start(final Workflow workflow) {
if (workflow.getTasks() != null && !workflow.getTasks().isEmpty()) {
final Task first = workflow.getTasks().get(0);
final WorkflowState oldState = workflow.getState();
if (first instanceof UserTask) {
final User user = shiro.getUser();
lockTask((UserTask) first);
workflow.setState(WorkflowState.STARTED);
if (oldState == WorkflowState.INIT) {
workflow.setActive(true);
updateState(workflow);
for (final Task current : workflow.getTasks()) {
current.setActive(true);
taskManager.updateState(current);
}
}
workflowRepo.save(workflow);
}
/**
* Gets the state of a workflow.
*
* @param workflow
* @return
*/
public int getState(final Workflow workflow) {
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
private void updateState(final Workflow workflow) {
if (workflow.getTasksState() == TaskState.ENABLED) {
final TypedQuery<Long> query = entityManager.createNamedQuery(
"Task.countUnfinishedAndActiveTasksForWorkflow", Long.class);
query.setParameter("workflow", workflow);
final Optional<Task> activeTask = workflow.getTasks()
.stream()
.filter(task -> task.isActive())
.findAny();
final Long result = query.getSingleResult();
if (activeTask.isPresent()) {
return WorkflowConstants.STARTED;
if (result > 0) {
return;
} else {
return -1;
finish(workflow);
}
}
if (workflow.getTasksState() == TaskState.FINISHED) {
final TypedQuery<Long> query = entityManager.createNamedQuery(
"Task.countUnfinishedTasksForWorkflow", Long.class);
query.setParameter("workflow", workflow);
final Long result = query.getSingleResult();
if (result > 0) {
enable(workflow);
}
}
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void stop(final Workflow workflow) {
workflow.setState(WorkflowState.STOPPED);
workflowRepo.save(workflow);
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void finish(final Workflow workflow) {
if (workflow.getTasksState() != TaskState.ENABLED) {
throw new IllegalArgumentException(String.format(
"Workflow \"%s\" is not enabled.",
workflow.getName().getValue(defaultLocale)));
}
workflow.setTasksState(TaskState.FINISHED);
workflowRepo.save(workflow);
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void enable(final Workflow workflow) {
if (workflow.getTasksState() == TaskState.ENABLED) {
return;
}
switch (workflow.getTasksState()) {
case DISABLED:
LOGGER.debug("Workflow \"{}\" is disabled; enabling it.",
workflow.getName().getValue(defaultLocale));
workflow.setTasksState(TaskState.ENABLED);
workflowRepo.save(workflow);
break;
case FINISHED:
LOGGER.debug("Workflow \"{}\" is finished; reenabling it.");
workflow.setTasksState(TaskState.ENABLED);
workflowRepo.save(workflow);
break;
default:
LOGGER.debug("Workflow \"{}\" has tasksState \"{}\", "
+ "#enable(Workflow) does nothing.",
workflow.getName().getValue(defaultLocale),
workflow.getTasksState());
break;
}
}
@AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public void disable(final Workflow workflow) {
if (workflow.getTasksState() == TaskState.DISABLED) {
return;
}
workflow.setTasksState(TaskState.DISABLED);
workflowRepo.save(workflow);
workflow.getTasks().forEach(task -> taskManager.disable(task));
workflow.setState(WorkflowState.INIT);
}
}

View File

@ -19,8 +19,13 @@
package org.libreccm.workflow;
import org.libreccm.core.AbstractEntityRepository;
import org.libreccm.core.CcmObject;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
/**
*
@ -39,4 +44,21 @@ public class WorkflowRepository extends AbstractEntityRepository<Long, Workflow>
return workflow.getWorkflowId() == 0;
}
public Optional<Workflow> findWorkflowForObject(final CcmObject object) {
if (object == null) {
throw new IllegalArgumentException(
"Can't find a workflow for object null.");
}
final TypedQuery<Workflow> query = getEntityManager().createNamedQuery(
"Workflow.findForObject", Workflow.class);
query.setParameter("object", object);
try {
return Optional.of(query.getSingleResult());
} catch(NoResultException ex) {
return Optional.empty();
}
}
}

View File

@ -20,6 +20,8 @@ package org.libreccm.workflow;
import static org.libreccm.core.CoreConstants.*;
import org.libreccm.core.CcmObject;
import java.io.Serializable;
import javax.persistence.Entity;
@ -35,6 +37,12 @@ public class WorkflowTemplate extends Workflow implements Serializable {
private static final long serialVersionUID = 5770519379144947171L;
@Override
protected void setObject(final CcmObject object) {
throw new UnsupportedOperationException(
"A WorkflowTemplate has no object.");
}
@Override
public int hashCode() {
return super.hashCode();

View File

@ -23,7 +23,7 @@ import org.junit.runners.Parameterized;
import org.libreccm.core.CcmObject;
import org.libreccm.tests.categories.UnitTest;
import org.libreccm.testutils.EqualsVerifier;
import org.libreccm.workflow.UserTask;
import org.libreccm.workflow.AssignableTask;
import java.util.Arrays;
import java.util.Collection;
@ -88,10 +88,10 @@ public class EqualsAndHashCodeTest extends EqualsVerifier {
ccmObject1.setObjectId(-200);
ccmObject1.setDisplayName("Object 2");
final UserTask task1 = new UserTask();
final AssignableTask task1 = new AssignableTask();
task1.setTaskId(-10);
final UserTask task2 = new UserTask();
final AssignableTask task2 = new AssignableTask();
task2.setTaskId(-20);
verifier
@ -100,7 +100,7 @@ public class EqualsAndHashCodeTest extends EqualsVerifier {
.withPrefabValues(Role.class, role1, role2)
.withPrefabValues(Party.class, party1, party2)
.withPrefabValues(CcmObject.class, ccmObject1, ccmObject2)
.withPrefabValues(UserTask.class, task1, task2);
.withPrefabValues(AssignableTask.class, task1, task2);
}
}

View File

@ -21,6 +21,7 @@ package org.libreccm.workflow;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.libreccm.core.CcmObject;
import org.libreccm.security.Group;
import org.libreccm.security.Role;
import org.libreccm.security.User;
@ -43,8 +44,9 @@ public class EqualsAndHashCodeTest extends EqualsVerifier {
public static Collection<Class<?>> data() {
return Arrays.asList(new Class<?>[]{
Task.class,
TaskComment.class,
TaskAssignment.class,
UserTask.class,
AssignableTask.class,
Workflow.class,
WorkflowTemplate.class
});
@ -60,10 +62,10 @@ public class EqualsAndHashCodeTest extends EqualsVerifier {
super.addPrefabValues(verifier);
final UserTask userTask1 = new UserTask();
final AssignableTask userTask1 = new AssignableTask();
userTask1.setTaskId(-10);
final UserTask userTask2 = new UserTask();
final AssignableTask userTask2 = new AssignableTask();
userTask2.setTaskId(-20);
final Role role1 = new Role();
@ -90,19 +92,33 @@ public class EqualsAndHashCodeTest extends EqualsVerifier {
final User user2 = new TestUser();
user2.setName("user2");
final Workflow workflow1 = new Workflow();
workflow1.getName().addValue(Locale.ENGLISH, "Workflow 1");
final Workflow workflow2 = new Workflow();
workflow2.getName().addValue(Locale.ENGLISH, "Workflow 2");
final WorkflowTemplate template1 = new WorkflowTemplate();
template1.getName().addValue(Locale.ENGLISH, "Template 1");
final WorkflowTemplate template2 = new WorkflowTemplate();
template1.getName().addValue(Locale.ENGLISH, "Template 2");
final CcmObject object1 = new CcmObject();
object1.setDisplayName("Object 1");
final CcmObject object2 = new CcmObject();
object2.setDisplayName("Object 2");
verifier
.withPrefabValues(UserTask.class, userTask1, userTask2)
.withPrefabValues(AssignableTask.class, userTask1, userTask2)
.withPrefabValues(Role.class, role1, role2)
.withPrefabValues(Task.class, task1, task2)
.withPrefabValues(Group.class, group1, group2)
.withPrefabValues(User.class, user1, user2)
.withPrefabValues(WorkflowTemplate.class, template1, template2);
.withPrefabValues(Workflow.class, workflow1, workflow2)
.withPrefabValues(WorkflowTemplate.class, template1, template2)
.withPrefabValues(CcmObject.class, object1, object2);
}
/**

View File

@ -40,7 +40,7 @@ public class ToStringTest extends ToStringVerifier {
return Arrays.asList(new Class<?>[]{
Task.class,
TaskAssignment.class,
UserTask.class,
AssignableTask.class,
Workflow.class
});
}

View File

@ -1,9 +1,3 @@
DROP SCHEMA IF EXISTS ccm_core;
DROP SEQUENCE IF EXISTS hibernate_sequence;
CREATE SCHEMA ccm_core;
create table CCM_CORE.APPLICATIONS (
APPLICATION_TYPE varchar(1024) not null,
@ -500,8 +494,8 @@ CREATE SCHEMA ccm_core;
NAME varchar(512) not null,
SETTING_VALUE_DOUBLE double,
SETTING_VALUE_BOOLEAN boolean,
SETTING_VALUE_BIG_DECIMAL decimal(19,2),
SETTING_VALUE_LONG bigint,
SETTING_VALUE_BIG_DECIMAL decimal(19,2),
SETTING_VALUE_STRING varchar(1024),
primary key (SETTING_ID)
);
@ -556,6 +550,17 @@ CREATE SCHEMA ccm_core;
primary key (PARTY_ID)
);
create table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS (
DUE_DATE timestamp,
DURATION_MINUTES bigint,
LOCKED boolean,
START_DATE timestamp,
TASK_ID bigint not null,
LOCKING_USER_ID bigint,
NOTIFICATION_SENDER bigint,
primary key (TASK_ID)
);
create table CCM_CORE.WORKFLOW_DESCRIPTIONS (
WORKFLOW_ID bigint not null,
LOCALIZED_VALUE longvarchar,
@ -571,8 +576,11 @@ CREATE SCHEMA ccm_core;
);
create table CCM_CORE.WORKFLOW_TASK_COMMENTS (
TASK_ID bigint not null,
COMMENT clob
COMMENT_ID bigint not null,
COMMENT longvarchar,
AUTHOR_ID bigint,
TASK_ID bigint,
primary key (COMMENT_ID)
);
create table CCM_CORE.WORKFLOW_TASK_DEPENDENCIES (
@ -607,19 +615,11 @@ CREATE SCHEMA ccm_core;
primary key (WORKFLOW_ID)
);
create table CCM_CORE.WORKFLOW_USER_TASKS (
DUE_DATE timestamp,
DURATION_MINUTES bigint,
LOCKED boolean,
START_DATE timestamp,
TASK_ID bigint not null,
LOCKING_USER_ID bigint,
NOTIFICATION_SENDER bigint,
primary key (TASK_ID)
);
create table CCM_CORE.WORKFLOWS (
WORKFLOW_ID bigint not null,
ACTIVE boolean,
WORKFLOW_STATE varchar(255),
OBJECT_ID bigint,
TEMPLATE_ID bigint,
primary key (WORKFLOW_ID)
);
@ -1054,9 +1054,9 @@ create sequence hibernate_sequence start with 1 increment by 1;
references CCM_CORE.CCM_ROLES;
alter table CCM_CORE.TASK_ASSIGNMENTS
add constraint FKc1vovbjg9mp5yegx2fdoutx7u
add constraint FKk6gl2yvqr7gnqq25s1bm2gy4i
foreign key (TASK_ID)
references CCM_CORE.WORKFLOW_USER_TASKS;
references CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS;
alter table CCM_CORE.THREADS
add constraint FKsx08mpwvwnw97uwdgjs76q39g
@ -1078,6 +1078,21 @@ create sequence hibernate_sequence start with 1 increment by 1;
foreign key (PARTY_ID)
references CCM_CORE.PARTIES;
alter table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS
add constraint FK1pnsq9ur3ylq0ghuj23p4cogs
foreign key (LOCKING_USER_ID)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS
add constraint FK9ngp088m8xa82swy7yg3qx6vh
foreign key (NOTIFICATION_SENDER)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS
add constraint FKt9ha3no3bj8a50pnw8cnqh2cq
foreign key (TASK_ID)
references CCM_CORE.WORKFLOW_TASKS;
alter table CCM_CORE.WORKFLOW_DESCRIPTIONS
add constraint FKgx7upkqky82dpxvbs95imfl9l
foreign key (WORKFLOW_ID)
@ -1088,6 +1103,11 @@ create sequence hibernate_sequence start with 1 increment by 1;
foreign key (WORKFLOW_ID)
references CCM_CORE.WORKFLOWS;
alter table CCM_CORE.WORKFLOW_TASK_COMMENTS
add constraint FKd2ymdg8nay9pmh2nn2whba0j8
foreign key (AUTHOR_ID)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_TASK_COMMENTS
add constraint FKkfqrf9jdvm7livu5if06w0r5t
foreign key (TASK_ID)
@ -1123,20 +1143,10 @@ create sequence hibernate_sequence start with 1 increment by 1;
foreign key (WORKFLOW_ID)
references CCM_CORE.WORKFLOWS;
alter table CCM_CORE.WORKFLOW_USER_TASKS
add constraint FKf09depwj5rgso2dair07vnu33
foreign key (LOCKING_USER_ID)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_USER_TASKS
add constraint FK6evo9y34awhdfcyl8gv78qb7f
foreign key (NOTIFICATION_SENDER)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_USER_TASKS
add constraint FKefpdf9ojplu7loo31hfm0wl2h
foreign key (TASK_ID)
references CCM_CORE.WORKFLOW_TASKS;
alter table CCM_CORE.WORKFLOWS
add constraint FKrm2yfrs6veoxoy304upq2wc64
foreign key (OBJECT_ID)
references CCM_CORE.CCM_OBJECTS;
alter table CCM_CORE.WORKFLOWS
add constraint FKeixdxau4jebw682gd49tdbsjy

View File

@ -1,8 +1,3 @@
DROP SCHEMA IF EXISTS ccm_core CASCADE;
DROP SEQUENCE IF EXISTS hibernate_sequence;
CREATE SCHEMA ccm_core;
create table CCM_CORE.APPLICATIONS (
APPLICATION_TYPE varchar(1024) not null,
@ -499,8 +494,8 @@ CREATE SCHEMA ccm_core;
NAME varchar(512) not null,
SETTING_VALUE_DOUBLE float8,
SETTING_VALUE_BOOLEAN boolean,
SETTING_VALUE_BIG_DECIMAL numeric(19, 2),
SETTING_VALUE_LONG int8,
SETTING_VALUE_BIG_DECIMAL numeric(19, 2),
SETTING_VALUE_STRING varchar(1024),
primary key (SETTING_ID)
);
@ -555,6 +550,17 @@ CREATE SCHEMA ccm_core;
primary key (PARTY_ID)
);
create table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS (
DUE_DATE timestamp,
DURATION_MINUTES int8,
LOCKED boolean,
START_DATE timestamp,
TASK_ID int8 not null,
LOCKING_USER_ID int8,
NOTIFICATION_SENDER int8,
primary key (TASK_ID)
);
create table CCM_CORE.WORKFLOW_DESCRIPTIONS (
WORKFLOW_ID int8 not null,
LOCALIZED_VALUE text,
@ -570,8 +576,11 @@ CREATE SCHEMA ccm_core;
);
create table CCM_CORE.WORKFLOW_TASK_COMMENTS (
TASK_ID int8 not null,
COMMENT text
COMMENT_ID int8 not null,
COMMENT text,
AUTHOR_ID int8,
TASK_ID int8,
primary key (COMMENT_ID)
);
create table CCM_CORE.WORKFLOW_TASK_DEPENDENCIES (
@ -606,19 +615,11 @@ CREATE SCHEMA ccm_core;
primary key (WORKFLOW_ID)
);
create table CCM_CORE.WORKFLOW_USER_TASKS (
DUE_DATE timestamp,
DURATION_MINUTES int8,
LOCKED boolean,
START_DATE timestamp,
TASK_ID int8 not null,
LOCKING_USER_ID int8,
NOTIFICATION_SENDER int8,
primary key (TASK_ID)
);
create table CCM_CORE.WORKFLOWS (
WORKFLOW_ID int8 not null,
ACTIVE boolean,
WORKFLOW_STATE varchar(255),
OBJECT_ID int8,
TEMPLATE_ID int8,
primary key (WORKFLOW_ID)
);
@ -1053,9 +1054,9 @@ create sequence hibernate_sequence start 1 increment 1;
references CCM_CORE.CCM_ROLES;
alter table CCM_CORE.TASK_ASSIGNMENTS
add constraint FKc1vovbjg9mp5yegx2fdoutx7u
add constraint FKk6gl2yvqr7gnqq25s1bm2gy4i
foreign key (TASK_ID)
references CCM_CORE.WORKFLOW_USER_TASKS;
references CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS;
alter table CCM_CORE.THREADS
add constraint FKsx08mpwvwnw97uwdgjs76q39g
@ -1077,6 +1078,21 @@ create sequence hibernate_sequence start 1 increment 1;
foreign key (PARTY_ID)
references CCM_CORE.PARTIES;
alter table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS
add constraint FK1pnsq9ur3ylq0ghuj23p4cogs
foreign key (LOCKING_USER_ID)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS
add constraint FK9ngp088m8xa82swy7yg3qx6vh
foreign key (NOTIFICATION_SENDER)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_ASSIGNABLE_TASKS
add constraint FKt9ha3no3bj8a50pnw8cnqh2cq
foreign key (TASK_ID)
references CCM_CORE.WORKFLOW_TASKS;
alter table CCM_CORE.WORKFLOW_DESCRIPTIONS
add constraint FKgx7upkqky82dpxvbs95imfl9l
foreign key (WORKFLOW_ID)
@ -1087,6 +1103,11 @@ create sequence hibernate_sequence start 1 increment 1;
foreign key (WORKFLOW_ID)
references CCM_CORE.WORKFLOWS;
alter table CCM_CORE.WORKFLOW_TASK_COMMENTS
add constraint FKd2ymdg8nay9pmh2nn2whba0j8
foreign key (AUTHOR_ID)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_TASK_COMMENTS
add constraint FKkfqrf9jdvm7livu5if06w0r5t
foreign key (TASK_ID)
@ -1122,20 +1143,10 @@ create sequence hibernate_sequence start 1 increment 1;
foreign key (WORKFLOW_ID)
references CCM_CORE.WORKFLOWS;
alter table CCM_CORE.WORKFLOW_USER_TASKS
add constraint FKf09depwj5rgso2dair07vnu33
foreign key (LOCKING_USER_ID)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_USER_TASKS
add constraint FK6evo9y34awhdfcyl8gv78qb7f
foreign key (NOTIFICATION_SENDER)
references CCM_CORE.USERS;
alter table CCM_CORE.WORKFLOW_USER_TASKS
add constraint FKefpdf9ojplu7loo31hfm0wl2h
foreign key (TASK_ID)
references CCM_CORE.WORKFLOW_TASKS;
alter table CCM_CORE.WORKFLOWS
add constraint FKrm2yfrs6veoxoy304upq2wc64
foreign key (OBJECT_ID)
references CCM_CORE.CCM_OBJECTS;
alter table CCM_CORE.WORKFLOWS
add constraint FKeixdxau4jebw682gd49tdbsjy