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

io.gravitee.rest.api.services.dynamicproperties.DynamicPropertyScheduler Maven / Gradle / Ivy

There is a newer version: 4.5.5
Show newest version
/*
 * Copyright © 2015 The Gravitee team (http://gravitee.io)
 *
 * 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 io.gravitee.rest.api.services.dynamicproperties;

import static io.gravitee.rest.api.service.common.SecurityContextHelper.authenticateAsSystem;

import io.gravitee.common.cron.CronTrigger;
import io.gravitee.definition.model.Properties;
import io.gravitee.definition.model.Property;
import io.gravitee.node.api.cluster.ClusterManager;
import io.gravitee.rest.api.model.EventType;
import io.gravitee.rest.api.model.UserRoleEntity;
import io.gravitee.rest.api.model.api.ApiDeploymentEntity;
import io.gravitee.rest.api.model.api.ApiEntity;
import io.gravitee.rest.api.model.permissions.RoleScope;
import io.gravitee.rest.api.model.permissions.SystemRole;
import io.gravitee.rest.api.service.ApiService;
import io.gravitee.rest.api.service.common.ExecutionContext;
import io.gravitee.rest.api.service.converter.ApiConverter;
import io.gravitee.rest.api.service.exceptions.TechnicalManagementException;
import io.gravitee.rest.api.services.dynamicproperties.model.DynamicProperty;
import io.gravitee.rest.api.services.dynamicproperties.provider.Provider;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;

/**
 * @author David BRASSELY (david.brassely at graviteesource.com)
 * @author GraviteeSource Team
 */
@Slf4j
public class DynamicPropertyScheduler {

    private final ClusterManager clusterManager;
    private final ApiService apiService;
    private final ApiConverter apiConverter;
    private final String schedule;
    private final ApiEntity api;
    private final ExecutionContext executionContext;
    private Disposable disposable;

    @Builder
    public DynamicPropertyScheduler(
        final ClusterManager clusterManager,
        final ApiService apiService,
        final ApiConverter apiConverter,
        final String schedule,
        final ApiEntity api,
        final ExecutionContext executionContext
    ) {
        this.clusterManager = clusterManager;
        this.apiService = apiService;
        this.apiConverter = apiConverter;
        this.schedule = schedule;
        this.api = api;
        this.executionContext = executionContext;
    }

    public void schedule(final Provider provider) {
        CronTrigger cronTrigger = new CronTrigger(schedule);
        log.debug("[{}] Running dynamic properties scheduler", api.getId());

        disposable =
            Observable
                .defer(() -> Observable.timer(cronTrigger.nextExecutionIn(), TimeUnit.MILLISECONDS))
                .observeOn(Schedulers.computation())
                .filter(aLong -> clusterManager.self().primary())
                .switchMapCompletable(aLong ->
                    provider
                        .get()
                        .flatMapCompletable(dynamicProperties ->
                            Completable.fromRunnable(() -> {
                                log.debug("[{}] Got {} dynamic properties to update", api.getId(), dynamicProperties.size());
                                authenticateAsAdmin();
                                update(dynamicProperties);
                            })
                        )
                        .doOnComplete(() -> log.debug("[{}] Dynamic properties updated", api.getId()))
                )
                .onErrorResumeNext(throwable -> {
                    log.error(
                        "[{}] Unexpected error while getting dynamic properties from provider: {}",
                        api.getId(),
                        provider.name(),
                        throwable
                    );
                    return Completable.complete();
                })
                .repeat()
                .subscribe(() -> {}, throwable -> log.error("Unable to run Dynamic Properties for Api: {}", api.getId()));
    }

    public void cancel() {
        if (disposable != null && !disposable.isDisposed()) {
            disposable.dispose();
        }
    }

    private void authenticateAsAdmin() {
        UserRoleEntity adminRole = new UserRoleEntity();
        adminRole.setScope(RoleScope.ENVIRONMENT);
        adminRole.setName(SystemRole.ADMIN.name());
        authenticateAsSystem("DynamicPropertyUpdater", Set.of(adminRole));
    }

    private void update(List dynamicProperties) {
        // Get latest changes
        ApiEntity latestApi = apiService.findById(executionContext, api.getId());

        List properties = (latestApi.getProperties() != null)
            ? latestApi.getProperties().getProperties()
            : Collections.emptyList();
        List userDefinedProperties = properties.stream().filter(property -> !property.isDynamic()).toList();

        Map propertyMap = properties.stream().collect(Collectors.toMap(Property::getKey, property -> property));

        List updatedProperties = new ArrayList<>();
        boolean needToBeSaved = false;
        for (DynamicProperty dynamicProperty : dynamicProperties) {
            Property property = propertyMap.get(dynamicProperty.getKey());
            if (property == null || property.isDynamic()) {
                updatedProperties.add(dynamicProperty);
            }
            // save properties only if there's something new
            if (property == null || (property.isDynamic() && !property.getValue().equals(dynamicProperty.getValue()))) {
                needToBeSaved = true;
            }
        }

        if (needToBeSaved) {
            // Add previous user-defined properties
            updatedProperties.addAll(userDefinedProperties);

            // Sort properties alphabetically to avoid redeploy if just the order has changed.
            List sortedUpdatedProperties = updatedProperties
                .stream()
                .sorted(Comparator.comparing(Property::getKey))
                .collect(Collectors.toList());
            // Create properties container
            Properties apiProperties = new Properties();
            try {
                apiProperties.setProperties(sortedUpdatedProperties);
            } catch (RuntimeException e) {
                log.error(e.getMessage(), e);
            }
            latestApi.setProperties(apiProperties);

            boolean isSync = apiService.isSynchronized(executionContext, api.getId());

            // Update API
            try {
                log.debug("[{}] Updating API", latestApi.getId());
                apiService.update(executionContext, latestApi.getId(), apiConverter.toUpdateApiEntity(latestApi), false, false);
                log.debug("[{}] API has been updated", latestApi.getId());
            } catch (TechnicalManagementException e) {
                log.error("An error occurred while updating the API with new values of dynamic properties, deployment will be skipped.", e);
                throw e;
            }

            // Do not deploy if there are manual changes to push
            if (isSync) {
                // Publish API only in case of changes
                if (!updatedProperties.containsAll(properties) || !properties.containsAll(updatedProperties)) {
                    log.debug("[{}] Property change detected, API is about to be deployed", api.getId());
                    ApiDeploymentEntity deployEntity = new ApiDeploymentEntity();
                    deployEntity.setDeploymentLabel("Dynamic properties sync");
                    apiService.deploy(executionContext, latestApi.getId(), "dynamic-property-updater", EventType.PUBLISH_API, deployEntity);
                    log.debug("[{}] API as been deployed", api.getId());
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy