public class HelloWorldPlugin extends AbstractPlugin {
public BasicPlugin(PluginWrapper wrapper, PluginEnvironment env) {
super(wrapper, env);
}
}
Plugins can extend the REST and GraphQL API and also hook into various places of Gentics Mesh.
A plugin developer needs to extend the AbstractPlugin
class. The plugin can hook into different parts of Gentics Mesh by implementing one of multiple interfaces for the different plugin types.
The plugin code needs to be packages as a regular jar
file.
In general the plugin developer just needs to implement the methods which will be invoked by Gentics Mesh.
New Plugin classes need to extend the AbstractPlugin
and add the mandatory constructor.
public class HelloWorldPlugin extends AbstractPlugin {
public BasicPlugin(PluginWrapper wrapper, PluginEnvironment env) {
super(wrapper, env);
}
}
Additionally the plugin class provides some utility functions which make it easier to handle plugin configuration and plugin data.
The configuration of the plugin will be stored in the plugin directory in a plugins/{pluginId}/config.yml
file which can be overridden by a /plugins/{pluginId}/config.local.yml
file.
Method | Description |
---|---|
|
Write the configuration POJO to the |
|
Read the configuration using the class. This method will also use the |
|
Return the config file. |
|
Return the local config file. |
|
Return the path to the storage directory |
The manifest of a plugin contains metadata information which will is used to load and categorize the plugin.
Name | Description |
---|---|
Plugin-Id |
Unique id of the plugin. Used to access the plugin once deployed. |
Plugin-Name |
Full name of the plugin |
Plugin-Version |
Current version of the plugin |
Plugin-Author |
Author of the plugin |
Plugin-Class |
Fully qualified path to the plugin class |
Plugin-Description |
Short description of the plugin |
Plugin-License |
License used by the plugin |
Plugin-Inception |
Date when the plugin was initially created (DD-MM-YYYY) |
Plugin-Dependencies |
List of plugin ids of other plugins on which the plugin depends. |
The plugin manifest can directly be added when generating the shaded jar file.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Plugin-Id>${plugin.id}</Plugin-Id>
<Plugin-Name>${plugin.name}</Plugin-Name>
<Plugin-Version>${plugin.version}</Plugin-Version>
<Plugin-Author>${plugin.author}</Plugin-Author>
<Plugin-Class>${plugin.class}</Plugin-Class>
<Plugin-Description>${plugin.description}</Plugin-Description>
<Plugin-License>${plugin.license}</Plugin-License>
<Plugin-Inception>${plugin.inception}</Plugin-Inception>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
All the needed maven artifact are located at our Maven Repository. Please add the following section to your pom.xml
in order to be able to download the dependencies.
<repositories>
<repository>
<id>maven.gentics.com</id>
<name>Gentics Maven Repository</name>
<url>https://maven.gentics.com/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
The dependency version management can be managed more efficiently by just including the mesh-plugin-bom
dependency. This will import the dependencyManagement for all plugin related dependencies.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.gentics.mesh</groupId>
<artifactId>mesh-plugin-bom</artifactId>
<version>${mesh.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
The plugin needs at least the mesh-plugin-dep
dependency. It is important to set the scope to provided
. Otherwise the plugin can’t be loaded by Gentics Mesh.
<dependency>
<groupId>com.gentics.mesh</groupId>
<artifactId>mesh-plugin-dep</artifactId>
<scope>provided</scope>
</dependency>
Plugins can access Gentics Mesh data via the provided clients.
The adminClient
will use the admin
permissions and can by-default read everything. The userClient
on the other hand will be created using the user information from the request that has been send to the plugin.
router.route("/example").handler(rc -> {
PluginContext context = wrap(rc);
MeshRestClient adminClient = adminClient();
MeshRestClient userClient = context.client();
…
});
Plugins can also use and provide extensions. Extension can be used to modularize plugins. A plugin can for example provide a default implementation of a feature and another plugin can provide an extension which overrides or extends this feature.
The plugin (consumer) which uses the extension must provide an ExtensionPoint
interface. This interface forms the contract which other plugins (provider) may use to implement the extension.
public interface DummyExtensionPoint extends ExtensionPoint {
String name();
}
The plugin which provides the extension now implements the extension.
@Extension
public class DummyExtension implements DummyExtensionPoint {
@Override
public String name() {
return "My dummy extension";
}
}
Finally the consuming plugin may access the available extension via the plugin manager.
getWrapper().getPluginManager().getExtensions(DummyExtensionPoint.class)
A full example can be found in our example repository.
The use of extensions requires additional build steps. Make sure to use the ExtensionAnnotationProcessor
in the compile plugin.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<verbose>true</verbose>
<compilerVersion>1.8</compilerVersion>
<source>1.8</source>
<target>1.8</target>
<annotationProcessors>
<annotationProcessor>org.pf4j.processor.ExtensionAnnotationProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
<dependencies>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
</plugin>
Additionally the plugin which provides the extension muse have an <Plugin-Dependencies>
manifest entry that contains the consuming plugin id.
Make sure to share the extension point interfaces via a common maven module. |
Plugins can be directly tested in your IDE by starting an embedded Gentics Mesh instance. We provide a JUnit class rule which can be used to quickly startup Gentics Mesh: OrientDBMeshLocalServer
@ClassRule
public static final MeshLocalServer server = new OrientDBMeshLocalServer()
.withInMemoryMode()
.withPlugin(GraphQLExamplePlugin.class, "myPlugin")
.waitForStartup();
You need to following test dependencies in order to use the class rule.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.gentics.mesh</groupId>
<artifactId>mesh-test-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.gentics.mesh</groupId>
<artifactId>mesh-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.gentics.mesh</groupId>
<artifactId>mesh-mdm-orientdb-wrapper</artifactId>
<scope>test</scope>
</dependency>
This example shows how to test the GraphQL plugin using Mesh with the OrientDB storage option.
public class GraphQlExamplePluginTest {
private static String PROJECT = "test";
@ClassRule
public static final MeshLocalServer server = new MeshLocalServer()
.withInMemoryMode()
.withPlugin(GraphQLExamplePlugin.class, "myPlugin")
.waitForStartup();
@Test
public void testPlugin() throws IOException {
MeshRestClient client = server.client();
// Create the project
Completable createProject = client.createProject(
new ProjectCreateRequest()
.setName(PROJECT)
.setSchemaRef("folder"))
.toCompletable();
// Run query on the plugin
String query = "{ pluginApi { myPlugin { text } } }";
Single<GraphQLResponse> rxQuery = client.graphqlQuery(PROJECT, query)
.toSingle();
// Run the operations
GraphQLResponse result = createProject.andThen(rxQuery).blockingGet();
// Print the GraphQL API result
System.out.println(result.getData().encodePrettily());
}
}
You can follow the Plugin Development Guide for a step by step introduction.
The Plugin Examples demonstrates basic plugin usage and test setup.
The plugin system relies on PF4J which uses a Parent Last classloader. This means that classes will be first loaded from the plugin even if they are also part of Gentics Mesh.
To avoid NoClassDefFoundError
/ NoSuchMethodError
it is thus recommended to add all the classes (libraries) that are used by the plugin jar.
The mesh-plugin-api
, vertx-core
, vertx-web
, netty
libraries must not be included in the plugin jar. Adding those libraries could cause stability issues and errors.
Instead you must mark those dependencies as provided.
...
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<scope>provided</scope>
</dependency>
...
I’m getting java.lang.NoSuchMethodException: com.gentics.mesh.plugin.MyPlugin.<init>(org.pf4j.PluginWrapper, com.gentics.mesh.plugin.env.PluginEnvironment)
You get this error if you have not added the mandatory constructor to your plugin.
I’m getting NoClassDefFoundError
/ NoSuchMethodError
You deployed your plugin on a Gentics Mesh instance which is not compatible with the classes you are using. You can rebuild the plugin with the new version or try to include your dependencies in the plugin jar to avoid classloader issues. Please note that vertx-core
, vertx-web
, netty
should not be included in the jar.
I’m getting Caused by: java.lang.ClassCastException: io.vertx.core.logging.SLF4JLogDelegateFactory cannot be cast to io.vertx.core.spi.logging.LogDelegateFactory
This error happens if you include vert-core
in your plugin. Please ensure that you don’t include any vert.x dependency that is part of Gentics Mesh. Instead mark those as provided
.
You wrote a plugin? Great! Share it with the community via the Gentics Mesh Awesome List