Spring context configuration in Magnolia
Here at Trifork we have quite a Magnolia portfolio. We do not only install, configure and host the actual CMS itself alone, we also design and implement pixel-perfect designs, and last but certainly not least: integrate new or existing business processes from external resources.
When we create any piece of software for such an integration, that software is 90% of the time a Spring configured application. For example, when we need to load content from an external database into the Data module, or when we want to create a Blossom dialog that renders a select box based on results from an external web service. We create these services in a separate abstraction layer, like most of us would, so that we end up with a nice separate, reusable and testable module. The services we create are usually configured in a number of Spring contexts.
Loading your contexts in the Magnolia admin central.
Are you building your own Magnolia modules instead of Magnolia (1)? Are you letting your modules configure your Magnolia instance (2)?
If the answer to both of these questions is yes, and you do a fair bit of Spring in your modules, you might want to read on!
How would we usually configure such a module
When you want to enable any kind of Spring in your Magnolia web application, you will probably add Springs’ ContextLoaderListener in your web.xml and either load all of the contexts directly using the contextConfigLocation parameter, or perhaps import ’em all in your root application context.
Your regular contextConfigLocation looks like the web.xml excerpt below. There is nothing new going on here. Just plain old stuff.
... <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <display-name>Magnolia global filters</display-name> <filter-name>magnoliaFilterChain</filter-name> <filter-class>info.magnolia.cms.filters.MgnlMainFilter</filter-class> </filter> ... <listener> <listener-class>info.magnolia.cms.servlets.MgnlServletContextListener</listener-class> </listener> ...
What is the issue here, you might think? Well, nothing to be quite honest. This works like a charm, and when the ContextLoaderListener is placed above the MgnlServletContextListener, your services will even be able to access the Magnolia repositories. But when you are creating multiple modules that are reusable in multiple applications, that have multiple contexts, module dependencies etcetera, setting up and maintaining these configurations can become quite a hassle.
In my opinion: when you want to add a module to Magnolia, whatever framework is used, the fact that you will possibly have to update the web.xml, modify any existing contexts to make it work is pretty annoying. This approach also does not fit in Greg’s “Don’t configure Magnolia: let your projects configure it.” principal. It should just work out of the box!
Ah, Spring in Magnolia you’re saying? You mean Blossom!!
This is something that often Springs to mind when discussing Magnolia in combination with Spring (no pun intended). Blossom is a great way of using dynamically generated Dialogs and their content validators, VirtualURIMappers or Spring MVC controllers in Magnolia (and much more). But the issue that I want to touch upon is purely the configuration of your Spring contexts in your module.
Usually there is already a ContextLoaderListener and root context present in your app. But when we actually want to enable a module like say Blossom in our existing web application, you guessed it…, you will perhaps have to edit your web.xml again and add some more contexts to your existing application.
For each and every module with an own configuration you’d have to repeat these steps.
So when I am talking about Spring context loading in Magnolia, I really just mean the context loading itself. Either for using Blossom, or some of our own many modules, the integration of the actual context loading and managing the configuration is not very subtle or centralized.
So then what would the ideal scenario look like?
Magnolia already has a very neat way of installing, updating and configuring modules. You can register dependencies in the config, or perhaps deltas in your version handler, and perform elaborate update tasks.
Why not make use of all of these great features and actually take control of Magnolia’s rich dynamic configuration mechanism. It would be very fancy if one could just create a configuration node in a module, specifying any contexts that need to be loaded. Let the Magnolia ObservedManager pick up any values that I set in the config for handling changes runtime. We could also export these configuration nodes, and bootstrap them upon version change. This way the configuration of a module is completely defined in the admincentral.
We at Trifork are using Maven a lot in our projects. With modules like this we would just have to add a dependency to the application, and that’s it! The contexts are automatically added to the root context.
This is exactly what we did a couple of years ago with our Magnolia Spring context configuration module. Simple is beautiful, as appears in just about every Magnolia adminCentral you open.
Set up instructions
The Magnolia Spring context configuration module is a maven module that you will have to add to your website’s pom.xml. From that moment on, all your context configurations will be managed inside the Magnolia admin central and not in your webapp structure.
Within the config node of your module, we can specify the spring-contexts and the spring-beans (override-able properties) that this module needs. In the example below, two contexts are loaded: the ‘spring-ws-context’ and an ‘external-db-context’. The contexts are expanded to show you the configuration needed for a context.
- The context-path node contains the import path to your context.
- The enabled node, gives you the option of enabling or disabling a context.
This might be useful for enabling an application on an author instance only, or whatever case you might have.
If you have any settings that need to be configured, you can put these in the spring-beans folder as shown below:
The above configuration creates two String beans in the Spring context. A solrServerUrl String bean and a wordPressJsonApi String bean. Set the type column accordingly.
Currently supported bean types are:
- Long / Integer
Only these three types are available due to lack of effort by me. You could create brilliant structures to configure complete beans with references, lists, maps etc., but these eventually need to be in your context, and not in your configuration. This might be a feature for the future, but in the last years, these bean types were more than sufficient.
NOTE: Providing a sample-config node in your module as shown in the illustration, could prove useful to show all of the available properties that are available in your config.
Upon first start up, I usually run into the fact that I forgot to configure my ContextLoaderListener all together. But that might be just me.. This would result in the following error in your log:
ERROR SpringContextLoadingModule: No WebApplicationContext found. The Spring listener must be loaded first in the web.xml, above the Magnolia listener.
When you start up Magnolia after fixing the above and with the configuration in place as discussed earlier, Magnolia will startup clean and you can see the following entries in your log:
DEBUG SpringContextLoadingModule: Context configuration 'spring-ws-context' is enabled. DEBUG SpringContextLoadingModule: Context configuration 'external-db-context' is enabled. INFO SpringContextLoadingModule: Loaded 2 contexts for my-module DEBUG SpringContextLoadingModule: Creating String bean wordPressJsonApi DEBUG SpringContextLoadingModule: Creating String bean solrServerUrl INFO SpringContextLoadingModule: Loaded 2 bean definitions for my-module
When your configuration is finished, just export the configuration nodes, add them to your VersionHandler that will bootstrap them upon install. You are also free to do some version management, like removing old definitions, or renaming or adding new ones to your module.
installOrUpdateTasks.add(new BootstrapConditionally( "Context config", "Bootstrap the context configuration for MyModule", "/path/to/context-config.xml"));
NOTE: Use the BootstrapConditionally task instead of the BootstrapSingleResource for your spring config folders. You might not want to overwrite any pre-existing configuration settings.
Ok, that’s what I need. let me put it in my pom to give it a try!
We are working on a way of sharing our numerous Magnolia modules with you. For now if you are interested in this one, click here, fill out the form and we will send it to you…and then you can read this blog and take some action for yourself! Enjoy and remember if you need any help just contact us.