content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed 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.alibaba.nacos.client.config;
  
  import com.alibaba.nacos.api.PropertyKeyConst;
  import com.alibaba.nacos.api.common.Constants;
  import com.alibaba.nacos.api.config.ConfigService;
  import com.alibaba.nacos.api.config.ConfigType;
  import com.alibaba.nacos.api.config.filter.IConfigFilter;
  import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher;
  import com.alibaba.nacos.api.config.listener.Listener;
  import com.alibaba.nacos.api.exception.NacosException;
  import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager;
  import com.alibaba.nacos.client.config.filter.impl.ConfigRequest;
  import com.alibaba.nacos.client.config.filter.impl.ConfigResponse;
  import com.alibaba.nacos.client.config.impl.ClientWorker;
  import com.alibaba.nacos.client.config.impl.ConfigFuzzyWatchContext;
  import com.alibaba.nacos.client.config.impl.ConfigServerListManager;
  import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor;
  import com.alibaba.nacos.client.config.impl.LocalEncryptedDataKeyProcessor;
  import com.alibaba.nacos.client.config.utils.ParamUtils;
  import com.alibaba.nacos.client.env.NacosClientProperties;
  import com.alibaba.nacos.client.utils.ClientBasicParamUtil;
  import com.alibaba.nacos.client.utils.LogUtils;
  import com.alibaba.nacos.client.utils.PreInitUtils;
  import com.alibaba.nacos.client.utils.ValidatorUtils;
  import com.alibaba.nacos.common.utils.StringUtils;
  import org.slf4j.Logger;
  
  import java.util.Collections;
  import java.util.Properties;
  import java.util.Set;
  import java.util.concurrent.Future;
  
  import static com.alibaba.nacos.api.common.Constants.ALL_PATTERN;
  
  /**
   * Config Impl.
   *
   * @author Nacos
   */
  @SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
  public class NacosConfigService implements ConfigService {
      
      private static final Logger LOGGER = LogUtils.logger(NacosConfigService.class);
      
      private static final String UP = "UP";
      
      private static final String DOWN = "DOWN";
      
      /**
       * long polling.
       */
      private final ClientWorker worker;
      
      private String namespace;
      
      private final ConfigFilterChainManager configFilterChainManager;
      
      public NacosConfigService(Properties properties) throws NacosException {
          PreInitUtils.asyncPreLoadCostComponent();
          final NacosClientProperties clientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
          LOGGER.info(ClientBasicParamUtil.getInputParameters(clientProperties.asProperties()));
          ValidatorUtils.checkInitParam(clientProperties);
          
          initNamespace(clientProperties);
          this.configFilterChainManager = new ConfigFilterChainManager(clientProperties.asProperties());
          ConfigServerListManager serverListManager = new ConfigServerListManager(clientProperties);
          serverListManager.start();
          
          this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, clientProperties);
          
      }
      
      private void initNamespace(NacosClientProperties properties) {
          namespace = ClientBasicParamUtil.parseNamespace(properties);
          properties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
      }
      
      @Override
      public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
          return getConfigInner(namespace, dataId, group, timeoutMs);
      }
      
      @Override
      public String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)
              throws NacosException {
          group = StringUtils.isBlank(group) ? Constants.DEFAULT_GROUP : group.trim();
          ConfigResponse configResponse = worker.getAgent()
                  .queryConfig(dataId, group, worker.getAgent().getTenant(), timeoutMs, false);
          String content = configResponse.getContent();
          String encryptedDataKey = configResponse.getEncryptedDataKey();
          worker.addTenantListenersWithContent(dataId, group, content, encryptedDataKey,
                  Collections.singletonList(listener));
          
          // get a decryptContent, fix https://github.com/alibaba/nacos/issues/7039
          ConfigResponse cr = new ConfigResponse();
          cr.setDataId(dataId);
          cr.setGroup(group);
          cr.setContent(content);
          cr.setEncryptedDataKey(encryptedDataKey);
          configFilterChainManager.doFilter(null, cr);
          return cr.getContent();
      }
      
      @Override
      public void addListener(String dataId, String group, Listener listener) throws NacosException {
          worker.addTenantListeners(dataId, group, Collections.singletonList(listener));
      }
      
      @Override
      public void fuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher watcher) throws NacosException {
          doAddFuzzyWatch(ALL_PATTERN, groupNamePattern, watcher);
      }
      
      @Override
      public void fuzzyWatch(String dataIdPattern, String groupNamePattern, FuzzyWatchEventWatcher watcher)
              throws NacosException {
          doAddFuzzyWatch(dataIdPattern, groupNamePattern, watcher);
      }
      
      @Override
      public Future<Set<String>> fuzzyWatchWithGroupKeys(String groupNamePattern, FuzzyWatchEventWatcher watcher)
              throws NacosException {
          return doAddFuzzyWatch(ALL_PATTERN, groupNamePattern, watcher);
      }
      
      @Override
      public Future<Set<String>> fuzzyWatchWithGroupKeys(String dataIdPattern, String groupNamePattern,
              FuzzyWatchEventWatcher watcher) throws NacosException {
          return doAddFuzzyWatch(dataIdPattern, groupNamePattern, watcher);
      }
      
      private Future<Set<String>> doAddFuzzyWatch(String dataIdPattern, String groupNamePattern,
              FuzzyWatchEventWatcher watcher) throws NacosException {
          ConfigFuzzyWatchContext configFuzzyWatchContext = worker.addTenantFuzzyWatcher(dataIdPattern, groupNamePattern,
                  watcher);
          return configFuzzyWatchContext.createNewFuture();
      }
      
      @Override
      public void cancelFuzzyWatch(String groupNamePattern, FuzzyWatchEventWatcher watcher) throws NacosException {
          cancelFuzzyWatch(ALL_PATTERN, groupNamePattern, watcher);
      }
      
      @Override
      public void cancelFuzzyWatch(String dataIdPattern, String groupNamePattern, FuzzyWatchEventWatcher watcher)
              throws NacosException {
          doCancelFuzzyWatch(dataIdPattern, groupNamePattern, watcher);
      }
      
      private void doCancelFuzzyWatch(String dataIdPattern, String groupNamePattern, FuzzyWatchEventWatcher watcher)
              throws NacosException {
          if (null == watcher) {
              return;
          }
          worker.removeFuzzyListenListener(dataIdPattern, groupNamePattern, watcher);
      }
      
      @Override
      public boolean publishConfig(String dataId, String group, String content) throws NacosException {
          return publishConfig(dataId, group, content, ConfigType.getDefaultType().getType());
      }
      
      @Override
      public boolean publishConfig(String dataId, String group, String content, String type) throws NacosException {
          return publishConfigInner(namespace, dataId, group, null, null, null, content, type, null);
      }
      
      @Override
      public boolean publishConfigCas(String dataId, String group, String content, String casMd5) throws NacosException {
          return publishConfigInner(namespace, dataId, group, null, null, null, content,
                  ConfigType.getDefaultType().getType(), casMd5);
      }
      
      @Override
      public boolean publishConfigCas(String dataId, String group, String content, String casMd5, String type)
              throws NacosException {
          return publishConfigInner(namespace, dataId, group, null, null, null, content, type, casMd5);
      }
      
      @Override
      public boolean removeConfig(String dataId, String group) throws NacosException {
          return removeConfigInner(namespace, dataId, group, null);
      }
      
      @Override
      public void removeListener(String dataId, String group, Listener listener) {
          worker.removeTenantListener(dataId, group, listener);
      }
      
      private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
          group = blank2defaultGroup(group);
          ParamUtils.checkKeyParam(dataId, group);
          ConfigResponse cr = new ConfigResponse();
          
          cr.setDataId(dataId);
          cr.setTenant(tenant);
          cr.setGroup(group);
          
          // We first try to use local failover content if exists.
          // A config content for failover is not created by client program automatically,
          // but is maintained by user.
          // This is designed for certain scenario like client emergency reboot,
          // changing config needed in the same time, while nacos server is down.
          String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
          if (content != null) {
              LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}", worker.getAgentName(),
                      dataId, group, tenant);
              cr.setContent(content);
              String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(worker.getAgentName(),
                      dataId, group, tenant);
              cr.setEncryptedDataKey(encryptedDataKey);
              configFilterChainManager.doFilter(null, cr);
              content = cr.getContent();
              return content;
          }
          
          try {
              ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
              cr.setContent(response.getContent());
              cr.setEncryptedDataKey(response.getEncryptedDataKey());
              configFilterChainManager.doFilter(null, cr);
              content = cr.getContent();
              
              return content;
          } catch (NacosException ioe) {
              if (NacosException.NO_RIGHT == ioe.getErrCode()) {
                  throw ioe;
              }
              LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
                      worker.getAgentName(), dataId, group, tenant, ioe.toString());
          }
          
          content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), dataId, group, tenant);
          if (content != null) {
              LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}", worker.getAgentName(),
                      dataId, group, tenant);
          }
          cr.setContent(content);
          String encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeySnapshot(worker.getAgentName(),
                  dataId, group, tenant);
          cr.setEncryptedDataKey(encryptedDataKey);
          configFilterChainManager.doFilter(null, cr);
          content = cr.getContent();
          return content;
      }
      
      private String blank2defaultGroup(String group) {
          return (StringUtils.isBlank(group)) ? Constants.DEFAULT_GROUP : group.trim();
      }
      
      private boolean removeConfigInner(String tenant, String dataId, String group, String tag) throws NacosException {
          group = blank2defaultGroup(group);
          ParamUtils.checkKeyParam(dataId, group);
          return worker.removeConfig(dataId, group, tenant, tag);
      }
      
      private boolean publishConfigInner(String tenant, String dataId, String group, String tag, String appName,
              String betaIps, String content, String type, String casMd5) throws NacosException {
          group = blank2defaultGroup(group);
          ParamUtils.checkParam(dataId, group, content);
          
          ConfigRequest cr = new ConfigRequest();
          cr.setDataId(dataId);
          cr.setTenant(tenant);
          cr.setGroup(group);
          cr.setContent(content);
          cr.setType(type);
          configFilterChainManager.doFilter(cr, null);
          content = cr.getContent();
          String encryptedDataKey = cr.getEncryptedDataKey();
          
          return worker.publishConfig(dataId, group, tenant, appName, tag, betaIps, content, encryptedDataKey, casMd5,
                  type);
      }
      
      @Override
      public String getServerStatus() {
          if (worker.isHealthServer()) {
              return UP;
          } else {
              return DOWN;
          }
      }
      
      @Override
      public void addConfigFilter(IConfigFilter configFilter) {
          configFilterChainManager.addFilter(configFilter);
      }
      
      @Override
      public void shutDown() throws NacosException {
          worker.shutdown();
      }
  }
  