gt:         for (PublishRequest copyToEnvironment : itemList) {
context: /*
   * Copyright (C) 2007-2019 Crafter Software Corporation. All Rights Reserved.
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program 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 General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  package org.craftercms.studio.impl.v1.service.deployment;
  
  import org.apache.commons.collections.FastArrayList;
  import org.apache.commons.collections4.CollectionUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.craftercms.commons.validation.annotations.param.ValidateIntegerParam;
  import org.craftercms.commons.validation.annotations.param.ValidateParams;
  import org.craftercms.commons.validation.annotations.param.ValidateSecurePathParam;
  import org.craftercms.commons.validation.annotations.param.ValidateStringParam;
  import org.craftercms.studio.api.v1.constant.StudioConstants;
  import org.craftercms.studio.api.v1.constant.DmConstants;
  import org.craftercms.studio.api.v1.dal.DeploymentSyncHistory;
  import org.craftercms.studio.api.v1.dal.ItemMetadata;
  import org.craftercms.studio.api.v1.dal.PublishRequest;
  import org.craftercms.studio.api.v1.dal.PublishRequestMapper;
  import org.craftercms.studio.api.v1.dal.SiteFeed;
  import org.craftercms.studio.api.v1.ebus.PreviewEventContext;
  import org.craftercms.studio.api.v1.exception.CommitNotFoundException;
  import org.craftercms.studio.api.v1.exception.EnvironmentNotFoundException;
  import org.craftercms.studio.api.v1.exception.ServiceLayerException;
  import org.craftercms.studio.api.v1.exception.SiteNotFoundException;
  import org.craftercms.studio.api.v1.exception.security.AuthenticationException;
  import org.craftercms.studio.api.v1.log.Logger;
  import org.craftercms.studio.api.v1.log.LoggerFactory;
  import org.craftercms.studio.api.v1.repository.ContentRepository;
  import org.craftercms.studio.api.v1.repository.RepositoryItem;
  import org.craftercms.studio.api.v1.service.configuration.ServicesConfig;
  import org.craftercms.studio.api.v1.service.content.ContentService;
  import org.craftercms.studio.api.v1.service.content.ObjectMetadataManager;
  import org.craftercms.studio.api.v1.service.dependency.DependencyService;
  import org.craftercms.studio.api.v1.service.deployment.CopyToEnvironmentItem;
  import org.craftercms.studio.api.v1.service.deployment.DeploymentException;
  import org.craftercms.studio.api.v1.service.deployment.DeploymentHistoryProvider;
  import org.craftercms.studio.api.v1.service.deployment.DeploymentService;
  import org.craftercms.studio.api.v1.service.deployment.DmPublishService;
  import org.craftercms.studio.api.v1.service.event.EventService;
  import org.craftercms.studio.api.v1.service.objectstate.ObjectStateService;
  import org.craftercms.studio.api.v1.service.security.SecurityService;
  import org.craftercms.studio.api.v1.service.site.SiteService;
  import org.craftercms.studio.api.v1.to.ContentItemTO;
  import org.craftercms.studio.api.v1.to.DmDeploymentTaskTO;
  import org.craftercms.studio.api.v1.to.PublishStatus;
  import org.craftercms.studio.api.v1.to.PublishingChannelTO;
  import org.craftercms.studio.api.v1.to.PublishingTargetTO;
  import org.craftercms.studio.api.v1.to.RepoOperationTO;
  import org.craftercms.studio.api.v1.util.DmContentItemComparator;
  import org.craftercms.studio.api.v1.util.StudioConfiguration;
  import org.craftercms.studio.api.v1.util.filter.DmFilterWrapper;
  import org.craftercms.studio.api.v2.dal.AuditLog;
  import org.craftercms.studio.api.v2.service.audit.internal.AuditServiceInternal;
  import org.craftercms.studio.api.v2.service.notification.NotificationService;
  import org.craftercms.studio.impl.v1.service.deployment.job.DeployContentToEnvironmentStore;
  import org.craftercms.studio.impl.v1.util.ContentUtils;
  
  import java.text.SimpleDateFormat;
  import java.time.ZoneOffset;
  import java.time.ZonedDateTime;
  import java.time.format.DateTimeFormatter;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  import java.util.UUID;
  
  import static org.craftercms.studio.api.v1.constant.StudioConstants.DATE_FORMAT_DEPLOYED;
  import static org.craftercms.studio.api.v1.constant.StudioConstants.DATE_PATTERN_WORKFLOW_WITH_TZ;
  import static org.craftercms.studio.api.v1.constant.StudioConstants.FILE_SEPARATOR;
  import static org.craftercms.studio.api.v1.ebus.EBusConstants.EVENT_PREVIEW_SYNC;
  import static org.craftercms.studio.api.v1.service.objectstate.State.NEW_DELETED;
  import static org.craftercms.studio.api.v1.service.objectstate.State.NEW_SUBMITTED_NO_WF_SCHEDULED;
  import static org.craftercms.studio.api.v1.service.objectstate.TransitionEvent.DELETE;
  import static org.craftercms.studio.api.v1.service.objectstate.TransitionEvent.SUBMIT_WITHOUT_WORKFLOW_SCHEDULED;
  import static org.craftercms.studio.api.v1.util.StudioConfiguration.JOB_DEPLOY_CONTENT_TO_ENVIRONMENT_STATUS_MESSAGE_QUEUED;
  import static org.craftercms.studio.api.v2.dal.AuditLogConstants.OPERATION_START_PUBLISHER;
  import static org.craftercms.studio.api.v2.dal.AuditLogConstants.OPERATION_STOP_PUBLISHER;
  import static org.craftercms.studio.api.v2.dal.AuditLogConstants.TARGET_TYPE_SITE;
  import static org.craftercms.studio.impl.v1.repository.git.GitContentRepositoryConstants.PREVIOUS_COMMIT_SUFFIX;
  
  /**
   */
  public class DeploymentServiceImpl implements DeploymentService {
  
      private static final Logger logger = LoggerFactory.getLogger(DeploymentServiceImpl.class);
  
      private static int CTED_AUTOINCREMENT = 0;
  
      protected ServicesConfig servicesConfig;
      protected ContentService contentService;
      protected DependencyService dependencyService;
      protected DmFilterWrapper dmFilterWrapper;
      protected SiteService siteService;
      protected ObjectStateService objectStateService;
      protected ObjectMetadataManager objectMetadataManager;
      protected ContentRepository contentRepository;
      protected DmPublishService dmPublishService;
      protected SecurityService securityService;
      protected EventService eventService;
      protected DeployContentToEnvironmentStore deployContentToEnvironmentStoreJob;
      protected NotificationService notificationService;
      protected DeploymentHistoryProvider deploymentHistoryProvider;
      protected StudioConfiguration studioConfiguration;
      protected PublishRequestMapper publishRequestMapper;
      protected AuditServiceInternal auditServiceInternal;
  
      @Override
      @ValidateParams
      public void deploy(@ValidateStringParam(name = "site") String site,
                         @ValidateStringParam(name = "environment") String environment, List<String> paths,
                         ZonedDateTime scheduledDate, @ValidateStringParam(name = "approver") String approver,
                         @ValidateStringParam(name = "submissionComment") String submissionComment,
                         final boolean scheduleDateNow) throws DeploymentException {
  
          if (scheduledDate != null && scheduledDate.isAfter(ZonedDateTime.now(ZoneOffset.UTC))) {
              objectStateService.transitionBulk(site, paths, SUBMIT_WITHOUT_WORKFLOW_SCHEDULED,
                      NEW_SUBMITTED_NO_WF_SCHEDULED);
          }
          List<String> newPaths = new ArrayList<String>();
          List<String> updatedPaths = new ArrayList<String>();
          List<String> movedPaths = new ArrayList<String>();
  
          Map<String, List<String>> groupedPaths = new HashMap<String, List<String>>();
  
          for (String p : paths) {
              boolean isFolder = contentRepository.isFolder(site, p);
              if (isFolder) {
                  logger.debug("Content item at path " + p + " for site " + site +
                          " is folder and will not be added to publishing queue.");
              } else {
                  if (objectStateService.isNew(site, p)) {
                      newPaths.add(p);
                  } else if (objectMetadataManager.isRenamed(site, p)) {
                      movedPaths.add(p);
                  } else {
                      updatedPaths.add(p);
                  }
              }
          }
  
          groupedPaths.put(PublishRequest.Action.NEW, newPaths);
          groupedPaths.put(PublishRequest.Action.MOVE, movedPaths);
          groupedPaths.put(PublishRequest.Action.UPDATE, updatedPaths);
  
          environment = resolveEnvironment(site, environment);
  
          List<PublishRequest> items = createItems(site, environment, groupedPaths, scheduledDate, approver,
                  submissionComment);
          for (PublishRequest item : items) {
              publishRequestMapper.insertItemForDeployment(item);
          }
          objectStateService.setSystemProcessingBulk(site, paths, false);
          // We need to pick up this on Inserting , not on execution!
          try {
              sendContentApprovalEmail(items, scheduleDateNow);
          } catch(Exception errNotify) {
              logger.error("Error sending approval notification ", errNotify);
          }
          String statusMessage = studioConfiguration
                  .getProperty(JOB_DEPLOY_CONTENT_TO_ENVIRONMENT_STATUS_MESSAGE_QUEUED);
          try {
              siteService.updatePublishingStatusMessage(site, statusMessage);
          } catch (SiteNotFoundException e) {
              logger.error("Error updating publishing status for site " + site);
          }
      }
  
      private String resolveEnvironment(String site, String environment) {
          String toRet = environment;
          List<PublishingTargetTO> publishingTargets = siteService.getPublishingTargetsForSite(site);
          for (PublishingTargetTO target : publishingTargets) {
              if (target.getDisplayLabel().equals(environment)) {
                  toRet = target.getRepoBranchName();
                  break;
              }
          }
          return toRet;
      }
  
      protected void sendContentApprovalEmail(List<PublishRequest> itemList, boolean scheduleDateNow) {
          for (PublishRequest listItem : itemList) {
              ItemMetadata itemMetadata = objectMetadataManager.getProperties(listItem.getSite(), listItem.getPath());
              if (itemMetadata != null) {
                  if (itemMetadata.getSendEmail() == 1) {
                      // found the first item that needs to be sent
                      notificationService.notifyContentApproval(listItem.getSite(),
                          itemMetadata.getSubmittedBy(),
                          getPathRelativeToSite(itemList),
                          listItem.getUser(),
                          // Null == now, anything else is scheduled
                          scheduleDateNow?null:listItem.getScheduledDate(),
                          Locale.ENGLISH);
                      // no point in looking further, quit looping
                      break;
                  }
              }
          }
      }
  
      private List<String> getPathRelativeToSite(final List<PublishRequest> itemList) {
          List<String> paths = new ArrayList<String>(itemList.size());
  