Site Initializers 101

Couldn't find any documentation on site initializers, so I thought I'd blog about them.

Introduction

Historically developers have always wanted to be able to control site creation.

It's not that Liferay admins are untrusted or anything like that. It is really more the case where setting up a new site can get complicated and the steps to create the site could be quite long. A site may have initial documents, web contents (with structures and templates), pages to create and populate with theme settings, layouts (for widget pages), portlets or fragments placements, segment definitions, permissions, custom fields, configuration, ...

Leaving all of that to a manual process, no matter how well documented it may be, well that's just asking for trouble.

One of the first ways that we (developers) tried to solve this problem was through the use of the LAR files. We get the site just the way we want it in a lower environment, then we export the LAR file and load it into a new site in a new environment. It sounds good, but in practice this is often challenging when LAR file loading fails but more importantly if the LAR really needs changes at load time due to site configuration and what not. The LAR process is just not flexible enough to handle all of the cases that we might need to consider when loading.

For a long time Liferay provided (and actually still does provide) a framework called the Resource Importer. The RI can load XML resources and non-XML assets from a known filesystem layout, and the loading itself could apply interpolation so the resources themselves were more like templates than just concrete instances. The RI itself has a lot of functionality and capability built into it, but outside of not being well documented it was also a little dated. RI cannot handle the new content pages or fragments, for example, plus it is missing other things that are necessary to be able to instantiate a site for later versions of Liferay 7.x.

Liferay recognized these and other shortcomings with the RI, so they set out to design a new way to load sites, and the Site Initializer was born.

What is the Site Initializer

Site Initializers are the cool way to create new sites. Note that I didn't say "cool new way to create new sites" because actually the Site Initializer has been around since Liferay 7.1.

So what is a Site Initializer anyway? Well, first it is an interface, the com.liferay.site.initializer.SiteInitializer interface, so it is intended to be an OSGi @Component that you'll define for your own SI implementation. The SiteInitializer implementation is a programmatic way to populate a site, so it is a tool for developers.

But most of all a SiteInitializer is a blank canvas. It is really a tool to pre-create everything you need in a new site: every page, every layout, every portlet, widget or fragment in the page, roles, users, permissions, folders and files, forms, etc.

That's great, you might ask, but how do you get started? First of all, it is backed by an interface so we're going to use a module to contain the SiteInitializer implementation. It is standard practice to use a separate module for your SI implementation because typically you'll have a number of resource files and dependencies that your SI will be loading, so separating it from your other modules will help prevent module pollution.

So if we wanted to create an Example Site Initializer, we might create the module in our Liferay Workspace in the modules directory using the command:

blade create -t api -p com.example.theme.example.site.initializer.internal example-site-initializer

We'll have some cleanup to do, but at least we have a module ready to start our additions. The first thing I do is update my bnd.bnd file for my new initializer:

Bundle-Name: Example Site Initializer
Bundle-SymbolicName: com.example.site.example.site.initializer
Bundle-Version: 1.0.0
Web-ContextPath: /site-example-site-initializer

Then I create my concrete implementation class like so:

@Component(
	immediate = true,
	property = "site.initializer.key=" + ExampleSiteInitializer.KEY,
	service = SiteInitializer.class
)
public class ExampleSiteInitializer implements SiteInitializer {

	public static final String KEY = "example-initializer";

	@Override
	public String getDescription(Locale locale) {
		ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
			"content.Language", locale, getClass());

		return LanguageUtil.get(resourceBundle, "example-description");
	}

	@Override
	public String getKey() {
		return KEY;
	}

	@Override
	public String getName(Locale locale) {
		ResourceBundle resourceBundle = ResourceBundleUtil.getBundle(
			"content.Language", locale, getClass());

		return LanguageUtil.get(resourceBundle, "example");
	}

	@Override
	public String getThumbnailSrc() {
		return _servletContext.getContextPath() + "/images/thumbnail.png";
	}

	@Override
	public void initialize(long groupId) throws InitializationException {
		// this is where the magic starts...
	}

	@Override
	public boolean isActive(long companyId) {
		return true;
	}
	
	@Reference(
	  target = 
	    "(osgi.web.symbolicname=com.example.site.example.site.initializer)"
	)
	private ServletContext _servletContext;
}

The remaining step is to provide the missing parts, namely the resources...

SI Module Parts

An SI module is going to contain a couple of basic parts:

  • A concrete class that implements the SiteInitializer interface and is registered as an OSGi @Component.
  • In the src/main/resources for our project, all of the additional files we need to use to populate the new site with.

Reads like a short list, but once you start building one of these you'll find that it is going to take some time and energy to get right.

The SiteInitializer interface is pretty short - here's the one that I grabbed from 7.4 CE:

public interface SiteInitializer {
  public String getDescription(Locale locale);
  public String getKey();
  public String getName(Locale locale);
  public String getThumbnailSrc();
  public void initialize(long groupId) throws InitializationException;
  public boolean isActive(long companyId);
}

The getKey() method returns the key for the site initializer, and all keys should be unique. Make sure when you're selecting your key that there will be no conflict with other possible SI implementations. A good way to do this is to use the name of your initializer with a "-initializer" suffix; for example, the Commerce Minium site initializer uses a key of "minium-initializer" and the Speedwell SI uses a key of "speedwell-initializer".

getName() and getDescription() return the name and the description for the SI using the provided Locale, and normally we just route this through the resource bundle for the module to return the correct values.

getThumbnail() provides the path to the thumbnail source image for the SI.

The isActive() method is used to test whether the SI should be active or not. Typically we use this method to check to see if a specific theme the SI is using is deployed or not, and only return true if the theme is available in the portal.

The magic happens in the initialize(final long groupId) method. We're given the id of the new site group to populate, and the rest is up to us as developers to build out.

SI Resources and Initialization

So we know the magic happens in the initialize() method, but what does that magic consist of? In the initial version implemented for 7.1, there really wasn't much more additional magic to point at. In fact, you can look at the SI implementation class for 7.1's Fjord theme, the Porygon theme and the Westeros Bank theme and you'll see that there is a lot of code but not a lot of supporting code in the implementations. The one thing they did standardize on was to use JSON resource files to hold the details used to drive the site population.

Since then, well honestly not much magic has been added in. When you check the latest 7.4, for example, we can look at the Welcome site initializer (the default welcome page that you get in new Liferay instances) and there's really a lot of low-level code in there.

And really, that's kind of the point. The SI provides the framework for handling declaring your SI implementation, providing a name and description so an admin can decide if they want to use your SI or not, and when they do control gets passed to your initialize() method so you can take care of all of the details your SI needs to do.

While the Welcome site initializer from 7.4 is pretty simple, you also get the Minium Site Initializer and Speedwell Site Initializer for the Commerce themes. These two are very good examples of the kinds of control you can apply when creating a SI.

Well, I really need to correct myself here... There actually is some available magic in 7.3 and 7.4, and it comes from the commerce-initializer-util module. If you check out the AssetTagsImporter, the BlogsImporter, the JournalArticleImporter, etc. you'll find a number of implementations which will use JSON resource files to load the related resources into Liferay.

Any SI that I build leverages these commerce-initializer-util classes when I can, and if I find one that is missing I'll typically create them using these classes as templates of a clean way to implement them.

Conclusion

So this was your grand tour of Liferay's Site Initializers.

As you build and deploy your initializers, you can go in and create a new site in the control panel and pick your site initializer and see your new site with all of your resources loaded in, ready to go.

This is an important thing about the Site Initializer - it really is only used when the site is first being created. After your site is created, you can't reapply a different SI implementation or apply changes from your updated SI to an existing site - it is only used during site creation. If you need something that can load resources into an existing site, you'll have to dig into the Resources Importer (fortunately there's already a blog for that: https://liferay.dev/en/b/thinking-outside-of-the-box-resources-importer) or some other technique to apply site changes.

But for creating a new site using a known set of resources, it is really hard to beat the Site Initializers...

Let me know below in the comments if you've created a Site Initializer and maybe some of the tricks you used in your implementation, or if you have ideas to make the SI's a little easier to implement, share them too, I'd love to hear them.

Blogs

If you want to see an example of a site initializer created in a workspace outside of Portal check out the one I created here: https://github.com/lfrsales/site-se-site-initializerIt includes examples for creating web-content including structures and templates, collections, display page templates, pages, master pages, navigation menus, style books, and widget templates. Site Initializers really are a powerful way to programmatically create sites and content.

Nice explanations David.However I still prefer using the resources importer. Exactly because it is the easiest way to get resources into an existing site. Webcontent templates or application display templates are mostly written by our designers and they don't know how to write Java code (which is required for site initializers). They just want to write their Freemarker templates and have them deployed somehow.

In our case we have written our own "advanced" resources importer that is able to import even more type of resources like application display templates for the new search portlets, vocabularies and categories or document types (https://github.com/dmarks2/advanced-resources-importer)