0
# Specialized Data Sources
1
2
Specialized data source implementations for specific use cases including JAR-embedded resources and empty data sources for default configurations.
3
4
## Capabilities
5
6
### FileInJarReadableDataSource Class
7
8
The `FileInJarReadableDataSource` class provides read-only access to files embedded within JAR archives, useful for loading default configurations packaged with applications.
9
10
```java { .api }
11
/**
12
* A ReadableDataSource based on jar file. This class can only read file initially
13
* when it loads file - no auto-refresh capability.
14
* Limitations: Default read buffer size is 1 MB, while max allowed buffer size is 4MB.
15
* File size should not exceed the buffer size, or exception will be thrown. Default charset is UTF-8.
16
* @param <T> target data type
17
*/
18
public class FileInJarReadableDataSource<T> extends AbstractDataSource<String, T> {
19
20
// Constants
21
public static final int DEFAULT_BUF_SIZE = 1024 * 1024; // 1 MB
22
public static final int MAX_SIZE = 1024 * 1024 * 4; // 4 MB
23
24
/**
25
* Create a JAR file reader with default settings.
26
* @param jarName the jar file path to read from
27
* @param fileInJarName the file path within the JAR
28
* @param configParser the config decoder (parser)
29
* @throws IOException if JAR file cannot be accessed or file not found in JAR
30
* @throws IllegalArgumentException if jarName or fileInJarName is blank
31
*/
32
public FileInJarReadableDataSource(String jarName, String fileInJarName,
33
Converter<String, T> configParser) throws IOException;
34
35
/**
36
* Create a JAR file reader with custom buffer size.
37
* @param jarName the jar file path to read from
38
* @param fileInJarName the file path within the JAR
39
* @param configParser the config decoder (parser)
40
* @param bufSize buffer size for reading (must be between 0 and MAX_SIZE)
41
* @throws IOException if JAR file cannot be accessed or file not found in JAR
42
* @throws IllegalArgumentException if parameters are invalid
43
*/
44
public FileInJarReadableDataSource(String jarName, String fileInJarName,
45
Converter<String, T> configParser, int bufSize) throws IOException;
46
47
/**
48
* Create a JAR file reader with custom charset.
49
* @param jarName the jar file path to read from
50
* @param fileInJarName the file path within the JAR
51
* @param configParser the config decoder (parser)
52
* @param charset character encoding for reading the file
53
* @throws IOException if JAR file cannot be accessed or file not found in JAR
54
* @throws IllegalArgumentException if charset is null
55
*/
56
public FileInJarReadableDataSource(String jarName, String fileInJarName,
57
Converter<String, T> configParser, Charset charset) throws IOException;
58
59
/**
60
* Create a JAR file reader with full customization.
61
* @param jarName the jar file path to read from
62
* @param fileInJarName the file path within the JAR
63
* @param configParser the config decoder (parser)
64
* @param bufSize buffer size for reading (must be between 0 and MAX_SIZE)
65
* @param charset character encoding for reading the file
66
* @throws IOException if JAR file cannot be accessed or file not found in JAR
67
* @throws IllegalArgumentException if parameters are invalid
68
*/
69
public FileInJarReadableDataSource(String jarName, String fileInJarName,
70
Converter<String, T> configParser, int bufSize, Charset charset) throws IOException;
71
72
/**
73
* Read file content from JAR as string.
74
* @return file content from JAR with specified charset
75
* @throws Exception if JAR reading fails or file size exceeds buffer
76
*/
77
public String readSource() throws Exception;
78
79
/**
80
* Close data source and clean up resources.
81
* @throws Exception if cleanup fails
82
*/
83
public void close() throws Exception;
84
}
85
```
86
87
**Usage Examples:**
88
89
```java
90
// Load default rules from application JAR
91
Converter<String, List<FlowRule>> defaultRuleConverter = source -> {
92
return JSON.parseArray(source, FlowRule.class);
93
};
94
95
// Read default flow rules packaged in the application JAR
96
FileInJarReadableDataSource<List<FlowRule>> defaultRulesDs =
97
new FileInJarReadableDataSource<>(
98
"/path/to/application.jar",
99
"config/default-flow-rules.json",
100
defaultRuleConverter
101
);
102
103
// Load default configuration on startup
104
List<FlowRule> defaultRules = defaultRulesDs.loadConfig();
105
FlowRuleManager.loadRules(defaultRules);
106
107
// Library configuration pattern
108
public class SentinelConfigurationManager {
109
private static final String CONFIG_JAR = "sentinel-config-1.0.jar";
110
111
public static void loadDefaultConfiguration() throws IOException {
112
// Load different rule types from embedded JAR
113
loadDefaultFlowRules();
114
loadDefaultDegradeRules();
115
loadDefaultSystemRules();
116
}
117
118
private static void loadDefaultFlowRules() throws IOException {
119
FileInJarReadableDataSource<List<FlowRule>> ds =
120
new FileInJarReadableDataSource<>(
121
CONFIG_JAR,
122
"defaults/flow-rules.json",
123
source -> JSON.parseArray(source, FlowRule.class)
124
);
125
126
FlowRuleManager.loadRules(ds.loadConfig());
127
ds.close();
128
}
129
130
private static void loadDefaultDegradeRules() throws IOException {
131
FileInJarReadableDataSource<List<DegradeRule>> ds =
132
new FileInJarReadableDataSource<>(
133
CONFIG_JAR,
134
"defaults/degrade-rules.xml",
135
xmlConverter,
136
2 * 1024 * 1024 // 2MB buffer for larger XML files
137
);
138
139
DegradeRuleManager.loadRules(ds.loadConfig());
140
ds.close();
141
}
142
}
143
144
// Reading configuration templates
145
FileInJarReadableDataSource<Map<String, Object>> templateDs =
146
new FileInJarReadableDataSource<>(
147
"config-templates.jar",
148
"templates/microservice-config.yaml",
149
source -> {
150
Yaml yaml = new Yaml();
151
return yaml.load(source);
152
}
153
);
154
155
Map<String, Object> template = templateDs.loadConfig();
156
```
157
158
### EmptyDataSource Class
159
160
The `EmptyDataSource` class provides a no-op data source implementation for default scenarios where no external configuration is needed.
161
162
```java { .api }
163
/**
164
* A ReadableDataSource based on nothing. getProperty() will always return the same cached
165
* SentinelProperty that does nothing.
166
* This class is used when we want to use default settings instead of configs from ReadableDataSource.
167
*/
168
public final class EmptyDataSource implements ReadableDataSource<Object, Object> {
169
170
/**
171
* Singleton instance of empty data source.
172
*/
173
public static final ReadableDataSource<Object, Object> EMPTY_DATASOURCE;
174
175
/**
176
* Load config (always returns null).
177
* @return null
178
* @throws Exception never throws
179
*/
180
public Object loadConfig() throws Exception;
181
182
/**
183
* Read source (always returns null).
184
* @return null
185
* @throws Exception never throws
186
*/
187
public Object readSource() throws Exception;
188
189
/**
190
* Get property (returns no-op property).
191
* @return SentinelProperty that performs no operations
192
*/
193
public SentinelProperty<Object> getProperty();
194
195
/**
196
* Close data source (no-op).
197
* @throws Exception never throws
198
*/
199
public void close() throws Exception;
200
}
201
```
202
203
**Usage Examples:**
204
205
```java
206
// Default configuration pattern
207
public class ConfigurationBuilder {
208
private ReadableDataSource<String, List<FlowRule>> flowRuleSource = EmptyDataSource.EMPTY_DATASOURCE;
209
private ReadableDataSource<String, List<DegradeRule>> degradeRuleSource = EmptyDataSource.EMPTY_DATASOURCE;
210
211
public ConfigurationBuilder withFlowRuleSource(ReadableDataSource<String, List<FlowRule>> source) {
212
this.flowRuleSource = source;
213
return this;
214
}
215
216
public ConfigurationBuilder withDegradeRuleSource(ReadableDataSource<String, List<DegradeRule>> source) {
217
this.degradeRuleSource = source;
218
return this;
219
}
220
221
public void build() {
222
// Use empty data source as fallback
223
if (flowRuleSource == EmptyDataSource.EMPTY_DATASOURCE) {
224
System.out.println("Using default flow rule configuration");
225
} else {
226
flowRuleSource.getProperty().addListener(FlowRuleManager::loadRules);
227
}
228
229
if (degradeRuleSource == EmptyDataSource.EMPTY_DATASOURCE) {
230
System.out.println("Using default degrade rule configuration");
231
} else {
232
degradeRuleSource.getProperty().addListener(DegradeRuleManager::loadRules);
233
}
234
}
235
}
236
237
// Conditional data source setup
238
public class DataSourceFactory {
239
public static ReadableDataSource<String, List<FlowRule>> createFlowRuleSource(String configPath) {
240
if (configPath == null || configPath.trim().isEmpty()) {
241
System.out.println("No flow rule configuration specified, using empty data source");
242
return EmptyDataSource.EMPTY_DATASOURCE;
243
}
244
245
try {
246
return new FileRefreshableDataSource<>(
247
new File(configPath),
248
source -> JSON.parseArray(source, FlowRule.class)
249
);
250
} catch (FileNotFoundException e) {
251
System.err.println("Flow rule file not found: " + configPath + ", using empty data source");
252
return EmptyDataSource.EMPTY_DATASOURCE;
253
}
254
}
255
}
256
257
// Testing scenarios
258
@Test
259
public void testWithoutExternalConfiguration() {
260
ReadableDataSource<Object, Object> emptyDs = EmptyDataSource.EMPTY_DATASOURCE;
261
262
// Should not throw exceptions
263
Object config = emptyDs.loadConfig();
264
Object source = emptyDs.readSource();
265
SentinelProperty<Object> property = emptyDs.getProperty();
266
267
assertNull(config);
268
assertNull(source);
269
assertNotNull(property);
270
271
// Property operations should be no-ops
272
property.addListener(value -> fail("Should not be called"));
273
property.updateValue("test"); // Should not trigger listener
274
275
emptyDs.close(); // Should not throw
276
}
277
```
278
279
## Integration Patterns
280
281
### Fallback Configuration Strategy
282
283
Use specialized data sources as fallbacks in configuration hierarchies:
284
285
```java
286
public class HierarchicalConfigurationManager {
287
288
public static ReadableDataSource<String, List<FlowRule>> createFlowRuleSource() {
289
// Try external file first
290
String externalConfigPath = System.getProperty("sentinel.flow.rules.file");
291
if (externalConfigPath != null) {
292
try {
293
return new FileRefreshableDataSource<>(
294
new File(externalConfigPath),
295
source -> JSON.parseArray(source, FlowRule.class)
296
);
297
} catch (FileNotFoundException e) {
298
System.err.println("External config file not found: " + externalConfigPath);
299
}
300
}
301
302
// Try JAR-embedded defaults
303
String jarPath = getApplicationJarPath();
304
if (jarPath != null) {
305
try {
306
return new FileInJarReadableDataSource<>(
307
jarPath,
308
"config/default-flow-rules.json",
309
source -> JSON.parseArray(source, FlowRule.class)
310
);
311
} catch (IOException e) {
312
System.err.println("Could not load default rules from JAR: " + e.getMessage());
313
}
314
}
315
316
// Final fallback to empty data source
317
System.out.println("Using empty data source - no flow rules will be loaded");
318
return EmptyDataSource.EMPTY_DATASOURCE;
319
}
320
}
321
```
322
323
### Resource Management
324
325
Properly manage resources when using JAR-based data sources:
326
327
```java
328
public class ConfigurationLoader implements AutoCloseable {
329
private final List<ReadableDataSource<?, ?>> dataSources = new ArrayList<>();
330
331
public void loadConfiguration(String applicationJar) throws IOException {
332
// Load multiple configuration files from JAR
333
String[] configFiles = {
334
"config/flow-rules.json",
335
"config/degrade-rules.json",
336
"config/system-rules.json"
337
};
338
339
for (String configFile : configFiles) {
340
try {
341
FileInJarReadableDataSource<List<Rule>> ds =
342
new FileInJarReadableDataSource<>(
343
applicationJar,
344
configFile,
345
source -> parseRules(source, configFile)
346
);
347
348
dataSources.add(ds);
349
350
// Load configuration immediately
351
List<Rule> rules = ds.loadConfig();
352
applyRules(rules, configFile);
353
354
} catch (IOException e) {
355
System.err.println("Failed to load " + configFile + ": " + e.getMessage());
356
}
357
}
358
}
359
360
@Override
361
public void close() throws Exception {
362
for (ReadableDataSource<?, ?> ds : dataSources) {
363
try {
364
ds.close();
365
} catch (Exception e) {
366
System.err.println("Error closing data source: " + e.getMessage());
367
}
368
}
369
dataSources.clear();
370
}
371
}
372
```
373
374
## Limitations and Considerations
375
376
### FileInJarReadableDataSource Limitations
377
378
- **No auto-refresh**: JAR files are read-only and don't support modification detection
379
- **Buffer size limits**: Files larger than buffer size will cause exceptions
380
- **JAR file access**: Requires proper file system access to JAR files
381
- **Resource cleanup**: Always close data sources to release JAR file handles
382
383
### EmptyDataSource Characteristics
384
385
- **Singleton pattern**: Use `EMPTY_DATASOURCE` constant, don't create new instances
386
- **No-op behavior**: All operations return null or perform no actions
387
- **Property system**: Returns a no-op property that ignores listener registrations
388
- **Testing friendly**: Safe to use in unit tests and default configurations