Thinking Outside of the Box: Resources Importer

Introduction

On a project recently I had a Theme war and, like those themes you can download from the MarketPlace, I also had pages, contents and documents imported by the Resources Importer (RI) as a site template.

Which is pretty cool, on its own, so I could deploy the theme and create a new site based on the theme and demo how it looks and works.

But I ran into something that I consider a bug: every time the container restarts, the WAR->WAB processes my theme but also my Resources Importer stuff and it goes crazy creating new versions for the contents and documents, and my sites (if propagation was enabled) would start throwing exceptions about missing versions (I had developer mode enabled so the old versions were getting deleted).

I have open bugs on all of these issues, but it made me wonder what I could do with the RI to work around these issues in the interim.

So I knew that I would still want to use RI to load my assets, but I only ever want RI to load them once, and not again if the containing bundle were already deployed.

Running once and only once, as part of an initial deployment or perhaps as part of an upgrade, well I've seen that before, that's a perfect fit for an Upgrade Process implementation.

So I had an idea that I wanted to build an upgrade process that could invoke RI to import new resources. The Upgrade framework would ensure that a particular upgrade step would only run once (so I don't end up w/ weird version issues), that I could support doing version upgrades when necessary, and since I'm using RI I don't have to recreate the wheel to import resources.

What Can RI Do OOTB?

So the Resources Importer (RI) is an integrated part of Liferay 7.x CE and Liferay 7.x DXP. It is implemented in the com.liferay:com.liferay.exportimport.resources.importer bundle. RI ships with the following capabilities OOTB:

That second one was a doozy to find. It's not really documented, but it is in the code to support it.

If you trace through the code from modules/apps/web-experience/export-import/export-import-resources-importer in the com.liferay.exportimport.resources.importer.internal.extender.ResourceImporterExtender class, you will see that it has code to track all com.liferay.exportimport.resources.importer.provider.ResourceImporterBundleProvider instances. When found, the RI infrastructure will look for a liferay-plugin-package.properties file in your bundle classes which defines where to find the resources to import. So if you register a ResourceImporterBundleProvider component in your bundle, RI will load your resources from that bundle.

Now I don't know if it suffers from the same issue as the WAR->WAB reloading loop, so it might have issues on its own, but that would take some testing to find out.

The LMB message aspect can be found in the ResourceImporterExtender class.  If you don't want to use the ResourceImporterBundleProvider aspect, you could use the code in this class to initialize the bundle servlet context and send RI a hot deploy message and it will kick of the resource import (which is how the Extender class actually invokes RI, so if you can use the ResourceImporterBundleProvider component it will save you some boilerplate code).

The LAR file handling was interesting.  You can have a single LAR file as /WEB-INF/classes/resources-importer/archive.lar to load public resources.  If you have public and private or just private resources, you have to use /WEB-INF/classes/resources-importer/public.lar and/or /WEB-INF/classes/resources-importer/private.lar respectively.

Can I Do More with RI?

In short, yes. The first problem is that the Liferay APIs are not exported, so even though their bundle has the necessary classes, they are hidden away.

So in my workspace, https://github.com/dnebing/rsrc-upgrade-import, I have a module, resources-importer-api, which has copies of the classes but they are exported. Included in this module are some extension classes I created to support running RI within a bundle's UpgradeProcess.

The second module, resources-importer-upgrade-sample, is a sample bundle that shows how to build out an upgrade process that invokes the Resources Importer in upgrade processes.

The code that is checked in is configured only for bundle version 1.0.0. You can build and deploy to get the Dog articles.

Next, change the version to 1.1.0 in the bnd.bnd file and uncomment the line, https://github.com/dnebing/rsrc-upgrade-import/blob/master/modules/resources-importer-upgrade-sample/src/main/java/com/liferay/exportimport/resources/importer/sample/ResourceImporterUpgradeStepRegistrator.java#L73, build and deploy to get the Cat articles.

Next, change the version to 1.1.1 in the bnd.bnd file and uncomment the line, https://github.com/dnebing/rsrc-upgrade-import/blob/master/modules/resources-importer-upgrade-sample/src/main/java/com/liferay/exportimport/resources/importer/sample/ResourceImporterUpgradeStepRegistrator.java#L76, build and deploy to get the Elephant articles.

At each deployment, only the assets tied to the upgrade process will be processed. And if you start your version at 1.1.1 and uncomment both of the registry lines referenced above, build and deploy the first time to a clean environment, you'll see all 3 upgrade steps run in sequence, 0.0.0 -> 1.0.0, 1.0.0 -> 1.1.0, and 1.1.0 -> 1.1.1.

Since we're using an upgrade process to handle the asset deployment, the RI will only run once and only once for each version.

Conclusion

With the provided API module, there are more ways to leverage the RI. I can imagine a message queue listener that receives specially crafted messages that contain articles that transforms these into consumable RI objects and invokes RI to do the heavy lifting, invoking the RI system to properly load up the assets correctly, letting it invoke all of the necessary Liferay APIs.

Or a directory watcher that looks for files dropped in a particular folder and does pretty much the same thing.

For the record, I don't think I'd want to use this for managing the deployment of a long list of assets. I wouldn't want to use the RI as some sort of content promotion process as content creation is not a development activity and should be handled by appropriate publication tools built into the Liferay platform.

Anyways, check out the blog project repo at https://github.com/dnebing/rsrc-upgrade-import and let me know what you think...

Blogs