Blueprint Developer Manual / Version 2304
Table Of ContentsEach plugin has its own class loader and can bring its own third-party libraries. This gives you control over the versions of the third-party libraries you use and makes you independent of third-party version changes in the particular CoreMedia Content Cloud application, so that your plugin will reliably continue to work with CoreMedia Content Cloud updates. The plugin's libraries are included in the plugin ZIP file, where the plugin class loader discovers them and loads the classes from.
However, at runtime your extensions are integrated into the particular CoreMedia Content Cloud component which features the extension point. Technically, this requires some classes (especially the Spring Framework) to be shared with the application.
Moreover, some frameworks like slf4j have application wide aspects, which do not work with objects of classes from different class loaders. And last but not least, any CoreMedia Content Cloud classes must be shared with the application. Even if it would work to use a particular CoreMedia Content Cloud feature just like an independent third-party library, CoreMedia does not guarantee this for updates, so just don't do it.
The following subsections describe how you can handle these issues.
Dependencies in Practice
Plugin developers must handle the difference between plugin libraries and shared classes
appropriately. You have to develop your plugin in a Maven project.
Each extension has its own module. Add a dependencyManagement
section
with at least the following entries to your POM file:
The BOM that manages the extension point
The BOM that manages the shared classes for the particular application (Each application with extension points provides such a BOM.)
Now, if you add a new dependency to your POM file, check whether it is managed by this
dependency management. If it is not, it does not need to be shared, and you can
simply declare the version of your choice in the dependency. Otherwise, omit the
version and set the dependency scope to provided
.
Scoping all shared dependencies as provided
,
you can easily use the maven-dependency-plugin
and the maven-assembly-plugin to build the
plugin zip file including the non-shared libraries.
For example, a POM file for a studio-server plugin looks like this:
<project> <!-- [...] --> <dependencyManagement> <dependencies> <!-- For the extension point, e.g. ContentHubAdapterFactory --> <dependency> <groupId>com.coremedia.cms</groupId> <artifactId>studio-server-core-bom</artifactId> <version>${studio-server.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- For the third-party classes that must be shared with the studio-server application --> <dependency> <groupId>com.coremedia.cms</groupId> <artifactId>studio-server-thirdparty-for-plugins-bom</artifactId> <version>${studio-server.version</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- An independent third-party library, managed right here. --> <dependency> <groupId>com.sun.activation</groupId> <artifactId>jakarta.activation</artifactId> <version>1.2.2</version> </dependency> <!-- coremedia dependencies must always be shared. --> <dependency> <groupId>com.coremedia.cms</groupId> <artifactId>content-hub-api</artifactId> <scope>provided</scope> </dependency> <!-- The Spring framework is managed by studio-server-thirdparty-for-plugins-bom, thus, it must be shared by the plugin. --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <scope>provided</scope> </dependency> </dependencies> <!-- [...] --> </project>
More Dependency Subtleties
Further dependency problems might occur when you try to link instances of plugin classes with objects of
application classes. The following example illustrates this.
Assume, that you want to use the FasterXML Jackson libraries in your plugin. Those are not managed in the
studio-server-thirdparty-for-plugins-bom
, so you add a normal dependency to your pom:
<dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>com.coremedia.cms</groupId> <artifactId>content-hub-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <scope>provided</scope> </dependency> <!-- [...] --> <dependency>
You code your plugin, you build your plugin, you run your plugin. Everything works fine.
The next change in your plugin involves Spring's MappingJackson2HttpMessageConverter
:
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; private HttpMessageConverter<Object> createMessageConverter() { ObjectMapper objectMapper = new ObjectMapper(); MappingJackson2HttpMessageConverter mc = new MappingJackson2HttpMessageConverter(); mc.setObjectMapper(objectMapper); return mc; }
You code your plugin, you build your plugin, you run your plugin. Everything wor... but wait, what's this?
the class loader org.pf4j.PluginClassLoader @227b4be7 of the current class, com/acme/my/famous/StudioPlugin, and the class loader 'app' for the method's defining class, org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter, have different Class objects for the type com/fasterxml/jackson/databind/ObjectMapper
The MappingJackson2HttpMessageConverter
class is shared with the application (because
the spring-web
dependency has the scope provided
) and is loaded
by the application class loader. MappingJackson2HttpMessageConverter
references
ObjectMapper
as a method argument, so the application class loader also loads the
ObjectMapper
class.
Your plugin declares an ordinary (scope compile
) jackson-databind
dependency.
Thus, the plugin class loader also loads the ObjectMapper
class, independently of the
application. Now, you invoke MappingJackson2HttpMessageConverter.setObjectMapper
.
The argument is an instance of the plugin's ObjectMapper
, but the method expects an instance
of the application's ObjectMapper
. These two classes are not assignment compatible, not even if
the plugin and the application use the same version of the jackson-databind
library!
This causes the observed error.
The solution is to set the scope provided
for the jackson-databind
dependency,
in order to share the ObjectMapper
class with the application,
even though jackson-databind
is not managed by studio-server-thirdparty-for-plugins-bom
.
However, this has some drawbacks that you know already from the CoreMedia Extensions:
You are bound to the particular version of
jackson-databind
that is used by Spring, so you cannot use the new features of later versions.Other CoreMedia Content Cloud versions may use other Spring/Jackson versions, which may differ in functionality and make your plugin less portable.
This combination of Spring and Jackson is just an example. You might have to use
other libraries with scope provided
too. CoreMedia refrained from adding all such transitive
dependencies to the thirdparty-for-plugins
BOMs, because of the following reasons:
The appropriate scope depends on the particular usage of the library. It is not generally
provided
. As long as you use a library only with other plugin classes, you can use acompile
scope dependency.Normally, BOMs do not list transitive dependencies explicitly, so CoreMedia adheres to this convention.
Due to the drawbacks mentioned above, CoreMedia recommends that you use scope compile
for dependencies
that are not managed by the thirdparty-for-plugins
BOMs whenever possible, and only switch to provided
when necessary.