org.opencms.workflow.CmsExtendedWorkflowManager.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.workflow.CmsExtendedWorkflowManager.java

Source

/*
 * 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<CmsResource> resources) throws CmsException {

        if (LOG.isInfoEnabled()) {
            LOG.info("workflow action: " + userCms.getRequestContext().getCurrentUser().getName() + " "
                    + action.getAction());
            List<String> resourceNames = new ArrayList<String>();
            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.<p>
     *
     * @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.<p>
     *
     * @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<CmsResource> 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<CmsResource> 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<String, CmsWorkflow> getWorkflows(CmsObject cms) {

        Map<String, CmsWorkflow> parentWorkflows = super.getWorkflows(cms);
        Map<String, CmsWorkflow> result = new LinkedHashMap<String, CmsWorkflow>();
        String releaseLabel = getLabel(cms, Messages.GUI_WORKFLOW_ACTION_RELEASE_0);
        CmsWorkflowAction release = new CmsWorkflowAction(ACTION_RELEASE, releaseLabel, true);
        List<CmsWorkflowAction> actions = new ArrayList<CmsWorkflowAction>();
        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.<p>
     *
     * @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<CmsResource> 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<CmsPublishResource>(),
                new ArrayList<CmsWorkflowAction>(), workflowProject.getUuid());
    }

    /**
     * Checks that the parent folders of new resources which are released are either not new or are also released.<p>
     *
     * @param userCms the user CMS context
     * @param resources the resources to check
     *
     * @throws CmsException if the check fails
     */
    protected void checkNewParentsInList(CmsObject userCms, List<CmsResource> resources) throws CmsException {

        Map<String, CmsResource> resourcesByPath = new HashMap<String, CmsResource>();
        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.<p>
     *
     * @param projects the workflow projects to clean up
     *
     * @throws CmsException if something goes wrong
     */
    protected void cleanupEmptyWorkflowProjects(List<CmsProject> 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.<p>
     *
     * @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.<p>
     *
     * @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<CmsResource> 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.<p>
     *
     * @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.<p>
     *
     * @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.<p>
     *
     * @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.<p>
     *
     * @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.<p>
     *
     * @return the list of users which should be notified when resources are released
     */
    protected List<CmsUser> getNotificationMailRecipients() {

        String group = getWorkflowProjectManagerGroup();
        CmsObject cms = m_adminCms;
        try {
            List<CmsUser> users = cms.getUsersOfGroup(group);
            return users;
        } catch (CmsException e) {
            LOG.error(e.getLocalizedMessage(), e);
            return new ArrayList<CmsUser>();
        }
    }

    /**
     * Gets the resource  notification content path.<p>
     *
     * @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.<p>
     *
     * @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<CmsPublishResource> publishResources) {

        List<CmsWorkflowAction> actions = new ArrayList<CmsWorkflowAction>();
        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.<p>
     *
     * @return the successful workflow response
     */
    protected CmsWorkflowResponse getSuccessResponse() {

        return new CmsWorkflowResponse(true, "", new ArrayList<CmsPublishResource>(),
                new ArrayList<CmsWorkflowAction>(), null);
    }

    /**
     * Checks whether there are resources which have last been modified in a given project.<p>
     *
     * @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<CmsResource> 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.<p>
     *
     * @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<CmsGroup> 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.<p>
     *
     * @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.<p>
     *
     * @param publishJob the publish job being started
     */
    protected void onStartPublishJob(CmsPublishJobEnqueued publishJob) {

        // do nothing
    }

    /**
     * Sends the notification for released resources.<p>
     *
     * @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<CmsResource> 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);
        }
    }
}