### Built-in `PluginController` REST API Source: https://context7.com/hank-cp/sbp/llms.txt SBP provides a REST API to manage plugins at runtime when a `base-path` is configured for the controller. This API allows listing, starting, and stopping plugins, as well as listing registered extensions. ```APIDOC ## Built-in `PluginController` REST API When `spring.sbp.controller.base-path` is set, SBP exposes a REST API to manage plugins at runtime. ```yaml # application.yml spring: sbp: controller: base-path: sbp ``` ```bash # List all resolved plugin IDs curl http://localhost:8080/sbp/plugin/list # Response: ["demo-plugin-author", "demo-plugin-shelf", "demo-plugin-admin"] # Start a plugin curl http://localhost:8080/sbp/plugin/start/demo-plugin-shelf # Stop a plugin curl http://localhost:8080/sbp/plugin/stop/demo-plugin-shelf # List all registered extensions curl http://localhost:8080/sbp/plugin/extensions/list # Response: ["author", "shelf", "admin"] ``` ``` -------------------------------- ### Plugin Integration Test Setup Source: https://context7.com/hank-cp/sbp/llms.txt Integration test class for plugins using Spring Boot's testing infrastructure. It excludes security autoconfiguration and uses MockMvc and SpringBootPluginManager to test plugin lifecycle and functionality. ```java @RunWith(SpringRunner.class) @SpringBootTest(classes = DemoTestApp.class) @TestPropertySource(properties = "spring.autoconfigure.exclude=" + "org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\n" + "org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration") @AutoConfigureMockMvc @ActiveProfiles("no_security") public class PluginIntegrationTest { @Autowired private MockMvc mvc; @Autowired private SpringBootPluginManager pluginManager; @Test public void testPluginStartStop() throws Exception { // Plugin is started — its routes are accessible mvc.perform(get("/shelf/list").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(2))); // Stop plugin — its routes return 4xx pluginManager.stopPlugin("demo-plugin-shelf"); mvc.perform(get("/shelf/list").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is4xxClientError()); // Restart plugin — routes come back, dependent beans re-injected pluginManager.startPlugin("demo-plugin-shelf"); mvc.perform(get("/shelf/list").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].author.name", equalTo("George Orwell"))); } @Test public void testExtensions() throws Exception { // All @Extension beans from started plugins are discoverable mvc.perform(get("/plugin/extensions/list").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$[*]", containsInAnyOrder("author", "shelf", "admin"))); } } ``` -------------------------------- ### Implement IPluginConfigurer for Plugin Resource Integration Source: https://context7.com/hank-cp/sbp/llms.txt Implement IPluginConfigurer to register or unregister framework resources like JPA entities or middleware when a plugin starts or stops. This interface allows sharing beans from the main application context into the plugin's context. ```java import org.pf4j.SpringBootPlugin; import org.pf4j.PluginWrapper; import org.pf4j.PluginConfigurer; import org.springframework.context.support.GenericApplicationContext; import org.pf4j.spring.boot.SpringBootstrap; // Custom configurer example: share a custom cache manager into plugin context public class MyCustomCacheConfigurer implements IPluginConfigurer { @Override public String[] excludeConfigurations() { // Prevent the plugin from creating its own CacheManager return new String[] { "org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration" }; } @Override public void onBootstrap(SpringBootstrap bootstrap, GenericApplicationContext pluginApplicationContext) { // Share the main app's cacheManager into the plugin context bootstrap.importBeanFromMainContext(pluginApplicationContext, "cacheManager"); } @Override public void onStart(SpringBootPlugin plugin) { // Plugin ApplicationContext is fully ready — register extensions, etc. System.out.println("Plugin " + plugin.getWrapper().getPluginId() + " started"); } @Override public void onStop(SpringBootPlugin plugin) { // Clean up anything registered in onStart } @Override public void releaseLeaveOverResource(PluginWrapper plugin, GenericApplicationContext mainAppCtx) { // Called even if the plugin failed to start, to clean up partial registrations } } ``` -------------------------------- ### Plugin REST Controller (WebFlux / Reactive) Source: https://context7.com/hank-cp/sbp/llms.txt For WebFlux plugins, set WebApplicationType.REACTIVE on the SpringBootstrap. This example shows a basic WebFlux controller with reactive Mono responses. ```java // demo-webflux/plugins/demo-plugin-webflux/src/main/java/demo/sbp/webflux/admin/AdminPlugin.java public class AdminPlugin extends SpringBootPlugin { public AdminPlugin(PluginWrapper wrapper) { super(wrapper, new SbpSpringDocConfigurer()); } @Override protected SpringBootstrap createSpringBootstrap() { SpringBootstrap bootstrap = new SpringBootstrap(this, AdminPluginStarter.class); bootstrap.setWebApplicationType(WebApplicationType.REACTIVE); // required for WebFlux return bootstrap; } } // Plugin WebFlux controller @RestController @RequestMapping(value = "/admin") public class AdminController { @GetMapping(value = "/user") public @ResponseBody Mono user() { return Mono.just("Hello User!"); } @GetMapping(value = "/admin") public @ResponseBody Mono admin() { return Mono.just("Hello Admin!"); } } ``` -------------------------------- ### SpringBootPluginManager Operations Source: https://context7.com/hank-cp/sbp/llms.txt Manage the lifecycle of plugins including starting, stopping, restarting, and reloading. ```APIDOC ## `SpringBootPluginManager` — Plugin Lifecycle Manager ### Description A Spring bean that manages loading, starting, stopping, and reloading plugins. ### Methods #### `startPlugin(String pluginId)` Start a single plugin; fires `SbpPluginStateChangedEvent`. #### `stopPlugin(String pluginId)` Stop a single plugin; fires `SbpPluginStateChangedEvent`. #### `restartPlugin(String pluginId)` Stop then start a single plugin. #### `restartPlugins()` Stop all then start all plugins. #### `reloadPlugins(boolean restartStartedOnly)` Unload all plugins from disk, reload, optionally restart. #### `reloadPlugins(String pluginId)` Unload, reload, and start a single plugin. #### `getPluginStartingError(String pluginId)` Returns error info if the plugin failed to start. #### `presetProperties(Map)` Set properties shared to all plugin ApplicationContexts. #### `isLoading()` Returns true while the plugin loading lock is held. ### State Inspection #### `getResolvedPlugins()` Returns a list of all resolved plugins. #### `getPlugin(String pluginId)` Returns a specific plugin wrapper by its ID. #### `getPluginState()` Returns the current state of a plugin (e.g., STARTED, STOPPED, DISABLED). #### `getExtensions(Class type)` Discovers extensions of a given type from all plugins. ``` -------------------------------- ### JPA and JTA Configuration for Plugin (Scenario 1) Source: https://github.com/hank-cp/sbp/blob/master/docs/persistence.md Configure JPA and JTA on the plugin side when both app and plugin use JPA. This setup ensures separate EntityManagers and distributed transaction management. ```yaml spring: datasource: url: "jdbc:postgresql://localhost/sbp" username: postgres driver-class-name: "org.postgresql.Driver" sbp: # plugin-properties will apply these properties for all plugins plugin-properties: spring: jpa: properties: hibernate: temp: use_jdbc_metadata_defaults: false database-platform: org.hibernate.dialect.PostgreSQL9Dialect jpa: hibernate: ddl-auto: update properties: hibernate: temp: use_jdbc_metadata_defaults: false database-platform: org.hibernate.dialect.PostgreSQL9Dialect jta: atomikos: datasource: max-pool-size: 20 min-pool-size: 5 borrow-connection-timeout: 60 ``` -------------------------------- ### Plugin REST Controller (WebMvc) Source: https://context7.com/hank-cp/sbp/llms.txt Plugins can expose REST endpoints by including Spring `@RestController` components. These controllers are automatically registered and unregistered with the application's `DispatcherServlet` as the plugin starts and stops. ```APIDOC ## Plugin REST Controller (WebMvc) Plugin controllers are automatically registered into and unregistered from the main application's `DispatcherServlet` when the plugin starts/stops. ```java // plugins/demo-plugin-admin/src/main/java/demo/sbp/admin/AdminController.java @RestController @RequestMapping(value = "/admin") public class AdminController { @Autowired private BookService bookService; // injected from main ApplicationContext via SbpDataSourceConfigurer @RequestMapping(value = "/user") public String user() { return "Hello User!"; } @RequestMapping(value = "/books") public List books() { return bookService.getBooks(); } } // Test: routes appear/disappear with plugin lifecycle pluginManager.stopPlugin("demo-plugin-admin"); // GET /admin/user -> 404 pluginManager.startPlugin("demo-plugin-admin"); // GET /admin/user -> 200 "Hello User!" ``` ``` -------------------------------- ### Plugin REST Controller (WebMvc) Source: https://context7.com/hank-cp/sbp/llms.txt Plugin controllers are automatically registered and unregistered with the main application's DispatcherServlet upon plugin lifecycle events. Test routes by stopping and starting the plugin. ```java // plugins/demo-plugin-admin/src/main/java/demo/sbp/admin/AdminController.java @RestController @RequestMapping(value = "/admin") public class AdminController { @Autowired private BookService bookService; // injected from main ApplicationContext via SbpDataSourceConfigurer @RequestMapping(value = "/user") public String user() { return "Hello User!"; } @RequestMapping(value = "/books") public List books() { return bookService.getBooks(); } } // Test: routes appear/disappear with plugin lifecycle pluginManager.stopPlugin("demo-plugin-admin"); // GET /admin/user -> 404 pluginManager.startPlugin("demo-plugin-admin"); // GET /admin/user -> 200 "Hello User!" ``` -------------------------------- ### Build and Migrate Demo Project Source: https://github.com/hank-cp/sbp/blob/master/docs/demo_project.md Run these Gradle commands in sequence to set up the demo project, including database migrations and dependency management. ```bash > ./gradlew doMigration > ./gradlew copyDependencies buildApp check ``` -------------------------------- ### Optional Starters for SBP Plugins Source: https://context7.com/hank-cp/sbp/llms.txt Add optional starter dependencies for features like JPA, SpringDoc, or Thymeleaf support within your plugins. ```groovy // JPA/Hibernate support implementation 'org.laxture:sbp-spring-boot-jpa-starter:3.5.27' // SpringDoc/OpenAPI support implementation 'org.laxture:sbp-spring-boot-springdoc-starter:3.5.27' // Thymeleaf template engine support implementation 'org.laxture:sbp-spring-boot-thymeleaf-starter:3.5.27' ``` -------------------------------- ### SpringBootstrap Key Constants and Methods Source: https://context7.com/hank-cp/sbp/llms.txt Key constants and methods for configuring and bootstrapping a plugin's ApplicationContext using SpringBootstrap. ```APIDOC ## SpringBootstrap Key Constants and Methods | Member | Description | |---|---| | `BEAN_PLUGIN` (`"pf4j.plugin"`) | Bean name under which the `SpringBootPlugin` itself is registered in the plugin context. | | `BEAN_IMPORTED_BEAN_NAMES` (`"sharedBeanNames"`) | Bean name of the `Set` of imported bean names. | | `importBean(String beanName)` | Chain-method: import bean by name from main or dependent plugin context. | | `importBean(Class beanClass)` | Chain-method: import all beans of given type. | | `addPresetProperty(String, Object)` | Chain-method: add a property to the plugin's `Environment`. | | `importBeanFromMainContext(ctx, name)` | Directly import a named bean from the main ApplicationContext. | | `importBeanFromDependentPlugin(ctx, name)` | Import a bean from a declared plugin dependency. | ``` -------------------------------- ### Admin Plugin Starter Class Source: https://context7.com/hank-cp/sbp/llms.txt Defines the entry point for a plugin's Spring Boot application. This class is used for standalone testing and by SpringBootstrap for initialization. Avoid defining a main() method unless for standalone testing. ```java // plugins/demo-plugin-admin/src/main/java/demo/sbp/admin/AdminPluginStarter.java @SpringBootApplication public class AdminPluginStarter { // This class is for plugin standalone run and for SpringBootstrap initialization // Do NOT define a main() unless you want to run the plugin standalone for testing public static void main(String[] args) { SpringApplication.run(AdminPluginStarter.class, args); } } ``` -------------------------------- ### Main Application Dependency (Maven) Source: https://context7.com/hank-cp/sbp/llms.txt Add the SBP starter dependency to your main Spring Boot application's pom.xml. ```xml org.laxture sbp-spring-boot-starter 3.5.27 ``` -------------------------------- ### Main Application SBP Configuration Source: https://context7.com/hank-cp/sbp/llms.txt Configure the SBP plugin manager in the main application's application.yml. This includes enabling SBP, setting runtime mode, defining plugin directories, and specifying enabled/disabled plugins. ```yaml spring: sbp: enabled: true # Enable sbp (default: false) runtime-mode: development # DEVELOPMENT or DEPLOYMENT auto-start-plugin: true # Auto-start plugins on app ready (default: true) plugins-root: plugins # Plugin home folder, relative to working dir (default: "plugins") system-version: "1.0.0" # Used for plugin requires version checks classes-directories: # Where to load plugin classes (relative to plugin folder) - "out/production/classes" # For IntelliJ IDEA - "out/production/resources" - "build/classes/java/main" # For Gradle - "build/resources/main" lib-directories: - "libs" # Where to load jar libs (relative to plugin folder) enabled-plugins: # Plugins enabled by default (prior to disabled-plugins) - demo-plugin-author disabled-plugins: # Plugins disabled by default - demo-plugin-legacy plugin-profiles: # Spring profiles applied to all plugin ApplicationContexts - plugin plugin-properties: # Properties applied globally to all plugin ApplicationContexts spring: jpa: properties: hibernate: temp: use_jdbc_metadata_defaults: false controller: base-path: sbp # If set, registers PluginController REST API under this path custom-plugin-loader: # Custom PluginLoader implementations (optional) - demo.sbp.app.CustomPluginLoader mvc: static-path-pattern: /public/** # Static resource URL pattern (applies to plugins too) resources: add-mappings: true cache: period: 3600 ``` -------------------------------- ### Add sbp-spring-boot-starter Dependency (Maven) Source: https://github.com/hank-cp/sbp/blob/master/README.md Include the sbp-spring-boot-starter dependency in your Maven project to integrate sbp functionality. ```xml org.laxture sbp-spring-boot-starter 3.5.27 ``` -------------------------------- ### Manage Plugin Lifecycle with SpringBootPluginManager Source: https://context7.com/hank-cp/sbp/llms.txt Inject SpringBootPluginManager into any Spring component to control plugin lifecycle events like starting, stopping, restarting, and reloading. It also allows inspecting plugin state and handling startup errors. ```java import org.pf4j.PluginWrapper; import org.pf4j.PluginState; import org.pf4j.PluginStartingError; import org.pf4j.demo.PluginRegister; import java.util.List; // Inject into any Spring component @Autowired private SpringBootPluginManager pluginManager; // --- Start / Stop --- pluginManager.startPlugin("demo-plugin-shelf"); pluginManager.stopPlugin("demo-plugin-shelf"); // --- Restart a single plugin (stop then start) --- pluginManager.restartPlugin("demo-plugin-shelf"); // --- Restart all plugins --- pluginManager.restartPlugins(); // --- Reload (unload from disk + reload + start) --- // Reload only plugins that were previously started pluginManager.reloadPlugins(true); // Reload and restart all plugins pluginManager.reloadPlugins(false); // Reload a single plugin by id pluginManager.reloadPlugins("demo-plugin-author"); // --- Inspect state --- List resolved = pluginManager.getResolvedPlugins(); PluginWrapper pw = pluginManager.getPlugin("demo-plugin-author"); PluginState state = pw.getPluginState(); // STARTED, STOPPED, DISABLED, etc. // --- Error handling --- PluginStartingError error = pluginManager.getPluginStartingError("demo-plugin-shelf"); if (error != null) { System.err.println(error.getMessage()); } // --- Pf4j Extension discovery --- List registers = pluginManager.getExtensions(PluginRegister.class); ``` -------------------------------- ### Add sbp-spring-boot-starter Dependency (Gradle) Source: https://github.com/hank-cp/sbp/blob/master/README.md Include the sbp-spring-boot-starter dependency in your Gradle project for sbp integration. ```gradle dependencies { implementation "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.boot:spring-boot-starter-aop" implementation 'org.laxture:sbp-spring-boot-starter:3.5.27' } ``` -------------------------------- ### Generate JOOQ Schema and Build App Source: https://github.com/hank-cp/sbp/blob/master/docs/demo_project.md Commands to generate JOOQ mapping code, migrate the database, and copy dependencies. Ensure JOOQ code is generated before building the application. ```bash > ./gradlew doMigration > ./gradlew clean generateSbpJooqSchemaSource > ./gradlew copyDependencies ``` -------------------------------- ### IPluginConfigurer Interface Source: https://context7.com/hank-cp/sbp/llms.txt Interface for integrating framework resources with plugins during their lifecycle. ```APIDOC ## `IPluginConfigurer` — Plugin Resource Integration Hook ### Description Interface for registering/unregistering framework resources when a plugin starts or stops. Implement this to integrate any external framework. ### Interface Methods #### `excludeConfigurations()` **Called When:** Before plugin context creation. **Description:** Return class names of `AutoConfiguration` to exclude. #### `onBootstrap(SpringBootstrap bootstrap, GenericApplicationContext pluginApplicationContext)` **Called When:** During plugin `ApplicationContext` creation. **Description:** Import beans, register resources. #### `afterBootstrap(SpringBootstrap bootstrap, GenericApplicationContext pluginApplicationContext)` **Called When:** After plugin context fully refreshed. **Description:** Use plugin beans here. #### `onStart(SpringBootPlugin plugin)` **Called When:** Plugin has fully started. **Description:** Register extensions, etc. #### `onStop(SpringBootPlugin plugin)` **Called When:** Plugin is stopping. **Description:** Clean up anything registered in `onStart`. #### `releaseLeaveOverResource(PluginWrapper plugin, GenericApplicationContext mainAppCtx)` **Called When:** Cleanup, called even after failed starts. **Description:** Called to clean up partial registrations. ``` -------------------------------- ### License Header Source: https://github.com/hank-cp/sbp/blob/master/README.md Standard Apache 2.0 license header for the project. ```java /* * Copyright (C) 2019-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ ``` -------------------------------- ### Implement Plugin Class Source: https://github.com/hank-cp/sbp/blob/master/README.md Create a Java class that extends SpringBootPlugin and provides the necessary constructor and SpringBootstrap configuration. This class serves as the entry point for your plugin. ```java public class DemoPlugin extends SpringBootPlugin { public DemoPlugin(PluginWrapper wrapper) { super(wrapper); } @Override protected SpringBootstrap createSpringBootstrap() { return new SpringBootstrap(this, AdminPluginStarter.class); } } ``` -------------------------------- ### Configure SBP Class Directories Source: https://github.com/hank-cp/sbp/blob/master/docs/configuration.md Specify directories relative to the plugin folder where plugin classes should be loaded. Useful for development environments like IDEA. ```yaml spring: sbp: classes-directories: - "out/production/classes" - "out/production/resources" ``` -------------------------------- ### Explicit SbpWebConfigurer Usage Source: https://context7.com/hank-cp/sbp/llms.txt Demonstrates the explicit inclusion of SbpWebConfigurer when creating a plugin. This is rarely needed as it's automatically added. ```java public class MyPlugin extends SpringBootPlugin { public MyPlugin(PluginWrapper wrapper) { super(wrapper, new SbpWebConfigurer()); } // ... } ``` -------------------------------- ### JPA and JTA Configuration for Plugin (Scenario 1 - Alternative) Source: https://github.com/hank-cp/sbp/blob/master/docs/persistence.md An alternative JPA and JTA configuration for the plugin, using 'create-drop' for ddl-auto and specifying a different Hibernate dialect. ```yaml spring: datasource: url: "jdbc:postgresql://localhost/sbp" username: postgres driver-class-name: "org.postgresql.Driver" jpa: hibernate: ddl-auto: create-drop properties: hibernate: dialect: org.hibernate.dialect.PostgreSQL95Dialect hbm2ddl: create_namespaces: true show-sql: true open-in-view: false jta: atomikos: datasource: beanName: dataSource-library # this line is necessary, could be any name except "dataSource" ``` -------------------------------- ### Register Main and Plugin API Docs with GroupedOpenApi Source: https://github.com/hank-cp/sbp/blob/master/docs/extensible_integration.md Configure GroupedOpenApi beans to register API documentation for both the main application and plugins by scanning specified packages. ```java // main app @Bean public GroupedOpenApi mainApiDoc() { return GroupedOpenApi.builder().group("main") .packagesToScan("demo.sbp.app") .build(); } // plugin @Bean public GroupedOpenApi adminApiDoc() { return GroupedOpenApi.builder().group("admin") .packagesToScan("demo.sbp.webflux.admin") .build(); } ``` -------------------------------- ### Plugin Properties File Source: https://context7.com/hank-cp/sbp/llms.txt Define essential metadata for each plugin in its root `plugin.properties` file, including ID, main class, version, provider, and dependencies. ```properties plugin.id=demo-plugin-admin plugin.class=demo.sbp.admin.AdminPlugin plugin.version=0.0.1 plugin.provider=Your Name plugin.dependencies=demo-plugin-author # Comma-separated; leave empty if no deps ``` -------------------------------- ### Plugin Properties Configuration Source: https://github.com/hank-cp/sbp/blob/master/README.md Define essential metadata for your plugin, such as its ID, main class, version, and provider. Ensure the plugin ID and class are correctly specified. ```properties plugin.id=<> plugin.class=demo.sbp.admin.DemoPlugin plugin.version=0.0.1 plugin.provider=Your Name plugin.dependencies= ``` -------------------------------- ### Configure sbp Properties Source: https://github.com/hank-cp/sbp/blob/master/README.md Configure essential sbp properties in application.properties for development mode and class loading, especially when using IDEs like IDEA. ```properties spring.sbp.runtimeMode = development spring.sbp.enabled = true # remember to add this line in case you are using IDEA spring.sbp.classes-directories = "out/production/classes, out/production/resources" ``` -------------------------------- ### Load Test Data Source: https://github.com/hank-cp/sbp/blob/master/docs/demo_project.md Optional command to load test data into the database after setting up the project. ```bash > ./gradlew doDataMigration ``` -------------------------------- ### Configure Static Resource Handling Source: https://github.com/hank-cp/sbp/blob/master/docs/resource_handling.md Configure Spring Boot's MVC to serve static resources from a custom path pattern and enable caching. ```yaml spring: mvc: static-path-pattern: /public/** resources: add-mappings: true cache: period: 3600 ``` -------------------------------- ### Configure SpringBootstrap for Plugin Context Source: https://context7.com/hank-cp/sbp/llms.txt Configure SpringBootstrap to bootstrap a plugin's ApplicationContext. Use importBean to share beans by name or type, and addPresetProperty to set environment properties. ```java // Minimal usage inside createSpringBootstrap() @Override protected SpringBootstrap createSpringBootstrap() { return new SpringBootstrap(this, MyPluginStarter.class); } ``` ```java // Full usage with bean importing and preset properties @Override protected SpringBootstrap createSpringBootstrap() { return new SpringBootstrap(this, MyPluginStarter.class) .importBean("mySharedService") // import by bean name from main ctx .importBean(SomeSharedClass.class) // import by type from main ctx .addPresetProperty("my.custom.prop", true);// set property in plugin environment } ``` ```java // WebFlux plugin — must set REACTIVE web type explicitly @Override protected SpringBootstrap createSpringBootstrap() { SpringBootstrap bootstrap = new SpringBootstrap(this, AdminPluginStarter.class); bootstrap.setWebApplicationType(WebApplicationType.REACTIVE); return bootstrap; } ``` -------------------------------- ### Gradle Plugin Configuration Source: https://github.com/hank-cp/sbp/blob/master/docs/deployment.md Configure local dependencies and extend the compile configuration to include them. This is part of preparing for a flat lib jar. ```gradle configurations { localLibs compile.extendsFrom(localLibs) } dependencies { localLibs fileTree(dir: 'libs', include: '**') ...... } ``` -------------------------------- ### Add sbp-core Dependency (Gradle) Source: https://github.com/hank-cp/sbp/blob/master/README.md Integrate the sbp-core library into your Gradle project for plugin development. Ensure the version matches your project's requirements. ```gradle dependencies { implementation 'org.laxture:sbp-core:3.5.27' } ``` -------------------------------- ### SbpSpringDocConfigurer for Dynamic OpenAPI Documentation Source: https://context7.com/hank-cp/sbp/llms.txt Illustrates the use of SbpSpringDocConfigurer for dynamically registering and unregistering plugin OpenAPI documentation groups with SpringDoc. This allows plugins to contribute their own API documentation. ```java // In plugin's build.gradle: implementation 'org.laxture:sbp-spring-boot-springdoc-starter:3.5.27' public class AdminPlugin extends SpringBootPlugin { public AdminPlugin(PluginWrapper wrapper) { super(wrapper, new SbpSpringDocConfigurer()); } @Override protected SpringBootstrap createSpringBootstrap() { return new SpringBootstrap(this, AdminPluginStarter.class); } } // Register API groups in both app and plugin // Main app: @Bean public GroupedOpenApi mainApiDoc() { return GroupedOpenApi.builder().group("main") .packagesToScan("demo.sbp.app") .build(); } // Plugin: @Bean public GroupedOpenApi adminApiDoc() { return GroupedOpenApi.builder().group("admin") .packagesToScan("demo.sbp.webflux.admin") .build(); } ``` -------------------------------- ### SpringBootPlugin Key Methods Source: https://context7.com/hank-cp/sbp/llms.txt Key methods available on the SpringBootPlugin class for managing plugin lifecycle and context. ```APIDOC ## SpringBootPlugin Key Methods | Method | Description | |---|---| | `createSpringBootstrap()` | **Abstract.** Return the configured `SpringBootstrap` for this plugin. | | `getApplicationContext()` | Returns this plugin's `GenericApplicationContext`. | | `getMainApplicationContext()` | Returns the main app's `GenericApplicationContext`. | | `getPluginManager()` | Returns the `SpringBootPluginManager`. | | `registerBeanToMainContext(name, bean)` | Manually register a singleton bean into the main context. | | `unregisterBeanFromMainContext(name)` | Remove a singleton bean from the main context. | ``` -------------------------------- ### Plugin Classpath Configuration (Scenario 2) Source: https://github.com/hank-cp/sbp/blob/master/docs/persistence.md Configure 'plugin-first-classes' in the plugin's YAML to prioritize plugin-loaded classes, preventing ClassCastExceptions due to conflicting Spring Boot AutoConfiguration JARs. ```yaml sbp-plugin: plugin-first-classes: - org.springframework.boot.autoconfigure.data.* - org.springframework.boot.autoconfigure.orm.* - org.springframework.boot.orm.jpa.* ``` -------------------------------- ### Listen for Plugin State Changes in Main Application Source: https://context7.com/hank-cp/sbp/llms.txt Implement listeners in the main application to monitor state changes of any plugin or when the main application itself is ready. This allows for global reaction to plugin lifecycle events. ```java @Component public class MainAppPluginListener { @EventListener public void onPluginStateChanged(SbpPluginStateChangedEvent event) { System.out.println("A plugin changed state: " + event.getSource()); } @EventListener public void onMainAppReady(SbpMainAppReadyEvent event) { // Fired when main ApplicationContext fully started (after ApplicationReadyEvent) } } ``` -------------------------------- ### Gradle Configuration for '-parameters' Compiler Option Source: https://github.com/hank-cp/sbp/blob/master/docs/trouble_shoot.md Configure Gradle to include the '-parameters' compiler argument, necessary for Spring Boot 3.2.x to resolve argument names, especially when encountering 'Name for argument of type [XXX] not specified' errors. ```gradle plugins { id 'idea' id 'org.jetbrains.gradle.plugin.idea-ext' version '1.1.7' } idea { project.settings { compiler { javac { javacAdditionalOptions "-parameters" } } } } compileJava { options.compilerArgs << '-parameters' } compileTestJava { options.compilerArgs << '-parameters' } ``` -------------------------------- ### Plugin REST Controller (WebFlux / Reactive) Source: https://context7.com/hank-cp/sbp/llms.txt For WebFlux-based plugins, ensure `WebApplicationType.REACTIVE` is set on the `SpringBootstrap`. The plugin can then define reactive REST controllers. ```APIDOC ## Plugin REST Controller (WebFlux / Reactive) WebFlux plugins must set `WebApplicationType.REACTIVE` on the `SpringBootstrap`. ```java // demo-webflux/plugins/demo-plugin-webflux/src/main/java/demo/sbp/webflux/admin/AdminPlugin.java public class AdminPlugin extends SpringBootPlugin { public AdminPlugin(PluginWrapper wrapper) { super(wrapper, new SbpSpringDocConfigurer()); } @Override protected SpringBootstrap createSpringBootstrap() { SpringBootstrap bootstrap = new SpringBootstrap(this, AdminPluginStarter.class); bootstrap.setWebApplicationType(WebApplicationType.REACTIVE); // required for WebFlux return bootstrap; } } // Plugin WebFlux controller @RestController @RequestMapping(value = "/admin") public class AdminController { @GetMapping(value = "/user") public @ResponseBody Mono user() { return Mono.just("Hello User!"); } @GetMapping(value = "/admin") public @ResponseBody Mono admin() { return Mono.just("Hello Admin!"); } } ``` ``` -------------------------------- ### Test Dynamic Resource Lifecycle with MockMvc Source: https://context7.com/hank-cp/sbp/llms.txt Integration test to verify that resources served by plugins are available when the plugin is active and disappear when stopped. Uses MockMvc for HTTP requests and pluginManager to control plugin state. ```java // Integration test demonstrating dynamic resource lifecycle @Test public void testResourceHandler() throws Exception { // Resource served by main app mvc.perform(get("/public/foo.html")).andExpect(status().isOk()) .andExpect(content().string("\nHello foo!\n")); // Resource served by demo-plugin-admin mvc.perform(get("/public/bar.html")).andExpect(status().isOk()) .andExpect(content().string("\nHello bar!\n")); // Stop plugin → plugin resource disappears pluginManager.stopPlugin("demo-plugin-admin"); mvc.perform(get("/public/bar.html")).andExpect(status().isNotFound()); // Start plugin → plugin resource reappears pluginManager.startPlugin("demo-plugin-admin"); mvc.perform(get("/public/bar.html")).andExpect(status().isOk()); } ``` -------------------------------- ### Verify Classloader Hierarchy in SBP Plugins Source: https://context7.com/hank-cp/sbp/llms.txt Use this code to confirm that classes are loaded from the correct plugin classloader. Ensure the necessary plugins are loaded before execution. ```java SpringBootPlugin authorPlugin = (SpringBootPlugin) pluginManager.getPlugin("demo-plugin-author").getPlugin(); SpringBootPlugin shelfPlugin = (SpringBootPlugin) pluginManager.getPlugin("demo-plugin-shelf").getPlugin(); // Shelf model loaded from shelf's own classloader Class shelf = shelfPlugin.getWrapper().getPluginClassLoader() .loadClass("demo.sbp.shelf.model.Shelf"); assertThat(shelf.getClassLoader(), equalTo(shelfPlugin.getWrapper().getPluginClassLoader())); // Author model (from dependent plugin) loaded from author's classloader Class author = shelfPlugin.getWrapper().getPluginClassLoader() .loadClass("demo.sbp.api.model.Author"); assertThat(author.getClassLoader(), equalTo(authorPlugin.getWrapper().getPluginClassLoader())); ``` -------------------------------- ### Gradle Jar Manifest Configuration Source: https://github.com/hank-cp/sbp/blob/master/docs/deployment.md Define a Gradle task to build the plugin JAR. Crucially, set the manifest attributes to identify it as a plugin, including its ID, main class, version, provider, and dependencies. ```gradle task buildPlugin(type: Jar) { ...... manifest.attributes( "Plugin-Id": pluginProp.get("plugin.id"), "Plugin-Class": pluginProp.get("plugin.class"), "Plugin-Version": pluginProp.get("plugin.version"), "Plugin-Provider": pluginProp.get("plugin.provider"), "Plugin-Dependencies": pluginProp.get("plugin.dependencies")) ...... } ``` -------------------------------- ### Build Plugin JAR with Manifest Metadata Source: https://context7.com/hank-cp/sbp/llms.txt Gradle task to build a plugin JAR for production deployment. It includes local libraries by flattening them into the plugin JAR and sets manifest attributes for plugin identification and metadata. ```groovy // plugins/demo-plugin-xxx/build.gradle def pluginProp = new Properties() file("plugin.properties").withInputStream { pluginProp.load(it) } configurations { localLibs implementation.extendsFrom(localLibs) } dependencies { localLibs fileTree(dir: 'libs', include: '**') implementation 'org.laxture:sbp-core:3.5.27' } task buildPlugin(type: Jar) { archiveBaseName = pluginProp.get("plugin.id") // Flatten all local lib jars into a single jar (fat-jar-in-jar not supported) from configurations.localLibs.asFileTree.files.collect { zipTree(it) } from sourceSets.main.output manifest.attributes( "Plugin-Id": pluginProp.get("plugin.id"), "Plugin-Class": pluginProp.get("plugin.class"), "Plugin-Version": pluginProp.get("plugin.version"), "Plugin-Provider": pluginProp.get("plugin.provider"), "Plugin-Dependencies": pluginProp.get("plugin.dependencies") ) } ``` -------------------------------- ### Listen for Plugin Application Context Events in Plugin Source: https://context7.com/hank-cp/sbp/llms.txt Implement listeners in a plugin to react to its lifecycle events, such as startup, restart, or stop. Use standard Spring @EventListener or ApplicationListener. ```java @Component public class MyPluginStartupListener { @EventListener public void onPluginStarted(SbpPluginStartedEvent event) { System.out.println("Plugin ApplicationContext is ready: " + event.getSource()); } @EventListener public void onPluginRestarted(SbpPluginRestartedEvent event) { // Fired when plugin starts AND main app was already started (i.e., hot-reload) System.out.println("Plugin hot-reloaded"); } @EventListener public void onPluginStopped(SbpPluginStoppedEvent event) { System.out.println("Plugin is stopping"); } } ``` -------------------------------- ### SbpSharedServiceConfigurer for Shared Services Source: https://context7.com/hank-cp/sbp/llms.txt Demonstrates the integration of SbpSharedServiceConfigurer to share various services like Quartz scheduler, task executors, RestTemplate, WebClient, ObjectMapper, and HTTP message converters from the main context to the plugin. ```java public class AdminPlugin extends SpringBootPlugin { public AdminPlugin(PluginWrapper wrapper) { super(wrapper, new SbpDataSourceConfigurer(), new SbpSharedServiceConfigurer() // gets Jackson, RestTemplate, task executors, etc. ); } @Override protected SpringBootstrap createSpringBootstrap() { return new SpringBootstrap(this, AdminPluginStarter.class); } } ``` -------------------------------- ### Maven Configuration for '-parameters' Compiler Option Source: https://github.com/hank-cp/sbp/blob/master/docs/trouble_shoot.md Configure the Maven Compiler Plugin to enable the '-parameters' option, which is required for Spring Boot 3.2.x to correctly identify argument names and avoid 'Name for argument of type [XXX] not specified' errors. ```xml org.apache.maven.plugins maven-compiler-plugin 3.11.0 true ``` -------------------------------- ### Plugin-Specific Application Configuration Source: https://context7.com/hank-cp/sbp/llms.txt Configure plugin-specific settings within the plugin's own `application.yml`. This allows for fine-tuning classloading and resource loading behavior. ```yaml sbp-plugin: plugin-first-classes: # Classes to load from plugin classloader first (avoids ClassCastException) - org.springframework.boot.autoconfigure.SomeAutoConfig plugin-only-resources: # Resources loaded exclusively from plugin classpath - plugin_only ``` -------------------------------- ### Plugin Project Dependency (Gradle) Source: https://context7.com/hank-cp/sbp/llms.txt Include the SBP core dependency in your plugin project's build.gradle file. ```groovy dependencies { implementation 'org.laxture:sbp-core:3.5.27' } ``` -------------------------------- ### Plugin Dependencies for JPA and JTA (Scenario 2) Source: https://github.com/hank-cp/sbp/blob/master/docs/persistence.md Configure Gradle dependencies for a plugin when the app does not provide JPA dependencies. This includes defining a 'jpa' configuration and tasks to manage dependency copying. ```gradle # define specific gradle configuration, we need to use to copy dependencies as libs for deployment purpose. configurations { jpa } dependencies { implementation "org.springframework.boot:spring-boot-starter-jta-atomikos" implementation "org.springframework.boot:spring-boot-starter-data-jpa" jpa "org.springframework.boot:spring-boot-starter-data-jpa" } task clearDependencies(type: Delete) { delete files("${project.projectDir}/libs") } # Since app doesn't provide dependencies, we need to copy dependent jars # to its classpath additinally. task copyDependencies(type: Copy) { group 'build' dependsOn clearDependencies from configurations.jpa.files into 'libs' } ``` -------------------------------- ### SbpDataSourceConfigurer for Plugin Data Sources Source: https://context7.com/hank-cp/sbp/llms.txt Shows how to use SbpDataSourceConfigurer to share JDBC data source, transaction manager, and other data-related beans from the main context to a plugin. This allows plugins to access and use these shared resources. ```java public class AdminPlugin extends SpringBootPlugin { public AdminPlugin(PluginWrapper wrapper) { super(wrapper, new SbpDataSourceConfigurer()); } @Override protected SpringBootstrap createSpringBootstrap() { return new SpringBootstrap(this, AdminPluginStarter.class); } } // Plugin can now @Autowire DataSource, JdbcTemplate, DSLContext, etc. ``` -------------------------------- ### Registering Repositories in Spring Boot Source: https://github.com/hank-cp/sbp/blob/master/docs/trouble_shoot.md This code is part of Spring Boot's auto-configuration for registering repositories. It might be relevant when encountering ClassNotFoundException if repository classes are not properly loaded. ```java // org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate( getConfigurationSource(registry, importBeanNameGenerator), this.resourceLoader, this.environment); delegate.registerRepositoriesIn(registry, getRepositoryConfigurationExtension()); } ``` -------------------------------- ### Built-in Plugin Controller REST API Usage Source: https://context7.com/hank-cp/sbp/llms.txt Interact with the plugin management API using curl commands to list plugins, start/stop plugins, and list registered extensions. ```bash # List all resolved plugin IDs curl http://localhost:8080/sbp/plugin/list # Response: ["demo-plugin-author", "demo-plugin-shelf", "demo-plugin-admin"] # Start a plugin curl http://localhost:8080/sbp/plugin/start/demo-plugin-shelf # Stop a plugin curl http://localhost:8080/sbp/plugin/stop/demo-plugin-shelf # List all registered extensions curl http://localhost:8080/sbp/plugin/extensions/list # Response: ["author", "shelf", "admin"] ``` -------------------------------- ### Extend Spring Boot Plugin with Library Integration Source: https://github.com/hank-cp/sbp/blob/master/docs/extensible_integration.md Extend SpringBootPlugin to include JPA and SpringDoc configurations for library integration. SbpWebConfigurer is mandatory and configured automatically. ```java public class LibraryPlugin extends SpringBootPlugin { public LibraryPlugin(PluginWrapper wrapper) { super(wrapper, // SbpWebConfigurer is mandatory, so it will be config automatically. new SbpJpaConfigurer(new String[] {"demo.sbp.library.model"}), new SbpSpringDocConfigurer() ); } } ``` -------------------------------- ### Add sbp-core Dependency (Maven) Source: https://github.com/hank-cp/sbp/blob/master/README.md Include the sbp-core library in your Maven project to enable plugin functionality. Specify the correct version for compatibility. ```xml org.laxture sbp-core 3.5.27 ``` -------------------------------- ### Built-in Plugin Controller REST API Configuration Source: https://context7.com/hank-cp/sbp/llms.txt Configure the base path for the plugin management REST API in application.yml. This API allows runtime management of plugins and extensions. ```yaml # application.yml spring: sbp: controller: base-path: sbp ``` -------------------------------- ### Spring Boot 3.x SNAPSHOT Dependency Source: https://github.com/hank-cp/sbp/blob/master/docs/multi_spring_boot_versions.md This dependency is for development or testing with the latest snapshot of sbp, compatible with Spring Boot 3.x. Requires JVM 17+. ```gradle 'org.laxture:sbp-spring-boot-starter:-SNAPSHOT' ``` -------------------------------- ### JPA and JTA Dependencies for App and Plugin Source: https://github.com/hank-cp/sbp/blob/master/docs/persistence.md Include these dependencies in your app and plugin's build.gradle to enable JPA and JTA with Atomikos. ```gradle implementation "org.springframework.boot:spring-boot-starter-data-jpa" implementation "org.springframework.boot:spring-boot-starter-jta-atomikos" ``` -------------------------------- ### Define and Implement Plugin Extensions Source: https://context7.com/hank-cp/sbp/llms.txt Define an extension interface in a shared API module and implement it in a plugin, annotating the implementation with @Extension. Discover extensions from any Spring component using PluginManager. ```java // 1. Define extension interface in a shared API module (demo-api) public interface PluginRegister { String name(); } // 2. Implement in plugin, annotate with @Extension // plugins/demo-plugin-admin/src/main/java/demo/sbp/admin/AdminRegister.java @Extension public class AdminRegister implements PluginRegister { @Override public String name() { return "admin"; } } // 3. Discover all registered extensions from any Spring component in the main app @Autowired private PluginManager pluginManager; List registers = pluginManager.getExtensions(PluginRegister.class); // Returns [AdminRegister, AuthorRegister, ShelfRegister] when all plugins are started List names = registers.stream().map(PluginRegister::name).collect(Collectors.toList()); // ["admin", "author", "shelf"] ``` -------------------------------- ### Spring Boot 2.5.x to 2.7.x Dependencies Source: https://github.com/hank-cp/sbp/blob/master/docs/multi_spring_boot_versions.md These dependencies are for projects using Spring Boot versions between 2.5.x and 2.7.x. Requires JVM 8+. ```gradle implementation "org.springframework.boot:spring-boot-starter-web:2.7.8" implementation "org.springframework.boot:spring-boot-starter-aop:2.7.8" implementation 'org.laxture:sbp-spring-boot-starter:2.7.17' ``` -------------------------------- ### Extend SpringBootPlugin with Customizers Source: https://context7.com/hank-cp/sbp/llms.txt Extend SpringBootPlugin to manage the plugin lifecycle. Pass IPluginConfigurer instances to share resources like dataSource, jdbcTemplate, and Thymeleaf configurations from the main application. ```java // plugins/demo-plugin-admin/src/main/java/demo/sbp/admin/AdminPlugin.java public class AdminPlugin extends SpringBootPlugin { public AdminPlugin(PluginWrapper wrapper) { // Pass IPluginConfigurer instances to share resources from the main app super(wrapper, new SbpDataSourceConfigurer(), // shares dataSource, jdbcTemplate, jooq DSLContext new SbpSharedServiceConfigurer(), // shares Jackson, RestTemplate, task executors new SbpThymeleafConfigurer() // shares Thymeleaf template resolver ); } @Override protected SpringBootstrap createSpringBootstrap() { // Create the Spring bootstrap with the plugin's @SpringBootApplication class SpringBootstrap bootstrap = new SpringBootstrap(this, AdminPluginStarter.class); // Conditionally set a property visible in the plugin's ApplicationContext if (getMainApplicationContext().containsBean(SecurityConfig.class.getName())) { bootstrap.addPresetProperty("sbp.security.enabled", true); } // Import a specific named bean from the main ApplicationContext into plugin context bootstrap.importBean("permissionCheckingAspect"); return bootstrap; } } ``` -------------------------------- ### Spring Boot 3.x Dependencies Source: https://github.com/hank-cp/sbp/blob/master/docs/multi_spring_boot_versions.md Use these dependencies for projects running on Spring Boot 3.x or later. Requires JVM 17+. ```gradle implementation "org.springframework.boot:spring-boot-starter-web:3.0.6" implementation "org.springframework.boot:spring-boot-starter-aop:3.0.6" implementation 'org.laxture:sbp-spring-boot-starter:3.0.18' ``` -------------------------------- ### SbpJpaConfigurer for Plugin Entity Management Source: https://context7.com/hank-cp/sbp/llms.txt Illustrates the use of SbpJpaConfigurer to register plugin JPA entity packages with the main application's EntityManagerFactory. This enables plugins to define their own @Entity classes managed by the shared Hibernate session factory. ```java // In plugin's build.gradle: implementation 'org.laxture:sbp-spring-boot-jpa-starter:3.5.27' public class LibraryPlugin extends SpringBootPlugin { public LibraryPlugin(PluginWrapper wrapper) { // Provide the package(s) where your plugin's @Entity classes live super(wrapper, new SbpJpaConfigurer(new String[] {"demo.sbp.library.model"})); } @Override protected SpringBootstrap createSpringBootstrap() { return new SpringBootstrap(this, LibraryPluginStarter.class); } } // Plugin application.yml — load entity classes from plugin classloader first // sbp-plugin: // plugin-first-classes: // - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration ``` -------------------------------- ### Excluding Auto-Configurations in Spring Bootstrap Source: https://github.com/hank-cp/sbp/blob/master/docs/trouble_shoot.md Use this pattern to exclude specific Spring auto-configurations when they might cause issues, such as injecting irrelevant beans into a plugin's ApplicationContext. Ensure 'graphql.spring.web.servlet.GraphQLEndpointConfiguration' is correctly specified. ```java @Override protected SpringBootstrap createSpringBootstrap() { return new SharedDataSourceSpringBootstrap(this, MdSimplePluginStarter.class) { @Override protected String[] getExcludeConfigurations() { return ArrayUtils.addAll(super.getExcludeConfigurations(), "graphql.spring.web.servlet.GraphQLEndpointConfiguration"); } } .addSharedBeanName("extensionRegisterService"); } ```