CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-alibaba-csp--sentinel-datasource-extension

Extensible data source implementations for Sentinel flow control and circuit breaker library.

Pending
Overview
Eval results
Files

specialized-datasources.mddocs/

Specialized Data Sources

Specialized data source implementations for specific use cases including JAR-embedded resources and empty data sources for default configurations.

Capabilities

FileInJarReadableDataSource Class

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();

EmptyDataSource Class

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
}

Integration Patterns

Fallback Configuration Strategy

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;
    }
}

Resource Management

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();
    }
}

Limitations and Considerations

FileInJarReadableDataSource Limitations

  • No auto-refresh: JAR files are read-only and don't support modification detection
  • Buffer size limits: Files larger than buffer size will cause exceptions
  • JAR file access: Requires proper file system access to JAR files
  • Resource cleanup: Always close data sources to release JAR file handles

EmptyDataSource Characteristics

  • Singleton pattern: Use EMPTY_DATASOURCE constant, don't create new instances
  • No-op behavior: All operations return null or perform no actions
  • Property system: Returns a no-op property that ignores listener registrations
  • Testing friendly: Safe to use in unit tests and default configurations

Install with Tessl CLI

npx tessl i tessl/maven-com-alibaba-csp--sentinel-datasource-extension

docs

base-classes.md

core-interfaces.md

file-datasources.md

index.md

specialized-datasources.md

tile.json