gt:         long time = System.currentTimeMillis();
context: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH (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().getGroupProjectmanagers());
      }
  
      /**
       * 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().getGroupProjectmanagers());
      }
  
      /**
       * @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
       */
      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();
  