content: package com.ctrip.framework.apollo.portal.component.emailbuilder;
  
  
  import com.google.common.collect.Lists;
  
  import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
  import com.ctrip.framework.apollo.common.constants.ReleaseOperationContext;
  import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
  import com.ctrip.framework.apollo.common.entity.AppNamespace;
  import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
  import com.ctrip.framework.apollo.core.enums.Env;
  import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
  import com.ctrip.framework.apollo.portal.constant.RoleType;
  import com.ctrip.framework.apollo.portal.entity.bo.Email;
  import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
  import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
  import com.ctrip.framework.apollo.portal.entity.vo.Change;
  import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult;
  import com.ctrip.framework.apollo.portal.service.AppNamespaceService;
  import com.ctrip.framework.apollo.portal.service.ReleaseService;
  import com.ctrip.framework.apollo.portal.service.RolePermissionService;
  import com.ctrip.framework.apollo.portal.spi.UserService;
  import com.ctrip.framework.apollo.portal.util.RoleUtils;
  
  import org.apache.commons.lang.time.FastDateFormat;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.util.CollectionUtils;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.regex.Matcher;
  
  
  public abstract class ConfigPublishEmailBuilder {
  
    private static final String EMERGENCY_PUBLISH_TAG = "<span style='color:red'>(紧急发布)</span>";
  
    //email content common field placeholder
    private static final String EMAIL_CONTENT_FIELD_APPID = "#\\{appId\\}";
    private static final String EMAIL_CONTENT_FIELD_ENV = "#\\{env}";
    private static final String EMAIL_CONTENT_FIELD_CLUSTER = "#\\{clusterName}";
    private static final String EMAIL_CONTENT_FIELD_NAMESPACE = "#\\{namespaceName}";
    private static final String EMAIL_CONTENT_FIELD_OPERATOR = "#\\{operator}";
    private static final String EMAIL_CONTENT_FIELD_RELEASE_TIME = "#\\{releaseTime}";
    private static final String EMAIL_CONTENT_FIELD_RELEASE_ID = "#\\{releaseId}";
    private static final String EMAIL_CONTENT_FIELD_RELEASE_HISTORY_ID = "#\\{releaseHistoryId}";
    private static final String EMAIL_CONTENT_FIELD_RELEASE_TITLE = "#\\{releaseTitle}";
    private static final String EMAIL_CONTENT_FIELD_RELEASE_COMMENT = "#\\{releaseComment}";
    private static final String EMAIL_CONTENT_FIELD_APOLLO_SERVER_ADDRESS = "#\\{apollo.portal.address}";
    private static final String EMAIL_CONTENT_FIELD_DIFF_CONTENT = "#\\{diffContent}";
    private static final String EMAIL_CONTENT_FIELD_EMERGENCY_PUBLISH = "#\\{emergencyPublish}";
  
    private static final String EMAIL_CONTENT_DIFF_MODULE = "#\\{diffModule}";
    protected static final String EMAIL_CONTENT_GRAY_RULES_MODULE = "#\\{rulesModule}";
  
    //email content special field placeholder
    protected static final String EMAIL_CONTENT_GRAY_RULES_CONTENT = "#\\{rulesContent}";
  
    //set config's value max length to protect email.
    protected static final int VALUE_MAX_LENGTH = 100;
  
    protected FastDateFormat dateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
  
  
    @Autowired
    private RolePermissionService rolePermissionService;
    @Autowired
    private ReleaseService releaseService;
    @Autowired
    private AppNamespaceService appNamespaceService;
    @Autowired
    private UserService userService;
    @Autowired
    protected PortalConfig portalConfig;
  
    /**
     * email subject
     */
    protected abstract String subject();
  
    /**
     * email body content
     */
    protected abstract String emailContent(Env env, ReleaseHistoryBO releaseHistory);
  
    /**
     * email body template framework
     */
    protected abstract String getTemplateFramework();
  
    /**
     * email body diff module template
     */
    protected abstract String getDiffModuleTemplate();
  
  
    public Email build(Env env, ReleaseHistoryBO releaseHistory) {
  
      Email email = new Email();
  
      email.setSubject(subject());
      email.setSenderEmailAddress(portalConfig.emailSender());
      email.setRecipients(recipients(releaseHistory.getAppId(), releaseHistory.getNamespaceName()));
  
      String emailBody = emailContent(env, releaseHistory);
      //clear not used module
      emailBody = emailBody.replaceAll(EMAIL_CONTENT_DIFF_MODULE, "");
      emailBody = emailBody.replaceAll(EMAIL_CONTENT_GRAY_RULES_MODULE, "");
      email.setBody(emailBody);
  
      return email;
    }
  
    protected String renderEmailCommonContent(Env env, ReleaseHistoryBO releaseHistory) {
      String template = getTemplateFramework();
      String renderResult = renderReleaseBasicInfo(template, env, releaseHistory);
      renderResult = renderDiffModule(renderResult, env, releaseHistory);
      return renderResult;
    }
  
    private String renderReleaseBasicInfo(String template, Env env, ReleaseHistoryBO releaseHistory) {
      String renderResult = template;
  
      Map<String, Object> operationContext = releaseHistory.getOperationContext();
      boolean isEmergencyPublish = operationContext.containsKey(ReleaseOperationContext.IS_EMERGENCY_PUBLISH) &&
                                   (boolean) operationContext.get(ReleaseOperationContext.IS_EMERGENCY_PUBLISH);
      if (isEmergencyPublish) {
        renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_EMERGENCY_PUBLISH, Matcher.quoteReplacement(EMERGENCY_PUBLISH_TAG));
      } else {
        renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_EMERGENCY_PUBLISH, "");
      }
  
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_APPID, Matcher.quoteReplacement(releaseHistory.getAppId()));
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_ENV, Matcher.quoteReplacement(env.toString()));
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_CLUSTER, Matcher.quoteReplacement(releaseHistory.getClusterName()));
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_NAMESPACE, Matcher.quoteReplacement(releaseHistory.getNamespaceName()));
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_OPERATOR, Matcher.quoteReplacement(releaseHistory.getOperator()));
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_TITLE, Matcher.quoteReplacement(releaseHistory.getReleaseTitle()));
      renderResult =
              renderResult.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_ID, String.valueOf(releaseHistory.getReleaseId()));
      renderResult =
              renderResult.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_HISTORY_ID, String.valueOf(releaseHistory.getId()));
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_COMMENT, Matcher.quoteReplacement(releaseHistory.getReleaseComment()));
      renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_APOLLO_SERVER_ADDRESS, getApolloPortalAddress());
      return renderResult
              .replaceAll(EMAIL_CONTENT_FIELD_RELEASE_TIME, dateFormat.format(releaseHistory.getReleaseTime()));
    }
  
    private String renderDiffModule(String bodyTemplate, Env env, ReleaseHistoryBO releaseHistory) {
      String appId = releaseHistory.getAppId();
      String namespaceName = releaseHistory.getNamespaceName();
  
      AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(appId, namespaceName);
      if (appNamespace == null) {
        appNamespace = appNamespaceService.findPublicAppNamespace(namespaceName);
      }
  
      //don't show diff content if namespace's format is file
      if (appNamespace == null ||
              !appNamespace.getFormat().equals(ConfigFileFormat.Properties.getValue())) {
  
        return bodyTemplate.replaceAll(EMAIL_CONTENT_DIFF_MODULE, "<br><h4>变更内容请点击链接到Apollo上查看</h4>");
      }
  
      ReleaseCompareResult result = getReleaseCompareResult(env, releaseHistory);
  
      if (!result.hasContent()) {
        return bodyTemplate.replaceAll(EMAIL_CONTENT_DIFF_MODULE, "<br><h4>无配置变更</h4>");
      }
  
      List<Change> changes = result.getChanges();
      StringBuilder changesHtmlBuilder = new StringBuilder();
      for (Change change : changes) {
        String key = change.getEntity().getFirstEntity().getKey();
        String oldValue = change.getEntity().getFirstEntity().getValue();
        String newValue = change.getEntity().getSecondEntity().getValue();
        newValue = newValue == null ? "" : newValue;
  
        changesHtmlBuilder.append("<tr>");
        changesHtmlBuilder.append("<td width=\"10%\">").append(change.getType().toString()).append("</td>");
        changesHtmlBuilder.append("<td width=\"20%\">").append(cutOffString(key)).append("</td>");
        changesHtmlBuilder.append("<td width=\"35%\">").append(cutOffString(oldValue)).append("</td>");
        changesHtmlBuilder.append("<td width=\"35%\">").append(cutOffString(newValue)).append("</td>");
  
        changesHtmlBuilder.append("</tr>");
      }
  
      String diffContent = Matcher.quoteReplacement(changesHtmlBuilder.toString());
      String diffModuleTemplate = getDiffModuleTemplate();
      String diffModuleRenderResult = diffModuleTemplate.replaceAll(EMAIL_CONTENT_FIELD_DIFF_CONTENT, diffContent);
      return bodyTemplate.replaceAll(EMAIL_CONTENT_DIFF_MODULE, diffModuleRenderResult);
    }
  
    private ReleaseCompareResult getReleaseCompareResult(Env env, ReleaseHistoryBO releaseHistory) {
      if (releaseHistory.getOperation() == ReleaseOperation.GRAY_RELEASE
              && releaseHistory.getPreviousReleaseId() == 0) {
        ReleaseDTO masterLatestActiveRelease = releaseService.loadLatestRelease(
                releaseHistory.getAppId(), env, releaseHistory.getClusterName(), releaseHistory.getNamespaceName());
        ReleaseDTO branchLatestActiveRelease = releaseService.findReleaseById(env, releaseHistory.getReleaseId());
  
        return releaseService.compare(masterLatestActiveRelease, branchLatestActiveRelease);
      }
  
      return releaseService.compare(env, releaseHistory.getPreviousReleaseId(), releaseHistory.getReleaseId());
    }
  
    private List<String> recipients(String appId, String namespaceName) {
      Set<UserInfo> modifyRoleUsers =
              rolePermissionService
                      .queryUsersWithRole(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.MODIFY_NAMESPACE));
      Set<UserInfo> releaseRoleUsers =
              rolePermissionService
                      .queryUsersWithRole(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.RELEASE_NAMESPACE));
      Set<UserInfo> owners = rolePermissionService.queryUsersWithRole(RoleUtils.buildAppMasterRoleName(appId));
  
      Set<String> userIds = new HashSet<>(modifyRoleUsers.size() + releaseRoleUsers.size() + owners.size());
  
      for (UserInfo userInfo : modifyRoleUsers) {
        userIds.add(userInfo.getUserId());
      }
  
      for (UserInfo userInfo : releaseRoleUsers) {
        userIds.add(userInfo.getUserId());
      }
  
      for (UserInfo userInfo : owners) {
        userIds.add(userInfo.getUserId());
      }
  
      List<UserInfo> userInfos = userService.findByUserIds(Lists.newArrayList(userIds));
  
      if (CollectionUtils.isEmpty(userInfos)) {
        return Collections.emptyList();
      }
  
      List<String> recipients = new ArrayList<>(userInfos.size());
      for (UserInfo userInfo : userInfos) {
        recipients.add(userInfo.getEmail());
      }
  
      return recipients;
    }
  
    protected String getApolloPortalAddress() {
      return portalConfig.portalAddress();
    }
  
    private String cutOffString(String source) {
      if (source.length() > VALUE_MAX_LENGTH) {
        return source.substring(0, VALUE_MAX_LENGTH) + "...";
      }
      return source;
    }
  
  }
  