Thursday 28 January 2016

Liferay Themes

This is a followup of the blog entry about Liferay1 2 3.

As I wished to tailor the website layout of my Liferay instance, I started looking at themes. What I found was a bit of a surprise for me, as it seems themes for Liferay needs to be compiled/build using ant or maven.

There is a big body of documentation regarding the creation of Themes, but the procedure seems a bit brittle. It uses ant, needs to be able to reach the files of the classic theme, is dependent of the type of Application Server you are using, and a bit of other stuff.

Below are some of the error messages I got and had to solve before I got anything that worked.

Creating a Theme with Ant

  • Download the Plugins SDK that compiles new themes (and plugins) using Ant.
  • Create a properties file like build.<username>.properties
    [glassfish@localhost glassfish]$ cat liferay-plugins-sdk-6.2/build.glassfish.properties
    app.server.type=glassfish
    app.server.parent.dir=/home/glassfish
    app.server.glassfish.dir=${app.server.parent.dir}/glassfish4/glassfish
    javac.compiler=modern
  • In this directory, there should be a directory themes, where we can create our new theme6.
    [glassfish@localhost themes]$ ./create.sh mine "Mine"
    Parallel execution with configuration on demand is an incubating feature.
    :createTheme

    BUILD SUCCESSFUL

    Total time: 2.377 secs
  • Changed the build.xml in mine-theme, to use 'classic' as the parent theme.
    <property name="theme.parent" value="classic" />
    If you create your own theme with the Plugins SDK, place the icon in themes/Your_theme/docroot/_diffs/images/favicon.ico if you want to have your own favicon to bookmark.
  • Then "ant war"
  • Then "ant deploy" (or just copy the war into glassfish4/deploy)
The first problem I ran into was a problem with the xml file.
[2015-09-19T20:45:06.136+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=140 _ThreadName=Thread-8] [timeMillis: 1442695506136] [levelValue: 800] [[
20:45:06,136 INFO  [AutoDeployer][HotDeployEvent:145] Plugin mine-theme requires marketplace-portlet]]

[2015-09-19T20:45:06.142+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=140 _ThreadName=Thread-8] [timeMillis: 1442695506142] [levelValue: 800] [[
20:45:06,136 INFO  [AutoDeployer][HotDeployImpl:217] Deploying mine-theme from queue]]

[2015-09-19T20:45:06.149+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=140 _ThreadName=Thread-8] [timeMillis: 1442695506149] [levelValue: 800] [[
20:45:06,142 INFO  [AutoDeployer][PluginPackageUtil:1016] Reading plugin package for mine-theme]]

[2015-09-19T20:45:06.193+0000] [glassfish 4.1] [INFO] [] [javax.enterprise.web] [tid: _ThreadID=140 _ThreadName=AutoDeployer] [timeMillis: 1442695506193] [levelValue: 800] [[
WebModule[null] ServletContext.log():Initializing Spring root WebApplicationContext]]

[2015-09-19T20:45:06.204+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=140 _ThreadName=Thread-8] [timeMillis: 1442695506204] [levelValue: 800] [[
20:45:06,204 INFO  [AutoDeployer][ThemeHotDeployListener:98] Registering themes for mine-theme]]

[2015-09-19T20:45:06.236+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=140 _ThreadName=Thread-8] [timeMillis: 1442695506236] [levelValue: 800] [[
20:45:06,228 ERROR [AutoDeployer][ThemeLocalServiceImpl:278] com.liferay.portal.kernel.xml.DocumentException: Error on line 8 of document  : Element type "theme" must be followed by either attribute specifications, ">" or "/>". Nested exception: Element type "theme" must be followed by either attribute specifications, ">" or "/>".
com.liferay.portal.kernel.xml.DocumentException: Error on line 8 of document  : Element type "theme" must be followed by either attribute specifications, ">" or "/>". Nested exception: Element type "theme" must be followed by either attribute specifications, ">" or "/>".
at com.liferay.portal.xml.SAXReaderImpl.read(SAXReaderImpl.java:429)
at com.liferay.portal.xml.SAXReaderImpl.read(SAXReaderImpl.java:447)
at com.liferay.portal.kernel.xml.SAXReaderUtil.read(SAXReaderUtil.java:187)
at com.liferay.portal.service.impl.ThemeLocalServiceImpl._readThemes(ThemeLocalServiceImpl.java:472)
at com.liferay.portal.service.impl.ThemeLocalServiceImpl.init(ThemeLocalServiceImpl.java:272)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:115)
at com.liferay.portal.spring.transaction.DefaultTransactionExecutor.execute(DefaultTransactionExecutor.java:62)
at com.liferay.portal.spring.transaction.TransactionInterceptor.invoke(TransactionInterceptor.java:51)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ChainableMethodAdvice.invoke(ChainableMethodAdvice.java:56)
at com.liferay.portal.spring.aop.ServiceBeanMethodInvocation.proceed(ServiceBeanMethodInvocation.java:111)
at com.liferay.portal.spring.aop.ServiceBeanAopProxy.invoke(ServiceBeanAopProxy.java:175)
at com.sun.proxy.$Proxy269.init(Unknown Source)
at com.liferay.portal.service.ThemeLocalServiceUtil.init(ThemeLocalServiceUtil.java:118)
at com.liferay.portal.deploy.hot.ThemeHotDeployListener.doInvokeDeploy(ThemeHotDeployListener.java:103)
at com.liferay.portal.deploy.hot.ThemeHotDeployListener.invokeDeploy(ThemeHotDeployListener.java:49)
at com.liferay.portal.deploy.hot.HotDeployImpl.doFireDeployEvent(HotDeployImpl.java:227)
at com.liferay.portal.deploy.hot.HotDeployImpl.fireDeployEvent(HotDeployImpl.java:96)
at com.liferay.portal.kernel.deploy.hot.HotDeployUtil.fireDeployEvent(HotDeployUtil.java:28)
at com.liferay.portal.kernel.servlet.PluginContextListener.fireDeployEvent(PluginContextListener.java:164)
at com.liferay.portal.kernel.servlet.PluginContextListener.doPortalInit(PluginContextListener.java:154)
at com.liferay.portal.kernel.util.BasePortalLifecycle.portalInit(BasePortalLifecycle.java:44)
at com.liferay.portal.kernel.util.PortalLifecycleUtil.register(PortalLifecycleUtil.java:74)
at com.liferay.portal.kernel.util.PortalLifecycleUtil.register(PortalLifecycleUtil.java:58)
at com.liferay.portal.kernel.util.BasePortalLifecycle.registerPortalLifecycle(BasePortalLifecycle.java:54)
at com.liferay.portal.kernel.servlet.PluginContextListener.contextInitialized(PluginContextListener.java:116)
at org.apache.catalina.core.StandardContext.contextListenerStart(StandardContext.java:5394)
at com.sun.enterprise.web.WebModule.contextListenerStart(WebModule.java:743)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:5932)
at com.sun.enterprise.web.WebModule.start(WebModule.java:691)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:1041)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:1024)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:747)
at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:2286)
at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1932)
at com.sun.enterprise.web.WebApplication.start(WebApplication.java:139)
at org.glassfish.internal.data.EngineRef.start(EngineRef.java:122)
at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:291)
at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:352)
at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:500)
at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:219)
at org.glassfish.deployment.admin.DeployCommand.execute(DeployCommand.java:491)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:539)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$2$1.run(CommandRunnerImpl.java:535)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:360)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$2.execute(CommandRunnerImpl.java:534)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:565)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$3.run(CommandRunnerImpl.java:557)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:360)
at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:556)
at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1464)
at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$1300(CommandRunnerImpl.java:109)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1846)
at org.glassfish.deployment.autodeploy.AutoOperation.run(AutoOperation.java:164)
at org.glassfish.deployment.autodeploy.AutoDeployer.deploy(AutoDeployer.java:597)
at org.glassfish.deployment.autodeploy.AutoDeployer.deployAll(AutoDeployer.java:484)
at org.glassfish.deployment.autodeploy.AutoDeployer.run(AutoDeployer.java:412)
at org.glassfish.deployment.autodeploy.AutoDeployer.run(AutoDeployer.java:403)
at org.glassfish.deployment.autodeploy.AutoDeployService$1.run(AutoDeployService.java:233)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
Caused by: org.dom4j.DocumentException: Error on line 8 of document  : Element type "theme" must be followed by either attribute specifications, ">" or "/>". Nested exception: Element type "theme" must be followed by either attribute specifications, ">" or "/>".
at org.dom4j.io.SAXReader.read(SAXReader.java:482)
a]]

[2015-09-19T20:45:06.236+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=140 _ThreadName=Thread-8] [timeMillis: 1442695506236] [levelValue: 800] [[
t org.dom4j.io.SAXReader.read(SAXReader.java:365)
at com.liferay.portal.xml.SAXReaderImpl.read(SAXReaderImpl.java:426)
... 82 more]]
Seems the problem was Quotes (") around the name of the theme "Mine" in the docroot/WEB-INF/liferay-plugin-package.properties file. Removed quotes, and things get picked up now.

Creating a Theme with Maven

mvn archetype:generate \
    -DarchetypeArtifactId=liferay-theme-archetype \
    -DarchetypeGroupId=com.liferay.maven.archetypes \
    -DarchetypeVersion=6.1.0 \
    -DartifactId=sample-theme \
    -DgroupId=com.liferay.sample \
    -Dversion=1.0-SNAPSHOT
I tried with Maven4 5 instead of Ant to develop a theme, but I couldn't rightly get it to work. Needs some time to work on it.

Uploading a theme

So there's a war file created containing the files. You can deploy the file into the application server.

There are several ways to do this. You can upload it to glassfish4/glassfish/domains/domain1/autodeploy.

But I find that in some cases the theme is not recorded by Liferay. Liferay listens to specific directories for new war files that are theme related. In my case the additional directory could well be glassfish4/deploy, because glassfish4 is the "home" of Liferay in my case.

You can verify if the theme was successfully picked up in your glassfish server log. You should see something lke:
[2016-01-11T14:38:26.668+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523106668] [levelValue: 800] [[
14:38:26,667 INFO  [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][AutoDeployDir:204] Processing mine-theme-6.2.0.1.war]]

[2016-01-11T14:38:26.673+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523106673] [levelValue: 800] [[
14:38:26,673 INFO  [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][ThemeAutoDeployListener:51] Copying themes for /home/glassfish/glassfish4/deploy/mine-theme-6.2.0.1.war]]

[2016-01-11T14:38:26.723+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523106723] [levelValue: 800] [[
14:38:26,722 INFO  [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][BaseDeployer:863] Deploying mine-theme-6.2.0.1.war]]

[2016-01-11T14:38:27.119+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107119] [levelValue: 800] [[
Expanding: /home/glassfish/glassfish4/deploy/mine-theme-6.2.0.1.war into /tmp/20160111143826861]]

[2016-01-11T14:38:27.563+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107563] [levelValue: 800] [[
Copying 1 file to /tmp/20160111143826861/WEB-INF]]

[2016-01-11T14:38:27.568+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107568] [levelValue: 800] [[
Copying 1 file to /tmp/20160111143826861/WEB-INF]]

[2016-01-11T14:38:27.581+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107581] [levelValue: 800] [[
Copying 1 file to /tmp/20160111143826861/WEB-INF/classes]]

[2016-01-11T14:38:27.589+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107589] [levelValue: 800] [[
Copying 1 file to /tmp/20160111143826861/WEB-INF/classes]]

[2016-01-11T14:38:27.606+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107606] [levelValue: 800] [[
Copying 1 file to /tmp/20160111143826861/WEB-INF]]

[2016-01-11T14:38:27.760+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107760] [levelValue: 800] [[
14:38:27,760 INFO  [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][BaseDeployer:2391] Modifying Servlet 2.4 /tmp/20160111143826861/WEB-INF/web.xml]]

[2016-01-11T14:38:27.860+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523107860] [levelValue: 800] [[
Building war: /tmp/20160111143827761]]

[2016-01-11T14:38:28.307+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523108307] [levelValue: 800] [[
Deleting directory /tmp/20160111143826861]]

[2016-01-11T14:38:28.331+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=112 _ThreadName=Thread-8] [timeMillis: 1452523108331] [levelValue: 800] [[
14:38:28,330 INFO  [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][ThemeAutoDeployListener:57] Themes for /home/glassfish/glassfish4/deploy/mine-theme-6.2.0.1.war copied successfully. Deployment will start in a few seconds

[2016-01-11T14:38:29.675+0000] [glassfish 4.1] [INFO] [NCLS-DEPLOYMENT-02027] [javax.enterprise.system.tools.deployment.autodeploy] [tid: _ThreadID=221 _ThreadName=AutoDeployer] [timeMillis: 1452523109675] [levelValue: 800] [[
Selecting file /home/glassfish/glassfish4/glassfish/domains/domain1/autodeploy/mine-theme.war for autodeployment]]

[2016-01-11T14:38:30.239+0000] [glassfish 4.1] [INFO] [] [javax.enterprise.system.tools.deployment.common] [tid: _ThreadID=221 _ThreadName=AutoDeployer] [timeMillis: 1452523110239] [levelValue: 800] [[
visiting unvisited references]]

[2016-01-11T14:38:31.349+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=221 _ThreadName=Thread-8] [timeMillis: 1452523111349] [levelValue: 800] [[
14:38:31,348 INFO  [AutoDeployer][HotDeployEvent:145] Plugin mine-theme requires marketplace-portlet]]

[2016-01-11T14:38:31.350+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=221 _ThreadName=Thread-8] [timeMillis: 1452523111350] [levelValue: 800] [[
14:38:31,349 INFO  [AutoDeployer][HotDeployImpl:217] Deploying mine-theme from queue]]

[2016-01-11T14:38:31.350+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=221 _ThreadName=Thread-8] [timeMillis: 1452523111350] [levelValue: 800] [[
14:38:31,350 INFO  [AutoDeployer][PluginPackageUtil:1016] Reading plugin package for mine-theme]]

[2016-01-11T14:38:31.404+0000] [glassfish 4.1] [INFO] [] [javax.enterprise.web] [tid: _ThreadID=221 _ThreadName=AutoDeployer] [timeMillis: 1452523111404] [levelValue: 800] [[
WebModule[null] ServletContext.log():Initializing Spring root WebApplicationContext]]

[2016-01-11T14:38:31.414+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=221 _ThreadName=Thread-8] [timeMillis: 1452523111414] [levelValue: 800] [[
14:38:31,413 INFO  [AutoDeployer][ThemeHotDeployListener:98] Registering themes for mine-theme]]

[2016-01-11T14:38:33.507+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=221 _ThreadName=Thread-8] [timeMillis: 1452523113507] [levelValue: 800] [[
14:38:33,507 INFO  [AutoDeployer][ThemeHotDeployListener:113] 1 theme for mine-theme is available for use]]

[2016-01-11T14:38:33.736+0000] [glassfish 4.1] [INFO] [AS-WEB-GLUE-00172] [javax.enterprise.web] [tid: _ThreadID=221 _ThreadName=AutoDeployer] [timeMillis: 1452523113736] [levelValue: 800] [[
Loading application [mine-theme] at [/mine-theme]]]

[2016-01-11T14:38:33.992+0000] [glassfish 4.1] [INFO] [] [javax.enterprise.system.core] [tid: _ThreadID=221 _ThreadName=AutoDeployer] [timeMillis: 1452523113992] [levelValue: 800] [[
mine-theme was successfully deployed in 4,148 milliseconds.]]

[2016-01-11T14:38:33.998+0000] [glassfish 4.1] [INFO] [NCLS-DEPLOYMENT-02035] [javax.enterprise.system.tools.deployment.autodeploy] [tid: _ThreadID=221 _ThreadName=AutoDeployer] [timeMillis: 1452523113998] [levelValue: 800] [[
[AutoDeploy] Successfully autodeployed : /home/glassfish/glassfish4/glassfish/domains/domain1/autodeploy/mine-theme.war.]]

Setting your theme

See Control Panel -> Configuration -> Portal settings -> Display Settings and below there's Look & Feel.

Also, there's this place: Control Panel -> Sites -> Your site -> Site Pages -> Look and Feel + Current Theme. You can select another theme from a list below in the same configuration page.

Your theme is actually a Plugin, and can be found among the Theme Plugins. Navigate to Control Panel -> Apps -> Plugins Configuration. Select the tab "Theme Plugins". Your theme should be visible in the list.

If not, you are likely to receive something like this in your logs7 8:
[2015-12-31T23:38:41.633+0000] [glassfish 4.1] [INFO] [] [] [tid: _ThreadID=29 _ThreadName=Thread-8] [timeMillis: 1451605121633] [levelValue: 800] [[
23:38:41,633 WARN [http-listener-1(4)][ThemeLocalServiceImpl:156] No theme found for specified theme id mine_WAR_minetheme. Returning the default theme.]]
I don't know why this happened in my case, but I found a reference in the Liferay database to the old mine-theme. Even though I thought I got rid of it permanently, but removing it from the autodeploy directory. If I remove it from the glassfish autodeploy directory, it is also removed from the Application Server. Liferay is listening to what happenes on the autodeploying front, detects the deletion, and starts cleaning up after itself as well.

Apparently, there still was an entry left hanging. I had to update the database manually to reset a database tablerow back to "classic".

Adapting your theme

It is possible to adapt your theme, by replacing images and css and html files judiciously.

The interesting bit of theming is that you specify the parent theme. So, you've got "styled" and "unstyled" and the default theme called "classic" to choose from. But it would also be easy to create for example a "Christmas" theme, that is derived from the theme that you normally use. So it's kind of an Inheritance structure, similar to Object Oriented Programming.

I also like the fact that in your new theme you specify the "changes" compared to the parent theme in the _diffs folder. This way, when a parent theme changes (upon a new release of Liferay Portal), you automatically get the new updates, and if you do not change these defaults in your theme, they are just build into the target war upon the next release.

If you wish to start making bigger changes, there is the possibility of using the Liferay Application Display Templates9 (ADT).

Liferay uses Apache Velocity10 as its templating engine.

Adding JQuery to the Liferay portal can be done as described at [11].

References

[1] Randomthoughts - Liferay
http://randomthoughtsonjavaprogramming.blogspot.nl/2015/12/liferay.html
[2] Liferay
http://www.liferay.com/
[3] Wikipedia - Liferay
https://en.wikipedia.org/wiki/Liferay
[4] Liferay Company Blogs - Creating Liferay Themes with Maven
https://www.liferay.com/web/mika.koivisto/blog/-/blogs/creating-liferay-themes-with-maven#_33_message_13069757
[5] Liferay Developer Network - Developing Liferay Theme Plugins with Maven
https://dev.liferay.com/develop/tutorials/-/knowledge_base/6-2/developing-liferay-theme-plugins-with-maven
[6] Liferay Developer Network - Creating a Theme Project in the Plugins SDK
https://dev.liferay.com/develop/tutorials/-/knowledge_base/6-2/creating-a-theme-project-in-the-plugins-sdk
[7] StackOverflow - Error occures when deploying theme to liferay portal. “No theme found for specified theme id .”
http://stackoverflow.com/questions/11721220/error-occures-when-deploying-theme-to-liferay-portal-no-theme-found-for-specif
[8] Techspace - No theme found for specified theme id abc_WAR_xyztheme
http://parasjain.net/2012/07/11/no-theme-found-for-specified-theme-id-abc_war_xyztheme/
[9] LiferaySavvy - List of Velocity Variables in Liferay
http://www.liferaysavvy.com/2015/02/list-of-velocity-variables-in-liferay.html
[10] Wikipedia - Apache Velocity
https://en.wikipedia.org/wiki/Apache_Velocity
[11] Liferay Company Blogs - Using jQuery (or any Javascript library) in Liferay 6.0
http://www.liferay.com/web/nathan.cavanaugh/blog/-/blogs/using-jquery-or-any-javascript-library-in-liferay-6-0

Thursday 21 January 2016

Yum replaced by DNF - Followup

Well, recently came into the problem that I had some configuration that requires porting, I think. I wanted to get into the DNF scene, whilst Centos 7 still uses yum by default.

From Yum to DNF

I installed dnf, simply using.
yum install dnf
The packages yum, yum-plugin-fastestmirror, yum-utils are now no longer needed. Your original configuration file can then be found in yum.conf.rpmsave.

I installed "dnf-yum", to be able to use yum as a automatic redirect to dnf.

Yum.conf

The /etc/yum.conf shall have been replaced with /etc/dnf/dnf.conf.

Yum cron jobs

In DNF, this is replaced by the package "dnf-automatic"3. The configuration of which is in /etc/dnf/automatic.conf.

Removed package yum-cron as it is no longer needed.

To get started:
systemctl enable dnf-automatic.timer && systemctl start dnf-automatic.timer
To list the timers set:
systemctl list-timers *dnf-* -all

Excluding packages

I have added the java* packages to the exclude directive in yum.conf1.

Of course, I do wish to manually install the java updates along with all the other updates2. To do this I can use the command line:
yum --disableexcludes=all update
These switches in the commandline and changes in the config file of yum are ported identically over to dnf. So that's not a problem.

Yum Repos

Apparently, DNF still uses all the repositories defined under /etc/yum.repos.d. So that's a relief.

References

[1] Redhat - How do I exclude kernel or other packages from getting updated in Red Hat Enterprise Linux while updating system via yum?
https://access.redhat.com/solutions/10185
[2] nixCraft - Force yum update Command To Exclude Certain Packages
http://www.cyberciti.biz/faq/redhat-centos-linux-yum-update-exclude-packages/
[3] Fedora Project - AutoUpdates
https://fedoraproject.org/wiki/AutoUpdates

Thursday 14 January 2016

Glassfish Logging to Database

I wanted to redirect the logging messages from the Glassfish Application Server to the database.

Glassfish uses the standard Logging API available in the JDK. There are some who find the already existing logging APIs (log4j, etc) available better than the default provided in the SDK. But I find it sufficient for my purposes.

The Logging API uses Handlers to indicate what needs to be done with all those log messages. The default handlers available in the API are shown in the diagram below.

I have opted for having the logrecords sent via the network (local interface) to a small daemon that posts the log records into a database table. So that would mean using the SocketHandler.

In general, this is what you want. Several application servers all sending their log messages to a central repository that can deal with it. Of course, there are way more sophisticated solutions readily available compared to this home-brewed thing. But it helps to get the general idea of how it works.

The software I made is available at https://github.com/maartenl/sql-logging. The sequence diagram below is basically how it works.

Since the data send over the network is in the XML format, I'm using StAX for interpreting the XML stream properly.

The funny thing is that the DatabaseHandler in the diagram above, is basically a child of the Handler class (depicted in the class diagram up top). So, what we have here is a kind of Logging proxy, really.

The database table follows the fields in the LogRecord class closely:
create table mm_systemlog (
  id bigint(20) NOT NULL AUTO_INCREMENT primary key,
  millis bigint(20) not null,
  sequence bigint(20) not null,
  logger varchar(255),
  level varchar(25),
  class varchar(255),
  method varchar(255),
  thread int(10),   
  message text not null,
  INDEX(millis)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Glassfish

Glassfish has a logging.properties file (glassfish4.1/glassfish/domains/domain1/config/logging.properties). It is possible to add or change the Log Handler used by Glassfish by changing it in the logging.properties file1 3.

I was unable to provide Glassfish with a Logging Handler that directly posted the LogRecords into a database (see [2]). Hence the current solution described above.

The (fairly easy) steps were as follows:
  • the developer has put the custom handler JAR file into the domain-dir/lib/ext directory.
  • the class that extends java.util.logging.Handler must be in the server classpath.
  • add the new handler to the handlers attribute in the glassfish/domains/domain1/config/logging.properties file
    handlers=java.util.logging.ConsoleHandler,com.mrbear.logging.handlers.SimpleSocketHandler
  • reboot glassfish

XML Formatter

Below is an example of what the XML format looks like.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2016-01-11T06:50:33</date>
<millis>1452491433234</millis>
<sequence>0</sequence>
<logger>com.mrbear.logging.tests.SimpleSocketHandlerTest</logger>
<level>SEVERE</level>
<class>com.mrbear.logging.tests.SimpleSocketHandlerTest</class>
<method>testSocketHandler</method>
<thread>1</thread>
<message>Hello, World</message>
</record>
<record>
<date>2016-01-11T06:50:33</date>
<millis>1452491433261</millis>
<sequence>1</sequence>
<logger>com.mrbear.logging.tests.SimpleSocketHandlerTest</logger>
<level>INFO</level>
<class>com.mrbear.logging.tests.SimpleSocketHandlerTest</class>
<method>testSocketHandler</method>
<thread>1</thread>
<message>Welcome Home</message>
</record>
<record>
<date>2016-01-11T06:50:33</date>
<millis>1452491433263</millis>
<sequence>2</sequence>
<logger>com.mrbear.logging.tests.SimpleSocketHandlerTest</logger>
<level>CONFIG</level>
<class>com.mrbear.logging.tests.SimpleSocketHandlerTest</class>
<method>testSocketHandler</method>
<thread>1</thread>
<message>Config ....</message>
</record>
<record>
<date>2016-01-11T06:50:33</date>
<millis>1452491433265</millis>
<sequence>3</sequence>
<logger>com.mrbear.logging.tests.SimpleSocketHandlerTest</logger>
<level>FINE</level>
<class>com.mrbear.logging.tests.SimpleSocketHandlerTest</class>
<method>testSocketHandler</method>
<thread>1</thread>
<message>Fine ....</message>
</record>
<record>
<date>2016-01-11T06:50:33</date>
<millis>1452491433266</millis>
<sequence>4</sequence>
<logger>com.mrbear.logging.tests.SimpleSocketHandlerTest</logger>
<level>FINEST</level>
<class>com.mrbear.logging.tests.SimpleSocketHandlerTest</class>
<method>testSocketHandler</method>
<thread>1</thread>
<message>Finest ....</message>
</record>
<record>
<date>2016-01-11T06:50:33</date>
<millis>1452491433266</millis>
<sequence>5</sequence>
<logger>com.mrbear.logging.tests.SimpleSocketHandlerTest</logger>
<level>WARNING</level>
<class>com.mrbear.logging.tests.SimpleSocketHandlerTest</class>
<method>testSocketHandler</method>
<thread>1</thread>
<message>Warning ....</message>
</record>
I would like to point out that, strictly speaking, the above XML document is not valid, as there is no closing </log> tag.

This makes sense, as the xml is streamed and never quite ended.

I would also like to point out that the XML document contains for each record both the number of milliseconds sinds 1970 and the date. This at first seems superfluous to me, but after some thought it makes sense. If you add both, you get valuable information in what timezone the server logging messages is working. This information is most likely lost in the milliseconds approach.

References

[1] Oracle Blog - Configure my Custom Log Handler in GlassFish 3.1
https://blogs.oracle.com/naman/entry/configure_my_custom_log_handler
[2] StackOverflow - Capture GlassFish log file into SQL/JPA data base
http://stackoverflow.com/questions/12397861/capture-glassfish-log-file-into-sql-jpa-data-base
[3] 7 Administering the Logging Service - Adding a Custom Logging Handler
Oracle GlassFish Server Administration Guide
HK2 - Dependency Injection Kernel
https://hk2.java.net/2.3.0/
Oracle JavaTM Tutorials - Lesson: JDBC Introduction
https://docs.oracle.com/javase/tutorial/jdbc/overview/index.html
JavaBendeR - Simple Log server with java SocketHandler and centralization of log records
http://javabender.blogspot.nl/2010/09/simple-log-server-with-java.html
Java Logging API Tutorial – Examples of Logger Levels, Handlers, Formatters and Filters
http://www.journaldev.com/977/java-logging-api-tutorial-examples-logger-levels-handlers-formatters-filters
Oracle JavaTM Tutorials - Reading XML Data into a DOM
https://docs.oracle.com/javase/tutorial/jaxp/dom/readingXML.html
Oracle JavaTM Tutorials - Using StAX
https://docs.oracle.com/javase/tutorial/jaxp/stax/using.html

Thursday 7 January 2016

Vaadin 7

Vaadin1 is an open source framework for developing web applications2 running in your browser. What is interesting is that it uses Java to do it.

It feels like you're programming like Swing in Java, but the results are transferred to your webbrowser using Vaadin. Vaadin is able to do this with the help of the Google Web Toolkit3.

Vaadin can apparently be run on the Google App Engine, if you like.

Vaadin 7 supports the JSR-286 Portlet specification. Several portals are supported. Vaadin also has some deeper integration with Liferay Portal11, in which I am very interested, as it is the one that my website is running on.

I did find a youtube video12 that introduces Vaadin in combination with JEE and NetBeans.

Upgrading to Vaadin 7

One of the problems I encountered, when using Vaadin 74 is that most of the resources available are still referring to Vaadin 6. It is important to notice the differences.

The points in [4] that have been most relevant to me are:
  • com.vaadin.Application class no longer exists. The main entry point to your application is now a com.vaadin.ui.UI, which replaces Application and its main window.
  • Also com.vaadin.terminal.gwt.server.ApplicationServlet has been replaced with com.vaadin.server.VaadinServlet in web.xml and its parameter "application" with "UI" pointing to your UI class, and the application is ready to go. Of course you can use annotations, if you prefer.

Testing

As it is Java, I should have no problem Stubbing the Vaadin framework and junit-testing my code. This to me is a big bonus.

Persistence

Persistence in Vaadin is possible by using the JPAContainer5.

JEE and CDI

I use the Servlet to inject a lot of things I need, and then provide those injected things to my UI. Works flawlessly6.

JEE and JPA together in Vaadin

I found helpful information on how to create a EjbEntityProvider here at [7]. That I can use as a base class for EJBBeans that I can inject in my Servlet and henceforth use in my UI for the JPAContainer.

A full downloadable example is available at [8].

Debugging

I have not had to debug anything, everything just works. But, for those interested, you can find it at [9].

FAQ

I get a "Failed to load the bootstrap javascript: ./VAADIN/vaadinBootstrap.js?v=7.5.5"
See [10]. Extend the @WebServlet annotation withe more than one urlPattern. See the example above.

References

[1] Vaadin
https://vaadin.com/home
[2] Wikipedia - Vaadin
https://en.wikipedia.org/wiki/Vaadin
[3] Google Web Toolkit
http://www.gwtproject.org/
[4] Vaadin Wiki - Mirating from Vaadin 6 to Vaadin 7
https://vaadin.com/wiki/-/wiki/Main/Migrating+from+Vaadin+6+to+Vaadin+7
[5] Book of Vaadin - Chapter 19 - JPAContainer
https://vaadin.com/book
[6] Vaadin Wiki - JEE6 integration with Vaadin 7
https://vaadin.com/wiki/-/wiki/Main/JEE6+integration+with+Vaadin+7
[7] Using JPA Container 2.0 in a JEE6 server
https://vaadin.com/web/matti/blog/-/blogs/using-jpa-container-2-0-in-a-jee6-server
[8] Vaadin Forum - Unable to follow J6EE wiki - NullPointerException
https://vaadin.com/forum/#!/thread/386542/389263?_19_redirect
[9] Book of Vaadin - Chapter 11.3. Debug Mode and Window
https://vaadin.com/book/-/page/advanced.debug.html
[10] Book of Vaadin - 4.9. Deploying an Application
https://vaadin.com/book/-/page/application.environment.html
[11] Liferay
http://www.liferay.com/
[12] YouTube - Java EE 7 with NetBeans and Vaadin
https://www.youtube.com/watch?v=3TompuzySD8