138 lines
6.2 KiB
Java
138 lines
6.2 KiB
Java
package ru.pdguard.config;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
|
import jakarta.enterprise.context.ApplicationScoped;
|
|
import jakarta.inject.Inject;
|
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
|
import org.jboss.logging.Logger;
|
|
import ru.pdguard.mask.MaskMode;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
|
|
/**
|
|
* Список систем, которым разрешено обращаться в модуль, и правила для каждой.
|
|
*
|
|
* <p>Читается из внешнего файла, чтобы настройки менялись без пересборки. Файл
|
|
* перечитывается сам, когда меняется время его изменения; проверка выполняется
|
|
* не чаще раза в секунду, чтобы не ходить в файловую систему на каждом запросе.
|
|
* Файла нет — работают настройки по умолчанию, и сервис поднимается без него.
|
|
*/
|
|
@ApplicationScoped
|
|
public class SystemsConfig {
|
|
|
|
private static final Logger LOG = Logger.getLogger(SystemsConfig.class);
|
|
|
|
/** Имя политики, которая применяется к запросам без заголовка системы. */
|
|
public static final String DEFAULT_SYSTEM = "default";
|
|
|
|
private static final long RECHECK_MILLIS = 1000;
|
|
|
|
/** Описание одной системы в файле настроек. */
|
|
@RegisterForReflection
|
|
public record SystemEntry(Boolean enabled, Boolean demask, String maskMode,
|
|
List<String> types, List<String> requireCompanion) {
|
|
}
|
|
|
|
private final Path file;
|
|
private final ObjectMapper mapper;
|
|
|
|
private volatile Map<String, SystemPolicy> policies = Map.of(DEFAULT_SYSTEM, SystemPolicy.DEFAULT);
|
|
private volatile long fileTimestamp;
|
|
private volatile long lastCheck;
|
|
|
|
@Inject
|
|
public SystemsConfig(@ConfigProperty(name = "pdguard.systems-file", defaultValue = "config/systems.json")
|
|
String path, ObjectMapper mapper) {
|
|
this.file = Path.of(path);
|
|
this.mapper = mapper;
|
|
reload();
|
|
}
|
|
|
|
/** Правила для системы; неизвестная система получает настройки по умолчанию. */
|
|
public SystemPolicy policyFor(String systemId) {
|
|
refreshIfChanged();
|
|
Map<String, SystemPolicy> current = policies;
|
|
SystemPolicy policy = systemId == null ? null : current.get(systemId);
|
|
if (policy != null) {
|
|
return policy;
|
|
}
|
|
return current.getOrDefault(DEFAULT_SYSTEM, SystemPolicy.DEFAULT);
|
|
}
|
|
|
|
/** Известна ли система по имени. */
|
|
public boolean isKnown(String systemId) {
|
|
refreshIfChanged();
|
|
return systemId != null && policies.containsKey(systemId);
|
|
}
|
|
|
|
/** Текущие настройки — для отдачи в административном интерфейсе. */
|
|
public Map<String, SystemPolicy> current() {
|
|
refreshIfChanged();
|
|
return new TreeMap<>(policies);
|
|
}
|
|
|
|
/** Перечитать файл настроек немедленно. */
|
|
public final synchronized void reload() {
|
|
lastCheck = System.currentTimeMillis();
|
|
if (!Files.isReadable(file)) {
|
|
LOG.infof("Файл настроек %s не найден, применяются настройки по умолчанию", file.toAbsolutePath());
|
|
policies = Map.of(DEFAULT_SYSTEM, SystemPolicy.DEFAULT);
|
|
fileTimestamp = 0;
|
|
return;
|
|
}
|
|
try {
|
|
fileTimestamp = Files.getLastModifiedTime(file).toMillis();
|
|
Map<String, SystemEntry> entries = mapper.readValue(Files.readAllBytes(file),
|
|
mapper.getTypeFactory().constructMapType(TreeMap.class, String.class, SystemEntry.class));
|
|
Map<String, SystemPolicy> parsed = new TreeMap<>();
|
|
entries.forEach((name, entry) -> parsed.put(name, toPolicy(entry)));
|
|
parsed.putIfAbsent(DEFAULT_SYSTEM, SystemPolicy.DEFAULT);
|
|
policies = Map.copyOf(parsed);
|
|
LOG.infof("Настройки систем перечитаны из %s: %s", file.toAbsolutePath(), parsed.keySet());
|
|
} catch (IOException | IllegalArgumentException e) {
|
|
// Битый файл не должен ронять работающий сервис: остаются прежние настройки.
|
|
LOG.errorf(e, "Не удалось прочитать %s, продолжаем с прежними настройками", file.toAbsolutePath());
|
|
}
|
|
}
|
|
|
|
private void refreshIfChanged() {
|
|
long now = System.currentTimeMillis();
|
|
if (now - lastCheck < RECHECK_MILLIS) {
|
|
return;
|
|
}
|
|
lastCheck = now;
|
|
try {
|
|
if (!Files.isReadable(file)) {
|
|
return;
|
|
}
|
|
if (Files.getLastModifiedTime(file).toMillis() != fileTimestamp) {
|
|
reload();
|
|
}
|
|
} catch (IOException e) {
|
|
LOG.debugf(e, "Не удалось проверить время изменения %s", file);
|
|
}
|
|
}
|
|
|
|
private static SystemPolicy toPolicy(SystemEntry entry) {
|
|
SystemPolicy base = SystemPolicy.DEFAULT;
|
|
Set<String> types = entry.types() == null ? base.types() : new HashSet<>(entry.types());
|
|
Set<String> companions = entry.requireCompanion() == null
|
|
? base.requireCompanion() : new HashSet<>(entry.requireCompanion());
|
|
MaskMode mode = entry.maskMode() == null
|
|
? base.maskMode() : MaskMode.valueOf(entry.maskMode().toUpperCase(Locale.ROOT));
|
|
return new SystemPolicy(
|
|
entry.enabled() == null || entry.enabled(),
|
|
entry.demask() == null || entry.demask(),
|
|
mode, types, companions);
|
|
}
|
|
}
|