Blogs
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
SiteInitializerinterface and is registered as an OSGi@Component. - In the
src/main/resourcesfor 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.

