Extensible data source implementations for Sentinel flow control and circuit breaker library.
—
Specialized data source implementations for specific use cases including JAR-embedded resources and empty data sources for default configurations.
The FileInJarReadableDataSource class provides read-only access to files embedded within JAR archives, useful for loading default configurations packaged with applications.
/**
* A ReadableDataSource based on jar file. This class can only read file initially
* when it loads file - no auto-refresh capability.
* Limitations: Default read buffer size is 1 MB, while max allowed buffer size is 4MB.
* File size should not exceed the buffer size, or exception will be thrown. Default charset is UTF-8.
* @param <T> target data type
*/
public class FileInJarReadableDataSource<T> extends AbstractDataSource<String, T> {
// Constants
public static final int DEFAULT_BUF_SIZE = 1024 * 1024; // 1 MB
public static final int MAX_SIZE = 1024 * 1024 * 4; // 4 MB
/**
* Create a JAR file reader with default settings.
* @param jarName the jar file path to read from
* @param fileInJarName the file path within the JAR
* @param configParser the config decoder (parser)
* @throws IOException if JAR file cannot be accessed or file not found in JAR
* @throws IllegalArgumentException if jarName or fileInJarName is blank
*/
public FileInJarReadableDataSource(String jarName, String fileInJarName,
Converter<String, T> configParser) throws IOException;
/**
* Create a JAR file reader with custom buffer size.
* @param jarName the jar file path to read from
* @param fileInJarName the file path within the JAR
* @param configParser the config decoder (parser)
* @param bufSize buffer size for reading (must be between 0 and MAX_SIZE)
* @throws IOException if JAR file cannot be accessed or file not found in JAR
* @throws IllegalArgumentException if parameters are invalid
*/
public FileInJarReadableDataSource(String jarName, String fileInJarName,
Converter<String, T> configParser, int bufSize) throws IOException;
/**
* Create a JAR file reader with custom charset.
* @param jarName the jar file path to read from
* @param fileInJarName the file path within the JAR
* @param configParser the config decoder (parser)
* @param charset character encoding for reading the file
* @throws IOException if JAR file cannot be accessed or file not found in JAR
* @throws IllegalArgumentException if charset is null
*/
public FileInJarReadableDataSource(String jarName, String fileInJarName,
Converter<String, T> configParser, Charset charset) throws IOException;
/**
* Create a JAR file reader with full customization.
* @param jarName the jar file path to read from
* @param fileInJarName the file path within the JAR
* @param configParser the config decoder (parser)
* @param bufSize buffer size for reading (must be between 0 and MAX_SIZE)
* @param charset character encoding for reading the file
* @throws IOException if JAR file cannot be accessed or file not found in JAR
* @throws IllegalArgumentException if parameters are invalid
*/
public FileInJarReadableDataSource(String jarName, String fileInJarName,
Converter<String, T> configParser, int bufSize, Charset charset) throws IOException;
/**
* Read file content from JAR as string.
* @return file content from JAR with specified charset
* @throws Exception if JAR reading fails or file size exceeds buffer
*/
public String readSource() throws Exception;
/**
* Close data source and clean up resources.
* @throws Exception if cleanup fails
*/
public void close() throws Exception;
}Usage Examples:
// Load default rules from application JAR
Converter<String, List<FlowRule>> defaultRuleConverter = source -> {
return JSON.parseArray(source, FlowRule.class);
};
// Read default flow rules packaged in the application JAR
FileInJarReadableDataSource<List<FlowRule>> defaultRulesDs =
new FileInJarReadableDataSource<>(
"/path/to/application.jar",
"config/default-flow-rules.json",
defaultRuleConverter
);
// Load default configuration on startup
List<FlowRule> defaultRules = defaultRulesDs.loadConfig();
FlowRuleManager.loadRules(defaultRules);
// Library configuration pattern
public class SentinelConfigurationManager {
private static final String CONFIG_JAR = "sentinel-config-1.0.jar";
public static void loadDefaultConfiguration() throws IOException {
// Load different rule types from embedded JAR
loadDefaultFlowRules();
loadDefaultDegradeRules();
loadDefaultSystemRules();
}
private static void loadDefaultFlowRules() throws IOException {
FileInJarReadableDataSource<List<FlowRule>> ds =
new FileInJarReadableDataSource<>(
CONFIG_JAR,
"defaults/flow-rules.json",
source -> JSON.parseArray(source, FlowRule.class)
);
FlowRuleManager.loadRules(ds.loadConfig());
ds.close();
}
private static void loadDefaultDegradeRules() throws IOException {
FileInJarReadableDataSource<List<DegradeRule>> ds =
new FileInJarReadableDataSource<>(
CONFIG_JAR,
"defaults/degrade-rules.xml",
xmlConverter,
2 * 1024 * 1024 // 2MB buffer for larger XML files
);
DegradeRuleManager.loadRules(ds.loadConfig());
ds.close();
}
}
// Reading configuration templates
FileInJarReadableDataSource<Map<String, Object>> templateDs =
new FileInJarReadableDataSource<>(
"config-templates.jar",
"templates/microservice-config.yaml",
source -> {
Yaml yaml = new Yaml();
return yaml.load(source);
}
);
Map<String, Object> template = templateDs.loadConfig();The EmptyDataSource class provides a no-op data source implementation for default scenarios where no external configuration is needed.
/**
* A ReadableDataSource based on nothing. getProperty() will always return the same cached
* SentinelProperty that does nothing.
* This class is used when we want to use default settings instead of configs from ReadableDataSource.
*/
public final class EmptyDataSource implements ReadableDataSource<Object, Object> {
/**
* Singleton instance of empty data source.
*/
public static final ReadableDataSource<Object, Object> EMPTY_DATASOURCE;
/**
* Load config (always returns null).
* @return null
* @throws Exception never throws
*/
public Object loadConfig() throws Exception;
/**
* Read source (always returns null).
* @return null
* @throws Exception never throws
*/
public Object readSource() throws Exception;
/**
* Get property (returns no-op property).
* @return SentinelProperty that performs no operations
*/
public SentinelProperty<Object> getProperty();
/**
* Close data source (no-op).
* @throws Exception never throws
*/
public void close() throws Exception;
}Usage Examples:
// Default configuration pattern
public class ConfigurationBuilder {
private ReadableDataSource<String, List<FlowRule>> flowRuleSource = EmptyDataSource.EMPTY_DATASOURCE;
private ReadableDataSource<String, List<DegradeRule>> degradeRuleSource = EmptyDataSource.EMPTY_DATASOURCE;
public ConfigurationBuilder withFlowRuleSource(ReadableDataSource<String, List<FlowRule>> source) {
this.flowRuleSource = source;
return this;
}
public ConfigurationBuilder withDegradeRuleSource(ReadableDataSource<String, List<DegradeRule>> source) {
this.degradeRuleSource = source;
return this;
}
public void build() {
// Use empty data source as fallback
if (flowRuleSource == EmptyDataSource.EMPTY_DATASOURCE) {
System.out.println("Using default flow rule configuration");
} else {
flowRuleSource.getProperty().addListener(FlowRuleManager::loadRules);
}
if (degradeRuleSource == EmptyDataSource.EMPTY_DATASOURCE) {
System.out.println("Using default degrade rule configuration");
} else {
degradeRuleSource.getProperty().addListener(DegradeRuleManager::loadRules);
}
}
}
// Conditional data source setup
public class DataSourceFactory {
public static ReadableDataSource<String, List<FlowRule>> createFlowRuleSource(String configPath) {
if (configPath == null || configPath.trim().isEmpty()) {
System.out.println("No flow rule configuration specified, using empty data source");
return EmptyDataSource.EMPTY_DATASOURCE;
}
try {
return new FileRefreshableDataSource<>(
new File(configPath),
source -> JSON.parseArray(source, FlowRule.class)
);
} catch (FileNotFoundException e) {
System.err.println("Flow rule file not found: " + configPath + ", using empty data source");
return EmptyDataSource.EMPTY_DATASOURCE;
}
}
}
// Testing scenarios
@Test
public void testWithoutExternalConfiguration() {
ReadableDataSource<Object, Object> emptyDs = EmptyDataSource.EMPTY_DATASOURCE;
// Should not throw exceptions
Object config = emptyDs.loadConfig();
Object source = emptyDs.readSource();
SentinelProperty<Object> property = emptyDs.getProperty();
assertNull(config);
assertNull(source);
assertNotNull(property);
// Property operations should be no-ops
property.addListener(value -> fail("Should not be called"));
property.updateValue("test"); // Should not trigger listener
emptyDs.close(); // Should not throw
}Use specialized data sources as fallbacks in configuration hierarchies:
public class HierarchicalConfigurationManager {
public static ReadableDataSource<String, List<FlowRule>> createFlowRuleSource() {
// Try external file first
String externalConfigPath = System.getProperty("sentinel.flow.rules.file");
if (externalConfigPath != null) {
try {
return new FileRefreshableDataSource<>(
new File(externalConfigPath),
source -> JSON.parseArray(source, FlowRule.class)
);
} catch (FileNotFoundException e) {
System.err.println("External config file not found: " + externalConfigPath);
}
}
// Try JAR-embedded defaults
String jarPath = getApplicationJarPath();
if (jarPath != null) {
try {
return new FileInJarReadableDataSource<>(
jarPath,
"config/default-flow-rules.json",
source -> JSON.parseArray(source, FlowRule.class)
);
} catch (IOException e) {
System.err.println("Could not load default rules from JAR: " + e.getMessage());
}
}
// Final fallback to empty data source
System.out.println("Using empty data source - no flow rules will be loaded");
return EmptyDataSource.EMPTY_DATASOURCE;
}
}Properly manage resources when using JAR-based data sources:
public class ConfigurationLoader implements AutoCloseable {
private final List<ReadableDataSource<?, ?>> dataSources = new ArrayList<>();
public void loadConfiguration(String applicationJar) throws IOException {
// Load multiple configuration files from JAR
String[] configFiles = {
"config/flow-rules.json",
"config/degrade-rules.json",
"config/system-rules.json"
};
for (String configFile : configFiles) {
try {
FileInJarReadableDataSource<List<Rule>> ds =
new FileInJarReadableDataSource<>(
applicationJar,
configFile,
source -> parseRules(source, configFile)
);
dataSources.add(ds);
// Load configuration immediately
List<Rule> rules = ds.loadConfig();
applyRules(rules, configFile);
} catch (IOException e) {
System.err.println("Failed to load " + configFile + ": " + e.getMessage());
}
}
}
@Override
public void close() throws Exception {
for (ReadableDataSource<?, ?> ds : dataSources) {
try {
ds.close();
} catch (Exception e) {
System.err.println("Error closing data source: " + e.getMessage());
}
}
dataSources.clear();
}
}EMPTY_DATASOURCE constant, don't create new instancesInstall with Tessl CLI
npx tessl i tessl/maven-com-alibaba-csp--sentinel-datasource-extension