content: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you under the Apache License, Version 2.0 (the
  // "License"); you may not use this file except in compliance
  // with the License.  You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing,
  // software distributed under the License is distributed on an
  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  // KIND, either express or implied.  See the License for the
  // specific language governing permissions and limitations
  // under the License.
  package com.cloud.upgrade;
  
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import java.sql.Connection;
  import java.sql.PreparedStatement;
  import java.sql.SQLException;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  
  import javax.inject.Inject;
  
  import com.cloud.hypervisor.Hypervisor.HypervisorType;
  import com.cloud.storage.GuestOSHypervisorMapping;
  import com.cloud.storage.GuestOSHypervisorVO;
  import com.cloud.storage.GuestOSVO;
  import com.cloud.storage.dao.GuestOSDao;
  import com.cloud.storage.dao.GuestOSDaoImpl;
  import com.cloud.storage.dao.GuestOSHypervisorDao;
  import com.cloud.storage.dao.GuestOSHypervisorDaoImpl;
  
  public class GuestOsMapper {
  
      protected Logger logger = LogManager.getLogger(getClass());
  
      @Inject
      GuestOSHypervisorDao guestOSHypervisorDao;
      @Inject
      GuestOSDao guestOSDao;
  
      private static final String updateGuestOsHypervisorSql =
              "UPDATE `cloud`.`guest_os_hypervisor` SET guest_os_id = ? WHERE guest_os_id = ? AND hypervisor_type = ? AND hypervisor_version = ? AND guest_os_name = ? AND is_user_defined = 0 AND removed IS NULL";
  
      public GuestOsMapper() {
          guestOSHypervisorDao = new GuestOSHypervisorDaoImpl();
          guestOSDao = new GuestOSDaoImpl();
      }
  
      public void mergeDuplicates() {
          logger.info("merging duplicate guest osses");
          Set<Set<GuestOSVO>> duplicates = findDuplicates();
          logger.debug(String.format("merging %d sets of duplicates", duplicates.size()));
          for (Set<GuestOSVO> setOfGuestOSes : duplicates) {
              // decide which to (mark as) remove(d)
              // # highest/lowest id
              // # or is user_defined == false
              GuestOSVO guestOSVO = highestIdFrom(setOfGuestOSes);
              logger.info(String.format("merging %d duplicates for %s ", setOfGuestOSes.size(), guestOSVO.getDisplayName()));
              makeNormative(guestOSVO, setOfGuestOSes);
  
          }
      }
  
      public void makeNormative(GuestOSVO guestOSVO, Set<GuestOSVO> setOfGuestOSes) {
          for (GuestOSVO oldGuestOs : setOfGuestOSes) {
              if (guestOSVO.getId() != oldGuestOs.getId()) {
                  List<GuestOSHypervisorVO> mappings = guestOSHypervisorDao.listByGuestOsId(oldGuestOs.getId());
                  copyMappings(guestOSVO, mappings);
                  makeHidden(oldGuestOs);
              }
          }
      }
  
      private void makeHidden(GuestOSVO guestOSVO) {
          guestOSVO.setDisplay(false);
          guestOSDao.update(guestOSVO.getId(),guestOSVO);
      }
  
      private void copyMappings(GuestOSVO guestOSVO, List<GuestOSHypervisorVO> mappings) {
          for (GuestOSHypervisorVO mapping : mappings) {
              if (null == guestOSHypervisorDao.findByOsIdAndHypervisor(guestOSVO.getId(), mapping.getHypervisorType(), mapping.getHypervisorVersion())) {
                  GuestOSHypervisorVO newMap = new GuestOSHypervisorVO();
                  newMap.setGuestOsId(guestOSVO.getId());
                  newMap.setGuestOsName(mapping.getGuestOsName());
                  newMap.setHypervisorType(mapping.getHypervisorType());
                  newMap.setHypervisorVersion(mapping.getHypervisorVersion());
                  guestOSHypervisorDao.persist(newMap);
              }
          }
      }
  
      private GuestOSVO highestIdFrom(Set<GuestOSVO> setOfGuestOSes) {
          GuestOSVO rc = null;
          for (GuestOSVO guestOSVO: setOfGuestOSes) {
              if (rc == null || (guestOSVO.getId() > rc.getId() && !guestOSVO.getIsUserDefined())) {
                  rc = guestOSVO;
                  break;
              }
          }
          return rc;
      }
  
      /**
       *
       ¨¨¨
       select * from guest_os go2
        where display_name
        in (select display_name from
                   (select display_name, count(1) as count from guest_os go1 group by display_name having count > 1) tab0);
       ¨¨¨
       * and group them by display_name
       *
       *
       * @return a list of sets of duplicate
       */
      private Set<Set<GuestOSVO>> findDuplicates() {
          Set<Set<GuestOSVO>> rc = new HashSet<>();
          Set<String> names = guestOSDao.findDoubleNames();
          for (String name : names) {
              List<GuestOSVO> guestOsses = guestOSDao.listByDisplayName(name);
              if (CollectionUtils.isNotEmpty(guestOsses)) {
                  rc.add(new HashSet<>(guestOsses));
              }
          }
          return rc;
      }
  
      public List<GuestOSVO> listByDisplayName(String displayName) {
          return guestOSDao.listByDisplayName(displayName);
      }
  
      private long getGuestOsId(long categoryId, String displayName) {
          GuestOSVO guestOS = guestOSDao.findByCategoryIdAndDisplayNameOrderByCreatedDesc(categoryId, displayName);
          long id = 0l;
          if (guestOS != null) {
              id = guestOS.getId();
          } else {
              logger.warn(String.format("Unable to find the guest OS details with category id: %d and display name: %s",  + categoryId, displayName));
          }
          return id;
      }
  
      private long getGuestOsIdFromHypervisorMapping(GuestOSHypervisorMapping mapping) {
          GuestOSHypervisorVO guestOSHypervisorVO = guestOSHypervisorDao.findByOsNameAndHypervisorOrderByCreatedDesc(mapping.getGuestOsName(), mapping.getHypervisorType(), mapping.getHypervisorVersion());
          long id = 0;
          if (guestOSHypervisorVO != null) {
              id = guestOSHypervisorVO.getGuestOsId();
          } else {
              logger.warn(String.format("Unable to find the guest OS hypervisor mapping details for %s", mapping.toString()));
          }
          return id;
      }
  
      public void addGuestOsAndHypervisorMappings(long categoryId, String displayName, List<GuestOSHypervisorMapping> mappings) {
          long guestOsId = getGuestOsId(categoryId, displayName);
          if (guestOsId == 0) {
              logger.debug("No guest OS found with category id: " + categoryId + " and display name: " + displayName);
              if (!addGuestOs(categoryId, displayName)) {
                  logger.warn("Couldn't add the guest OS with category id: " + categoryId + " and display name: " + displayName);
                  return;
              }
              guestOsId = getGuestOsId(categoryId, displayName);
          } else {
              updateToSystemDefined(guestOsId);
          }
  
          if (CollectionUtils.isEmpty(mappings)) {
              return;
          }
  
          for (final GuestOSHypervisorMapping mapping : mappings) {
              addGuestOsHypervisorMapping(mapping, guestOsId);
          }
      }
  
      private void updateToSystemDefined(long guestOsId) {
          GuestOSVO guestOsVo = guestOSDao.findById(guestOsId);
          guestOsVo.setIsUserDefined(false);
          guestOSDao.update(guestOsId, guestOsVo);// TODO: update is_user_defined to false
      }
  
      public boolean addGuestOs(long categoryId, String displayName) {
          logger.debug("Adding guest OS with category id: " + categoryId + " and display name: " + displayName);
          GuestOSVO guestOS = new GuestOSVO();
          guestOS.setCategoryId(categoryId);
          guestOS.setDisplayName(displayName);
          guestOS = guestOSDao.persist(guestOS);
          return (guestOS != null);
      }
      public void addGuestOsHypervisorMapping(GuestOSHypervisorMapping mapping, long category, String displayName) {
          long guestOsId =  getGuestOsId(category, displayName);
          if (guestOsId == 0) {
              logger.error(String.format("no guest os found for category %d and name %s, skipping mapping it to %s/%s", guestOsId, displayName, mapping.getHypervisorType(), mapping.getHypervisorVersion()));
          } else {
              addGuestOsHypervisorMapping(mapping, guestOsId);
          }
      }
  
      private void addGuestOsHypervisorMapping(GuestOSHypervisorMapping mapping, long guestOsId) {
          if(!isValidGuestOSHypervisorMapping(mapping)) {
              return;
          }
  
          logger.debug("Adding guest OS hypervisor mapping - " + mapping.toString() + ", for guest OS with id - " + guestOsId);
          GuestOSHypervisorVO guestOsMapping = new GuestOSHypervisorVO();
          guestOsMapping.setHypervisorType(mapping.getHypervisorType());
          guestOsMapping.setHypervisorVersion(mapping.getHypervisorVersion());
          guestOsMapping.setGuestOsName(mapping.getGuestOsName());
          guestOsMapping.setGuestOsId(guestOsId);
          guestOSHypervisorDao.persist(guestOsMapping);
      }
  
      public void updateGuestOsName(long categoryId, String oldDisplayName, String newDisplayName) {
          GuestOSVO guestOS = guestOSDao.findByCategoryIdAndDisplayNameOrderByCreatedDesc(categoryId, oldDisplayName);
          if (guestOS == null) {
              logger.debug("Unable to update guest OS name, as there is no guest OS with category id: " + categoryId + " and display name: " + oldDisplayName);
              return;
          }
  
          guestOS.setDisplayName(newDisplayName);
          guestOSDao.update(guestOS.getId(), guestOS);
      }
  
      public void updateGuestOsNameFromMapping(String newDisplayName, GuestOSHypervisorMapping mapping) {
          if(!isValidGuestOSHypervisorMapping(mapping)) {
              return;
          }
  
          GuestOSHypervisorVO guestOSHypervisorVO = guestOSHypervisorDao.findByOsNameAndHypervisorOrderByCreatedDesc(mapping.getGuestOsName(), mapping.getHypervisorType(), mapping.getHypervisorVersion());
          if (guestOSHypervisorVO == null) {
              logger.debug("Unable to update guest OS name, as there is no guest os hypervisor mapping");
              return;
          }
  
          long guestOsId = guestOSHypervisorVO.getGuestOsId();
          GuestOSVO guestOS = guestOSDao.findById(guestOsId);
          if (guestOS != null) {
              guestOS.setDisplayName(newDisplayName);
              guestOSDao.update(guestOS.getId(), guestOS);
          }
      }
  
      public void updateGuestOsIdInHypervisorMapping(Connection conn, long categoryId, String displayName, GuestOSHypervisorMapping mapping) {
          if(!isValidGuestOSHypervisorMapping(mapping)) {
              return;
          }
  
          long oldGuestOsId = getGuestOsIdFromHypervisorMapping(mapping);
          if (oldGuestOsId == 0) {
              logger.debug("Unable to update guest OS in hypervisor mapping, as there is no guest os hypervisor mapping - " + mapping.toString());
              return;
          }
  
          long newGuestOsId = getGuestOsId(categoryId, displayName);
          if (newGuestOsId == 0) {
              logger.debug("Unable to update guest OS id in hypervisor mapping, as there is no guest OS with category id: " + categoryId + " and display name: " + displayName);
              return;
          }
  
          updateGuestOsIdInMapping(conn, oldGuestOsId, newGuestOsId, mapping);
      }
  
      private void updateGuestOsIdInMapping(Connection conn, long oldGuestOsId, long newGuestOsId, GuestOSHypervisorMapping mapping) {
          logger.debug("Updating guest os id: " + oldGuestOsId + " to id: " + newGuestOsId + " in hypervisor mapping - " + mapping.toString());
          try {
              PreparedStatement pstmt = conn.prepareStatement(updateGuestOsHypervisorSql);
              pstmt.setLong(1, newGuestOsId);
              pstmt.setLong(2, oldGuestOsId);
              pstmt.setString(3, mapping.getHypervisorType());
              pstmt.setString(4, mapping.getHypervisorVersion());
              pstmt.setString(5, mapping.getGuestOsName());
              pstmt.executeUpdate();
          } catch (SQLException e) {
              logger.error("Failed to update guest OS id in hypervisor mapping due to: " + e.getMessage(), e);
          }
      }
  
      private boolean isValidGuestOSHypervisorMapping(GuestOSHypervisorMapping mapping) {
          if (mapping != null && mapping.isValid()) {
              return true;
          }
  
          logger.warn("Invalid Guest OS hypervisor mapping");
          return false;
      }
  
      /**
       * Copies guest OS mappings from src version to dest version for the hypervisor (use this to copy all mappings from older version to newer version during upgrade)
       * @return true if copied successfully, else false.
       */
      public boolean copyGuestOSHypervisorMappings(HypervisorType hypervisorType, String srcVersion, String destVersion) {
          if (hypervisorType == HypervisorType.None || hypervisorType == HypervisorType.Any) {
              logger.warn("Unable to copy, invalid hypervisor");
              return false;
          }
  
          if (StringUtils.isAnyBlank(srcVersion, destVersion)) {
              logger.warn("Unable to copy, invalid hypervisor version details");
              return false;
          }
  
          List<GuestOSHypervisorVO> guestOSHypervisorMappingsForSrcVersion = guestOSHypervisorDao.listByHypervisorTypeAndVersion(hypervisorType.toString(), srcVersion);
          if (CollectionUtils.isEmpty(guestOSHypervisorMappingsForSrcVersion)) {
              logger.warn(String.format("Unable to copy, couldn't find guest OS mappings for hypervisor: %s and src version: %s", hypervisorType.toString(), srcVersion));
              return false;
          }
  
          logger.debug(String.format("Adding guest OS mappings for hypervisor: %s and version: %s, from version: %s ", hypervisorType.toString(), destVersion, srcVersion));
          for (GuestOSHypervisorVO guestOSHypervisorMapping : guestOSHypervisorMappingsForSrcVersion) {
              GuestOSHypervisorMapping mapping = new GuestOSHypervisorMapping(hypervisorType.toString(), destVersion, guestOSHypervisorMapping.getGuestOsName());
              addGuestOsHypervisorMapping(mapping, guestOSHypervisorMapping.getGuestOsId());
          }
          return true;
      }
  
      public void updateGuestOsNameInHypervisorMapping(long categoryId, String displayName, GuestOSHypervisorMapping mapping) {
          if (!isValidGuestOSHypervisorMapping(mapping)) {
              return;
          }
  
          long guestOsId = getGuestOsId(categoryId, displayName);
          if (guestOsId == 0) {
              logger.error(String.format("no guest os found for category %d and name %s, skipping mapping it to %s/%s", guestOsId, displayName, mapping.getHypervisorType(), mapping.getHypervisorVersion()));
              return;
          }
  
          GuestOSHypervisorVO guestOsMapping = guestOSHypervisorDao.findByOsIdAndHypervisor(guestOsId, mapping.getHypervisorType(), mapping.getHypervisorVersion());
          guestOsMapping.setGuestOsName(mapping.getGuestOsName());
          guestOSHypervisorDao.update(guestOsMapping.getId(), guestOsMapping);
      }
  }
  