org.opencms.workflow.CmsExtendedWorkflowManager Maven / Gradle / Ivy
Show all versions of opencms-test Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
*
* 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.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* 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 org.opencms.workflow;
import org.opencms.ade.publish.CmsPublishService;
import org.opencms.ade.publish.I_CmsVirtualProject;
import org.opencms.ade.publish.shared.CmsPublishOptions;
import org.opencms.ade.publish.shared.CmsPublishResource;
import org.opencms.ade.publish.shared.CmsWorkflow;
import org.opencms.ade.publish.shared.CmsWorkflowAction;
import org.opencms.ade.publish.shared.CmsWorkflowResponse;
import org.opencms.db.CmsResourceState;
import org.opencms.file.CmsGroup;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsUser;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.lock.CmsLock;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.publish.CmsPublishEventAdapter;
import org.opencms.publish.CmsPublishJobEnqueued;
import org.opencms.publish.CmsPublishJobRunning;
import org.opencms.security.CmsPermissionSet;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.logging.Log;
/**
* The default workflow manager implementation, which supports 2 basic actions, Release and Publish.
*/
public class CmsExtendedWorkflowManager extends CmsDefaultWorkflowManager {
/** The release workflow action. */
public static final String ACTION_RELEASE = "release";
/** The parameter which points to the XML content used for notifications. */
public static final String PARAM_NOTIFICATION_CONTENT = "notificationContent";
/** The key for the configurable workflow project manager group. */
public static final String PARAM_WORKFLOW_PROJECT_MANAGER_GROUP = "workflowProjectManagerGroup";
/** The key for the configurable workflow project user group. */
public static final String PARAM_WORKFLOW_PROJECT_USER_GROUP = "workflowProjectUserGroup";
/** The key for the 'release' workflow. */
public static final String WORKFLOW_RELEASE = "WORKFLOW_RELEASE";
/** The logger instance for this class. */
private static final Log LOG = CmsLog.getLog(CmsExtendedWorkflowManager.class);
/**
* @see org.opencms.workflow.CmsDefaultWorkflowManager#createFormatter(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions)
*/
@Override
public I_CmsPublishResourceFormatter createFormatter(
CmsObject cms,
CmsWorkflow workflow,
CmsPublishOptions options) {
String workflowKey = workflow.getId();
boolean release = WORKFLOW_RELEASE.equals(workflowKey);
CmsExtendedPublishResourceFormatter formatter = new CmsExtendedPublishResourceFormatter(cms);
formatter.setRelease(release);
return formatter;
}
/**
* @see org.opencms.workflow.CmsDefaultWorkflowManager#executeAction(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflowAction, org.opencms.ade.publish.shared.CmsPublishOptions, java.util.List)
*/
@Override
public CmsWorkflowResponse executeAction(
CmsObject userCms,
CmsWorkflowAction action,
CmsPublishOptions options,
List resources) throws CmsException {
if (LOG.isInfoEnabled()) {
LOG.info(
"workflow action: "
+ userCms.getRequestContext().getCurrentUser().getName()
+ " "
+ action.getAction());
List resourceNames = new ArrayList();
for (CmsResource resource : resources) {
resourceNames.add(resource.getRootPath());
}
LOG.info("Resources: " + CmsStringUtil.listAsString(resourceNames, ","));
}
try {
String actionKey = action.getAction();
if (ACTION_RELEASE.equals(actionKey)) {
return actionRelease(userCms, resources);
} else {
return super.executeAction(userCms, action, options, resources);
}
} catch (CmsException e) {
LOG.error("workflow action failed");
LOG.error(e.getLocalizedMessage(), e);
throw e;
}
}
/**
* @see org.opencms.workflow.CmsDefaultWorkflowManager#getRealOrVirtualProject(org.opencms.util.CmsUUID)
*/
@Override
public I_CmsVirtualProject getRealOrVirtualProject(CmsUUID projectId) {
I_CmsVirtualProject result = m_virtualProjects.get(projectId);
if (result == null) {
result = new CmsExtendedRealProjectWrapper(projectId);
}
return result;
}
/**
* Gets the name of the group which should be used as the 'manager' group for newly created workflow projects.
*
* @return a group name
*/
public String getWorkflowProjectManagerGroup() {
return getParameter(PARAM_WORKFLOW_PROJECT_MANAGER_GROUP, OpenCms.getDefaultUsers().getGroupAdministrators());
}
/**
* Gets the name of the group which should be used as the 'user' group for newly created workflow projects.
*
* @return a group name
*/
public String getWorkflowProjectUserGroup() {
return getParameter(PARAM_WORKFLOW_PROJECT_USER_GROUP, OpenCms.getDefaultUsers().getGroupAdministrators());
}
/**
* @see org.opencms.workflow.CmsDefaultWorkflowManager#getWorkflowResources(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions, boolean)
*/
@Override
public CmsWorkflowResources getWorkflowResources(
CmsObject cms,
CmsWorkflow workflow,
CmsPublishOptions options,
boolean canOverrideWorkflow) {
String workflowKey = workflow.getId();
String overrideId = null;
if (WORKFLOW_RELEASE.equals(workflowKey)) {
List result = super.getWorkflowResources(
cms,
workflow,
options,
canOverrideWorkflow).getWorkflowResources();
if (canOverrideWorkflow) {
boolean override = false;
for (CmsResource permCheckResource : result) {
try {
boolean canPublish = cms.hasPermissions(
permCheckResource,
CmsPermissionSet.ACCESS_DIRECT_PUBLISH);
if (canPublish) {
override = true;
}
} catch (Exception e) {
LOG.error(
"Can't check permissions for "
+ permCheckResource.getRootPath()
+ ":"
+ e.getLocalizedMessage(),
e);
}
if (override) {
List resources = getWorkflowResources(
cms,
getWorkflows(cms).get(CmsDefaultWorkflowManager.WORKFLOW_PUBLISH),
options,
false).getWorkflowResources();
result = resources;
overrideId = WORKFLOW_PUBLISH;
}
}
}
CmsWorkflowResources realResult = new CmsWorkflowResources(result, getWorkflows(cms).get(overrideId));
return realResult;
} else {
CmsWorkflowResources realResult = super.getWorkflowResources(cms, workflow, options, canOverrideWorkflow);
return realResult;
}
}
/**
* @see org.opencms.workflow.I_CmsWorkflowManager#getWorkflows(org.opencms.file.CmsObject)
*/
@Override
public Map getWorkflows(CmsObject cms) {
Map parentWorkflows = super.getWorkflows(cms);
Map result = new LinkedHashMap();
String releaseLabel = getLabel(cms, Messages.GUI_WORKFLOW_ACTION_RELEASE_0);
CmsWorkflowAction release = new CmsWorkflowAction(ACTION_RELEASE, releaseLabel, true);
List actions = new ArrayList();
actions.add(release);
CmsWorkflow releaseWorkflow = new CmsWorkflow(WORKFLOW_RELEASE, releaseLabel, actions);
try {
boolean isProjectManager = isProjectManager(cms);
// make release action always available, but make it the default if the user
// isn't a project manager.
if (isProjectManager) {
result.putAll(parentWorkflows);
result.put(WORKFLOW_RELEASE, releaseWorkflow);
} else {
result.put(WORKFLOW_RELEASE, releaseWorkflow);
result.putAll(parentWorkflows);
}
} catch (CmsException e) {
result = parentWorkflows;
}
return result;
}
/**
* @see org.opencms.workflow.A_CmsWorkflowManager#initialize(org.opencms.file.CmsObject)
*/
@Override
public void initialize(CmsObject adminCms) {
super.initialize(adminCms);
OpenCms.getPublishManager().addPublishListener(new CmsPublishEventAdapter() {
@Override
public void onFinish(CmsPublishJobRunning publishJob) {
CmsExtendedWorkflowManager.this.onFinishPublishJob(publishJob);
}
/**
* @see org.opencms.publish.CmsPublishEventAdapter#onStart(org.opencms.publish.CmsPublishJobEnqueued)
*/
@Override
public void onStart(CmsPublishJobEnqueued publishJob) {
CmsExtendedWorkflowManager.this.onStartPublishJob(publishJob);
}
});
}
/**
* Implementation of the 'release' workflow action.
*
* @param userCms the current user's CMS context
* @param resources the resources which should be released
*
* @return the workflow response for this action
*
* @throws CmsException if something goes wrong
*/
protected CmsWorkflowResponse actionRelease(CmsObject userCms, List resources) throws CmsException {
checkNewParentsInList(userCms, resources);
String projectName = generateProjectName(userCms);
String projectDescription = generateProjectDescription(userCms);
CmsObject offlineAdminCms = OpenCms.initCmsObject(m_adminCms);
offlineAdminCms.getRequestContext().setCurrentProject(userCms.getRequestContext().getCurrentProject());
String managerGroup = getWorkflowProjectManagerGroup();
String userGroup = getWorkflowProjectUserGroup();
CmsProject workflowProject = m_adminCms.createProject(
projectName,
projectDescription,
userGroup,
managerGroup,
CmsProject.PROJECT_TYPE_WORKFLOW);
CmsObject newProjectCms = OpenCms.initCmsObject(offlineAdminCms);
newProjectCms.getRequestContext().setCurrentProject(workflowProject);
newProjectCms.getRequestContext().setSiteRoot("");
newProjectCms.copyResourceToProject("/");
CmsUser admin = offlineAdminCms.getRequestContext().getCurrentUser();
clearLocks(userCms.getRequestContext().getCurrentProject(), resources);
for (CmsResource resource : resources) {
CmsLock lock = offlineAdminCms.getLock(resource);
if (lock.isUnlocked()) {
offlineAdminCms.lockResource(resource);
} else if (!lock.isOwnedBy(admin)) {
offlineAdminCms.changeLock(resource);
}
offlineAdminCms.writeProjectLastModified(resource, workflowProject);
offlineAdminCms.unlockResource(resource);
}
for (CmsUser user : getNotificationMailRecipients()) {
sendNotification(userCms, user, workflowProject, resources);
}
return new CmsWorkflowResponse(
true,
"",
new ArrayList(),
new ArrayList(),
workflowProject.getUuid());
}
/**
* Checks that the parent folders of new resources which are released are either not new or are also released.
*
* @param userCms the user CMS context
* @param resources the resources to check
*
* @throws CmsException if the check fails
*/
protected void checkNewParentsInList(CmsObject userCms, List resources) throws CmsException {
Map resourcesByPath = new HashMap();
CmsObject rootCms = OpenCms.initCmsObject(m_adminCms);
rootCms.getRequestContext().setCurrentProject(userCms.getRequestContext().getCurrentProject());
rootCms.getRequestContext().setSiteRoot("");
for (CmsResource resource : resources) {
resourcesByPath.put(resource.getRootPath(), resource);
}
for (CmsResource resource : resources) {
if (resource.getState().isNew()) {
String parentPath = CmsResource.getParentFolder(resource.getRootPath());
CmsResource parent = resourcesByPath.get(parentPath);
if (parent == null) {
parent = rootCms.readResource(parentPath);
if (parent.getState().isNew()) {
throw new CmsNewParentNotInWorkflowException(
Messages.get().container(
Messages.ERR_NEW_PARENT_NOT_IN_WORKFLOW_1,
resource.getRootPath()));
}
}
}
}
}
/**
* Cleans up empty workflow projects.
*
* @param projects the workflow projects to clean up
*
* @throws CmsException if something goes wrong
*/
protected void cleanupEmptyWorkflowProjects(List projects) throws CmsException {
if (projects == null) {
projects = OpenCms.getOrgUnitManager().getAllManageableProjects(m_adminCms, "", true);
}
for (CmsProject project : projects) {
if (project.isWorkflowProject()) {
if (isProjectEmpty(project)) {
m_adminCms.deleteProject(project.getUuid());
}
}
}
}
/**
* Removes a project if there are no longer any resources which have been last modified in that project.
*
* @param project the project
* @throws CmsException if something goes wrong
*/
protected void cleanupProjectIfEmpty(CmsProject project) throws CmsException {
if ((project.getType().getMode() == CmsProject.PROJECT_TYPE_WORKFLOW.getMode()) && isProjectEmpty(project)) {
LOG.info("Removing project " + project.getName() + " because it is an empty workflow project.");
m_adminCms.deleteProject(project.getUuid());
}
}
/**
* Ensures that the resources to be released are unlocked.
*
* @param project the project in which to operate
* @param resources the resources for which the locks should be removed
*
* @throws CmsException if something goes wrong
*/
protected void clearLocks(CmsProject project, List resources) throws CmsException {
CmsObject rootCms = OpenCms.initCmsObject(m_adminCms);
rootCms.getRequestContext().setCurrentProject(project);
rootCms.getRequestContext().setSiteRoot("");
for (CmsResource resource : resources) {
CmsLock lock = rootCms.getLock(resource);
if (lock.isUnlocked()) {
continue;
}
String currentPath = resource.getRootPath();
while (lock.isInherited()) {
currentPath = CmsResource.getParentFolder(currentPath);
lock = rootCms.getLock(currentPath);
}
rootCms.changeLock(currentPath);
rootCms.unlockResource(currentPath);
}
}
/**
* Helper method to check whether a project exists.
*
* @param projectName the project name
*
* @return true if the project exists
*/
protected boolean existsProject(String projectName) {
try {
m_adminCms.readProject(projectName);
return true;
} catch (CmsException e) {
return false;
}
}
/**
* Generates the description for a new workflow project based on the user for whom it is created.
*
* @param userCms the user's current CMS context
*
* @return the workflow project description
*/
protected String generateProjectDescription(CmsObject userCms) {
CmsUser user = userCms.getRequestContext().getCurrentUser();
OpenCms.getLocaleManager();
Locale locale = CmsLocaleManager.getDefaultLocale();
long time = System.currentTimeMillis();
Date date = new Date(time);
DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale);
String dateString = format.format(date);
String result = Messages.get().getBundle(locale).key(
Messages.GUI_WORKFLOW_PROJECT_DESCRIPTION_2,
user.getName(),
dateString);
return result;
}
/**
* Generates the name for a new workflow project based on the user for whom it is created.
*
* @param userCms the user's current CMS context
*
* @return the workflow project name
*/
protected String generateProjectName(CmsObject userCms) {
String projectName = generateProjectName(userCms, true);
if (existsProject(projectName)) {
projectName = generateProjectName(userCms, false);
}
return projectName;
}
/**
* Generates the name for a new workflow project based on the user for whom it is created.
*
* @param userCms the user's current CMS context
* @param shortTime if true, the short time format will be used, else the medium time format
*
* @return the workflow project name
*/
protected String generateProjectName(CmsObject userCms, boolean shortTime) {
CmsUser user = userCms.getRequestContext().getCurrentUser();
long time = System.currentTimeMillis();
Date date = new Date(time);
OpenCms.getLocaleManager();
Locale locale = CmsLocaleManager.getDefaultLocale();
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
DateFormat timeFormat = DateFormat.getTimeInstance(shortTime ? DateFormat.SHORT : DateFormat.MEDIUM, locale);
String dateStr = dateFormat.format(date) + " " + timeFormat.format(date);
String username = user.getName();
String result = Messages.get().getBundle(locale).key(Messages.GUI_WORKFLOW_PROJECT_NAME_2, username, dateStr);
result = result.replaceAll("/", "|");
return result;
}
/**
* Gets the list of recipients for the notifications.
*
* @return the list of users which should be notified when resources are released
*/
protected List getNotificationMailRecipients() {
String group = getWorkflowProjectManagerGroup();
CmsObject cms = m_adminCms;
try {
List users = cms.getUsersOfGroup(group);
return users;
} catch (CmsException e) {
LOG.error(e.getLocalizedMessage(), e);
return new ArrayList();
}
}
/**
* Gets the resource notification content path.
*
* @return the resource notification content path
*/
protected String getNotificationResource() {
String result = getParameter(
PARAM_NOTIFICATION_CONTENT,
"/system/workplace/admin/notification/workflow-notification");
return result;
}
/**
* Helper method for generating the workflow response which should be sent when publishing the resources would break relations.
*
* @param userCms the user's CMS context
* @param publishResources the resources whose links would be broken
*
* @return the workflow response
*/
protected CmsWorkflowResponse getPublishBrokenRelationsResponse(
CmsObject userCms,
List publishResources) {
List actions = new ArrayList();
String forcePublishLabel = Messages.get().getBundle(getLocale(userCms)).key(
Messages.GUI_WORKFLOW_ACTION_FORCE_PUBLISH_0);
CmsWorkflowAction forcePublish = new CmsWorkflowAction(ACTION_FORCE_PUBLISH, forcePublishLabel, true, true);
actions.add(forcePublish);
return new CmsWorkflowResponse(
false,
Messages.get().getBundle(getLocale(userCms)).key(Messages.GUI_BROKEN_LINKS_0),
publishResources,
actions,
null);
}
/**
* Gets the workflow response which should be sent when the resources have successfully been published.
*
* @return the successful workflow response
*/
protected CmsWorkflowResponse getSuccessResponse() {
return new CmsWorkflowResponse(
true,
"",
new ArrayList(),
new ArrayList(),
null);
}
/**
* Checks whether there are resources which have last been modified in a given project.
*
* @param project the project which should be checked
* @return true if there are no resources which have been last modified inside the project
*
* @throws CmsException if something goes wrong
*/
protected boolean isProjectEmpty(CmsProject project) throws CmsException {
List resources = m_adminCms.readProjectView(project.getUuid(), CmsResourceState.STATE_KEEP);
return resources.isEmpty();
}
/**
* Checks whether the user for a given CMS context can manage workflow projects.
*
* @param userCms the user CMS Context
* @return true if this user can manage workflow projects
*
* @throws CmsException if something goes wrong
*/
protected boolean isProjectManager(CmsObject userCms) throws CmsException {
CmsGroup managerGroup = m_adminCms.readGroup(getWorkflowProjectManagerGroup());
List groups = m_adminCms.getGroupsOfUser(
userCms.getRequestContext().getCurrentUser().getName(),
false);
return groups.contains(managerGroup);
}
/**
* Handles finished publish jobs by removing projects of resources in the publish job if they are empty workflow projects.
*
* @param publishJob the finished published job
*/
protected void onFinishPublishJob(CmsPublishJobRunning publishJob) {
try {
cleanupEmptyWorkflowProjects(null);
} catch (CmsException e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
/**
* This is called when a publish job is started.
*
* @param publishJob the publish job being started
*/
protected void onStartPublishJob(CmsPublishJobEnqueued publishJob) {
// do nothing
}
/**
* Sends the notification for released resources.
*
* @param userCms the user's CMS context
* @param recipient the OpenCms user to whom the notification should be sent
* @param workflowProject the workflow project which
* @param resources the resources which have been affected by a workflow action
*/
protected void sendNotification(
CmsObject userCms,
CmsUser recipient,
CmsProject workflowProject,
List resources) {
try {
String linkHref = OpenCms.getLinkManager().getServerLink(
userCms,
"/system/workplace/commons/publish.jsp?"
+ CmsPublishService.PARAM_PUBLISH_PROJECT_ID
+ "="
+ workflowProject.getUuid()
+ "&"
+ CmsPublishService.PARAM_CONFIRM
+ "=true");
CmsWorkflowNotification notification = new CmsWorkflowNotification(
m_adminCms,
userCms,
recipient,
getNotificationResource(),
workflowProject,
resources,
linkHref);
notification.send();
} catch (Throwable e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
}