content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://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, boolean)
       */
      @Override
      public CmsWorkflowResources getWorkflowResources(
          CmsObject cms,
          CmsWorkflow workflow,
          CmsPublishOptions options,
          boolean canOverrideWorkflow,
          boolean ignoreLimit) {
  
          String workflowKey = workflow.getId();
          String overrideId = null;
          Integer tooManyCount = null;
          if (WORKFLOW_RELEASE.equals(workflowKey)) {
              boolean tooMany = false;
              CmsWorkflowResources workflowResourcesBean = super.getWorkflowResources(
                  cms,
                  workflow,
                  options,
                  canOverrideWorkflow,
                  ignoreLimit);
              List<CmsResource> result = workflowResourcesBean.getWorkflowResources();
              tooMany = workflowResourcesBean.isTooMany();
              if (tooMany) {
                  tooManyCount = workflowResourcesBean.getTooManyCount();
              }
  
              if (canOverrideWorkflow && !workflowResourcesBean.isTooMany()) {
                  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) {
                          CmsWorkflowResources overrideWorkflowResources = getWorkflowResources(
                              cms,
                              getWorkflows(cms).get(CmsDefaultWorkflowManager.WORKFLOW_PUBLISH),
                              options,
                              false,
                              false);
                          tooMany = overrideWorkflowResources.isTooMany();
                          tooManyCount = overrideWorkflowResources.getTooManyCount();
                          result = overrideWorkflowResources.getWorkflowResources();
                          overrideId = WORKFLOW_PUBLISH;
                      }
                  }
              }
              CmsWorkflowResources realResult = new CmsWorkflowResources(
                  result,
                  getWorkflows(cms).get(overrideId),
                  tooManyCount);
              return realResult;
          } else {
              CmsWorkflowResources realResult = super.getWorkflowResources(
                  cms,
                  workflow,
                  options,
                  canOverrideWorkflow,
                  ignoreLimit);
              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>
       *
       * @param cms the cms context
       *
       * @return the resource notification content path
       */
      protected String getNotificationResource(CmsObject cms) {
  
          String result = getParameter(
              PARAM_NOTIFICATION_CONTENT,
              OpenCms.getSystemInfo().getConfigFilePath(cms, "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(m_adminCms),
                  workflowProject,
                  resources,
                  linkHref);
              notification.send();
          } catch (Throwable e) {
              LOG.error(e.getLocalizedMessage(), e);
          }
      }
  }
  