001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.workflow;
029
030import org.opencms.ade.publish.CmsPublishService;
031import org.opencms.ade.publish.I_CmsVirtualProject;
032import org.opencms.ade.publish.shared.CmsPublishOptions;
033import org.opencms.ade.publish.shared.CmsPublishResource;
034import org.opencms.ade.publish.shared.CmsWorkflow;
035import org.opencms.ade.publish.shared.CmsWorkflowAction;
036import org.opencms.ade.publish.shared.CmsWorkflowResponse;
037import org.opencms.db.CmsResourceState;
038import org.opencms.file.CmsGroup;
039import org.opencms.file.CmsObject;
040import org.opencms.file.CmsProject;
041import org.opencms.file.CmsResource;
042import org.opencms.file.CmsUser;
043import org.opencms.i18n.CmsLocaleManager;
044import org.opencms.lock.CmsLock;
045import org.opencms.main.CmsException;
046import org.opencms.main.CmsLog;
047import org.opencms.main.OpenCms;
048import org.opencms.publish.CmsPublishEventAdapter;
049import org.opencms.publish.CmsPublishJobEnqueued;
050import org.opencms.publish.CmsPublishJobRunning;
051import org.opencms.security.CmsPermissionSet;
052import org.opencms.util.CmsStringUtil;
053import org.opencms.util.CmsUUID;
054
055import java.text.DateFormat;
056import java.util.ArrayList;
057import java.util.Date;
058import java.util.HashMap;
059import java.util.LinkedHashMap;
060import java.util.List;
061import java.util.Locale;
062import java.util.Map;
063
064import org.apache.commons.logging.Log;
065
066/**
067 * The default workflow manager implementation, which supports 2 basic actions, Release and Publish.
068 */
069public class CmsExtendedWorkflowManager extends CmsDefaultWorkflowManager {
070
071    /** The release workflow action. */
072    public static final String ACTION_RELEASE = "release";
073
074    /** The parameter which points to the XML content used for notifications. */
075    public static final String PARAM_NOTIFICATION_CONTENT = "notificationContent";
076
077    /** The key for the configurable workflow project manager group. */
078    public static final String PARAM_WORKFLOW_PROJECT_MANAGER_GROUP = "workflowProjectManagerGroup";
079
080    /** The key for the configurable workflow project user group. */
081    public static final String PARAM_WORKFLOW_PROJECT_USER_GROUP = "workflowProjectUserGroup";
082
083    /** The key for the 'release' workflow. */
084    public static final String WORKFLOW_RELEASE = "WORKFLOW_RELEASE";
085
086    /** The logger instance for this class. */
087    private static final Log LOG = CmsLog.getLog(CmsExtendedWorkflowManager.class);
088
089    /**
090     * @see org.opencms.workflow.CmsDefaultWorkflowManager#createFormatter(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions)
091     */
092    @Override
093    public I_CmsPublishResourceFormatter createFormatter(
094        CmsObject cms,
095        CmsWorkflow workflow,
096        CmsPublishOptions options) {
097
098        String workflowKey = workflow.getId();
099        boolean release = WORKFLOW_RELEASE.equals(workflowKey);
100        CmsExtendedPublishResourceFormatter formatter = new CmsExtendedPublishResourceFormatter(cms);
101        formatter.setRelease(release);
102        return formatter;
103    }
104
105    /**
106     * @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)
107     */
108    @Override
109    public CmsWorkflowResponse executeAction(
110        CmsObject userCms,
111        CmsWorkflowAction action,
112        CmsPublishOptions options,
113        List<CmsResource> resources)
114    throws CmsException {
115
116        if (LOG.isInfoEnabled()) {
117            LOG.info(
118                "workflow action: "
119                    + userCms.getRequestContext().getCurrentUser().getName()
120                    + " "
121                    + action.getAction());
122            List<String> resourceNames = new ArrayList<String>();
123            for (CmsResource resource : resources) {
124                resourceNames.add(resource.getRootPath());
125            }
126            LOG.info("Resources: " + CmsStringUtil.listAsString(resourceNames, ","));
127        }
128        try {
129
130            String actionKey = action.getAction();
131            if (ACTION_RELEASE.equals(actionKey)) {
132                return actionRelease(userCms, resources);
133            } else {
134                return super.executeAction(userCms, action, options, resources);
135            }
136        } catch (CmsException e) {
137            LOG.error("workflow action failed");
138            LOG.error(e.getLocalizedMessage(), e);
139            throw e;
140        }
141    }
142
143    /**
144     * @see org.opencms.workflow.CmsDefaultWorkflowManager#getRealOrVirtualProject(org.opencms.util.CmsUUID)
145     */
146    @Override
147    public I_CmsVirtualProject getRealOrVirtualProject(CmsUUID projectId) {
148
149        I_CmsVirtualProject result = m_virtualProjects.get(projectId);
150        if (result == null) {
151            result = new CmsExtendedRealProjectWrapper(projectId);
152        }
153        return result;
154    }
155
156    /**
157     * Gets the name of the group which should be used as the 'manager' group for newly created workflow projects.<p>
158     *
159     * @return a group name
160     */
161    public String getWorkflowProjectManagerGroup() {
162
163        return getParameter(PARAM_WORKFLOW_PROJECT_MANAGER_GROUP, OpenCms.getDefaultUsers().getGroupAdministrators());
164    }
165
166    /**
167     * Gets the name of the group which should be used as the 'user' group for newly created workflow projects.<p>
168     *
169     * @return a group name
170     */
171    public String getWorkflowProjectUserGroup() {
172
173        return getParameter(PARAM_WORKFLOW_PROJECT_USER_GROUP, OpenCms.getDefaultUsers().getGroupAdministrators());
174    }
175
176    /**
177     * @see org.opencms.workflow.CmsDefaultWorkflowManager#getWorkflowResources(org.opencms.file.CmsObject, org.opencms.ade.publish.shared.CmsWorkflow, org.opencms.ade.publish.shared.CmsPublishOptions, boolean, boolean)
178     */
179    @Override
180    public CmsWorkflowResources getWorkflowResources(
181        CmsObject cms,
182        CmsWorkflow workflow,
183        CmsPublishOptions options,
184        boolean canOverrideWorkflow,
185        boolean ignoreLimit) {
186
187        String workflowKey = workflow.getId();
188        String overrideId = null;
189        Integer tooManyCount = null;
190        if (WORKFLOW_RELEASE.equals(workflowKey)) {
191            boolean tooMany = false;
192            CmsWorkflowResources workflowResourcesBean = super.getWorkflowResources(
193                cms,
194                workflow,
195                options,
196                canOverrideWorkflow,
197                ignoreLimit);
198            List<CmsResource> result = workflowResourcesBean.getWorkflowResources();
199            tooMany = workflowResourcesBean.isTooMany();
200            if (tooMany) {
201                tooManyCount = workflowResourcesBean.getTooManyCount();
202            }
203
204            if (canOverrideWorkflow && !workflowResourcesBean.isTooMany()) {
205                boolean override = false;
206                for (CmsResource permCheckResource : result) {
207                    try {
208                        boolean canPublish = cms.hasPermissions(
209                            permCheckResource,
210                            CmsPermissionSet.ACCESS_DIRECT_PUBLISH);
211                        if (canPublish) {
212                            override = true;
213                        }
214                    } catch (Exception e) {
215                        LOG.error(
216                            "Can't check permissions for "
217                                + permCheckResource.getRootPath()
218                                + ":"
219                                + e.getLocalizedMessage(),
220                            e);
221                    }
222                    if (override) {
223                        CmsWorkflowResources overrideWorkflowResources = getWorkflowResources(
224                            cms,
225                            getWorkflows(cms).get(CmsDefaultWorkflowManager.WORKFLOW_PUBLISH),
226                            options,
227                            false,
228                            false);
229                        tooMany = overrideWorkflowResources.isTooMany();
230                        tooManyCount = overrideWorkflowResources.getTooManyCount();
231                        result = overrideWorkflowResources.getWorkflowResources();
232                        overrideId = WORKFLOW_PUBLISH;
233                    }
234                }
235            }
236            CmsWorkflowResources realResult = new CmsWorkflowResources(
237                result,
238                getWorkflows(cms).get(overrideId),
239                tooManyCount);
240            return realResult;
241        } else {
242            CmsWorkflowResources realResult = super.getWorkflowResources(
243                cms,
244                workflow,
245                options,
246                canOverrideWorkflow,
247                ignoreLimit);
248            return realResult;
249        }
250    }
251
252    /**
253     * @see org.opencms.workflow.I_CmsWorkflowManager#getWorkflows(org.opencms.file.CmsObject)
254     */
255    @Override
256    public Map<String, CmsWorkflow> getWorkflows(CmsObject cms) {
257
258        Map<String, CmsWorkflow> parentWorkflows = super.getWorkflows(cms);
259        Map<String, CmsWorkflow> result = new LinkedHashMap<String, CmsWorkflow>();
260        String releaseLabel = getLabel(cms, Messages.GUI_WORKFLOW_ACTION_RELEASE_0);
261        CmsWorkflowAction release = new CmsWorkflowAction(ACTION_RELEASE, releaseLabel, true);
262        List<CmsWorkflowAction> actions = new ArrayList<CmsWorkflowAction>();
263        actions.add(release);
264        CmsWorkflow releaseWorkflow = new CmsWorkflow(WORKFLOW_RELEASE, releaseLabel, actions);
265        try {
266            boolean isProjectManager = isProjectManager(cms);
267            // make release action always available, but make it the default if the user
268            // isn't a project manager.
269            if (isProjectManager) {
270                result.putAll(parentWorkflows);
271                result.put(WORKFLOW_RELEASE, releaseWorkflow);
272            } else {
273                result.put(WORKFLOW_RELEASE, releaseWorkflow);
274                result.putAll(parentWorkflows);
275            }
276        } catch (CmsException e) {
277            result = parentWorkflows;
278        }
279        return result;
280    }
281
282    /**
283     * @see org.opencms.workflow.A_CmsWorkflowManager#initialize(org.opencms.file.CmsObject)
284     */
285    @Override
286    public void initialize(CmsObject adminCms) {
287
288        super.initialize(adminCms);
289        OpenCms.getPublishManager().addPublishListener(new CmsPublishEventAdapter() {
290
291            @Override
292            public void onFinish(CmsPublishJobRunning publishJob) {
293
294                CmsExtendedWorkflowManager.this.onFinishPublishJob(publishJob);
295            }
296
297            /**
298             * @see org.opencms.publish.CmsPublishEventAdapter#onStart(org.opencms.publish.CmsPublishJobEnqueued)
299             */
300            @Override
301            public void onStart(CmsPublishJobEnqueued publishJob) {
302
303                CmsExtendedWorkflowManager.this.onStartPublishJob(publishJob);
304            }
305        });
306    }
307
308    /**
309     * Implementation of the 'release' workflow action.<p>
310     *
311     * @param userCms the current user's CMS context
312     * @param resources the resources which should be released
313     *
314     * @return the workflow response for this action
315     *
316     * @throws CmsException if something goes wrong
317     */
318    protected CmsWorkflowResponse actionRelease(CmsObject userCms, List<CmsResource> resources) throws CmsException {
319
320        checkNewParentsInList(userCms, resources);
321        String projectName = generateProjectName(userCms);
322        String projectDescription = generateProjectDescription(userCms);
323        CmsObject offlineAdminCms = OpenCms.initCmsObject(m_adminCms);
324        offlineAdminCms.getRequestContext().setCurrentProject(userCms.getRequestContext().getCurrentProject());
325        String managerGroup = getWorkflowProjectManagerGroup();
326        String userGroup = getWorkflowProjectUserGroup();
327        CmsProject workflowProject = m_adminCms.createProject(
328            projectName,
329            projectDescription,
330            userGroup,
331            managerGroup,
332            CmsProject.PROJECT_TYPE_WORKFLOW);
333        CmsObject newProjectCms = OpenCms.initCmsObject(offlineAdminCms);
334        newProjectCms.getRequestContext().setCurrentProject(workflowProject);
335        newProjectCms.getRequestContext().setSiteRoot("");
336        newProjectCms.copyResourceToProject("/");
337        CmsUser admin = offlineAdminCms.getRequestContext().getCurrentUser();
338        clearLocks(userCms.getRequestContext().getCurrentProject(), resources);
339        for (CmsResource resource : resources) {
340            CmsLock lock = offlineAdminCms.getLock(resource);
341            if (lock.isUnlocked()) {
342                offlineAdminCms.lockResource(resource);
343            } else if (!lock.isOwnedBy(admin)) {
344                offlineAdminCms.changeLock(resource);
345            }
346            offlineAdminCms.writeProjectLastModified(resource, workflowProject);
347            offlineAdminCms.unlockResource(resource);
348        }
349        for (CmsUser user : getNotificationMailRecipients()) {
350            sendNotification(userCms, user, workflowProject, resources);
351        }
352        return new CmsWorkflowResponse(
353            true,
354            "",
355            new ArrayList<CmsPublishResource>(),
356            new ArrayList<CmsWorkflowAction>(),
357            workflowProject.getUuid());
358    }
359
360    /**
361     * Checks that the parent folders of new resources which are released are either not new or are also released.<p>
362     *
363     * @param userCms the user CMS context
364     * @param resources the resources to check
365     *
366     * @throws CmsException if the check fails
367     */
368    protected void checkNewParentsInList(CmsObject userCms, List<CmsResource> resources) throws CmsException {
369
370        Map<String, CmsResource> resourcesByPath = new HashMap<String, CmsResource>();
371        CmsObject rootCms = OpenCms.initCmsObject(m_adminCms);
372        rootCms.getRequestContext().setCurrentProject(userCms.getRequestContext().getCurrentProject());
373        rootCms.getRequestContext().setSiteRoot("");
374        for (CmsResource resource : resources) {
375            resourcesByPath.put(resource.getRootPath(), resource);
376        }
377        for (CmsResource resource : resources) {
378            if (resource.getState().isNew()) {
379                String parentPath = CmsResource.getParentFolder(resource.getRootPath());
380                CmsResource parent = resourcesByPath.get(parentPath);
381                if (parent == null) {
382                    parent = rootCms.readResource(parentPath);
383                    if (parent.getState().isNew()) {
384                        throw new CmsNewParentNotInWorkflowException(
385                            Messages.get().container(
386                                Messages.ERR_NEW_PARENT_NOT_IN_WORKFLOW_1,
387                                resource.getRootPath()));
388                    }
389                }
390            }
391        }
392    }
393
394    /**
395     * Cleans up empty workflow projects.<p>
396     *
397     * @param projects the workflow projects to clean up
398     *
399     * @throws CmsException if something goes wrong
400     */
401    protected void cleanupEmptyWorkflowProjects(List<CmsProject> projects) throws CmsException {
402
403        if (projects == null) {
404            projects = OpenCms.getOrgUnitManager().getAllManageableProjects(m_adminCms, "", true);
405        }
406        for (CmsProject project : projects) {
407            if (project.isWorkflowProject()) {
408                if (isProjectEmpty(project)) {
409                    m_adminCms.deleteProject(project.getUuid());
410                }
411            }
412        }
413    }
414
415    /**
416     * Removes a project if there are no longer any resources which have been last modified in that project.<p>
417     *
418     * @param project the project
419     * @throws CmsException if something goes wrong
420     */
421    protected void cleanupProjectIfEmpty(CmsProject project) throws CmsException {
422
423        if ((project.getType().getMode() == CmsProject.PROJECT_TYPE_WORKFLOW.getMode()) && isProjectEmpty(project)) {
424            LOG.info("Removing project " + project.getName() + " because it is an empty workflow project.");
425            m_adminCms.deleteProject(project.getUuid());
426        }
427    }
428
429    /**
430     * Ensures that the resources to be released are unlocked.<p>
431     *
432     * @param project the project in which to operate
433     * @param resources the resources for which the locks should be removed
434     *
435     * @throws CmsException if something goes wrong
436     */
437    protected void clearLocks(CmsProject project, List<CmsResource> resources) throws CmsException {
438
439        CmsObject rootCms = OpenCms.initCmsObject(m_adminCms);
440        rootCms.getRequestContext().setCurrentProject(project);
441        rootCms.getRequestContext().setSiteRoot("");
442        for (CmsResource resource : resources) {
443            CmsLock lock = rootCms.getLock(resource);
444            if (lock.isUnlocked()) {
445                continue;
446            }
447            String currentPath = resource.getRootPath();
448            while (lock.isInherited()) {
449                currentPath = CmsResource.getParentFolder(currentPath);
450                lock = rootCms.getLock(currentPath);
451            }
452            rootCms.changeLock(currentPath);
453            rootCms.unlockResource(currentPath);
454        }
455    }
456
457    /**
458     * Helper method to check whether a project exists.<p>
459     *
460     * @param projectName the project name
461     *
462     * @return true if the project exists
463     */
464    protected boolean existsProject(String projectName) {
465
466        try {
467            m_adminCms.readProject(projectName);
468            return true;
469        } catch (CmsException e) {
470            return false;
471        }
472    }
473
474    /**
475     * Generates the description for a new workflow project based on the user for whom it is created.<p>
476     *
477     * @param userCms the user's current CMS context
478     *
479     * @return the workflow project description
480     */
481    protected String generateProjectDescription(CmsObject userCms) {
482
483        CmsUser user = userCms.getRequestContext().getCurrentUser();
484        OpenCms.getLocaleManager();
485        Locale locale = CmsLocaleManager.getDefaultLocale();
486        long time = System.currentTimeMillis();
487        Date date = new Date(time);
488        DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale);
489        String dateString = format.format(date);
490        String result = Messages.get().getBundle(locale).key(
491            Messages.GUI_WORKFLOW_PROJECT_DESCRIPTION_2,
492            user.getName(),
493            dateString);
494        return result;
495    }
496
497    /**
498     * Generates the name for a new workflow project based on the user for whom it is created.<p>
499     *
500     * @param userCms the user's current CMS context
501     *
502     * @return the workflow project name
503     */
504    protected String generateProjectName(CmsObject userCms) {
505
506        String projectName = generateProjectName(userCms, true);
507        if (existsProject(projectName)) {
508            projectName = generateProjectName(userCms, false);
509        }
510        return projectName;
511    }
512
513    /**
514     * Generates the name for a new workflow project based on the user for whom it is created.<p>
515     *
516     * @param userCms the user's current CMS context
517     * @param shortTime if true, the short time format will be used, else the medium time format
518     *
519     * @return the workflow project name
520     */
521    protected String generateProjectName(CmsObject userCms, boolean shortTime) {
522
523        CmsUser user = userCms.getRequestContext().getCurrentUser();
524        long time = System.currentTimeMillis();
525        Date date = new Date(time);
526        OpenCms.getLocaleManager();
527        Locale locale = CmsLocaleManager.getDefaultLocale();
528        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
529        DateFormat timeFormat = DateFormat.getTimeInstance(shortTime ? DateFormat.SHORT : DateFormat.MEDIUM, locale);
530        String dateStr = dateFormat.format(date) + " " + timeFormat.format(date);
531        String username = user.getName();
532        String result = Messages.get().getBundle(locale).key(Messages.GUI_WORKFLOW_PROJECT_NAME_2, username, dateStr);
533        result = result.replaceAll("/", "|");
534
535        return result;
536    }
537
538    /**
539     * Gets the list of recipients for the notifications.<p>
540     *
541     * @return the list of users which should be notified when resources are released
542     */
543    protected List<CmsUser> getNotificationMailRecipients() {
544
545        String group = getWorkflowProjectManagerGroup();
546        CmsObject cms = m_adminCms;
547        try {
548            List<CmsUser> users = cms.getUsersOfGroup(group);
549            return users;
550        } catch (CmsException e) {
551            LOG.error(e.getLocalizedMessage(), e);
552            return new ArrayList<CmsUser>();
553        }
554    }
555
556    /**
557     * Gets the resource  notification content path.<p>
558     *
559     * @param cms the cms context
560     *
561     * @return the resource notification content path
562     */
563    protected String getNotificationResource(CmsObject cms) {
564
565        String result = getParameter(
566            PARAM_NOTIFICATION_CONTENT,
567            OpenCms.getSystemInfo().getConfigFilePath(cms, "notification/workflow-notification"));
568        return result;
569    }
570
571    /**
572     * Helper method for generating the workflow response which should be sent when publishing the resources would break relations.<p>
573     *
574     * @param userCms the user's CMS context
575     * @param publishResources the resources whose links would be broken
576     *
577     * @return the workflow response
578     */
579    protected CmsWorkflowResponse getPublishBrokenRelationsResponse(
580        CmsObject userCms,
581        List<CmsPublishResource> publishResources) {
582
583        List<CmsWorkflowAction> actions = new ArrayList<CmsWorkflowAction>();
584        String forcePublishLabel = Messages.get().getBundle(getLocale(userCms)).key(
585            Messages.GUI_WORKFLOW_ACTION_FORCE_PUBLISH_0);
586
587        CmsWorkflowAction forcePublish = new CmsWorkflowAction(ACTION_FORCE_PUBLISH, forcePublishLabel, true, true);
588        actions.add(forcePublish);
589        return new CmsWorkflowResponse(
590            false,
591            Messages.get().getBundle(getLocale(userCms)).key(Messages.GUI_BROKEN_LINKS_0),
592            publishResources,
593            actions,
594            null);
595    }
596
597    /**
598     * Gets the workflow response which should be sent when the resources have successfully been published.<p>
599     *
600     * @return the successful workflow response
601     */
602    protected CmsWorkflowResponse getSuccessResponse() {
603
604        return new CmsWorkflowResponse(
605            true,
606            "",
607            new ArrayList<CmsPublishResource>(),
608            new ArrayList<CmsWorkflowAction>(),
609            null);
610    }
611
612    /**
613     * Checks whether there are resources which have last been modified in a given project.<p>
614     *
615     * @param project the project which should be checked
616     * @return true if there are no resources which have been last modified inside the project
617     *
618     * @throws CmsException if something goes wrong
619     */
620    protected boolean isProjectEmpty(CmsProject project) throws CmsException {
621
622        List<CmsResource> resources = m_adminCms.readProjectView(project.getUuid(), CmsResourceState.STATE_KEEP);
623        return resources.isEmpty();
624    }
625
626    /**
627     * Checks whether the user for a given CMS context can manage workflow projects.<p>
628     *
629     * @param userCms the user CMS Context
630     * @return true if this user can manage workflow projects
631     *
632     * @throws CmsException if something goes wrong
633     */
634    protected boolean isProjectManager(CmsObject userCms) throws CmsException {
635
636        CmsGroup managerGroup = m_adminCms.readGroup(getWorkflowProjectManagerGroup());
637        List<CmsGroup> groups = m_adminCms.getGroupsOfUser(
638            userCms.getRequestContext().getCurrentUser().getName(),
639            false);
640        return groups.contains(managerGroup);
641    }
642
643    /**
644     * Handles finished publish jobs by removing projects of resources in the publish job if they are empty workflow projects.<p>
645     *
646     * @param publishJob the finished published job
647     */
648    protected void onFinishPublishJob(CmsPublishJobRunning publishJob) {
649
650        try {
651            cleanupEmptyWorkflowProjects(null);
652        } catch (CmsException e) {
653            LOG.error(e.getLocalizedMessage(), e);
654        }
655    }
656
657    /**
658     * This is called when a publish job is started.<p>
659     *
660     * @param publishJob the publish job being started
661     */
662    protected void onStartPublishJob(CmsPublishJobEnqueued publishJob) {
663
664        // do nothing
665    }
666
667    /**
668     * Sends the notification for released resources.<p>
669     *
670     * @param userCms the user's CMS context
671     * @param recipient the OpenCms user to whom the notification should be sent
672     * @param workflowProject the workflow project which
673     * @param resources the resources which have been affected by a workflow action
674     */
675    protected void sendNotification(
676        CmsObject userCms,
677        CmsUser recipient,
678        CmsProject workflowProject,
679        List<CmsResource> resources) {
680
681        try {
682            String linkHref = OpenCms.getLinkManager().getServerLink(
683                userCms,
684                "/system/workplace/commons/publish.jsp?"
685                    + CmsPublishService.PARAM_PUBLISH_PROJECT_ID
686                    + "="
687                    + workflowProject.getUuid()
688                    + "&"
689                    + CmsPublishService.PARAM_CONFIRM
690                    + "=true");
691            CmsWorkflowNotification notification = new CmsWorkflowNotification(
692                m_adminCms,
693                userCms,
694                recipient,
695                getNotificationResource(m_adminCms),
696                workflowProject,
697                resources,
698                linkHref);
699            notification.send();
700        } catch (Throwable e) {
701            LOG.error(e.getLocalizedMessage(), e);
702        }
703    }
704}