Plugin SDK
The MetaonePlugin base class is the entry point for all in-process extensions. It extends PF4J's SpringPlugin and sets up an isolated Spring ApplicationContext inside the plugin, sharing the host's database connection.
MetaonePlugin
public abstract class MetaonePlugin extends SpringPlugin {
protected final Logger log;
protected MetaonePlugin(PluginWrapper wrapper) { ... }
/** Called after the Spring plugin context is refreshed. Override for startup logic. */
protected void onPluginStart() {}
/** Called before Spring context is closed. Override for cleanup logic. */
protected void onPluginStop() {}
/** Return the Spring @Configuration classes to register in the plugin context. */
protected abstract Class<?>[] getPluginConfigClasses();
}Plugin Spring Context
Each plugin gets its own isolated AnnotationConfigApplicationContext. The host injects these beans automatically:
| Bean | Type | Description |
|---|---|---|
dataSource | DataSource | Workspace-scoped DataSource (via WorkspaceDataSourceProvider) |
eventBusGateway | EventBusGateway | Publish events to the platform |
extensionConfigProvider | ExtensionConfigProvider | Read per-workspace config values |
You can inject all of these directly in your plugin's Spring beans.
Creating a Plugin
1. Add PF4J manifest entries to pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Plugin-Id>crm-extension</Plugin-Id>
<Plugin-Version>1.0.0</Plugin-Version>
<Plugin-Class>vn.metaone.crm.CrmPlugin</Plugin-Class>
<Plugin-Provider>MetaOne</Plugin-Provider>
<Plugin-Dependencies></Plugin-Dependencies>
</manifestEntries>
</archive>
</configuration>
</plugin>2. Implement the Plugin class
package vn.metaone.crm;
import org.pf4j.PluginWrapper;
import vn.metaone.sdk.plugin.MetaonePlugin;
public class CrmPlugin extends MetaonePlugin {
public CrmPlugin(PluginWrapper wrapper) {
super(wrapper);
}
@Override
protected Class<?>[] getPluginConfigClasses() {
return new Class<?>[]{ CrmJpaConfig.class, CrmServiceConfig.class };
}
@Override
protected void onPluginStart() {
log.info("CRM plugin started");
}
@Override
protected void onPluginStop() {
log.info("CRM plugin stopped");
}
}3. Configure JPA
Use a dedicated JPA config that scans only the plugin's own packages:
@Configuration
@EnableJpaRepositories(
basePackages = "vn.metaone.crm.repository",
entityManagerFactoryRef = "crmEntityManagerFactory",
transactionManagerRef = "crmTransactionManager"
)
public class CrmJpaConfig {
@Bean
public LocalContainerEntityManagerFactoryBean crmEntityManagerFactory(DataSource dataSource) {
var factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource); // Injected by MetaonePlugin
factory.setPackagesToScan("vn.metaone.crm.entity");
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return factory;
}
@Bean
public PlatformTransactionManager crmTransactionManager(
@Qualifier("crmEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}Keep entity/repository/service packages inside the plugin's own root package to avoid scan collisions with the host or other plugins.
4. Write Capability Handlers
See the Capability SDK page.
5. Write Event Handlers
See the Event SDK page.
WorkspaceDataSourceProvider
Available in the host context (injected into the plugin automatically). Not typically used directly — MetaonePlugin.createApplicationContext() already registers the workspace DataSource bean.
public interface WorkspaceDataSourceProvider {
DataSource getWorkspaceDataSource();
}ExtensionConfigProvider
Read per-workspace configuration values stored in ext_config. Inject it in any plugin Spring bean:
public interface ExtensionConfigProvider {
String get(String workspaceId, String extensionKey, String key);
String get(String workspaceId, String extensionKey, String key, String defaultValue);
Map<String, String> getAll(String workspaceId, String extensionKey);
}Example:
@Service
public class CrmApiClient {
private final ExtensionConfigProvider config;
public CrmApiClient(ExtensionConfigProvider config) {
this.config = config;
}
public String getApiKey(String workspaceId) {
return config.get(workspaceId, "crm-extension", "apiKey");
}
}Plugin Deployment
For IN_PROCESS extensions, place the built JAR in the metaone.plugins-path directory (default: plugins/):
# Build the plugin
mvn -pl crm-extension package -DskipTests
# Copy to plugins directory
cp crm-extension/target/crm-extension-1.0.0.jar plugins/The platform loads it automatically when the extension is installed and enabled. The JAR must be named {pluginId}-{version}.jar.
Plugin Lifecycle
JAR in plugins/ directory
│
install()
│
▼
PF4J loads JAR
MetaonePlugin.start()
→ createApplicationContext() (host DataSource injected)
→ @Configuration classes refreshed
→ onPluginStart() called
│
enable() ──────────────────────── Capabilities/events registered
│
disable() ─────────────────────── Capabilities/events deregistered
│
uninstall()
│
▼
onPluginStop() called
Spring context closed
PF4J unloads JAR