Friday, September 13, 2013

Liferay blog available as android app now

http://www.appsgeyser.com/594928  
Try this out.And suggestions invited


All the posts here are for sharing knowledge.


For more info:-http://www.liferay.com/

© 2013 LIFERAY INC. ALL RIGHTS RESERVED

Monday, February 27, 2012

Liferay Portal 6.1 - Development Guide

Monday, February 6, 2012

Liferay India developers group on Facebook

This group is running successfully from past few months.It helps me out to learn many new things in Liferay.I just take the initiate but all the members Satish,Mazhar,Vamc (i just remeber these names only ) even everyone who are here contribute alot. Hope this will continue and get a large number of members this year. "http://www.facebook.com/groups/180811265343833/" Thanks alot for being a part of this community

Wednesday, January 18, 2012

Liferay 6.1 and SASS

Lieray 6.1 CE is released and many of you have started using it. There are lots of features in Liferay 6.1 and one among them is the facility to use SASS in Liferay.

SASS:

SASS( http://sass-lang.com ) stands for Syntactically Awesome StyleSheets. It is a tool that allows you to write CSS more easily It is an extension of CSS3 which means whatever CSS file is a valid SassyCSS (SCSS). It allows you to use variables, nested rules, mixins, inline imports, interpolation, arguments, manipulating colors and more with a fully CSS-compatible syntax. Sass generates well formatted CSS and it is much easier to organize and maintain your stylesheets.

Compass :

Compass( http://compass-style.org ) is an open-source CSS authoring framework which makes easier to build and maintain your markup and stylesheets. By using compass, you can write your own stylesheet in Sass instead of CSS.

How does Compass Work?

It can be imported using @import "compass" in CSS. SCSS is built based on the CSS3 specification which means that it is highly compatible with CSS3. SCSS also has all the power of Sass.

We’ll see here as how to use the variables, nested rules, mixins, inline imports, interpolation and arguments through CSS files.

Variables:

We’ll see how to use variables and a custom mixin to show how Compass makes it easy to reuse colors, sizes, and other values without repeating.

/* Variable Declaration */

/*These variables can be used globally throughout your stylesheets */ 

 

Example:

Liferay6.1     Liferay 6.0.x

Mixins:

By using the mixin, you can re-use the styles, properties & selectors without copying to other selectors. To achieve this , you need to package the properties with @mixin and to include it in other element with @include. 

Example:

Liferay 6.1   Liferay 6.0.x

Nested Rules:

Nesting avoids repeating CSS selectors and also keeps the style compact for a certain area of the User Interface. 

Let us have a look at an example of custom.css code for navigation part in themes. After using the nesting rule, the code should look like this :

Liferay 6.1     Liferay 6.0.x

Parent Reference:

Parent reference allows you to reference a parent selector  in the case of pseudo-classes like :hover, :link, :visited and :active states.

Liferay 6.1 Liferay 6.0.x

Interpolation:

We can also use SassScript variables in selectors and property names using #{} interpolation. Here’s the example:

Liferay 6.1 Liferay 6.0.x

Liferay6.1 allows you to use Sass which helps the code be simpler for mobile site design. (Thanks to BradleyWood’s solution in this  forum post ). 

As said in Sass , it makes CSS fun again and with Liferay 6.1, you can easily create manageable style sheets and start updating to Liferay 6.1 and use Sass to manage your style sheets  smiley .

 

Friday, January 13, 2012

Upgrading Liferay Portal 6.0 to Liferay Portal 6.1

Upgrading Liferay

Liferay upgrades are fairly straightforward. A consistent set of steps is all you need to follow to upgrade a standard Liferay installation. Things do get more complicated if your organization has used Ext plugins to customize Liferay. It's possible that API changes in the new version will break your existing code. This, however, is usually pretty easy for your developers to fix. Portlet plugins which use Liferay APIs should be reviewed and their services rebuilt against the new release. Theme plugins may require some modifications in order to take advantage of new features, and if they're using Liferay APIs, they should be reviewed. Much effort has been made to make upgrades as painless as possible; however, this is not a guarantee that everything will work without modification. Ext plugins are the most complicating factor in an upgrade, so it is important to test as much as possible.

As a general rule, you can upgrade from one major release to the next major release. For example, you can upgrade directly from Liferay 5.2.x to 6.0.x, but not from 5.2.x to 6.1.x. If you need to upgrade over several major releases, you'll need to run the upgrade procedure for each major release until you reach the release you want. This doesn't mean you need to run the procedure for every point release or service pack; you only need to run the procedure for the major releases. A good practice is to use the latest version of each major release to upgrade your system.

Now that we've gotten the general philosophy of upgrading out of the way, let's outline the procedure you'll undergo for upgrading a Liferay 6.0 installation to a 6.1 installation. If you're running a previous version of Liferay and need to upgrade to 6.0 first, please see the instructions in the previous version of this document.

Upgrading Liferay Portal 6.0 to Liferay Portal 6.1

There are a few things you'll want to prepare before you actually perform the upgrade. Specifically, you'll need to review your image gallery usage, review new Liferay 6.1 defaults, and catalog all the plugins you have installed. After you've performed these three tasks, you'll be ready to upgrade.

Liferay 6.1 introduces a major change to how Liferay handles files. No longer do we have a separate Document Library and Image Gallery; instead, these have been combined into Documents and Media. If you were using Liferay's Image Gallery to store images, these can be migrated over during an upgrade, but you'll have to take some extra steps first.

In Liferay 6.0, you had three ways you could store images in the Image Gallery. You could use the DatabaseHook and store them as BLOBs in the database; you could use the DLHook to store them in the Document Library, or you could use the FileSystemHook to store them in a folder on your server's file system. Before you upgrade, you'll need to set whichever property you were using in your 6.0 portal-ext.properties file, because by default, none of them are enabled in 6.1. Setting one of the properties triggers the migration during the upgrade process. Below are the three properties; you'll need to set only one of them (the one you were using).

image.hook.impl=com.liferay.portal.image.DatabaseHook

image.hook.impl=com.liferay.portal.image.DLHook

image.hook.impl=com.liferay.portal.image.FileSystemHook

The next thing you'll need to look at are the defaults that have changed from 6.0 to 6.1. These are preserved in portal-legacy-6.0.properties in the source. The 6.0 values are:

users.last.name.required=true layout.types=portlet,panel,embedded,article,url,

link_to_layout editor.wysiwyg.portal-web.

docroot.html.portlet.message_boards.edit_message.bb_code.jsp=bbcode

setup.wizard.enabled=false discussion.subscribe.by.default=false

message.boards.subscribe.by.default=false

The 6.1 values have changed to:

users.last.name.required=false layout.types=portlet,

panel,embedded,url,link_to_layout editor.wysiwyg.portal-web.

docroot.html.portlet.message_boards.edit_message.bb_code.jsp=ckeditor_bbcode

setup.wizard.enabled=true discussion.subscribe.by.default=true message.boards.subscribe.by.default=true

If you don't like the defaults, you can change them back in one shot by adding a system property to your JVM's startup. This differs by application servers. In Tomcat, you'd modify setenv.sh / setenv.bat and append the option -Dexternal-properties=portal-legacy-6.0.properties to the environment variable JAVA_OPTS. The scripts setenv.sh or setenv.bat are not delivered with default Tomcat, but do exist in the bundles. If they're there, Tomcat uses them in the startup process, so it's a nice way to separate your own settings from Tomcat's default shell scripts. Alternatively, of course, you can override some or all of them in your portal-ext.properties along with your other overrides.

Finally, you need to take note of any plugins you have installed. Liferay's plugins are usually version-specific, so you'll need to obtain new versions of them for the new release of Liferay. If you have custom plugins created by your development team, they'll need to build, test, and optionally modify them to work with the new release of Liferay. Don't attempt an upgrade without collecting all the plugins you'll need first.

Once you've reviewed your properties and collected all the plugins you'll need, you're ready to follow the upgrade procedure. Remember to back up your system before you begin.

There are two different procedures to upgrade Liferay. The first one, upgrading a Liferay bundle, is the most common. The second procedure is for upgrading a Liferay installation on an application server. We'll go over both.

In both cases, Liferay auto-detects whether the database requires an upgrade the first time the new version is started. When Liferay does this, it upgrades the database to the format required by the new version. In order to perform this task, Liferay must be accessing the database with an ID that can create, drop, and modify tables. Make sure that you have granted these permissions to the ID before you attempt to upgrade Liferay. And, of course, we'll run the risk of overly repeating ourselves: back up your database.

Let's look at upgrading a bundle, which is the easiest upgrade path.

Upgrading a bundle

If you're running a Liferay bundle, the best way to do the upgrade is to follow the steps below. The new Liferay is installed in a newer version of your bundle runtime. For example, the Liferay/Tomcat bundle for 6.0 used Tomcat 6 by default; the 6.1 bundle uses Tomcat 7. Though there is a Tomcat 6 bundle of Liferay 6.1, that bundle also uses a newer release of Tomcat than the one from 6.0. This is the case for all runtimes that Liferay supports. We generally recommend that you use the latest version of your runtime bundle, as it will be supported the longest.

  1. Obtain the new bundle. Unzip the bundle to an appropriate location on your system.
  2. Copy your portal-ext.properties file and your data folder to the new bundle.
  3. Review your portal-ext.properties file as described above. If you were using the Image Gallery, make the necessary modifications so that your files are migrated to Documents and Media. Review the new defaults and decide whether you want to use them. Review any other modifications you've made.
  4. Start your application server. Watch the console as Liferay starts: it should upgrade the database automatically.
  5. When the upgrade completes, install any plugins you were using in your old version of Liferay. Make sure you use the versions of those plugins that are designed for Liferay 6.1. If you have your own plugins, your development team will need to migrate the code in these ahead of time and provide .war files to you.
  6. Browse around in your new installation and verify that everything is working. Have your QA team test everything. If all looks good, you can delete the old application server with the old release of Liferay in it from the bundle directory. You have a backup of it anyway, right?

As you can see, upgrading a bundle is generally pretty simple. But not everybody can use bundles: sometimes, specific application servers or application server versions are mandated by the environment you're in or by management. For this reason, Liferay also ships as an installable .war file that can be used on any supported application server.

Upgrading using a .war file

Running a manual upgrade is almost as easy as upgrading a bundle:

  1. Obtain the Liferay Portal .war file and the dependency .jars archive.
  2. Copy your customized portal-ext.properties file to a safe place and review it as described above, making all the appropriate changes.
  3. Undeploy the old version of Liferay and shut down your application server.
  4. Copy the new versions of Liferay's dependency .jars to a location on your server's class path, overwriting the ones you already have for the old version of Liferay. This location is documented for your application server in chapter 11.
  5. Deploy the new Liferay .war file to your application server. Follow the deployment instructions in chapter 11.
  6. Start (or, if your app server has a console from which you've installed the .war, restart) your application server. Watch the console as Liferay starts: it should upgrade the database automatically. Verify that your portal is operating normally, and then install any plugins you were using in your old version of Liferay. Make sure you use the versions of theose plugins that are designed for Liferay 6.1. If you have your own plugins, your development team will need to migrate the code in these ahead of time and provide .war files to you.
  7. Browse around in your new installation and verify that everything is working. Have your QA team test everything. If all looks good, you're finished.

That's all there is to it. Most everything is handled by Liferay's upgrade procedure. Note that as stated above, if you have to upgrade over several Liferay versions, you will need to repeat these steps for each major release.

Thursday, January 12, 2012

New Liferay 6.1 Feature - Remote Development from Liferay IDE

I'd like to mention one of the new features in the upcoming release of Liferay 6.1, which is the remote development and deployment support from Liferay IDE.  The idea is that with this feature you can build Liferay projects in Liferay IDE like normal but instead of the Liferay server running locally it is running on a remote host.  This is accomplished through two things, 1) new remote server adapter in Liferay IDE and 2) Server manager plugin (new in 6.1) deployed to the remote Liferay server 

This feature will be officially available with the Liferay 6.1 CE release, but since it is only in beta, now is a good time to try it out and give feedback.

For those who wish to try this out here are the steps to setting it up.

1. Download the Liferay Portal 6.1 Beta4 Tomcat bundle (Only tomcat is supported in beta4,  both JBoss and Glassfish will be supported by 6.1 GA release)

2. Start Tomcat bundle on remote host (or even localhost)

3. Download the Server Manager plugin for 6.1 Beta4 (This plugin provides the API needed by Liferay IDE to administer remote plugins)

4. Deploy Server Manager plugin into remote Tomcat server

5. Install the latest stable or nightly  version of Liferay IDE (links are updatesite URLs to use in Eclipse install manager)

6. Create a new server in Liferay IDE, File > New > Server > Liferay > Remote Liferay Server

7. Specify a local tomcat bundle for the runtime stub (this is needed for libraries to compile project)

8. Specify the IP address of remote tomcat server along with Omni admin username/password

Once you have done this you should see the server console log messages printed in the normal Eclipse console view.

9. Create and deploy Liferay projects as usual then publish (deploy) them to the server, see Liferay IDE getting started guide for a walkthrough of this.

Now when you modify files in your project, every 15 seconds it will publish the delta of any changes to remote server.  However, you can publish changes immediately using the "publish" action in the servers view.  If you want to change the automatic publish rate you can do that in the server configuration editor (double click server in Servers view).

For anyone who decides to try this out and want to give feedback with suggestions or problems please either open a JIRA ticket for the IDE project or enter a new topic on the IDE forums .

Writing Liferay Apps with Web Content Templates

One of the often overlooked features of Liferay's WCM system is the ability to write non-trivial apps using it.  There have been a few blog posts about this, notably Ray AugĂ©'s Advanced Web Content Example With AJAX .  In the community, it's great for me because I can quickly create interesting visualizations of community data and share it with you immediately.  There are some pros and cons to this approach:

Benefits:

  • No compilation needed - WCM relies on the use of Templates , written in interpreted (i.e. scripted) languages such as Velocity Templates .  This means you can quickly make a change and see your results quickly.
  • No deployment needed - since Web Content isn't a java portlet, you don't need to re-deploy.   More importantly it means you don't have to wait for a website administrator to deploy it if you cannot deploy yourself!
  • You can combine presentation (e.g. HTML/JS/CSS) and logic (e.g. Velocity) into the same template, keeping related code together.

Drawbacks:

  • Velocity is first and foremost a templating/presentation language.  It is not a general purpose computing language, so die hard MVC types will probably dismiss the use of Velocity in this way and call me a heretic/lunatic.  It's great for prototyping though!  
  • Currently, Structures and Templates aren't versioned, and they do not participate in Liferay's Workflow system.  So you can't revert to older (working) versions of templates if you make a mistake.
  • No compilation needed - so it's not as fast as the native bytecode that would result from the equivalent java source code.  But it's still quite fast.
  • Velocity and other scripting languages have weird quirks that often cannot be caught except through trial and error, and limitations (e.g. no use of generics) that compiled/strongly typed languages have.
  • You can combine presentation (e.g. HTML) and logic (e.g. Velocity) into the same template :)

In my opinion, Liferay WCM is a very good solution for app prototyping or for non-trivial apps that don't have tons of logic or page flows in them.  You have already seen an example of this in the Community Activity Map , and the example I use below forms the basis for the Hot Topics app that you can now see on liferay.org .

Basic Template Template

To get started creating an app of this nature, you need to start with simple Web Content Template that is itself a template:

#if ($request.lifecycle == "RENDER_PHASE")
 ## This phase will handle the presentation code (i.e. HTML/CSS/JS). Any calls ## to the ${request.resource-url} will retrieve the result of evaluating the below ## RESOURCE_PHASE below. 
#elseif ($request.lifecycle == "RESOURCE_PHASE")
 ## This phase will handle the AJAX request like a portlet's serveResource() method 
#end

So decide what needs to be executed on the server side, and put it in the RESOURCE_PHASE .  This is typically where most if not all of the business (i.e. non-presentation) logic goes.  Put the presentation logic in the RENDER_PHASE .

Hot Topics Example

For this app, I want to show which threads have the most posts in the last week.  So, I needed to query Liferay's Message Boards.  Since there is no getMostActiveThreadsInTheLastWeek() method (I know.. what's up with that??), I needed a custom query.  This means using Liferay's DynamicQuery feature.  But from Velocity?  Turns out it's not that bad.  Here's the full RESOURCE_PHASE code to create and execute a Dynamic Query, and generate a JSON object as a result which contains the most active threads in the last week:

#set ($portletNamespace = $request.portlet-namespace) #set ($scopeGroupId = $getterUtil.getLong($request.theme-display.scope-group-id)) #if ($request.lifecycle == "RENDER_PHASE") ## bunch of display logic to show the JSON result nicely #elseif ($request.lifecycle == "RESOURCE_PHASE") #set ($logFactory = $portal.getClass().forName('com.liferay.portal.kernel.log.LogFactoryUtil')) #set ($log = $logFactory.getLog('mylog')) #set ($portalBeanLocator = $portal.getClass().forName("com.liferay.portal.kernel.bean.PortalBeanLocatorUtil")) #set ($jsonFactory = $portalBeanLocator.locate("com.liferay.portal.kernel.json.JSONFactoryUtil")) #set ($mbMessageLocalService = $portalBeanLocator.locate("com.liferay.portlet.messageboards.service.MBMessageLocalService.velocity")) #set ($mbThreadLocalService = $portalBeanLocator.locate("com.liferay.portlet.messageboards.service.MBThreadLocalService.velocity")) #set ($calClass = $portal.getClass().forName("java.util.GregorianCalendar")) #set ($mbMessageClass = $portal.getClass().forName("com.liferay.portlet.messageboards.model.MBMessage")) #set ($mbThreadClass = $portal.getClass().forName("com.liferay.portlet.messageboards.model.MBThread")) #set ($dqfu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.DynamicQueryFactoryUtil")) #set ($pfu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.ProjectionFactoryUtil")) #set ($ofu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.OrderFactoryUtil")) #set ($now = $calClass.getInstance()) #set ($weeksago = $calClass.getInstance()) #set ($prevweeks = 0 - $getterUtil.getInteger($period.data)) #set ($V = $weeksago.add(3, $prevweeks)) #set ($q = $dqfu.forClass($mbThreadClass)) #set ($rfu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.RestrictionsFactoryUtil")) #set ($groupIdCriteria = $rfu.ne("categoryId", $getterUtil.getLong("-1"))) #set ($V = $q.add($groupIdCriteria)) #set ($groupIdCriteria = $rfu.eq("groupId", $getterUtil.getLong($scopeGroupId))) #set ($V = $q.add($groupIdCriteria)) #set ($companyIdCriteria = $rfu.eq("companyId", $getterUtil.getLong($companyId))) #set ($V = $q.add($companyIdCriteria)) #set ($statusCriteria = $rfu.eq("status", 0)) #set ($V = $q.add($statusCriteria)) #set ($lastPostDateCriteria = $rfu.between("lastPostDate", $weeksago.getTime(), $now.getTime())) #set ($V = $q.add($lastPostDateCriteria)) #set ($V = $q.setProjection($pfu.property("threadId"))) #set ($res1 = $mbMessageLocalService.dynamicQuery($q)) #set ($q2 = $dqfu.forClass($mbMessageClass)) #set ($inCriteria = $rfu.in("threadId", $res1)) #set ($V = $q2.add($inCriteria)) #set ($createDateCriteria = $rfu.between("createDate", $weeksago.getTime(), $now.getTime())) #set ($V = $q2.add($createDateCriteria)) #set ($V = $q2.setProjection($pfu.projectionList().add($pfu.groupProperty("rootMessageId")).add($pfu.alias($pfu.rowCount(), "msgCount")))) #set ($V = $q2.addOrder($ofu.desc("msgCount"))) #set ($V = $q2.setLimit(0, 7)) #set ($res2 = $mbMessageLocalService.dynamicQuery($q2)) #set ($jsonArray = $jsonFactory.createJSONArray()) #foreach ($msgSum in $res2) #set ($rootMsgId = $msgSum.get(0)) #set ($msgCount = $msgSum.get(1)) #set ($subject = $mbMessageLocalService.getMessage($rootMsgId).getSubject()) #set ($jsonObject = $jsonFactory.createJSONObject()) #set ($V = $jsonObject.put("subject", $stringUtil.shorten($htmlUtil.escape($subject), 55))) #set ($V = $jsonObject.put("msgid", $rootMsgId)) #set ($V = $jsonObject.put("msgCount", $msgCount)) #set ($V = $jsonArray.put($jsonObject)) #end { "jsonArray": $jsonArray } #end 

Details

There are many things going on here:

Velocity Debugging/Logging

 #set ($logFactory = $portal.getClass().forName('com.liferay.portal.kernel.log.LogFactoryUtil')) #set ($log = $logFactory.getLog('mylog')) 
This gives me a way to debug the code by looking at the server log (if you are using this kind of app so that you can bypass your website admin, chances are you won't have access to the server logs, so this won't help you).  To emit debug info, I can do things like $log.error($msgCount) or $log.error("Hi There").
 

Creating references for arbitrary JVM classes

 #set ($calClass = $portal.getClass().forName("java.util.GregorianCalendar")) 
This allows me to create references to any class known in the JVM for doing things like calling static methods, etc.  Many of these are needed for constructing Dynamic Queries.
 

Calculating Now and a Week Ago

 #set ($now = $calClass.getInstance()) #set ($weeksago = $calClass.getInstance()) #set ($prevweeks = 0 - $getterUtil.getInteger($period.data)) #set ($V = $weeksago.add(3, $prevweeks)) 
This creates Calendar objects representing the current time, and a week ago.  Note that the number of weeks is specified in a web content structure using the period structure element.
 
The rest of the code constructs two dynamic queries:
  • The first one ( $q ) queries for MBThread entities that have a categoryId of -1 (MBThreads that do not have a categoryId of -1 are not threads from the message boards portlet, instead they are threads for things like comments on document library entries, etc).  The query also includes other criteria, like groupId/companyId must match the "current" groupId/companyId of the site in which the web content is placed, the status must be 0 (indicating it is an approved (i.e. not draft or deleted) entry), and most importantly the lastPostDate must be between my desired time period start and end.  Finally, I am not interested in all of the MBThread entity - I just need the threadId .  So my query includes a Projection that only returns the threadId .
  • The second query ( $q2 ) queries for all MBMessage entities that match my new critieria: they must have a threadId of one of the threads identified in the first query (hence the in criteria), and the message's createDate must also be between my start/end dates.  This is to avoid counting messages in the thread that occured before the cutoff dates.  Finally, this gem:
 #set ($V = $q2.setProjection($pfu.projectionList().add($pfu.groupProperty("rootMessageId")).add($pfu.alias($pfu.rowCount(), "msgCount")))) #set ($V = $q2.addOrder($ofu.desc("msgCount"))) #set ($V = $q2.setLimit(0, 7)) 
This creates a projection that is grouped by the rootMessageId , since each message in the same thread will have the same rootMessageId (which I eventually use to construct the URL to the message), and includes a count of the messages that match (with an alias defined so I can refer to the row count when specifying the order of the results via addOrder() ).  I also limit the results to 7 because I don't want to show any more than that (this is a simple app).  This second query returns a result table that looks like:
 

So after the Dynamic Queries executes, it's just a matter of constructing a JSONObject (and sanitizing/sizing the actual text of the subject of the thread) and returning it.

Liferay WCM and Velocity Gotchas

Velocity is first and foremost a templating/presentation language.  It is not a general purpose computing language, so die hard MVC types will probably dismiss the use of Velocity in this way and call me a heretic/lunatic.  But it also means that some things are hard (or impossible, for example did anyone catch that I hard-coded Calendar.MONTH to be 3 ?  You can't reference static member variables of a class unless it is already part of the Velocity context in which a template is evaluated).  There are many other perils awaiting the adventurous Velocity coder.  I learned many things through trial and error (and the help of my IRC friends on the #liferay channel!).  Here are some more:
 
  • Don't forget to use $var instead of var .  If you forget the $, you won't get syntax errors, just silent errors and half of your code will next execute.
  • If you can use an intelligent IDE (like IntelliJ IDEA or Eclipse ) and its Velocity syntax checking, do it!  I saved tons of time by using IntelliJ and declaring variable types, which allowed for autocompletion and type checking.  For example, I had tons of these:
#* @vtlvariable name="request" type="java.util.Map" *# #* @vtlvariable name="httpUtil" type="com.liferay.portal.kernel.util.HttpUtil" *# #* @vtlvariable name="htmlUtil" type="com.liferay.portal.kernel.util.HtmlUtil" *# #* @vtlvariable name="obc" type="com.liferay.portal.util.comparator.UserLastNameComparator" *# #* @vtlvariable name="serviceLocator" type="com.liferay.portal.velocity.ServiceLocator" *# #* @vtlvariable name="teamLocalService" type="com.liferay.portal.service.TeamLocalServiceUtil" *# #* @vtlvariable name="mbMessageLocalService" type="com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil" *# #* @vtlvariable name="mbThreadLocalService" type="com.liferay.portlet.messageboards.service.MBThreadLocalServiceUtil" *#
  • Any time you access things from the ${request.theme-display} , or access one of your WCM structure fields that represent a number (but are of type "Text" in the template), they are probably not of the the type that you want.  You need to use generous amounts of $getterUtil.getXXX calls to make sure.  For example, 
#set ($scopeGroupId = $getterUtil.getLong($request.theme-display.scope-group-id)) 
will work (and makes  $scopeGroupId a Long), whereas
#set ($scopeGroupId = $request.theme-display.scope-group-id) 
Will results in a $scopeGroupId that is not a Long, and so if you pass it in to a method that is expecting a Long, it won't work, and will probably silently fail and you'll be befuddled.
  • If you want to create a new instance of a class (e.g. a HashMap) you can'y say new HashMap() .  Velocity does not know what " new " is - after all, you're using a presentation/templating language, not Java!   But we're using it for more than display.  So as a workaround you can do things like $portal.getClass().forName("java.util.HashMap").newInstance() .
  • Accessing elements of an array cannot be done using $array[0] .  You have to use  $array.get(0) .
 

The Full Source to Hot Topics

Here's the full source, including my display code, and my IntelliJ variable declarations (which may have some unnecessary declarations, but I use this block for other stuff too).  If you spot errors or poor coding technique or whatever else, please let me know so I can learn!
#* @vtlvariable name="portletNamespace" type="java.lang.String" *# #* @vtlvariable name="portal" type="com.liferay.portal.util.Portal" *# #* @vtlvariable name="getterUtil" type="com.liferay.portal.kernel.util.GetterUtil" *# #* @vtlvariable name="stringUtil" type="com.liferay.portal.kernel.util.StringUtil" *# #* @vtlvariable name="max-members" type="com.liferay.portlet.journal.util.TemplateNode" *# #* @vtlvariable name="team-name" type="com.liferay.portlet.journal.util.TemplateNode" *# #* @vtlvariable name="section-members" type="com.liferay.portlet.journal.util.TemplateNode" *# #* @vtlvariable name="groupId" type="java.lang.String" *# #* @vtlvariable name="sectionMembers" type="java.lang.String" *# #* @vtlvariable name="locale" type="java.util.Locale" *# #* @vtlvariable name="companyId" type="java.lang.String" *# #* @vtlvariable name="scopeGroupId" type="java.lang.String" *# #* @vtlvariable name="sectionName" type="java.lang.String" *# #* @vtlvariable name="section-name" type="com.liferay.portlet.journal.util.TemplateNode" *# #* @vtlvariable name="params" type="java.util.LinkedHashMap" *# #* @vtlvariable name="users" type="java.util.List" *# #* @vtlvariable name="user" type="com.liferay.portal.model.User" *# #* @vtlvariable name="themeDisplay" type="com.liferay.portal.theme.ThemeDisplay" *# #* @vtlvariable name="languageUtil" type="com.liferay.portal.kernel.language.LanguageUtil" *# #* @vtlvariable name="request" type="java.util.Map" *# #* @vtlvariable name="httpUtil" type="com.liferay.portal.kernel.util.HttpUtil" *# #* @vtlvariable name="htmlUtil" type="com.liferay.portal.kernel.util.HtmlUtil" *# #* @vtlvariable name="obc" type="com.liferay.portal.util.comparator.UserLastNameComparator" *# #* @vtlvariable name="serviceLocator" type="com.liferay.portal.velocity.ServiceLocator" *# #* @vtlvariable name="teamLocalService" type="com.liferay.portal.service.TeamLocalServiceUtil" *# #* @vtlvariable name="mbMessageLocalService" type="com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil" *# #* @vtlvariable name="mbThreadLocalService" type="com.liferay.portlet.messageboards.service.MBThreadLocalServiceUtil" *# #* @vtlvariable name="imageToken" type="com.liferay.portal.kernel.servlet.ImageServletToken" *# #* @vtlvariable name="userLocalService" type="com.liferay.portal.service.UserLocalServiceUtil" *# #* @vtlvariable name="groupIdCriteria" type="com.liferay.portal.kernel.dao.orm.Criterion" *# #* @vtlvariable name="groupIdProp" type="com.liferay.portal.kernel.dao.orm.Property" *# #* @vtlvariable name="threadMap" type="java.util.Map<java.lang.Long, java.lang.Integer>" *# #* @vtlvariable name="q" type="com.liferay.portal.kernel.dao.orm.DynamicQuery" *# #* @vtlvariable name="q2" type="com.liferay.portal.kernel.dao.orm.DynamicQuery" *# #* @vtlvariable name="rfu" type="com.liferay.portal.kernel.dao.orm.RestrictionsFactoryUtil" *# #* @vtlvariable name="pfu" type="com.liferay.portal.kernel.dao.orm.ProjectionFactoryUtil" *# #* @vtlvariable name="ofu" type="com.liferay.portal.kernel.dao.orm.OrderFactoryUtil" *# #* @vtlvariable name="msgs" type="java.util.List<com.liferay.portlet.messageboards.model.MBMessage>" *# #set ($portletNamespace = $request.portlet-namespace) #set ($scopeGroupId = $getterUtil.getLong($request.theme-display.scope-group-id)) #if ($request.lifecycle == "RENDER_PHASE") <body onload="${portletNamespace}getTable();"> <article> <h1 class="section-heading section-heading-b"> <div>$title.data</div> <div class="section-heading-hr"></div> </h1> <div id='${portletNamespace}tablediv' style='width: 85%;'><!-- --></div> </article> </body> <script type="text/javascript"> var ${portletNamespace}table = new Object(); var ${portletNamespace}ICON = '<img class="icon" \ src="http://my-liferay-site-cdn.com/osb-theme/images/spacer.png" \ alt="Message Boards" title="Message Boards" \ style=" background-image: url(\'/html/icons/_sprite.png\');\ background-position: 50% -736px; \ background-repeat: no-repeat; height: 16px; width: 16px;">'; function ${portletNamespace}drawChart() { var html = '<div> \ <table style="margin-bottom:0em;"> \ <tbody>'; for (i = 0; i < ${portletNamespace}table.length; i++) { html += '<tr> \ <td class="portlet-icon" style="padding-right:6px;"> \ <table style="margin-top:-4px; margin-bottom:0px;">\ <tr>\ <td>\ <span>' + ${portletNamespace}ICON + '</span> \ </td>\ </tr>\ <tr>\ <td>\ <span style="color:#908E91; font-size:9px;">'+ ${portletNamespace}table[i].msgCount + '</span>\ </td>\ </tr>\ </table>\ </td> \ <td>\ <div>\ <h3 class="txt-n fs-11 m-0 o-h">\ <span class="display-b m-tn3 m-b6">\ <a href="/community/forums/-/message_boards/message/' + ${portletNamespace}table[i].msgid +'">'+ ${portletNamespace}table[i].subject + '</a>\ </span>\ </h3>\ </div>\ </td>\ </tr>'; } html += '</tbody>\ </table>\ </div>'; document.getElementById('${portletNamespace}tablediv').innerHTML = html; } function ${portletNamespace}getTable() { AUI().use( "aui-base", "aui-io-plugin", "aui-io-request", function(A) { A.io.request( '${request.resource-url}', { data: { }, dataType: "json", on: { success: function(event, id, obj) { var responseData = this.get("responseData"); ${portletNamespace}table = responseData.jsonArray || []; ${portletNamespace}drawChart(); }, failure: function(event, id, obj) { } } } ); } ); } </script> #elseif ($request.lifecycle == "RESOURCE_PHASE") #set ($logFactory = $portal.getClass().forName('com.liferay.portal.kernel.log.LogFactoryUtil')) #set ($log = $logFactory.getLog('mylog')) #set ($portalBeanLocator = $portal.getClass().forName("com.liferay.portal.kernel.bean.PortalBeanLocatorUtil")) #set ($jsonFactory = $portalBeanLocator.locate("com.liferay.portal.kernel.json.JSONFactoryUtil")) #set ($mbMessageLocalService = $portalBeanLocator.locate("com.liferay.portlet.messageboards.service.MBMessageLocalService.velocity")) #set ($mbThreadLocalService = $portalBeanLocator.locate("com.liferay.portlet.messageboards.service.MBThreadLocalService.velocity")) #set ($calClass = $portal.getClass().forName("java.util.GregorianCalendar")) #set ($mbMessageClass = $portal.getClass().forName("com.liferay.portlet.messageboards.model.MBMessage")) #set ($mbThreadClass = $portal.getClass().forName("com.liferay.portlet.messageboards.model.MBThread")) #set ($dqfu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.DynamicQueryFactoryUtil")) #set ($pfu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.ProjectionFactoryUtil")) #set ($ofu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.OrderFactoryUtil")) #set ($now = $calClass.getInstance()) #set ($weeksago = $calClass.getInstance()) #set ($prevweeks = 0 - $getterUtil.getInteger($period.data)) #set ($V = $weeksago.add(3, $prevweeks)) #set ($q = $dqfu.forClass($mbThreadClass)) #set ($rfu = $portal.getClass().forName("com.liferay.portal.kernel.dao.orm.RestrictionsFactoryUtil")) #set ($groupIdCriteria = $rfu.ne("categoryId", $getterUtil.getLong("-1"))) #set ($V = $q.add($groupIdCriteria)) #set ($groupIdCriteria = $rfu.eq("groupId", $getterUtil.getLong($scopeGroupId))) #set ($V = $q.add($groupIdCriteria)) #set ($companyIdCriteria = $rfu.eq("companyId", $getterUtil.getLong($companyId))) #set ($V = $q.add($companyIdCriteria)) #set ($statusCriteria = $rfu.eq("status", 0)) #set ($V = $q.add($statusCriteria)) #set ($lastPostDateCriteria = $rfu.between("lastPostDate", $weeksago.getTime(), $now.getTime())) #set ($V = $q.add($lastPostDateCriteria)) #set ($V = $q.setProjection($pfu.property("threadId"))) #set ($res1 = $mbMessageLocalService.dynamicQuery($q)) #set ($q2 = $dqfu.forClass($mbMessageClass)) #set ($inCriteria = $rfu.in("threadId", $res1)) #set ($V = $q2.add($inCriteria)) #set ($createDateCriteria = $rfu.between("createDate", $weeksago.getTime(), $now.getTime())) #set ($V = $q2.add($createDateCriteria)) #set ($V = $q2.setProjection($pfu.projectionList().add($pfu.groupProperty("rootMessageId")).add($pfu.alias($pfu.rowCount(), "msgCount")))) #set ($V = $q2.addOrder($ofu.desc("msgCount"))) #set ($V = $q2.setLimit(0, 7)) #set ($res2 = $mbMessageLocalService.dynamicQuery($q2)) #set ($jsonArray = $jsonFactory.createJSONArray()) #foreach ($msgSum in $res2) #set ($rootMsgId = $msgSum.get(0)) #set ($msgCount = $msgSum.get(1)) #set ($subject = $mbMessageLocalService.getMessage($rootMsgId).getSubject()) #set ($jsonObject = $jsonFactory.createJSONObject()) #set ($V = $jsonObject.put("subject", $stringUtil.shorten($htmlUtil.escape($subject), 55))) #set ($V = $jsonObject.put("msgid", $rootMsgId)) #set ($V = $jsonObject.put("msgCount", $msgCount)) #set ($V = $jsonArray.put($jsonObject)) #end { "jsonArray": $jsonArray } #end 

 

source