001/*
002 * Copyright (c) 2023. Baidu, Inc. All Rights Reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *    http://www.apache.org/licenses/LICENSE-2.0
008 * Unless required by applicable law or agreed to in writing,
009 * software distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and limitations under the License.
012 */
013
014package com.baidu.bifromq.plugin.settingprovider;
015
016import com.github.benmanes.caffeine.cache.Cache;
017import com.github.benmanes.caffeine.cache.Caffeine;
018import com.github.benmanes.caffeine.cache.Scheduler;
019import java.time.Duration;
020import java.util.function.Predicate;
021import lombok.extern.slf4j.Slf4j;
022
023@Slf4j
024public enum Setting {
025    DebugModeEnabled(Boolean.class, val -> true, false),
026    BoostModeEnabled(Boolean.class, val -> true, false),
027    MaxTopicLevelLength(Integer.class, val -> (int) val > 0, 40),
028    MaxTopicLevels(Integer.class, val -> (int) val > 0, 16),
029    MaxTopicLength(Integer.class, val -> (int) val > 0 && (int) val < 65536, 255),
030    MaxSharedGroupMembers(Integer.class, val -> (int) val > 0, 200),
031    MaxTopicFiltersPerInbox(Integer.class, val -> (int) val > 0, 100),
032    MsgPubPerSec(Integer.class, val -> (int) val >= 0 && (int) val <= 1000, 200),
033    InBoundBandWidth(Long.class, val -> (long) val >= 0, 512 * 1024L),
034    OutBoundBandWidth(Long.class, val -> (long) val >= 0, 512 * 1024L),
035    ForceTransient(Boolean.class, val -> true, false),
036    ByPassPermCheckError(Boolean.class, val -> true, true),
037    MaxUserPayloadBytes(Integer.class, val -> (int) val > 0 && (int) val <= 1024 * 1024, 256 * 1024),
038    MaxTopicFiltersPerSub(Integer.class, val -> (int) val > 0 && (int) val <= 100, 10),
039    OfflineExpireTimeSeconds(Long.class, val -> (long) val > 0, 24 * 60 * 60L),
040    OfflineQueueSize(Integer.class, val -> (int) val > 0 && (int) val <= 100000, 1000),
041    OfflineOverflowDropOldest(Boolean.class, val -> true, false),
042    RetainedTopicLimit(Integer.class, val -> (int) val >= 0, 10),
043    RetainMessageMatchLimit(Integer.class, val -> (int) val >= 0, 10),
044    RetainEnabled(Boolean.class, val -> true, true),
045    DistReservedUnitInterval(Long.class, val -> (long) val > 0 && (long) val <= 0xFFFFFFFFL, 0xFFFFFFFFL),
046    DistLimitUnitInterval(Long.class, val -> (long) val >= 0 && (long) val <= 0xFFFFFFFFL, 0L);
047
048    public final Class<?> valueType;
049    final Predicate<Object> validator;
050    final Object initial;
051    final Cache<String, Object> currentVals;
052
053    Setting(Class<?> valueType, Predicate<Object> validator, Object initial) {
054        this.valueType = valueType;
055        this.validator = validator;
056        initial = resolve(initial);
057        assert isValid(initial);
058        this.initial = initial;
059        currentVals = Caffeine.newBuilder()
060            .expireAfterWrite(Duration.ofSeconds(5))
061            .scheduler(Scheduler.systemScheduler())
062            .build();
063    }
064
065    /**
066     * The current effective setting's value for given tenant
067     *
068     * @param tenantId the id of the calling tenant
069     * @return The effective value of the setting for the client
070     */
071    public <R> R current(String tenantId) {
072        return (R) currentVals.asMap().getOrDefault(tenantId, initial);
073    }
074
075    /**
076     * Validate if provided value is a valid for the setting
077     *
078     * @param val the setting value to be verified
079     * @return true if the value is valid
080     */
081    public <R> boolean isValid(R val) {
082        if (!valueType.isInstance(val)) {
083            return false;
084        }
085        return this.validator.test(val);
086    }
087
088    // intentionally package level access
089    void current(String tenantId, Object newVal) {
090        assert isValid(newVal);
091        if (!newVal.equals(initial)) {
092            // only cache changed value
093            currentVals.put(tenantId, newVal);
094        } else {
095            // revert to initial
096            currentVals.invalidate(tenantId);
097        }
098    }
099
100    Object resolve(Object initial) {
101        String override = System.getProperty(name());
102        if (override != null) {
103            try {
104                if (valueType == Integer.class) {
105                    return Integer.parseInt(override);
106                }
107                if (valueType == Long.class) {
108                    return Long.parseLong(override);
109                }
110                if (valueType == Boolean.class) {
111                    return Boolean.parseBoolean(override);
112                }
113            } catch (Throwable e) {
114                log.error("Unable to parse setting value from system property: setting={}, value={}",
115                    name(), override);
116                return initial;
117            }
118        }
119        return initial;
120    }
121}