0
# Bundle System
1
2
Dropwizard's bundle system provides reusable application components that can be registered during bootstrap to add functionality like SSL certificate reloading, asset serving, database migrations, and more.
3
4
## Capabilities
5
6
### ConfiguredBundle Interface
7
8
Base interface for creating reusable application components that can access configuration and modify the application environment.
9
10
```java { .api }
11
/**
12
* A reusable bundle of functionality, used to define blocks of application behavior
13
* that are conditional on configuration parameters.
14
* @param <T> the required configuration interface
15
*/
16
public interface ConfiguredBundle<T> {
17
/**
18
* Initializes the environment.
19
* @param configuration the configuration object
20
* @param environment the application's Environment
21
* @throws Exception if something goes wrong
22
*/
23
default void run(T configuration, Environment environment) throws Exception;
24
25
/**
26
* Initializes the application bootstrap.
27
* @param bootstrap the application bootstrap
28
*/
29
default void initialize(Bootstrap<?> bootstrap);
30
}
31
```
32
33
**Usage Examples:**
34
35
```java
36
public class DatabaseBundle implements ConfiguredBundle<MyConfiguration> {
37
@Override
38
public void initialize(Bootstrap<?> bootstrap) {
39
// Configure object mapper for database-specific serialization
40
bootstrap.getObjectMapper().registerModule(new JavaTimeModule());
41
}
42
43
@Override
44
public void run(MyConfiguration configuration, Environment environment) throws Exception {
45
// Set up database connection pool
46
final DataSource dataSource = configuration.getDataSourceFactory()
47
.build(environment.metrics(), "database");
48
49
// Register health check
50
environment.healthChecks().register("database",
51
new DatabaseHealthCheck(dataSource));
52
53
// Register managed object for connection pool lifecycle
54
environment.lifecycle().manage(new DataSourceManager(dataSource));
55
}
56
}
57
58
// Register in Application.initialize()
59
@Override
60
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
61
bootstrap.addBundle(new DatabaseBundle());
62
}
63
```
64
65
### SslReloadBundle
66
67
Built-in bundle that provides SSL certificate reloading capability via an admin task, useful for certificate rotation without application restart.
68
69
```java { .api }
70
/**
71
* Bundle that gathers all the ssl connectors and registers an admin task that will
72
* refresh ssl configuration on request.
73
*/
74
public class SslReloadBundle implements ConfiguredBundle<Configuration> {
75
/**
76
* Creates a new SSL reload bundle.
77
*/
78
public SslReloadBundle();
79
80
@Override
81
public void run(Configuration configuration, Environment environment) throws Exception;
82
83
@Override
84
public void initialize(Bootstrap<?> bootstrap);
85
}
86
```
87
88
The SSL reload bundle automatically:
89
- Discovers all SSL connectors in the server configuration
90
- Registers an admin task at `/tasks/ssl-reload` for triggering certificate reloads
91
- Provides logging for reload operations
92
93
**Usage Examples:**
94
95
```java
96
@Override
97
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
98
// Add SSL reload capability
99
bootstrap.addBundle(new SslReloadBundle());
100
}
101
```
102
103
After adding this bundle, you can reload SSL certificates by sending a POST request to the admin interface:
104
105
```bash
106
curl -X POST http://localhost:8081/tasks/ssl-reload
107
```
108
109
### SslReloadTask
110
111
The admin task that performs the actual SSL certificate reloading operation.
112
113
```java { .api }
114
/**
115
* Admin task for reloading SSL certificates.
116
*/
117
public class SslReloadTask extends Task {
118
/**
119
* Creates a new SSL reload task.
120
*/
121
public SslReloadTask();
122
123
@Override
124
public void execute(Map<String, List<String>> parameters, PrintWriter output) throws Exception;
125
}
126
```
127
128
This task is automatically registered by SslReloadBundle and can also be manually registered:
129
130
```java
131
environment.admin().addTask(new SslReloadTask());
132
```
133
134
## Creating Custom Bundles
135
136
### Simple Bundle Example
137
138
```java
139
public class MetricsBundle implements ConfiguredBundle<Configuration> {
140
@Override
141
public void initialize(Bootstrap<?> bootstrap) {
142
// Configure metrics registry during bootstrap
143
final MetricRegistry metrics = bootstrap.getMetricRegistry();
144
metrics.register("jvm.uptime", new UptimeGauge());
145
}
146
147
@Override
148
public void run(Configuration configuration, Environment environment) throws Exception {
149
// Set up metrics reporters
150
final ConsoleReporter reporter = ConsoleReporter.forRegistry(environment.metrics())
151
.convertRatesTo(TimeUnit.SECONDS)
152
.convertDurationsTo(TimeUnit.MILLISECONDS)
153
.build();
154
155
// Start reporting every 60 seconds
156
reporter.start(60, TimeUnit.SECONDS);
157
158
// Register as managed object to stop when application shuts down
159
environment.lifecycle().manage(new Managed() {
160
@Override
161
public void start() throws Exception {
162
// Already started above
163
}
164
165
@Override
166
public void stop() throws Exception {
167
reporter.stop();
168
}
169
});
170
}
171
}
172
```
173
174
### Parameterized Bundle Example
175
176
```java
177
public class CorsBundle<T extends Configuration> implements ConfiguredBundle<T> {
178
private final Function<T, CorsConfiguration> configExtractor;
179
180
public CorsBundle(Function<T, CorsConfiguration> configExtractor) {
181
this.configExtractor = configExtractor;
182
}
183
184
@Override
185
public void run(T configuration, Environment environment) throws Exception {
186
final CorsConfiguration corsConfig = configExtractor.apply(configuration);
187
188
if (corsConfig.isEnabled()) {
189
final FilterRegistration.Dynamic cors = environment.servlets()
190
.addFilter("CORS", CrossOriginFilter.class);
191
192
cors.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
193
cors.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM,
194
String.join(",", corsConfig.getAllowedOrigins()));
195
cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM,
196
String.join(",", corsConfig.getAllowedHeaders()));
197
cors.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM,
198
String.join(",", corsConfig.getAllowedMethods()));
199
}
200
}
201
}
202
203
// Usage
204
@Override
205
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
206
bootstrap.addBundle(new CorsBundle<>(MyConfiguration::getCorsConfiguration));
207
}
208
```
209
210
### Asset Bundle Example
211
212
```java
213
public class AssetsBundle implements ConfiguredBundle<Configuration> {
214
private final String resourcePath;
215
private final String uriPath;
216
private final String indexFile;
217
218
public AssetsBundle(String resourcePath, String uriPath, String indexFile) {
219
this.resourcePath = resourcePath;
220
this.uriPath = uriPath;
221
this.indexFile = indexFile;
222
}
223
224
@Override
225
public void run(Configuration configuration, Environment environment) throws Exception {
226
// Register servlet for serving static assets
227
final ServletRegistration.Dynamic servlet = environment.servlets()
228
.addServlet("assets", new AssetServlet(resourcePath, uriPath, indexFile));
229
230
servlet.addMapping(uriPath + "*");
231
servlet.setInitParameter("dirAllowed", "false");
232
servlet.setInitParameter("etags", "true");
233
}
234
}
235
236
// Usage
237
@Override
238
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
239
// Serve static assets from /assets/ directory at /ui/ path
240
bootstrap.addBundle(new AssetsBundle("/assets/", "/ui/", "index.html"));
241
}
242
```
243
244
### Migration Bundle Example
245
246
```java
247
public abstract class MigrationsBundle<T extends Configuration> implements ConfiguredBundle<T> {
248
@Override
249
public void initialize(Bootstrap<?> bootstrap) {
250
// Add migration command to CLI
251
bootstrap.addCommand(new DbMigrateCommand<>("db migrate", this));
252
bootstrap.addCommand(new DbStatusCommand<>("db status", this));
253
}
254
255
@Override
256
public void run(T configuration, Environment environment) throws Exception {
257
// Register health check for database connectivity
258
final DataSource dataSource = getDataSourceFactory(configuration)
259
.build(environment.metrics(), "migrations");
260
261
environment.healthChecks().register("database",
262
new DatabaseHealthCheck(dataSource));
263
}
264
265
/**
266
* Override this method to provide the data source factory from your configuration.
267
*/
268
public abstract DataSourceFactory getDataSourceFactory(T configuration);
269
}
270
271
// Usage
272
@Override
273
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
274
bootstrap.addBundle(new MigrationsBundle<MyConfiguration>() {
275
@Override
276
public DataSourceFactory getDataSourceFactory(MyConfiguration configuration) {
277
return configuration.getDataSourceFactory();
278
}
279
});
280
}
281
```
282
283
## Bundle Registration Order
284
285
Bundles are initialized and run in the order they are registered:
286
287
```java
288
@Override
289
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
290
// These bundles will be initialized in this order
291
bootstrap.addBundle(new DatabaseBundle()); // 1st
292
bootstrap.addBundle(new SecurityBundle()); // 2nd
293
bootstrap.addBundle(new AssetsBundle()); // 3rd
294
bootstrap.addBundle(new SslReloadBundle()); // 4th
295
}
296
```
297
298
During application startup:
299
1. All bundles' `initialize()` methods are called in registration order
300
2. Configuration is parsed and validated
301
3. All bundles' `run()` methods are called in registration order
302
4. Application's `run()` method is called
303
304
## Bundle Best Practices
305
306
### Configuration Integration
307
308
```java
309
public class MyBundle implements ConfiguredBundle<MyConfiguration> {
310
@Override
311
public void run(MyConfiguration configuration, Environment environment) throws Exception {
312
// Access bundle-specific configuration
313
MyBundleConfig bundleConfig = configuration.getMyBundleConfig();
314
315
if (bundleConfig.isEnabled()) {
316
// Only configure if enabled
317
}
318
}
319
}
320
```
321
322
### Resource Management
323
324
```java
325
public class ResourceBundle implements ConfiguredBundle<Configuration> {
326
@Override
327
public void run(Configuration configuration, Environment environment) throws Exception {
328
final ExpensiveResource resource = new ExpensiveResource();
329
330
// Register as managed object for proper lifecycle
331
environment.lifecycle().manage(new Managed() {
332
@Override
333
public void start() throws Exception {
334
resource.start();
335
}
336
337
@Override
338
public void stop() throws Exception {
339
resource.close();
340
}
341
});
342
}
343
}
344
```
345
346
### Error Handling
347
348
```java
349
public class SafeBundle implements ConfiguredBundle<Configuration> {
350
@Override
351
public void run(Configuration configuration, Environment environment) throws Exception {
352
try {
353
// Bundle initialization logic
354
} catch (Exception e) {
355
// Log error and optionally rethrow
356
LOGGER.error("Failed to initialize SafeBundle", e);
357
throw new RuntimeException("SafeBundle initialization failed", e);
358
}
359
}
360
}
361
```
362
363
## Types
364
365
```java { .api }
366
// Task base class for admin interface
367
public abstract class Task {
368
protected Task(String name);
369
public abstract void execute(Map<String, List<String>> parameters, PrintWriter output) throws Exception;
370
}
371
372
// Managed interface for lifecycle management
373
public interface Managed {
374
void start() throws Exception;
375
void stop() throws Exception;
376
}
377
378
// Common configuration interfaces
379
public interface DataSourceFactory {
380
DataSource build(MetricRegistry metricRegistry, String name);
381
}
382
383
// SSL reload interfaces
384
public interface SslReload {
385
void reload() throws Exception;
386
}
387
```