Blog

«Back

Making Freemarker templates truly flexible with Liferay Document and Media Library

In my previous post about Freemarker I explained what was needed in order to use a custom template loader for loading your Freemarker template views from Liferay’s Document and Media Library. In this blog post I will go more into detail on how it is used and how I made it.

First you need to have your class implement freemarker.cache.TemplateLoader in order to have your template loader bean recognize your class as a Freemarker Template Loader. There are four methods that you need to implement, well actually there is three since you can leave one empty.

  • public final Object findTemplateSource(final String name)
  • public long getLastModified(final Object templateSource)
  • public final Reader getReader(final Object templateSource, final String encoding)
  • public void closeTemplateSource(final Object templateSource)

It also has a constructor that takes five arguments which initializes the instance variables. These variables decides some of the behavior of the template loader and they are set using a properties file called freemarker.properties which is located in src/main/resources. The variables are then loaded into the liferayFreemarkerTemplateLoader bean in applicationContext.xml through constructor-arg parameters and the use of property-placeholder for reading the properties file.

Snippet of the bean in applicationContext.xml:

 <context:property-placeholder  location="classpath:freemarker.properties"  ignore-unresolvable="true" />

    <bean id="liferayFreemarkerTemplateLoader"  class="com.monator.freemarker.service.LiferayFreemarkerTemplateLoader">
        <constructor-arg value="${site.name}" index="0"/>
        <constructor-arg value="${create.site.if.not.exists}" index="1"/>
        <constructor-arg value="${freemarker.template.path}" index="2"/>
        <constructor-arg value="${create.folder.if.not.exists}" index="3"/>
        <constructor-arg value="/WEB-INF/freemarker/default/view.ftl" index="4"/>
    </bean>

 

The instance variables and how they are initialized in the constructor:

    /** Name of the Site under which the template will be found. */
    private String site_name;

    /** Path to the folder where your templates lies. */
    private String template_folder_path;

    /** Determines if the Site should be created or not when not existing. */
    private boolean create_site;

    /** Determines if the folders should be created or not when not existing. */
    private boolean create_folder;

    /** Default template which will be copied to the created folder path if
create_folder is true. */

    private Resource default_template;

 public LiferayFreemarkerTemplateLoader(final String siteName,  final boolean createSite, final String folderPath,  final boolean createFolder, final Resource defaultTemplate) {
        this.site_name = siteName;
        this.create_site = createSite;
        this.template_folder_path = folderPath;
        this.create_folder = createFolder;
        this.default_template = defaultTemplate;
    }

 

As seen above the behavior that is controlled with instance variables is if the template loader should create the Site and folder path if they don’t exist and thereby also copy a default template into the newly created template folder. If either of the booleans are false the template loader will return null and print a log message explaining what has happened and what could be done to fix it.

The method findTemplateSource is where all the magic happens and to speed things up it uses Liferay’s cache to save which folderId the template lies in and also under which groupId (Site) the folder is located. If the folderId is available in the cache the template loader uses it to fetch the template from the Document and Media Library. If it should not exist in the cache the template loader gets the folderId using the template_folder_path. The path is split into an array and then used as an argument to the recursive method getTemplateFolderIdFromPath(long groupId, long folderId, List<String> templateFoldersArray, ServiceContext serviceContext), which traverses through the folder array and returns the folderId for the last folder in the array. If the folder path does not exist, the method creates the folders or returns MISSING_FOLDER if the boolean create_folder is false.

The same goes for groupId, if it is available in the cache it is used when fetching the template. If not, the template loader uses the private method getTemplatesGroupId(final String siteName, final ServiceContext serviceContext) to collect it depending on the boolean create_site. If the boolean is true the method creates the Site and returns the new groupId, otherwise it prints a log message explaining the problem and a possible solution and returns MISSING_SITE.

When we have both the folderId and the groupId (Site) for where the template is located we can use DLFileEntryLocalServiceUtil.getFileEntry(long groupId, long folderId, String title) to fetch the template and return it to the view resolver.

We have now done the bulk job for the template loader and only have three small methods left. public final long getLastModified(final Object templateSource) returns in milliseconds when the template (DLFileEntry) was last modified, public final Reader getReader(final Object templateSource, final String encoding) just returns an InputStreamReader created from the DLFileEntry’s ContentStream and the last method to implement is public void closeTemplateSource(final Object templateSource) which in our case doesn’t need any code and can be left blank.

Now we are done with the Freemarker Template Loader and only need to make sure that our applicationContext.xml is configured for using custom template loaders. If your freemarkerConfig bean looks like this you’re all set.

 <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer" p:preferFileSystemAccess="false" >
        <property name="preTemplateLoaders" ref="freemarkerTemplateLoaderList" />
    </bean>

 

This was kind of a crash course in how to create your own Freemarker Template Loader which reads its templates from Liferay’s Document and Media Library. But for those of you interested in seeing all of the code and try out the portlet, you can find the project on GitHub: https://github.com/monator/freemarker-template-loader-samples

Stay tuned for coming blog posts about how to use this newly learned knowledge of loading Freemarker templates from Liferay’s Document and Media Library together with Liferay Sync to get seamless live editing of your portlet views! And as if that wasn’t enough I will also explain more in detail how to create a Freemarker Template Loader which uses CMIS for reading templates from different repositories.

Comments
Trackback URL:




Liferay on Twitter

What are people saying about Liferay on Twitter?