All Downloads are FREE. Search and download functionalities are using the official Maven repository.

group.rxcloud.capa.spi.aws.config.AwsCapaConfigStore Maven / Gradle / Ivy

There is a newer version: 1.11.13.5.RELEASE
Show newest version
/*
 * 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 group.rxcloud.capa.spi.aws.config;

import com.google.common.collect.Lists;
import group.rxcloud.capa.addons.foundation.CapaFoundation;
import group.rxcloud.capa.addons.foundation.FoundationType;
import group.rxcloud.capa.component.CapaConfigurationProperties;
import group.rxcloud.capa.component.configstore.ConfigurationItem;
import group.rxcloud.capa.component.configstore.StoreConfig;
import group.rxcloud.capa.component.configstore.SubscribeResp;
import group.rxcloud.capa.infrastructure.exceptions.CapaErrorContext;
import group.rxcloud.capa.infrastructure.exceptions.CapaException;
import group.rxcloud.capa.infrastructure.serializer.CapaObjectSerializer;
import group.rxcloud.capa.spi.aws.config.entity.Configuration;
import group.rxcloud.capa.spi.aws.config.scheduler.AwsCapaConfigurationScheduler;
import group.rxcloud.capa.spi.aws.config.serializer.SerializerProcessor;
import group.rxcloud.capa.spi.configstore.CapaConfigStoreSpi;
import group.rxcloud.cloudruntimes.utils.TypeRef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.appconfig.AppConfigAsyncClient;
import software.amazon.awssdk.services.appconfig.model.BadRequestException;
import software.amazon.awssdk.services.appconfig.model.GetConfigurationRequest;
import software.amazon.awssdk.services.appconfig.model.GetConfigurationResponse;
import software.amazon.awssdk.utils.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;


/**
 * @author Reckless Xu
 */
public class AwsCapaConfigStore extends CapaConfigStoreSpi {

    private static final Logger LOGGER = LoggerFactory.getLogger(AwsCapaConfigStore.class);

    /**
     * key of versionMap--applicationName,format:appid_ENV,e.g:"12345_FAT"
     * value of versionMap--configurationMap of this application,which contains all the configuration info for this application
     * 

* key of configurationMap--configurationName; * value of configurationMap--configuration content and version of this configuration *

* ps:currentHashMap may not be necessary, as update in synchronized method */ private static final Map>> versionMap; static { versionMap = new ConcurrentHashMap<>(); } private final CapaObjectSerializer objectSerializer; private SerializerProcessor serializerProcessor; private AppConfigAsyncClient appConfigAsyncClient; private static final String APPCONFIG_NAME_FORMAT = "%s_%s"; private static final Long REQUEST_TIMEOUT_IN_SECONDS = AwsCapaConfigurationProperties.AppConfigProperties.Settings.getRequestTimeoutInSeconds(); /** * Instantiates a new Capa configuration. * * @param objectSerializer Serializer for transient request/response objects. */ public AwsCapaConfigStore(CapaObjectSerializer objectSerializer) { super(objectSerializer); this.objectSerializer = objectSerializer; } @Override protected void doInit(StoreConfig storeConfig) { appConfigAsyncClient = AppConfigAsyncClient.create(); serializerProcessor = new SerializerProcessor(objectSerializer); } @Override public String stopSubscribe() { AwsCapaConfigurationScheduler.INSTANCE.configSubscribePollingScheduler.dispose(); return "success"; } @Override public void close() { // no need } /** * direct get current config from server * * @param appId appId * @param group group * @param label label * @param keys configurationName list * @param metadata metadata * @param type type * @param T * @return config value or throw exception if cannot get config in specific time. * Exception contains e.g. {@link BadRequestException},{@link TimeoutException} and so on. */ @Override protected Mono>> doGet(String appId, String group, String label, List keys, Map metadata, TypeRef type) { if (CollectionUtils.isNullOrEmpty(keys)) { LOGGER.warn("[[type=Capa.Config]] keys is null or empty,appId:{}", appId); return Mono.error(new CapaException(CapaErrorContext.PARAMETER_ERROR, "keys is null or empty")); } String applicationName = String.format(APPCONFIG_NAME_FORMAT, appId, CapaFoundation.getEnv(FoundationType.TRIP)); String configurationName = keys.get(0); //init config and create subscribe polling initAndSubscribe(applicationName, configurationName, group, label, metadata, type); //get current config from local ConfigurationItem configurationItem = (ConfigurationItem) getConfiguration(applicationName, configurationName).getConfigurationItem(); return Mono.just(Lists.newArrayList(configurationItem)); } @Override protected Flux> doSubscribe(String appId, String group, String label, List keys, Map metadata, TypeRef type) { String applicationName = String.format(APPCONFIG_NAME_FORMAT, appId, CapaFoundation.getEnv(FoundationType.TRIP)); String configurationName = keys.get(0); initAndSubscribe(applicationName, configurationName, group, label, metadata, type); return doSub(applicationName, configurationName, group, label, metadata, type, appId); } /** * @param applicationName applicationName * @param configurationName configurationName * @param group group * @param label label * @param metadata metadata * @param type type * @param T * @return return Configuration.EMPTY if has been initialized before by others and not been done this time; * or return configuration value which initialized just now. */ private synchronized Configuration initConfig(String applicationName, String configurationName, String group, String label, Map metadata, TypeRef type) { // double check whether has been initialized if (isInitialized(applicationName, configurationName)) { LOGGER.info("[[type=Capa.Config.initConfig]] config has been initialized before,applicationName:{},configurationName:{}", applicationName, configurationName); return Configuration.EMPTY; } String version = getCurVersion(applicationName, configurationName); GetConfigurationRequest request = GetConfigurationRequest.builder() .application(applicationName) .clientId(UUID.randomUUID().toString()) .configuration(configurationName) .clientConfigurationVersion(version) .environment(AwsCapaConfigurationProperties.AppConfigProperties.Settings.getConfigAwsAppConfigEnv()) .build(); LOGGER.debug("[[type=Capa.Config.initConfig]] call getconfiguration in init process,request:{}", request); return Mono.fromCallable(() -> { GetConfigurationResponse response = appConfigAsyncClient.getConfiguration(request).get(REQUEST_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); LOGGER.debug("[[type=Capa.Config.initConfig]] call getconfiguration in init process,response:{}", response); return response; }) .doOnError(e -> LOGGER.warn("[[type=Capa.Config.initConfig]] error occurs when getconfiguration in init process, request:{}", request, e)) .map(resp -> initConfigurationItem(applicationName, configurationName, type, resp.content(), resp.configurationVersion())) .block(); } private void initAndSubscribe(String applicationName, String configurationName, String group, String label, Map metadata, TypeRef type) { if (!isInitialized(applicationName, configurationName)) { initConfig(applicationName, configurationName, group, label, metadata, type); } if (!isSubscribed(applicationName, configurationName)) { createSubscribePolling(applicationName, configurationName, type); } } private synchronized void createSubscribePolling(String applicationName, String configurationName, TypeRef type) { if (isSubscribed(applicationName, configurationName)) { return; } Flux.create(fluxSink -> { AwsCapaConfigurationScheduler.INSTANCE.configSubscribePollingScheduler .schedulePeriodically(() -> { // update subscribed status if needs getConfiguration(applicationName, configurationName).getSubscribed().compareAndSet(false, true); String version = getCurVersion(applicationName, configurationName); GetConfigurationRequest request = GetConfigurationRequest.builder() .application(applicationName) .clientId(UUID.randomUUID().toString()) .configuration(configurationName) .clientConfigurationVersion(version) .environment(AwsCapaConfigurationProperties.AppConfigProperties.Settings.getConfigAwsAppConfigEnv()) .build(); LOGGER.debug("[[type=Capa.Config.subscribePolling]] subscribe polling task start,request:{}", request); GetConfigurationResponse resp = null; try { resp = appConfigAsyncClient.getConfiguration(request).get(REQUEST_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { //catch error,log error and not trigger listeners LOGGER.warn("[[type=Capa.Config.subscribePolling]] error occurs when getConfiguration in polling process,configurationName:{},version:{}", request.configuration(), request.clientConfigurationVersion(), e); } LOGGER.debug("[[type=Capa.Config.subscribePolling]] subscribe polling task end,response:{}", resp); if (resp != null && !Objects.equals(resp.configurationVersion(), version)) { fluxSink.next(resp); } // todo: make the polling frequency configurable }, 0, 5, TimeUnit.SECONDS); }) .publishOn(AwsCapaConfigurationScheduler.INSTANCE.configPublisherScheduler) .map(origin -> { GetConfigurationResponse resp = (GetConfigurationResponse) origin; Configuration configuration = updateConfigurationItem(applicationName, configurationName, type, resp.content(), resp.configurationVersion()); return configuration == null ? Configuration.EMPTY : configuration; }) .filter(resp -> resp != Configuration.EMPTY) .subscribe(resp -> { LOGGER.info("[[type=Capa.Config.triggerListener]] receive changes and trigger listeners,response:{}", resp); resp.triggers(resp.getConfigurationItem()); }); } private Flux> doSub(String applicationName, String configurationName, String group, String label, Map metadata, TypeRef type, String appId) { Configuration configuration = getConfiguration(applicationName, configurationName); if (Objects.equals(configuration, Configuration.EMPTY)) { return Flux.empty(); } return Flux .create(fluxSink -> { configuration.addListener(configurationItem -> { LOGGER.info("[[type=Capa.Config.listenerOnChange]] listener onChanges, configurationItem key:{}", configurationItem.getKey()); fluxSink.next(configurationItem); }); }) .map(resp -> (ConfigurationItem) resp) .map(resp -> convertToSubscribeResp(resp, appId)); } private SubscribeResp convertToSubscribeResp(ConfigurationItem conf, String appId) { SubscribeResp subscribeResp = new SubscribeResp<>(); subscribeResp.setItems(Lists.newArrayList(conf)); subscribeResp.setAppId(appId); subscribeResp.setStoreName(CapaConfigurationProperties.Settings.getStoreNames().get(0)); return subscribeResp; } /** * get current version * ps:version can be null * * @param applicationName applicationName * @param configurationName configurationName * @return current version */ private String getCurVersion(String applicationName, String configurationName) { String version = null; ConcurrentHashMap> configVersionMap = versionMap.get(applicationName); if (configVersionMap != null && configVersionMap.containsKey(configurationName)) { version = configVersionMap.get(configurationName).getClientConfigurationVersion(); } return version; } /** * @param applicationName applicationName * @param configurationName configurationName * @param type type of content * @param contentSdkBytes content value with SdkBytes type * @param version new version * @param T * @return return the new value or null if not actually update */ private Configuration updateConfigurationItem(String applicationName, String configurationName, TypeRef type, SdkBytes contentSdkBytes, String version) { ConcurrentHashMap> configMap = versionMap.get(applicationName); // in fact,configMap.get(configurationName) is always not null, as it has been initialized in initialization process Configuration configuration = (Configuration) configMap.get(configurationName); synchronized (configuration.lock) { // check whether content has been updated by other thread if (configMap.containsKey(configurationName) && Objects.equals(configMap.get(configurationName).getClientConfigurationVersion(), version)) { return null; } // do need to update T content = serializerProcessor.deserialize(contentSdkBytes, type, configurationName); configuration.setClientConfigurationVersion(version); ConfigurationItem configurationItem = Optional.ofNullable(configuration.getConfigurationItem()).orElse(new ConfigurationItem<>()); configurationItem.setContent(content); configuration.setConfigurationItem(configurationItem); configMap.put(configurationName, configuration); LOGGER.info("[[type=Capa.Config.updateConfig]] update config,key configurationName:{},value configuration:{}", configurationName, configuration); return configuration; } } private Configuration initConfigurationItem(String applicationName, String configurationName, TypeRef type, SdkBytes contentSdkBytes, String version) { ConcurrentHashMap> configMap = versionMap.get(applicationName); boolean initApplication = false; if (configMap == null) { configMap = new ConcurrentHashMap<>(); initApplication = true; } Configuration configuration = new Configuration<>(); configuration.setClientConfigurationVersion(version); configuration.getInitialized().compareAndSet(false, true); ConfigurationItem configurationItem = new ConfigurationItem<>(); configurationItem.setKey(configurationName); T content = serializerProcessor.deserialize(contentSdkBytes, type, configurationName); configurationItem.setContent(content); configuration.setConfigurationItem(configurationItem); configMap.put(configurationName, configuration); LOGGER.info("[[type=Capa.Config.initConfig]] process initConfigurationItem,put key configurationName:{},value configuration:{}", configurationName, configuration); if (initApplication) { versionMap.put(applicationName, configMap); LOGGER.info("[[type=Capa.Config.initConfig]] process initConfigurationItem,put key applicationName:{}", applicationName); } return configuration; } private boolean isInitialized(String applicationName, String configurationName) { ConcurrentHashMap> configMap = versionMap.get(applicationName); return configMap != null && configMap.containsKey(configurationName) && configMap.get(configurationName).getInitialized().get(); } private boolean isSubscribed(String applicationName, String configurationName) { ConcurrentHashMap> configMap = versionMap.get(applicationName); return configMap != null && configMap.containsKey(configurationName) && configMap.get(configurationName).getInitialized().get() && configMap.get(configurationName).getSubscribed().get(); } private Configuration getConfiguration(String applicationName, String configurationName) { ConcurrentHashMap> configMap = versionMap.get(applicationName); if (configMap != null && configMap.containsKey(configurationName)) { return configMap.get(configurationName); } return Configuration.EMPTY; } private List> convertToConfigurationList(GetConfigurationResponse response, String configurationName, TypeRef type) { List> list = new ArrayList<>(); if (response == null) { return list; } ConfigurationItem configurationItem = new ConfigurationItem<>(); configurationItem.setKey(configurationName); T content = serializerProcessor.deserialize(response.content(), type, configurationName); configurationItem.setContent(content); list.add(configurationItem); return list; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy