content: package com.ctrip.framework.apollo.configservice.util;
  
  import com.google.common.base.Joiner;
  import com.google.common.base.Strings;
  import com.google.common.collect.FluentIterable;
  import com.google.common.collect.HashMultimap;
  import com.google.common.collect.Multimap;
  import com.google.common.collect.Sets;
  
  import com.ctrip.framework.apollo.configservice.service.AppNamespaceServiceWithCache;
  import com.ctrip.framework.apollo.common.entity.AppNamespace;
  import com.ctrip.framework.apollo.core.ConfigConsts;
  
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Component;
  
  import java.util.Collections;
  import java.util.List;
  import java.util.Objects;
  import java.util.Set;
  
  /**
   * @author Jason Song(song_s@ctrip.com)
   */
  @Component
  public class WatchKeysUtil {
    private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
    @Autowired
    private AppNamespaceServiceWithCache appNamespaceService;
  
    /**
     * Assemble watch keys for the given appId, cluster, namespace, dataCenter combination
     */
    public Set<String> assembleAllWatchKeys(String appId, String clusterName, String namespace,
                                            String dataCenter) {
      Multimap<String, String> watchedKeysMap =
          assembleAllWatchKeys(appId, clusterName, Sets.newHashSet(namespace), dataCenter);
      return Sets.newHashSet(watchedKeysMap.get(namespace));
    }
  
    /**
     * Assemble watch keys for the given appId, cluster, namespaces, dataCenter combination
     *
     * @return a multimap with namespace as the key and watch keys as the value
     */
    public Multimap<String, String> assembleAllWatchKeys(String appId, String clusterName,
                                                         Set<String> namespaces,
                                                         String dataCenter) {
      Multimap<String, String> watchedKeysMap =
          assembleWatchKeys(appId, clusterName, namespaces, dataCenter);
  
      //Every app has an 'application' namespace
      if (!(namespaces.size() == 1 && namespaces.contains(ConfigConsts.NAMESPACE_APPLICATION))) {
        Set<String> namespacesBelongToAppId = namespacesBelongToAppId(appId, namespaces);
        Set<String> publicNamespaces = Sets.difference(namespaces, namespacesBelongToAppId);
  
        //Listen on more namespaces if it's a public namespace
        if (!publicNamespaces.isEmpty()) {
          watchedKeysMap
              .putAll(findPublicConfigWatchKeys(appId, clusterName, publicNamespaces, dataCenter));
        }
      }
  
      return watchedKeysMap;
    }
  
    private Multimap<String, String> findPublicConfigWatchKeys(String applicationId,
                                                               String clusterName,
                                                               Set<String> namespaces,
                                                               String dataCenter) {
      Multimap<String, String> watchedKeysMap = HashMultimap.create();
      List<AppNamespace> appNamespaces = appNamespaceService.findPublicNamespacesByNames(namespaces);
  
      for (AppNamespace appNamespace : appNamespaces) {
        //check whether the namespace's appId equals to current one
        if (Objects.equals(applicationId, appNamespace.getAppId())) {
          continue;
        }
  
        String publicConfigAppId = appNamespace.getAppId();
  
        watchedKeysMap.putAll(appNamespace.getName(),
            assembleWatchKeys(publicConfigAppId, clusterName, appNamespace.getName(), dataCenter));
      }
  
      return watchedKeysMap;
    }
  
    private String assembleKey(String appId, String cluster, String namespace) {
      return STRING_JOINER.join(appId, cluster, namespace);
    }
  
    private Set<String> assembleWatchKeys(String appId, String clusterName, String namespace,
                                          String dataCenter) {
      if (ConfigConsts.NO_APPID_PLACEHOLDER.equalsIgnoreCase(appId)) {
        return Collections.emptySet();
      }
      Set<String> watchedKeys = Sets.newHashSet();
  
      //watch specified cluster config change
      if (!Objects.equals(ConfigConsts.CLUSTER_NAME_DEFAULT, clusterName)) {
        watchedKeys.add(assembleKey(appId, clusterName, namespace));
      }
  
      //watch data center config change
      if (!Strings.isNullOrEmpty(dataCenter) && !Objects.equals(dataCenter, clusterName)) {
        watchedKeys.add(assembleKey(appId, dataCenter, namespace));
      }
  
      //watch default cluster config change
      watchedKeys.add(assembleKey(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespace));
  
      return watchedKeys;
    }
  
    private Multimap<String, String> assembleWatchKeys(String appId, String clusterName,
                                                       Set<String> namespaces,
                                                       String dataCenter) {
      Multimap<String, String> watchedKeysMap = HashMultimap.create();
  
      for (String namespace : namespaces) {
        watchedKeysMap
            .putAll(namespace, assembleWatchKeys(appId, clusterName, namespace, dataCenter));
      }
  
      return watchedKeysMap;
    }
  
    private Set<String> namespacesBelongToAppId(String appId, Set<String> namespaces) {
      if (ConfigConsts.NO_APPID_PLACEHOLDER.equalsIgnoreCase(appId)) {
        return Collections.emptySet();
      }
      List<AppNamespace> appNamespaces =
          appNamespaceService.findByAppIdAndNamespaces(appId, namespaces);
  
      if (appNamespaces == null || appNamespaces.isEmpty()) {
        return Collections.emptySet();
      }
  
      return FluentIterable.from(appNamespaces).transform(AppNamespace::getName).toSet();
    }
  }
  