Community BugSquad Begins

Staff Blogs 2011/07/13 投稿者 James Falkner Staff

You may have seen a previous mention of the Community BugSquad program in my last Community Roundup.  I'm excited to announce that the program has officially kicked off today!  The amazing volunteers of this program are contributing their time an expertise to review features in the upcoming Liferay 6.1 release, identify any new or regressed bugs (of course, Liferay is 100% bug-free, but it never hurts to make sure wink ), and improve their Liferay knowledge in a fun and educational environment.  My goal is to do this for every release.  

The first phase of the program involves reviewing the new Liferay 6.1 CE Features (such as Staging w/Branching, Personalizable Pages, Dynamic Site Templates, and others), with the goal of identifying any usability gaps, or other issues that would be encountered if this were released as-is, and recording in the form:

The second phase of the program involves downloading and trying our upcoming Release Candidate builds for Liferay 6.1, and finding (but not necessarily fixing) bugs.  Again, not that there would be any, but if there are, and they are known about before the final release, they have a good chance of getting fixed!  A win-win situation all around, and the team gets some hands-on Liferay experience, and valuable networking with other community members and Liferay staff.  If you're interested in participating, just leave a note in the comments below, and you'll be signed up!

 

Community Roundup

Staff Blogs 2011/06/13 投稿者 James Falkner Staff

8 Days to the official start of summer in the northern hemisphere.  2 days until the France Symposium.  That sounds like a good reason for another community roundup!  Hope everyone has a safe and fun summer season.  I have a love/hate relationship with it.  I love the summer season, but it means that from now on, the days will grow increasingly shorter and colder.  Luckily the Liferay community keeps me energized and warm.  On to the links!

  • As you may know, Liferay is looking to release the next version of Liferay Portal, version 6.1, later this year. We have found that engaging the community early on in the release cycle makes for higher quality and less migration issues, so I am organizing two special programs: Liferay Community Verifiers, and The Liferay BugSquad. Check out this forum thread, and respond if interested in participating!
  • There has been a lot of interest recently around Liferay User Groups.  Liferay has maintained a list of community events for our user groups in the past, however we are going to take it a step further with the introduction of user group "homes" on liferay.org!  Groups will get a set of collaboration applications (forums, wikis, calendars, etc) and a presence on the site.  Watch for this in the next couple of weeks.
  • Have a SCORM-based LMS and wish to surface its learning content in Liferay?  Arcusys has just released their SCORM module for Liferay to the community under LGPL.  Very nice work!  More background here, and here.
  • The 100 PaperCuts team is taking a short breather in advance of the 6.1 release.  Many of the same volunteers have signed up for the Community Verifier and 6.1 BugSquad program (see above).  It's been great getting to know everyone, and all of your hard work is definitely appreciated by the wider community!
  • The Liferay France Symposium is only days away, in beautiful Paris.  Featuring a host of Liferay experts and partners, the symposium will be a full day of discussion, demo, and use case studies.  If you are in the area, onsite registration is available!
  • The Liferay Hungary Symposium was held last month, and also featured a great set of speakers.  Steve provides some detail in his blog. If you missed the event, slides are available, and pics are up! [set 1, set 2].
  • On June 21, at the University of Szeged, Szeged Tech Meetup will be held, featuring discussions about Liferay.
  • Interesting.  Liferay is very often used to.. well.. integrate stuff.  So Steffen's very useful blog is well-titled. 
  • Swisher International designed a great corporate site on Liferay - and won an ADDY!
  • Alexey and EmForge continue to give back to the community with the release of Liferay 6.0.6 AMI images for Amazon EC2.  An easy and cheap way to quickly get a manageable Liferay instance up and running!
  • Alexey also provided a nice overview to Model Listener hooks for Liferay.  I'll see if I can't find time to update the wiki page.
  • Speaking of giving back, it's always nice to see ideas pop up and be driven by the community.  Case in point, providing Liferay's Document Library with anti-virus scanning ability.  This idea is well on its way to completion by those involved!  Great work by all!
  • Liferay LIVE continues its excellent series on all things Liferay.  This week Neil Griffin presented on Developing JSF 2 Portlets with ICEfaces, AlloyFaces, and LiferayFaces [slides now, video coming soon].  In the coming weeks we plan on doing encore presentations of several of  the East Coast Symposium presentations, so watch for those!
  • In a previous life I used to work on Accessibility, so I know how much attention to detail is required to make a site fully accessible.  Liferay makes it easy, as these sites can testify.  It's way more than alt attributes :)
  • Ben provides a nice overview of Liferay Layout Templates.
  • translate.liferay.com continues to enjoy tremendous success, giving our community a much-needed refresh of many of the translations provided out of box.  120 users and 41 languages, and the list keeps growing!  Top translations last week included German, Croation, Basque, Japanese, and Portugese.  And now, the core Liferay Portal translations are being managed through translate.liferay.com.  Wow!
  • Wondering about the benefits of an Alfresco + Liferay solution?  An interesting discussion has broken out on LinkedIn about the relative merits.  Take a look!
  • Couple of JIRA updates: we turned on rich text editing last week, so now your descriptions and comments can include all sorts of cool features.  Also, you may notice two new ticket types: "Epics" and "Stories".  We are going to begin using these to track Roadmap items and finally get rid of the always-outdated-the-moment-it-is-edited Roadmap wiki page.  This way the community can see, at a glance, what's coming down the pipe!
  • Creating new services using Liferay's Service Builder framework is now possible, graphically, using Liferay IDE.  This is now part of the nightly update, and slated for inclusion into the upcoming 1.3 release.  Nice work Greg et al!  [Also, a nice excerpt from Rich Sezov's Liferay in Action discusses Service Builder in detail].
  • The Holy Grail of Liferay Services would be a giant book that had a chapter for each Liferay service, along with descriptions of the underlying data model used to link everything together, java, javascript, and web services APIs to access the content, descriptions of all those esoteric formal parameters, etc.  While this isn't that, it's a neat start and a nice contribution.
  • Kristof provides us a fully-fleshed out example of embedded Web Content in your Liferay theme.  This keeps your editors from ever having to mess with your finely-crafted theme files!
  • Our very own Juan Fernández has released his latest beast of a contribution, the Wordpress Importer!  Bring all your content from Wordpress into Liferay with one click.  Very useful!
  • Interesting comparison of Liferay vs. Drupal.  Some of the items are questionable in their accuracy and even relevancy (why do we get an X next to PHP?). 
  • Recent Liferay Blog Posts:  Yet Another Liferay JSON Service Example, Liferay Portal Translation Project, Igor on JSON web service improvements, Liferay Is Not A Sausage Factory, and finally Liferay.com Mobile Sites and Responsive Layouts.
  • Recent Wiki Updates: Translation Team, WebDAV, JBoss Tips, Custom Logging, Accessible Sites, 100 PaperCuts, Liferay Development Style, JSON Serialization, JSON Web Services, and finally CMS Template (Velocity).

Ok, I said last time I'd do this every 2 weeks.  It's been 3 weeks.  That's an improvement from 4 weeks.  Baby steps, y'know? Finally, I'll leave you with my new desktop background pic.

Yet Another Liferay JSON Service Example

Staff Blogs 2011/06/10 投稿者 James Falkner Staff

The past couple of years I've seen many examples of using Liferay's built-in JSON services in various ways.  The architecture and syntax of this feature has undergone several refinements in the past few versions, so the documentation/examples you can find via a website search are usually slightly wrong and misleading.  Recently I saw this thread come alive and decided to sit down and make a non-trival read/write example of using Liferay's JSON services work.

Bear in mind that this applies to Liferay 6.0.x (I am using 6.0.6 CE in the examples).  In Liferay 6.1 there are new interfaces coming like (such as RESTful interfaces), ability to do automatic serialization and improved method argument passing, and there are also existing "heavy lifting" web service interfaces like SOAP endpoints that one can use. So this is not the only way to do things.  But it is great for prototyping and getting things to work quickly without dragging a bunch of dependencies and debugging hard-to-understand wire protocols.  I hope this example is still relevant!

The only dependency I am using here is Apache Commons HTTPClient.  I also decided to write it in Java (as opposed to Ray's earlier example on 5.2 in PHP).

Couple of things to be aware of:

  • By default, access is through Liferay's tunnel-web web app.  So the proper full URL in a default Liferay 6.0.6 install is http://localhost:8080/tunnel-web/secure/json .  
  • Since it is a "secure" (authenticated) interface we need to provide a username and password.  This is done using HTTP Basic Authentication, which of course is not appropriate for a production environment, since the password is unencrypted (it is instead base64-encoded following HTTP Basic Authentication).  The default username/password is "test/test".
  • There's no error checking whatsoever here.  You should add it for a real world scenario.

First Example

So, here's the first example.  A simple "Hello World" that does the same thing as Ray's example, only using Liferay 6 and written in simple Java.  It simply access the "Country" service and returns a list of country entities known to Liferay.

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

public class TestLiferayJSON {

    public static void main(String[] args) throws Exception {

        HttpHost targetHost = new HttpHost("localhost", 8080, "http");
        DefaultHttpClient httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(targetHost.getHostName(), targetHost.getPort()),
                new UsernamePasswordCredentials("test", "test"));

        // Create AuthCache instance
        AuthCache authCache = new BasicAuthCache();
        // Generate BASIC scheme object and add it to the local
        // auth cache
        BasicScheme basicAuth = new BasicScheme();
        authCache.put(targetHost, basicAuth);

        // Add AuthCache to the execution context
        BasicHttpContext ctx = new BasicHttpContext();
        ctx.setAttribute(ClientContext.AUTH_CACHE, authCache);

        HttpPost post = new HttpPost("/tunnel-web/secure/json");

        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("serviceClassName", "com.liferay.portal.service.CountryServiceUtil"));
        params.add(new BasicNameValuePair("serviceMethodName", "getCountries"));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
        post.setEntity(entity);

        HttpResponse resp = httpclient.execute(targetHost, post, ctx);
        resp.getEntity().writeTo(System.out);
        System.out.println();
        httpclient.getConnectionManager().shutdown();
    }
}

If you compile and run this (you'll have to download the Apache Commons HTTPClient libraries and put them on the classpath and/or add them as dependencies in your IDE), then you should see something like this on your output screen (this is the returned content from the HTTP POST):

[{"countryId":20,"idd":"093","name":"Afghanistan","active":true,"a2":"AF","number":"4","a3":"AFG"},{"countryId":21,"idd":"355","name":"Albania","active":true,"a2":"AL","number":"8","a3":"ALB"},

.....many more countries listed

{"countryId":227,"idd":"263","name":"Zimbabwe","active":true,"a2":"ZW","number":"716","a3":"ZWE"}]

Notice where I specify the username/password using Apache HTTPClient APIs.  This should be easily translatable to your favorite client (or if you are using curl or some other RESTful client test console such as rest-client and you want to specify the authentication header manually, use an Authorization header with a value of Basic dGVzdDp0ZXN0Cg== )

Also note the serviceClassName parameter.  This name (com.lifery.portal.service.CountryServiceUtil) specifies the service name (and maps to an actual class).  Not all services have remote service endpoints (for example, there's no com.liferay.portlet.social.service.SocialActivityServiceUtil for creating new activity stream items.  I wish there were).

The parameters are encoded into the body of the HTTP POST request using the Apache Commons HTTPClient's UrlEncodedFormEntity utility.  This is the same as Ray's $a->addPostData examples in PHP.

Second Example

Ok, now that the easy one works (it does work for you, right?), let's move on to something trickier using the Web Content system.  Notice that this system used to be called "Journal" so all the APIs refer to the Journal Service since the actual APIs were not changed in the interest of compatibility.  This second example calls the JournalArticle service to retrieve a sample article using the default install's groupId of 10156 and the articleId of 10455.  These numbers are automatically generated during initial startup the first time and may be different for you.  If they are you'll need to change them.  You can find them through the Control Panel by going to Communities -> Actions -> Edit to find the groupId, and pick any articleId from Web Content.

This example calls a specific method by identifying it using its formal parameters and passes the values for the parameters.

 

    public static void journal() throws Exception {
        HttpHost targetHost = new HttpHost("localhost", 8080, "http");
        DefaultHttpClient httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(targetHost.getHostName(), targetHost.getPort()),
                new UsernamePasswordCredentials("test", "test"));

        // Create AuthCache instance
        AuthCache authCache = new BasicAuthCache();
        // Generate BASIC scheme object and add it to the local
        // auth cache
        BasicScheme basicAuth = new BasicScheme();
        authCache.put(targetHost, basicAuth);

        // Add AuthCache to the execution context
        BasicHttpContext ctx = new BasicHttpContext();
        ctx.setAttribute(ClientContext.AUTH_CACHE, authCache);

        HttpPost post = new HttpPost("/tunnel-web/secure/json");

        // create Liferay API parameters
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("serviceClassName", "com.liferay.portlet.journal.service.JournalArticleServiceUtil"));
        params.add(new BasicNameValuePair("serviceMethodName", "getArticle"));
        params.add(new BasicNameValuePair("serviceParameters", "[groupId,articleId]"));
        params.add(new BasicNameValuePair("groupId", "10156"));
        params.add(new BasicNameValuePair("articleId", "10455"));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
        post.setEntity(entity);

        // make actual HTTP request and print results to System.out
        HttpResponse resp = httpclient.execute(targetHost, post, ctx);
        resp.getEntity().writeTo(System.out);
        httpclient.getConnectionManager().shutdown();

    }

Notice here that the "setup" is exactly the same as before, only the parameters are different.  Also note that the list of parameter names (serviceParameters) starts and ends with brackets.  It's an array of Strings!  So don't forget the brackets.

The getArticle method returns a JSON-encoded article from Liferay's web content system, so if this example works for you, you should get this on your output stream:

{"urlTitle":"welcome","indexable":true,"statusDate":"1287600093000","type":"general","smallImageId":10458,"articleId":"10455","version":1,"id":10456,"title":"Welcome","description":"","userId":10134,"userName":" ","smallImage":false,"createDate":"1287600093000","displayDate":"1201824000000","smallImageURL":"","expirationDate":"","status":0,"statusByUserName":" ","reviewDate":"","modifiedDate":"1287600093000","content":...

Third Example

Ok, now that you a trivial and almost-trivial example, let's do something more interesting.  Let's add (and remove) a Journal Article (this is what the initial thread asked about anyway).  

Here are two methods: addArticle and removeArticle.  They use a hard-coded 60000 for articleId to make removal easy.  There are a ton of parameters for addArticle (29 to be exact), and since there are multiple addArticle methods in the JournalArticleServiceUtil class we have to specify a serviceParameterTypes list to tell Liferay which service API we wish to invoke.  So the parameter list gets nasty and I didn't do a very good job of coding it to look nice.  You can do that though.

 

  public static void addArticle() throws Exception {
        HttpHost targetHost = new HttpHost("localhost", 8080, "http");
        DefaultHttpClient httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(targetHost.getHostName(), targetHost.getPort()),
                new UsernamePasswordCredentials("test", "test"));

        // Create AuthCache instance
        AuthCache authCache = new BasicAuthCache();
        // Generate BASIC scheme object and add it to the local
        // auth cache
        BasicScheme basicAuth = new BasicScheme();
        authCache.put(targetHost, basicAuth);

        // Add AuthCache to the execution context
        BasicHttpContext ctx = new BasicHttpContext();
        ctx.setAttribute(ClientContext.AUTH_CACHE, authCache);

        HttpPost post = new HttpPost("/tunnel-web/secure/json");
        Calendar yesterday = Calendar.getInstance();
        yesterday.add(Calendar.DAY_OF_YEAR, -1);
        Calendar nextWeek = Calendar.getInstance();
        nextWeek.add(Calendar.WEEK_OF_YEAR, 1);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("serviceClassName", "com.liferay.portlet.journal.service.JournalArticleServiceUtil"));
        params.add(new BasicNameValuePair("serviceMethodName", "addArticle"));
        params.add(new BasicNameValuePair("serviceParameters", "[groupId,articleId,autoArticleId,title,description,content,type,structureId,templateId,displayDateMonth,displayDateDay,displayDateYear,displayDateHour,displayDateMinute,expirationDateMonth,expirationDateDay,expirationDateYear,expirationDateHour,expirationDateMinute,neverExpire,reviewDateMonth,reviewDateDay,reviewDateYear,reviewDateHour,reviewDateMinute,neverReview,indexable,articleURL,serviceContext]"));
        params.add(new BasicNameValuePair("serviceParameterTypes", "[long,java.lang.String,boolean,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,int,int,int,int,int,int,int,int,int,int,boolean,int,int,int,int,int,boolean,boolean,java.lang.String,com.liferay.portal.service.ServiceContext]"));
        params.add(new BasicNameValuePair("groupId", "10156"));
        params.add(new BasicNameValuePair("articleId", "60000"));
        params.add(new BasicNameValuePair("autoArticleId", "false"));
        params.add(new BasicNameValuePair("title", "Test JSON Article"));
        params.add(new BasicNameValuePair("description", "Test JSON Description"));
        params.add(new BasicNameValuePair("content", "<?xml version='1.0' encoding='UTF-8'?><root available-locales=\"en_US\" default-locale=\"en_US\"><static-content language-id=\"en_US\"><![CDATA[<p>\n" +
                "\ttest content</p>]]></static-content></root>"));
        params.add(new BasicNameValuePair("type", "general"));
        params.add(new BasicNameValuePair("structureId", ""));
        params.add(new BasicNameValuePair("templateId", ""));
        params.add(new BasicNameValuePair("displayDateMonth", "" + (1 + yesterday.get(Calendar.MONTH))));
        params.add(new BasicNameValuePair("displayDateDay", "" + yesterday.get(Calendar.DAY_OF_MONTH)));
        params.add(new BasicNameValuePair("displayDateYear", "" + yesterday.get(Calendar.YEAR)));
        params.add(new BasicNameValuePair("displayDateHour", "" + yesterday.get(Calendar.HOUR_OF_DAY)));
        params.add(new BasicNameValuePair("displayDateMinute", "" + yesterday.get(Calendar.MINUTE)));
        params.add(new BasicNameValuePair("expirationDateMonth", "" + (1 + nextWeek.get(Calendar.MONTH))));
        params.add(new BasicNameValuePair("expirationDateDay", "" + nextWeek.get(Calendar.DAY_OF_MONTH)));
        params.add(new BasicNameValuePair("expirationDateYear", "" + nextWeek.get(Calendar.YEAR)));
        params.add(new BasicNameValuePair("expirationDateHour", "" + nextWeek.get(Calendar.HOUR_OF_DAY)));
        params.add(new BasicNameValuePair("expirationDateMinute", "" + nextWeek.get(Calendar.MINUTE)));
        params.add(new BasicNameValuePair("neverExpire", "false"));
        params.add(new BasicNameValuePair("reviewDateMonth", "" + (1 + nextWeek.get(Calendar.MONTH))));
        params.add(new BasicNameValuePair("reviewDateDay", "" + nextWeek.get(Calendar.DAY_OF_MONTH)));
        params.add(new BasicNameValuePair("reviewDateYear", "" + nextWeek.get(Calendar.YEAR)));
        params.add(new BasicNameValuePair("reviewDateHour", "" + nextWeek.get(Calendar.HOUR_OF_DAY)));
        params.add(new BasicNameValuePair("reviewDateMinute", "" + nextWeek.get(Calendar.MINUTE)));
        params.add(new BasicNameValuePair("neverReview", "false"));
        params.add(new BasicNameValuePair("indexable", "true"));
        params.add(new BasicNameValuePair("articleURL", "articleURL"));
        params.add(new BasicNameValuePair("serviceContext", "{}"));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
        post.setEntity(entity);
        HttpResponse resp = httpclient.execute(targetHost, post, ctx);
        System.out.println(resp.getStatusLine());
        resp.getEntity().writeTo(System.out);
        httpclient.getConnectionManager().shutdown();

    }

    public static void removeArticle() throws Exception {
        HttpHost targetHost = new HttpHost("localhost", 8080, "http");
        DefaultHttpClient httpclient = new DefaultHttpClient();
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(targetHost.getHostName(), targetHost.getPort()),
                new UsernamePasswordCredentials("test", "test"));

        // Create AuthCache instance
        AuthCache authCache = new BasicAuthCache();
        // Generate BASIC scheme object and add it to the local
        // auth cache
        BasicScheme basicAuth = new BasicScheme();
        authCache.put(targetHost, basicAuth);

        // Add AuthCache to the execution context
        BasicHttpContext ctx = new BasicHttpContext();
        ctx.setAttribute(ClientContext.AUTH_CACHE, authCache);

        HttpPost post = new HttpPost("/tunnel-web/secure/json");
        Calendar now = Calendar.getInstance();
        Calendar nextWeek = Calendar.getInstance();
        nextWeek.add(Calendar.WEEK_OF_YEAR, 1);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("serviceClassName", "com.liferay.portlet.journal.service.JournalArticleServiceUtil"));
        params.add(new BasicNameValuePair("serviceMethodName", "deleteArticle"));
        params.add(new BasicNameValuePair("serviceParameterTypes", "[long,java.lang.String,java.lang.String,com.liferay.portal.service.ServiceContext]"));
        params.add(new BasicNameValuePair("serviceParameters", "[groupId,articleId,articleURL,serviceContext]"));
        params.add(new BasicNameValuePair("groupId", "10156"));
        params.add(new BasicNameValuePair("articleId", "60000"));
        params.add(new BasicNameValuePair("articleURL", "articleURL"));
        params.add(new BasicNameValuePair("serviceContext", "{}"));

        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8");
        post.setEntity(entity);
        HttpResponse resp = httpclient.execute(targetHost, post, ctx);
        System.out.println(resp.getStatusLine());
        resp.getEntity().writeTo(System.out);
        httpclient.getConnectionManager().shutdown();

    }

More Notes:

  • I specified "" (blank) strings for the structureId and templateId.  This means that the article has no structure and no template, so it will just be interpreted as raw HTML (as though you had created a new Web Content Article and didn't specify a structure or template).
  • The content parameter is XML with the content inside of a CDATA block.  If there were an associated structure for this content, the content would look different.  That's for an advanced reader to explore.
  • The timestamps use Java's Calendar method to set the display date and other dates.
  • The articleId is hard-coded to 60000.  If you wish to auto-generate specify autoArticleId to be true.
  • The serviceContext parameter is special and tricky and not well documented.  The "content" of the parameter is a JSON-serialized instance of the com.liferay.portal.service.ServiceContext class.  For other services, it might require a more complex serialized instance of the ServiceContext class.  For example, instead of {} (which, which decoded, results in a simple new instance of the ServiceContext class with null/empty/blank/0 for all of its properties), one might have something like {scopeGroupId:themeDisplay.getScopeGroupId()} (I took this from this example). Anyway, for this example, an empty {} works.  

If addArticle works, you should get in return a serialized version of the new article:

{"urlTitle":"test-json-article","indexable":true,"statusDate":"","type":"general","smallImageId":14540,"articleId":"60000","version":1,"id":14538,"title":"Test JSON Article","description":"Test JSON Description","userId":10168,"userName":"Test Test","smallImage":false,"createDate":"1307729885333","displayDate":"1310221080000","smallImageURL":"","expirationDate":"1310912280000","status":2,"statusByUserName":"","reviewDate":"1310912280000","modifiedDate":"1307729885333","content":"<?xml version='1.0' encoding='UTF-8'?><root available-locales=\"en_US\" default-locale=\"en_US\"><static-content language-id=\"en_US\"><![CDATA[<p>\n\ttest content<\/p>]]><\/static-content><\/root>","templateId":"","groupId":10156,"resourcePrimKey":14539,"structureId":"","statusByUserId":0,"companyId":10131,"uuid":"d2a09ad8-43d5-476b-bcb9-3a1621409835"}

Since deleteArticle returns void, you won't get anything (But the article should be gone, which you can verify in the GUI, or via a database browser).  

Good luck!

Community Roundup

Staff Blogs 2011/05/19 投稿者 James Falkner Staff

Wow, has it really been one month since my last roundup?  Apologies!  As usual, there are many moving parts to the World of Liferay and I spend a lot of time trying to keep up with it all, and sometimes things drop off the radar.  But they always come back, thanks to my handy organizer (aka TextWrangler).  We've been very busy here in the Liferay Community, and it will be getting more action-packed as the year progresses.  So grab your coffee and start clickin':

That is all for now.  I'll try to do more regular roundups (like every 2 weeks).  I hope I don't have to break Twitter's storage policies :-)

Liferay on Amazon Elastic Beanstalk and EC2

Staff Blogs 2011/05/03 投稿者 James Falkner Staff

Recently I came across a forum post asking about deploying Liferay on Amazon's Elastic Beanstalk.  Elastic Beanstalk (EB) is basically a managed Amazon EC2 instance running Linux and a pre-installed Tomcat.  You can install (web) apps into that container, and with clever configuration (and your credit card information) it will automatically scale horizontally and vertically as usage demands rise or fall. Pretty sweet right?  This is a part of being in "The Cloud".  Since it runs Tomcat, it shouldn't be too hard to get Liferay running in this environment, should it?  Turns out, it's actually not that hard.  

For this example, I used a stock Liferay configuration, using embedded HSQLDB.  Clever folks can configure this to use Amazon's RDS service.

For the impatient/advanced

Here's a quick rundown of what you need to do:

  1. On your local system, create custom Liferay WAR file which includes missing dependencies and a custom portal-ext.properties file
  2. On EB, Create new EB Application, and upload your custom WAR file
  3. Configure JVM instance for higher Java memory settings
  4. (optional) Configure EC2 instance to use SSH
  5. Restart

Details:

First, as of this date, Elastic Beanstalk runs Tomcat 6.0.32.  Pretty close to the 6.0.29 that Liferay 6.0.6 ships with.  However, the version that Liferay Bundles ship with are slightly modified to configure things like default network ports, classloader behavior, filesystem paths, dependent libraries, and other various things.  So, to successfully deploy to EB, we will need to build a custom Liferay WAR file that includes the things that the EB Tomcat is missing, and configure ${liferay.home}.  So, to create this custom app:

  1. Download and un-jar the stock Liferay WAR file into some temporary directory (for example, download the .war file to /tmp and extract it to /tmp/lr-eb-war).  Save the original .war, you'll be updating it shortly.
  2. Download and un-jar the stock Liferay Tomcat Bundle into some other place (for example /tmp/lr-606-bundle). You'll be borrowing libraries from it shortly.
  3. Copy all of the global jar files from the Liferay Tomcat Bundle in lib/ext to your custom Liferay WAR file directory
    1. cp /tmp/lr-606-bundle/liferay-portal-6.0.6/tomcat-6.0.29/lib/ext/*.jar /tmp/lr-eb-war/WEB-INF/lib
  4. Create a text file /tmp/lr-eb-war/WEB-INF/classes/portal-ext.properties which includes a single line: liferay.home=/tmp/liferay-home-eb
  5. Add these files back into the stock Liferay WAR file using the jar utility (note these instructions will work on unix/Linux/Mac OS X  Windows users can use a utility like 7-Zip or InfoZip or something else to accomplish the same thing).
    1. cd /tmp/lr-eb-war
    2. jar uvf ../liferay-portal-6.0.6-20110225.war WEB-INF/classes/portal-ext.properties WEB-INF/lib/*.jar

Once this is done, you're ready to create an EB account and log in.  Go here and click "Begin Using AWS Elastic Beanstalk" to create a new account. After creating an account, giving Amazon your credit card info, confirming your account via telephone, go to the main AWS Console screen.

Create a new EB Application, selecting to upload a custom file, and select to create a new deployment environment.  Make sure to pick the t1.micro configuration, otherwise you'll probably blow right past the "free" limits and start accruing charges on your credit card.  Better to save your money for the Liferay EE license and beefier EC2 instance later in production ;-)

After clicking "Finish" it will take a while to upload the (~130MB) custom WAR file.  You'll stare at a spinner for a while, with no indication of progress. Once you get the "Your Application has successfully been created" message you'll be ready to proceed.  You'll be placed at the AWS (Amazon Web Services) Console.  The only two tabs you'll use here is the "Elastic Beanstalk" tab (to configure Tomcat and applications) and the "EC2" tab to configure the virtual machines on which Tomcat runs.

Memory Configuration

Once the upload is complete, Amazon will create a virtual machine running Linux, put a load balancer in front, create a DNS record, deploy a bunch of other junk (such as an Auto Scaling service to add more machines when needed), launch the virtual machine and Tomcat, deploying your custom Liferay web app, but it'll be broken because it's not configured correctly and probably ran out of memory.  Ignore any errors in any log file output you may be able to see.   Once you get Liferay up and running you can play with all this stuff to your heart's content.  For now, let's go configure things so that Liferay works.

You'll have to wait for the environment to be fully "up" (might take 5-10 minutes) before you can start tweaking configuration.  Once the spinner is done spinning and everything seems steady-state,  click on the "Edit Configuration" link:

If things aren't ready to be configured, it'll tell you.  Here, you can see all the wonderful things you can configure, but the only thing you need to do here is on the Container tab.  Set the memory settings as shown below:

Click "Apply Changes".

Your First Liferay On The Cloud

If all goes well, Liferay should eventually come up (After 5-10 minutes).  On the main AWS Console, if the status turns to "Green" (you can also watch the "Events" tab), then you're good to go!  Click on the "Overview" tab and click on "View Running Version" to see and log into your new Liferay Instance On The Cloud.  Welcome to Web five-dot-oh :)  (You can also get the hostname from the Environment Details screen, and it's the same hostname you initially configured when setting up things at the beginning).

Troubleshooting

If things don't go so well, and you want to do traditional administration from the command line (and really, who doesn't?), you need to log into your running OS using ssh.  To do that, you need to create a keypair, download the private key, apply the key pair to the configuration for your EC2 instance, and open up port 22 (the ssh port) on the EC2 instance, and finally use your favorite ssh utility to ssh (or scp, or sftp, or...) into the virtual machine.

SSH: Creating a keypair

Here is where you use the "EC2" tab at the very top of your AWS console.  Click on it, and click "Key Pairs" on the left.  It'll tell you you don't have any.  Click "Create Key Pair" to create a new one.

Give it any name, and click create.  It'll automatically download a .pem file that contains the "private" side of this key pair.  This probably violates several PKI best practices, but what are you gonna do?  Store this file somewhere convenient, you'll be using it in a bit when ssh'ing to your running virtual machine. You'll also need to chmod 400 the file if on Unix/Linux/Mac OS X.  Otherwise ssh will refuse to use it, citing security concerns.  Thanks ssh.

SSH: Apply the Key Pair to your EC2 instance

You need to associate the newly-created key pair to your machine (this effectively configures SSH on the linux box).  To do this, go back to the "Elastic Beanstalk" tab at the very top of the screen.  Under "Environment Details" click on "Edit Configuration".  On the "Server" tab of this dialog, enter the name of your newly created keypair in the "Existing Key Pair" field.  Note there is no auto-completion or selection here.  You have to type in the full name (bah!!). Click "Apply Changes" and agree to the little warning that comes up:

SSH: Configure the Security Group

When you first created your Elastic Beanstalk application, Amazon automatically allocated a virtual machine to you, and applied a default Security Group configuration to it.  The Security Group, among other things, defines a set of networking rules that allow or disallow network traffic to and from the EC2 instance running Tomcat/Liferay.  You need to add ssh as one of the "allowed" applications for which network traffic is permitted.  Go back to the "EC2" tab at the very top of your screen, and click on "Security Groups" on the left, then click on the "elasticbeanstalk-default" group.  At the bottom, click on the "Inbound" tab, and create a new rule, selecting SSH from the "Create a new rule" dropdown:

Then click "Apply Rule Change" at the bottom of the "Inbound" tab.  This opens up port 22 to your EC2 instance so you can ssh to your running Linux instance.

SSH: Using The Command Line

Now that port 22 is open, you can ssh.  Again, these instructions work on unix/Linux/Mac OS X.  Windows users can use things like PuTTY to ssh to the machine.  To ssh to your instance, you need to know the DNS name of the virtual machine (Amazon dynamically allocates these so there's no way to guess).  To figure this out, click on the "Instances" tab on the left.  There may be multiple instances listed here.  You want the one that has the associated key pair mentioned in the "Key Pair Name" column.  Right-click on this, and select "Connect".  It'll show you the appropriate command line (you'll need to change the path to the saved .pem private key file from earlier).  It also shows you the DNS hostname of your running instance:

Note that the sample cut-and-pastable command line they give uses the root user.  Amazon has since updated their policy to require you to ssh as the ec2-user user.  You'll get error if you attempt to ssh as the root user.  So don't.  Here's the example, correct command line I use:

Once in, it's like any other Linux shell.  You can see the Tomcat process and other interesting info using ps and other utiities:

Notice Liferay is already deployed into Tomcat, located at /usr/share/tomcat6/webapps/ROOT.  You can edit anything under here to tweak Liferay.  However, you can't alter the Tomcat configuration directly, as the application files and execution runtime is owned by root, and you are merely ec2-user.  You can look in /tmp to see the Liferay Home directory you specified earlier in your portal-ext.properties.

That's it for now, hope you find it useful.  If you expand on this (e.g. configure RDS, or do some other cool thing), leave comments below!  Some improvements could be:

  • Configuring the JVM so that you don't have to inject all of the Liferay dependency JARs into the main Liferay web app.  This would allow future deployed Liferay extensions to use the same libraries and avoid classloader issues.
  • Configure Liferay IDE to be able to deploy to EB instances automatically given your login info
  • Configure the Liferay Home directory to not be in /tmp.

Have fun!

Social Visualization and Analytics with Liferay

Staff Blogs 2011/04/20 投稿者 James Falkner Staff

Recently I did a Liferay LIVE presentation on driving community participation with Liferay.  I'll post links to the video and slides once they are available.  The main topic was how to encourage community participation on sites that are based on Liferay.  There are many features in Liferay that make it easy to create and maintain good community sites.  One of the most underrated features in Liferay 6 is Social Equity, which is a social value system, essentially assigning a number to each person or piece of content.  The number represents the quality of the thing it is assigned to.  So an individual's equity value is a measure of their value to the community, based on content they've created and feedback from other users.  The value assigned to a piece of content (such as a blog post) represents the value of the content itself, based on user feedback.  Higher valued content is "better" in some way than lesser-valued content.  Same for people.

There have been a couple of writeups made regarding Social Equity in Liferay, and make for a great pre-read before continuing on this blog post!

Once Social Equity is enabled by an Administrator, applications which enable user content generation in Liferay (such as Blogs, Wikis, Message Boards, and Web Content) will contribute equity to users and content (information)  when actions occur (such as authoring a blog post, rating a forum thread, or commenting on a wiki page).  These equity values can then be exposed via visualization widgets such as Liferay's Top Users portlet.

One of the things missing in today's Liferay are other interesting visualizations of Social Equity data.  There are a bunch of use cases that Social Equity is good for, and if we had visualizations for them it would really encourage community participation.  Here are several of them:

  • Show the overall contribution and participation equity across all communities for a user
  • Show all tags associated with content created by a user, ordered by Tag Equity
    • Objective: show expertise of a user
  • Show all contributions from a user ordered by information equity
    • Objective: show a user’s contributions with high value
  • Show the top xx communities
    • Objective: Motivate communities to improve ranking
  • Show the top xx contributors or countries of all or a specific community
    • Objective: Motivate people to get into a top ranking
  • Show the top xx content of all or a specific community
    • Objective: Show the most valued content in a community
  • Show the top xx experts for tag yy
    • Objective: Show top experts for a given subject

These are highly useful visualizations, and are possible using basic equity information available to Liferay applications.  Unfortuntely, they do not exist today.  This is where you and I come in!  Below are two examples of simple yet powerful visualizations that align with two of the above use cases.  Can you think of more use cases that involve personal, tag, or information equity?

1. Equity-based Tag Cloud

This example builds on the popular notion of a Tag Cloud (indeed, Liferay has a built-in Tag Cloud portlet, but this portlet ranks tags solely on number of mentions of a tag, regardless of social value).  Ultimately it would be nice if the built-in portlet had this option (to order by equity), but it doesn't yet, and for this demo, I found an nifty 3D tag cloud (which relies on jQuery).  To calculate equity of a tag, you simply add up all the information equities for content that has been tagged with a given tag.  So, the pseudocode for this algorithm is:

for each asset a in community {

  for each tag t on asset a {

    Add [t, equity(a)] to Map<Tag, EquitySum> m;

  }

}

display resulting map m in tag sphere;

In this example, each tag's equity value is normalized into a range [0, 100), so that tags with higher equity values have larger font sizes.  In addition, when you click on a tag, it uses Liferay's Shared Render Parameter support to cause any other portlets on that page that support the tag parameter to update their displays to only show content tagged with that specific tag.

One thing to keep in mind with the below code: it is not very performant!  Every time the portlet is rendered, the equity sums are re-calculated.  For lots of tags or assets, this does not scale.  A real solution would calculate and store tag equities as actions occur, to avoid having to re-calculate every time.  I leave that as an exercise to the reader.

This portlet is a very basic MVCPortlet-based portlet.  There is no java portlet class.  There are just a couple of JSPs.  It is the default portlet type when using the Liferay IDE.  So a very easy way to use these examples is to create a new Liferay Portlet project in Liferay IDE, cut and paste the below files on top of the boilerplate files that Liferay IDE gives you, and deploy to your configured Liferay instance.  I also didn't bother to factor out imports, includes, or CSS into separate files, just for simplicity.  So everything is inline (please don't kick me out of the developer club).  The portlet uses Liferay's Asset Framework APIs, Tag APIs, and Social Equity APIs.

Finally, there are some tunable parameters for this, which are exposed using Liferay's Portlet Configuration Framework.  This way, to tune a tunable, you simply visit the portlet's configuration page, edit the value, and click save. That is represented by entries in liferay-portlet.xml and the configuration.jsp source code found below.  This uses the AlloyUI component library to render configuration items.

  • view.jsp (main algorithm and html):
<%@page import="com.liferay.portal.util.PortalUtil"%>
<%@page import="java.util.Calendar"%>
<%@page import="java.util.Collections"%>
<%@page import="java.util.Comparator"%>
<%@page import="java.util.Date"%>
<%@page import="com.liferay.portlet.asset.model.AssetTag"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page import="com.liferay.portal.kernel.util.GetterUtil"%>
<%@page import="com.liferay.portlet.PortletPreferencesFactoryUtil"%>
<%@page import="com.liferay.portal.kernel.util.Validator"%>
<%@page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%@page import="javax.portlet.PortletPreferences"%>

<%@page
        import="com.liferay.portlet.asset.service.AssetEntryLocalServiceUtil"%>
<%@page import="com.liferay.portlet.asset.model.AssetEntry"%>
<%@page import="java.util.List"%>
<%@page
        import="com.liferay.portlet.asset.service.persistence.AssetEntryQuery"%>
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet"%>

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>

<portlet:defineObjects />
<liferay-theme:defineObjects />

<style type="text/css">
.tagCloud ul,.tagCloud li {
        list-style: none;
        margin: 0;
        padding: 0;
}

.tagCloud .tagClass img {
        border: 0 none;
}

.tagCloud .tagClass a {
        font-family: Arial;
        font-weight: normal;
        padding: 3px;
        text-align: center;
        text-decoration: none;
        vertical-align: middle;
        color: yellow;
}

.tagCloud .tagClass a:hover {
        border: solid 1px #ffdb00;
        color: #ff00ff;
        text-decoration: none;
}

#tagCloud1 {
        -moz-border-radius: 15px;
        border-radius: 15px;
        background: grey;
}
</style>
<%

PortletPreferences preferences = renderRequest.getPreferences();

String portletResource = ParamUtil.getString(request,
                "portletResource");

if (Validator.isNotNull(portletResource)) {
        preferences = PortletPreferencesFactoryUtil.getPortletSetup(
                        request, portletResource);
}

int MAX_TAGCLOUD = GetterUtil.getInteger(preferences.getValue(
                "maxTags", "50"));
double ANIMATION_TIME = (GetterUtil.getInteger(preferences.getValue(
                "animationTime", "1"))) / 10.0;

int EQ_CUTOFF = GetterUtil.getInteger(preferences.getValue(
                "eqCutoff", "1"));

final int EQ_NORMALIZED_RANGE = 100;

        // get all assets in this group (community)
        AssetEntryQuery q = new AssetEntryQuery();
        long[] ids = new long[] { scopeGroupId };
        q.setGroupIds(ids);
        q.setVisible(true);
        List<AssetEntry> entries = AssetEntryLocalServiceUtil.getEntries(q);
        final Map<String, Double> eqValues = new HashMap<String, Double>();
        final Map<String, Integer> eqNormal = new HashMap<String, Integer>();
        double maxEq = 0;

        // iterate over all assets and their tags, and build equity map
        for (AssetEntry e : entries) {
                for (AssetTag tagA : e.getTags()) {
                        Double oldVal = eqValues.get(tagA.getName());
                        if (oldVal == null) {
                                oldVal = (double)0;
                        }
                        Double newVal = oldVal + e.getSocialInformationEquity();

                        eqValues.put(tagA.getName(), newVal);
                        if (newVal > maxEq) maxEq = newVal;
                }
        }

        // normalize equity values to range [0, EQ_NORMALIZED_RANGE]
        for (String tag : eqValues.keySet()) {
                eqNormal.put(tag, (int)Math.round( ((double)eqValues.get(tag) / maxEq) * (double)EQ_NORMALIZED_RANGE));
        }

        // generate sorted list of tag equity values, eliminating those under the cutoff value
        List<String> sortedEq = new ArrayList<String>();
        for (Map.Entry<String, Double> entry : eqValues.entrySet()) {
                if (entry.getValue() > EQ_CUTOFF) {
                        sortedEq.add(entry.getKey());
                }
        }

        // sort the resulting list
        Collections.sort(sortedEq, new Comparator<String>() {
                public int compare(String s1, String s2) {
                        double eq1 = eqValues.get(s1);
                        double eq2 = eqValues.get(s2);
                        if (eq1 == eq2) return 0;
                        else if (eq2 > eq1) return 1;
                        else return -1;
                }
        });
        
        // truncate so our tag sphere isn't too CPU-intensive
        if (sortedEq.size() > MAX_TAGCLOUD) {
                sortedEq.subList(MAX_TAGCLOUD, sortedEq.size() - 1).clear();
        }
%>

<div id="tagCloud1"></div>

<script
        src="<%= PortalUtil.getStaticResourceURL(request, request.getContextPath() + "/js/jquery-1.4.1.js", new Date().getTime()) %>"
        type="text/javascript"></script>
<script
        src="<%= PortalUtil.getStaticResourceURL(request, request.getContextPath() + "/js/js-cumulus.min.js", new Date().getTime()) %>"
        type="text/javascript"></script>

<script type="text/javascript">

var tagCloud1;

var tags = [
        <%
                for (String tag : sortedEq) {
                        Integer tagEqNormal = eqNormal.get(tag);
        %>
        
        new Tag("<%=tag%>",<%=tagEqNormal%>, "<portlet:renderURL><portlet:param name="tag" value="<%=tag%>"/></portlet:renderURL>"),
        <%
                }
        %>
        ];
        tagCloud1 = new TagCloud(document.getElementById("tagCloud1"), tags, 400, 400, {AnimationTime:<%=ANIMATION_TIME%>, HoverStop:false});
        
</script> 
  • configuration.jsp (used to configure tunables such as maximum # of tags to display):
<%@page import="com.liferay.portal.kernel.util.Constants"%>
<%@page import="com.liferay.portal.kernel.util.GetterUtil"%>
<%@page import="com.liferay.portlet.PortletPreferencesFactoryUtil"%>
<%@page import="com.liferay.portal.kernel.util.Validator"%>
<%@page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%@page import="javax.portlet.PortletPreferences"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet"%>
<%@ taglib uri="http://liferay.com/tld/security"
        prefix="liferay-security"%>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util"%>

<%@ page contentType="text/html; charset=UTF-8"%>

<portlet:defineObjects />
<liferay-theme:defineObjects />

<%
        PortletPreferences preferences = renderRequest.getPreferences();

        String portletResource = ParamUtil.getString(request,
                        "portletResource");

        if (Validator.isNotNull(portletResource)) {
                preferences = PortletPreferencesFactoryUtil.getPortletSetup(
                                request, portletResource);
        }

        int maxTags = GetterUtil.getInteger(preferences.getValue(
                        "maxTags", "50"));
        int animationTime = GetterUtil.getInteger(preferences.getValue(
                        "animationTime", "1"));
        int eqCutoff = GetterUtil.getInteger(preferences.getValue(
                        "eqCutoff", "1"));
%>

<liferay-portlet:actionURL portletConfiguration="true"
        var="configurationURL" />

<aui:form action="<%= configurationURL %>" method="post" name="fm">
        <aui:input name="<%= Constants.CMD %>" type="hidden"
                value="<%= Constants.UPDATE %>" />
        <aui:fieldset>

                <aui:select label="Maximum Tags to Display" name="preferences--maxTags--">
                        <aui:option label="5" selected="<%= maxTags == 5 %>" />
                        <aui:option label="10" selected="<%= maxTags == 10 %>" />
                        <aui:option label="15" selected="<%= maxTags == 15 %>" />
                        <aui:option label="20" selected="<%= maxTags == 20 %>" />
                        <aui:option label="30" selected="<%= maxTags == 30 %>" />
                        <aui:option label="40" selected="<%= maxTags == 40 %>" />
                        <aui:option label="50" selected="<%= maxTags == 50 %>" />
                        <aui:option label="100" selected="<%= maxTags == 100 %>" />
                </aui:select>
                <aui:select label="Animation Time" name="preferences--cutoffDays--">
                        <aui:option label="1" selected="<%= animationTime == 1 %>" />
                        <aui:option label="2" selected="<%= animationTime == 2 %>" />
                        <aui:option label="3" selected="<%= animationTime == 3 %>" />
                        <aui:option label="4" selected="<%= animationTime == 4 %>" />
                        <aui:option label="5" selected="<%= animationTime == 5 %>" />
                        <aui:option label="6" selected="<%= animationTime == 6 %>" />
                        <aui:option label="7" selected="<%= animationTime == 7 %>" />
                        <aui:option label="8" selected="<%= animationTime == 8 %>" />
                        <aui:option label="9" selected="<%= animationTime == 9 %>" />
                        <aui:option label="10" selected="<%= animationTime == 10 %>" />
                </aui:select>
                <aui:select label="Equity Cutoff"
                        name="preferences--eqCutoff--">
                        <aui:option label="1" selected="<%= eqCutoff == 1 %>" />
                        <aui:option label="5" selected="<%= eqCutoff == 5 %>" />
                        <aui:option label="10" selected="<%= eqCutoff == 10 %>" />
                        <aui:option label="15" selected="<%= eqCutoff == 15 %>" />
                        <aui:option label="20" selected="<%= eqCutoff == 20 %>" />
                        <aui:option label="25" selected="<%= eqCutoff == 25 %>" />
                        <aui:option label="30" selected="<%= eqCutoff == 30 %>" />
                        <aui:option label="60" selected="<%= eqCutoff == 60 %>" />
                        <aui:option label="70" selected="<%= eqCutoff == 70 %>" />
                        <aui:option label="80" selected="<%= eqCutoff == 80 %>" />
                        <aui:option label="90" selected="<%= eqCutoff == 90 %>" />
                        <aui:option label="100" selected="<%= eqCutoff == 100 %>" />
                        <aui:option label="110" selected="<%= eqCutoff == 110 %>" />
                        <aui:option label="150" selected="<%= eqCutoff == 150 %>" />
                        <aui:option label="200" selected="<%= eqCutoff == 200 %>" />
                        <aui:option label="500" selected="<%= eqCutoff == 500 %>" />
                </aui:select>

        </aui:fieldset>
        <aui:button-row>
                <aui:button type="submit" />
        </aui:button-row>


</aui:form>
  • portlet.xml: You need to add the <supported-public-render-parameter> and <public-render-parameter> elements to your portlet.xml, or use the below code in its entirety.  If you use the below code as-is, note that your portlet name must be "EquitySphere" in your other files (it may be easiest to name your project and portlet EquitySphere if you intend to use the below code as-is).
<?xml version="1.0"?>

<portlet-app
        version="2.0"
        xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
>
        <portlet>
                <portlet-name>EquitySphere</portlet-name>
                <display-name>EquitySphere</display-name>
                <portlet-class>com.liferay.util.bridges.mvc.MVCPortlet</portlet-class>
                <init-param>
                        <name>view-jsp</name>
                        <value>/view.jsp</value>
                </init-param>
                <expiration-cache>0</expiration-cache>
                <supports>
                        <mime-type>text/html</mime-type>
                </supports>
                <portlet-info>
                        <title>EquitySphere</title>
                        <short-title>EquitySphere</short-title>
                        <keywords>EquitySphere</keywords>
                </portlet-info>
                <security-role-ref>
                        <role-name>administrator</role-name>
                </security-role-ref>
                <security-role-ref>
                        <role-name>guest</role-name>
                </security-role-ref>
                <security-role-ref>
                        <role-name>power-user</role-name>
                </security-role-ref>
                <security-role-ref>
                        <role-name>user</role-name>
                </security-role-ref>
                <supported-public-render-parameter>                         tag                 </supported-public-render-parameter>                 
        </portlet>
        <public-render-parameter>                 <identifier>tag</identifier>                 <qname xmlns:x="http://www.liferay.com/public-render-parameters">x:tag</qname>         </public-render-parameter>         
</portlet-app>
  • liferay-portlet.xml (contains declaration of ConfigurationImpl class - using Liferay's default so I don't have to write my own ConfigurationImpl but instead follow the naming convention for preference names, such as preferences--cutoffDays--.
<?xml version="1.0"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.0.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_0_0.dtd">

<liferay-portlet-app>
        <portlet>
                <portlet-name>EquitySphere</portlet-name>
                <icon>/icon.png</icon>
                <configuration-action-class>com.liferay.portal.kernel.portlet.DefaultConfigurationAction</configuration-action-class>
                <instanceable>false</instanceable>
                <header-portlet-css>/css/main.css</header-portlet-css>
                <footer-portlet-javascript>/js/main.js</footer-portlet-javascript>
                <css-class-wrapper>EquitySphere-portlet</css-class-wrapper>
        </portlet>
        <role-mapper>
                <role-name>administrator</role-name>
                <role-link>Administrator</role-link>
        </role-mapper>
        <role-mapper>
                <role-name>guest</role-name>
                <role-link>Guest</role-link>
        </role-mapper>
        <role-mapper>
                <role-name>power-user</role-name>
                <role-link>Power User</role-link>
        </role-mapper>
        <role-mapper>
                <role-name>user</role-name>
                <role-link>User</role-link>
        </role-mapper>
</liferay-portlet-app>

The rest of the files (e.g. web.xml, liferay-plugin-package.properties, etc) should not need any changes from their default values.  The only other thing you must do is place jQuery (jquery-1.4.1.js) and the JS-Cumulus (js-cumulus.min.js) javascript files into the js/ subdirectory of your project.  You can use the minified jquery if you wish.  The final structure of your project would look like the picture on the right.  You can download these files from here.

Deploying and understanding Tag Equity Visualization

Once you have sucessfully deployed this application, you can add the tag cloud to any page within a community.  It proably won't show anything because you haven't enabled Social Equity and you haven't created any content.  So first, go to Control Panel -> Social Equity and click "Enable Social Equity" to turn it on.  Once that is done, add the Blogs application to a page, and start creating some blogs and adding tags to the blog posts.   Once the blogs are published, you should start to see tags appearing in the tag sphere!  Any content you create (blogs, wikis, forums, bookmarks, images, etc) that can be tagged will cause their tags to show up.  To tweak the tunables, click the wrench icon and select 'Configuration' to change the values.  Click 'save' to apply your changes.  You should see something like the tag cloud below.  

 Note that as tags get more equity (by people creating new content using that tag, OR by an existing piece of tagged content getting rated, updated, viewed, etc), the size of the font should get bigger (relative to the other tags).  A great way to see, at a glance, what topics are most valuable. Feel free to change the inline CSS to make this thing prettier (I am not a designer!).

 

 

 

 

2. Trending Topics

You're probably familiar with Twitter and its concept of "trending topics".  It shows a list of "hot topics" for that particular point in time, scrolling across the screen in a ticker-tape-like display.  Clicking on the topic shows you a list of tweets associated with that topic.  A handy way to see what is on people's minds at any one point in the Twittersphere.

How can we achieve the same thing within a Liferay community?  For community members, it would be a great way for them to keep in touch on the hot topics of the day.

Trending Algorithm

I looked around the web for some decent trending algorithms, and there were lots of them, but they were complicated and relied on advanced math that I left far behind in grade school.  So I came up with my own algorithm.  Here it is:

In this digram, "today" is represented at the far right.  To determine whether a topic is trending, we look at the average number of "mentions" in two time periods: before the "pivot" and "after".  If the average number of mentions in the time period between pivot and today is higher than a certain threshold (compared to the average number of mentions in the time period between cutoff and pivot), then the topic is considered trending.  In the example above, there were 5 mentions over 15 days in the first time period (.3333/day average), and 3 mentions in 5 days in the second time period (.6/day average), which is an 80% improvement.  If 80% is higher than your selected threshold, then the topic will display as a trending topic.

This portlet is identical in structure to the previous one, so not a lot of explanation is needed.  Everything is inline in view.jsp and the shared render parameter behavior is the same.  The main difference in this portlet is the algorithm, # of tunables, and javascript used to display the result.  I used another FOSS jQuery plugin called TurboTicker, which displays tags in a smooth, non-CPU-intensive, vertical scroll.  You can style all you want using CSS.  

view.jsp

<%@page import="com.liferay.portal.kernel.util.GetterUtil"%>
<%@page import="com.liferay.portlet.PortletPreferencesFactoryUtil"%>
<%@page import="com.liferay.portal.kernel.util.Validator"%>
<%@page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%@page import="javax.portlet.PortletPreferences"%>
<%@page import="java.util.Set"%>
<%@page import="java.util.HashSet"%>
<%@page import="com.liferay.portal.util.PortalUtil"%>
<%@page import="java.util.Calendar"%>
<%@page import="java.util.Collections"%>
<%@page import="java.util.Comparator"%>
<%@page import="java.util.Date"%>
<%@page import="com.liferay.portlet.asset.model.AssetTag"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@page
        import="com.liferay.portlet.asset.service.AssetEntryLocalServiceUtil"%>
<%@page import="com.liferay.portlet.asset.model.AssetEntry"%>
<%@page import="java.util.List"%>
<%@page
        import="com.liferay.portlet.asset.service.persistence.AssetEntryQuery"%>
<%@ taglib uri="http://java.sun.com/portlet" prefix="portlet"%>

<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>

<portlet:defineObjects />
<liferay-theme:defineObjects />

<style type="text/css">
                
                #trends {
                width: 200px;
                
                }
                
                #trends ul {
                margin: 0;
                padding: 0;
                list-style: none;
                }
                
                #trends div div ul li {
                font-family: "Arial Black", Arial, Helvetica, sans-serif;
                font-size: 1.0em;
                }
</style>
<%
        // get preferences for tunables
        PortletPreferences preferences = renderRequest.getPreferences();

        String portletResource = ParamUtil.getString(request, "portletResource");

        if (Validator.isNotNull(portletResource)) {
                preferences = PortletPreferencesFactoryUtil.getPortletSetup(request, portletResource);
        }
        final int MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;

        int PIVOT_DAYS = GetterUtil.getInteger(preferences.getValue(
                "pivotDays", "5"));
        int CUTOFF_DAYS = GetterUtil.getInteger(preferences.getValue(
                "cutoffDays", "20"));
        int TREND_PCT_THRESHOLD = GetterUtil.getInteger(preferences.getValue(
                "trendPctThreshold", "100"));
        int NORMALIZED_RANGE = GetterUtil.getInteger(preferences.getValue("maxSize",
                "3"));
        int NEW_THRESHOLD = GetterUtil.getInteger(preferences.getValue(
                "newThreshold", "3"));

        // query for all assets
        AssetEntryQuery q = new AssetEntryQuery();
        long[] ids = new long[] { scopeGroupId };
        q.setGroupIds(ids);
        q.setVisible(true);
        List<AssetEntry> entries = AssetEntryLocalServiceUtil.getEntries(q);

        final Set<String> tagSet = new HashSet<String>();
        final Map<String, Integer> beforeMentions = new HashMap<String, Integer>();
        final Map<String, Integer> afterMentions = new HashMap<String, Integer>();
        
        final Map<String, Integer> trends = new HashMap<String, Integer>();
        final Map<String, Integer> trendNormal = new HashMap<String, Integer>();

        Date now = new Date();
        Date pivot = new Date(now.getTime() - (PIVOT_DAYS * MILLISECONDS_PER_DAY));
        Date cutoff = new Date(now.getTime() - (CUTOFF_DAYS * MILLISECONDS_PER_DAY));

        //iterate over assets, storing mentions before and after the pivot in separate maps
        for (AssetEntry e : entries) {
                for (AssetTag tagA : e.getTags()) {

                        Date updated = e.getModifiedDate();
                        long tagAge = now.getTime() - updated.getTime();

                        if (updated.before(cutoff) || tagAge < 0) {
                                // mention occurred before the cutoff or in the future, so ignore
                                continue;
                        } 
                        
                        if (updated.before(pivot)) {
                                beforeMentions.put(tagA.getName(), beforeMentions.get(tagA.getName()) != null ? beforeMentions.get(tagA.getName()) + 1 : 1);
                        } else {
                                afterMentions.put(tagA.getName(), afterMentions.get(tagA.getName()) != null ? afterMentions.get(tagA.getName()) + 1 : 1);                               
                        }
                        tagSet.add(tagA.getName());
                }
        }
        
        Integer maxValue = 0;
        for (String tag : tagSet) {
                if (afterMentions.get(tag) == null) {
                        // tag never mentioned after pivot, so tag can't be trending
                        continue;
                }
                if (beforeMentions.get(tag) == null) {
                        // tag never mentioned before pivot, so only trending if breaks NEW_THRESHOLD
                        if (afterMentions.get(tag) > NEW_THRESHOLD) {
                                Integer newTrendVal = 100 * afterMentions.get(tag);
                                trends.put(tag, newTrendVal);
                                if (newTrendVal > maxValue) {
                                        maxValue = newTrendVal;
                                }
                        }
                } else {
                        double avgBeforeMentions = (double)beforeMentions.get(tag) / (double) (CUTOFF_DAYS - PIVOT_DAYS);
                        double avgAfterMentions = (double)afterMentions.get(tag) / (double)PIVOT_DAYS;
                        int pctImprovement = (int)((avgAfterMentions - avgBeforeMentions) * 100.0 / avgBeforeMentions);
                        if (pctImprovement > TREND_PCT_THRESHOLD) {
                                // tag is trending!
                                trends.put(tag, pctImprovement);
                                if (pctImprovement > maxValue) maxValue = pctImprovement;
                        }
                }
        }
        
        // normalize over specified range
        for (String tag : trends.keySet()) {
                trendNormal.put(tag, (int)Math.round( ((double)trends.get(tag) / (double)maxValue) * (double)NORMALIZED_RANGE));
        }
        
%>

<div id="trends">
<ul>

        <%
                // create <li> entries for each trending topic, with appropriate liferay link
                for (Map.Entry<String, Integer> trendEntry : trendNormal.entrySet()) {
                        String trendEntryKey = trendEntry.getKey();
                        Integer trendEntryVal = trendEntry.getValue();
        %>
        <li><font size="+<%=trendEntryVal%>"><a href="<portlet:renderURL><portlet:param name="tag" value="<%=trendEntryKey%>"/></portlet:renderURL>"><%=trendEntryKey%></a></font></li>
        <%
                }
        %>
</ul>
</div>

<script  src="<%= PortalUtil.getStaticResourceURL(request, request.getContextPath() + "/js/jquery-1.4.1.js", new Date().getTime()) %>" type="text/javascript"></script>
<script src="<%= PortalUtil.getStaticResourceURL(request, request.getContextPath() + "/js/turboTicker.JQuery.js", new Date().getTime()) %>" type="text/javascript"></script>
  
<script type="text/javascript">

        $("#trends").ticker(50, true, true);
        
</script>

configuration.jsp

<%@page import="com.liferay.portal.kernel.util.Constants"%>
<%@page import="com.liferay.portal.kernel.util.GetterUtil"%>
<%@page import="com.liferay.portlet.PortletPreferencesFactoryUtil"%>
<%@page import="com.liferay.portal.kernel.util.Validator"%>
<%@page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%@page import="javax.portlet.PortletPreferences"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet"%>
<%@ taglib uri="http://liferay.com/tld/security"
        prefix="liferay-security"%>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util"%>

<%@ page contentType="text/html; charset=UTF-8"%>

<portlet:defineObjects />
<liferay-theme:defineObjects />

<%
        PortletPreferences preferences = renderRequest.getPreferences();

        String portletResource = ParamUtil.getString(request,
                        "portletResource");

        if (Validator.isNotNull(portletResource)) {
                preferences = PortletPreferencesFactoryUtil.getPortletSetup(
                                request, portletResource);
        }

        int pivotDays = GetterUtil.getInteger(preferences.getValue(
                        "pivotDays", "5"));
        int cutoffDays = GetterUtil.getInteger(preferences.getValue(
                        "cutoffDays", "20"));
        int trendPctThreshold = GetterUtil.getInteger(preferences.getValue(
                        "trendPctThreshold", "100"));
        int maxSize = GetterUtil.getInteger(preferences.getValue("maxSize",
                        "3"));
        int newThreshold = GetterUtil.getInteger(preferences.getValue(
                        "newThreshold", "3"));
%>

<liferay-portlet:actionURL portletConfiguration="true"
        var="configurationURL" />

<aui:form action="<%= configurationURL %>" method="post" name="fm">
        <aui:input name="<%= Constants.CMD %>" type="hidden"
                value="<%= Constants.UPDATE %>" />
        <aui:fieldset>

                <aui:select label="Pivot Days" name="preferences--pivotDays--">
                        <aui:option label="1" selected="<%= pivotDays == 1 %>" />
                        <aui:option label="2" selected="<%= pivotDays == 2 %>" />
                        <aui:option label="3" selected="<%= pivotDays == 3 %>" />
                        <aui:option label="4" selected="<%= pivotDays == 4 %>" />
                        <aui:option label="5" selected="<%= pivotDays == 5 %>" />
                </aui:select>
                <aui:select label="Cutoff Days" name="preferences--cutoffDays--">
                        <aui:option label="10" selected="<%= cutoffDays == 10 %>" />
                        <aui:option label="20" selected="<%= cutoffDays == 20 %>" />
                        <aui:option label="30" selected="<%= cutoffDays == 30 %>" />
                        <aui:option label="40" selected="<%= cutoffDays == 40 %>" />
                        <aui:option label="50" selected="<%= cutoffDays == 50 %>" />
                        <aui:option label="60" selected="<%= cutoffDays == 60 %>" />
                        <aui:option label="70" selected="<%= cutoffDays == 70 %>" />
                        <aui:option label="80" selected="<%= cutoffDays == 80 %>" />
                        <aui:option label="90" selected="<%= cutoffDays == 90 %>" />
                        <aui:option label="100" selected="<%= cutoffDays == 100 %>" />
                </aui:select>
                <aui:select label="Improvement Threshold (%)"
                        name="preferences--trendPctThreshold--">
                        <aui:option label="10" selected="<%= trendPctThreshold == 10 %>" />
                        <aui:option label="20" selected="<%= trendPctThreshold == 20 %>" />
                        <aui:option label="30" selected="<%= trendPctThreshold == 30 %>" />
                        <aui:option label="40" selected="<%= trendPctThreshold == 40 %>" />
                        <aui:option label="50" selected="<%= trendPctThreshold == 50 %>" />
                        <aui:option label="60" selected="<%= trendPctThreshold == 60 %>" />
                        <aui:option label="70" selected="<%= trendPctThreshold == 70 %>" />
                        <aui:option label="80" selected="<%= trendPctThreshold == 80 %>" />
                        <aui:option label="90" selected="<%= trendPctThreshold == 90 %>" />
                        <aui:option label="100" selected="<%= trendPctThreshold == 100 %>" />
                        <aui:option label="110" selected="<%= trendPctThreshold == 110 %>" />
                        <aui:option label="150" selected="<%= trendPctThreshold == 150 %>" />
                        <aui:option label="200" selected="<%= trendPctThreshold == 200 %>" />
                        <aui:option label="500" selected="<%= trendPctThreshold == 500 %>" />
                </aui:select>
                <aui:select label="Max Size" name="preferences--maxSize--">
                        <aui:option label="1" selected="<%= maxSize == 1 %>" />
                        <aui:option label="2" selected="<%= maxSize == 2 %>" />
                        <aui:option label="3" selected="<%= maxSize == 3 %>" />
                        <aui:option label="4" selected="<%= maxSize == 4 %>" />
                        <aui:option label="5" selected="<%= maxSize == 5 %>" />
                        <aui:option label="6" selected="<%= maxSize == 6 %>" />
                        <aui:option label="7" selected="<%= maxSize == 7 %>" />
                </aui:select>
                <aui:select label="New Threshold" name="preferences--newThreshold--">
                        <aui:option label="1" selected="<%= newThreshold == 1 %>" />
                        <aui:option label="2" selected="<%= newThreshold == 2 %>" />
                        <aui:option label="3" selected="<%= newThreshold == 3 %>" />
                        <aui:option label="4" selected="<%= newThreshold == 4 %>" />
                        <aui:option label="5" selected="<%= newThreshold == 5 %>" />
                        <aui:option label="6" selected="<%= newThreshold == 6 %>" />
                        <aui:option label="7" selected="<%= newThreshold == 7 %>" />
                </aui:select>
        </aui:fieldset>
        <aui:button-row>
                <aui:button type="submit" />
        </aui:button-row>


</aui:form>

portlet.xml

<?xml version="1.0"?>

<portlet-app
        version="2.0"
        xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
>
        <portlet>
                <portlet-name>TrendingTopics</portlet-name>
                <display-name>TrendingTopics</display-name>
                <portlet-class>com.liferay.util.bridges.mvc.MVCPortlet</portlet-class>
                <init-param>
                        <name>view-jsp</name>
                        <value>/view.jsp</value>
                </init-param>
                <expiration-cache>0</expiration-cache>
                <supports>
                        <mime-type>text/html</mime-type>
                </supports>
                <portlet-info>
                        <title>TrendingTopics</title>
                        <short-title>TrendingTopics</short-title>
                        <keywords>TrendingTopics</keywords>
                </portlet-info>
                <security-role-ref>
                        <role-name>administrator</role-name>
                </security-role-ref>
                <security-role-ref>
                        <role-name>guest</role-name>
                </security-role-ref>
                <security-role-ref>
                        <role-name>power-user</role-name>
                </security-role-ref>
                <security-role-ref>
                        <role-name>user</role-name>
                </security-role-ref>
                <supported-public-render-parameter>                         tag                 </supported-public-render-parameter>         </portlet>
        <public-render-parameter>                 <identifier>tag</identifier>                 <qname xmlns:x="http://www.liferay.com/public-render-parameters">x:tag</qname>         </public-render-parameter>
</portlet-app>

liferay-portlet.xml

<?xml version="1.0"?>
<!DOCTYPE liferay-portlet-app PUBLIC "-//Liferay//DTD Portlet Application 6.0.0//EN" "http://www.liferay.com/dtd/liferay-portlet-app_6_0_0.dtd">

<liferay-portlet-app>
        <portlet>
                <portlet-name>TrendingTopics</portlet-name>
                <icon>/icon.png</icon>
        <configuration-action-class>com.liferay.portal.kernel.portlet.DefaultConfigurationAction</configuration-action-class>         <instanceable>false</instanceable>
                <header-portlet-css>/css/main.css</header-portlet-css>
                <footer-portlet-javascript>/js/main.js</footer-portlet-javascript>
                <css-class-wrapper>TrendingTopics-portlet</css-class-wrapper>
        </portlet>
        <role-mapper>
                <role-name>administrator</role-name>
                <role-link>Administrator</role-link>
        </role-mapper>
        <role-mapper>
                <role-name>guest</role-name>
                <role-link>Guest</role-link>
        </role-mapper>
        <role-mapper>
                <role-name>power-user</role-name>
                <role-link>Power User</role-link>
        </role-mapper>
        <role-mapper>
                <role-name>user</role-name>
                <role-link>User</role-link>
        </role-mapper>
</liferay-portlet-app>

 

 

 

 

 

Once you have that all in place (including the turboTicker.JQuery.js and jquery-1.4.1.js files, which you can download from here), the project structure should be identical to the previous one (see image to the right, I think I have a couple of superfluous js files in there as well, which you can ignore).

 

 

 

 

Deploying and understanding Trending Topics

Once you have deployed the application, you can add it to any page in a community.  The topics display are based on mentions.  To mention a particular topic, just create any user-generated content that participates in Liferay's Asset Framework and can be tagged.  This includes a blog post, a wiki page, an image, a forum post, etc.  Any time content is created with a tag (not a category, though that can also be visualized with a slight change to the code), that constitutes a mention.

The tunables are:

  • Pivot Days: The number of days prior to today to calculate an average mention count (see above image).
  • Cutoff Days: The number of days prior today to ignore mentions of tags.
  • Trend Percent Threshold: The percentage improvement in average mentions between the two time periods, above which a tag is considered trending.
  • Max Size: After an improvement percent is measured for each trending topic, the numbers are normalized into a range of [0, NORMALIZED_RANGE] for display.  The number here is the biggest font size that should be used for the best trending topic.  For example, if you put 7 here, then, the topic with the highest trend improvement will be rendered with <font size="+7"></font>.  I know you HTML purists are thinking that this should be done with CSS and that <font> tags are the spawn of the devil, but I slept through that class in college, and this is about community participation, not web coding :-)
  • New Threshold: For tags that have never been mentioned before, their "improvement" percentage would be infinity, if calculated as above, since their average in the earlier time period was 0.  So for newly-mentioned tags, this number allows you to specify how many 'new' mentions it takes for a tag to be considered trending.  A value of 5, for example, means that for a new tag never mentioned before, it would take 5 mentions to get it onto the trending topics list.

Once deployed, your list should look like this:

and it should be slowly scrolling.  Clicking on one of the tags should cause any other Liferay portlets on that page (e.g. blog, wiki, asset publisher, etc) to only show content with the tag you clicked on.  Enjoy! 

Community Roundup

Staff Blogs 2011/04/13 投稿者 James Falkner Staff

Miss me yet?   It's been a busy several weeks here at Liferay so I've been a little absent (not disconnected, just heads down).  We had a very successful showing at the Gartner Portals, Content, and Collaboration Summit in late March.  And we're gearing up for Liferay's annual East Coast Symposium near Washington, D.C. next month (well, really in about 3 weeks!).  I have a whole pocketful of excuses, but for now, onto the links!

  • Liferay's annual East Coast Symposium (ECS) in Washington, D.C. starts May 10th.  Along with key Liferay business and technical leaders, for the first time we'll also have community speakers!  Check out the speakers page, and the agenda for more detail.
  • The 100 PaperCuts program continues to make great strides in resolving paper cuts.  Sprint 4 began this week.   In sprints 1-3 we have fixed almost 30 papercuts.  If you're interested in joining the team, leave comments below.  It's a great motivator for digging into the Liferay project!
  • There's a new community poll on liferay.org - what do you consider key Liferay features?  Not necessarily features that are important to you, but key to Liferay's success.
  • The Liferay Community Leadership team had its kickoff meeting last week.  You can follow the team on its forum category. I created an outline and took some notes from the meeting.  The plan is to have quarterly meetings to keep the community informed of the latest developments.  We even got some press coverage!  Christopher of The VAR Guy wrote up some positive comments on this.  If you're interested in joining the team, leave comments below.
  • Check out the Liferay Hungary team on their new Facebook page!  I wonder if this will become a pattern for other Liferay offices..
  • Rich's Liferay In Action book now has all its chapters.  Great work Rich!  And a great read. 

  • eWeek Labs continues to attract reviews on Liferay Portal, with an average score of 8.4!  Many users and industry veterans are commenting on its collaboration, social, and content management features.  If you have some time today, why not add your review?
  • CMSWire recently wrote up a piece on enterprise open source, and mentions Liferay.  It's a good read and we'd like to think it contributes to the phenomenal growth Liferay is experiencing!  

Finally, look for a new look for the liferay.com website in the coming weeks.  We are in the final stages of upgrading the site to use Liferay 6 and refresh the style.  More importantly, from the community perspective, we'll enable Social Equity and many other features from Liferay 6 you've come to know and love.  Can't wait!

 

Community Roundup

Staff Blogs 2011/03/07 投稿者 James Falkner Staff

Hi Gang, welcome back to another edition of Community Roundup.  We've had another exciting several weeks of activity in the community, culminating with the release of Liferay Portal 6 CE GA4 (also known as 6.0.6) [Download] [Release Notes].  This serves as a nice interim release between 6.0 and 6.1, but more importantly, the changes that went into GA4 were dominated by changes that YOU said you wanted to see, so kudos to you for making your voice heard!  Now, onto the links.

  • Liferay's annual East Coast Symposium (ECS) in Washington, D.C. in May.  The Call For Papers is still open through March 10 (3 more days), so keep 'em coming!
  • Sprint 2 of the 100 PaperCuts program is wrapping up this week, with 5 issues remaining.  If you're interested in joining the team, leave comments below.  It's a great motivator for digging into the Liferay project!
  • The Liferay Community Leadership program kicked off last week.  You can follow the team on its forum category. As part of Liferay's re-commitment to open source and its community in 2011, we've started this program to identify existing community leaders, foster new leaders, and give voice to the community's growing impact on Liferay's open source project and product. We'll begin face-to-face (virtual) meetings in April.  If you're interested in joining up, leave comments below!
  • Jorge posted a new revision of the Liferay Development Guide [HTML] [PDF], after a thorough community and internal review.  New content includes Understanding Portlet Phases, extended detail on EXT plugins and Liferay's JSR-286 support, Asset and other Framework coverage, Liferay IDE, and extra reference pointers.
  • Liferay will be submitting itself as a potential Mentoring Organization for the 2011 Google Summer of Code program.   The list of potential projects can be found on the wiki.  If you are a university student interested in participating, registration opens March 28.  This is a great way to get solid development and mentoring experience, and 5000 USD for each completed project over the 12-week project period.
  • eWeek Labs continues to attract reviews on Liferay Portal, with an average score of 8.4!  Many users and industry veterans are commenting on its collaboration, social, and content management features.  If you have some time today, why not add your review?
  • CMSWire recently wrote up a piece on enterprise open source, and mentions Liferay.  It's a good read and we'd like to think it contributes to the phenomenal growth Liferay is experiencing! 
  • Speaking of open source, Srimana Mitra sat down with Bryan Cheung, Liferay CEO, and asked him a range of questions regarding Liferay, from its open source roots, to product positioning, and Liferay's unique culture.  It's a fantasic read on the history of the company.
  • Interested in training on Liferay?  Throughout the months of March and April 2011, get $400 off any US-based training course just for visiting our Facebook Fans page!
  • en Español: Liberado Portlet Lists para Liferay.  A simple portlet for managing lists of items in Liferay.
  • Liferay IDE 1.2 was released last month.  Did you also know it now has native Vaadin support?
  • EmDev Limited announces new version of the Activiti Liferay plugin. This is a workflow plugin that uses BPMN and has a graphical workflow editor in Liferay.
  • Movistar has selected Liferay's portal technology to revamp its mobile web shop. Nice to see more mobile offerings based on Liferay!
  • If you missed the last few Liferay LIVE offerings, replays are available: Liferay and Maven, Liferay and iPad/iPhone with Roambi, OpenSocial Development.  And, we heard you and are planning upcoming events for Developer Tools, Workflow, Sun Migration, and Upgrading to Liferay 6.  Register and attend for free!
  • JumpBox recently upgraded their Liferay-based VM to 6.0.5.  JumpBox is a Ready to run Virtual Machine/Virtual Appliance for VMware, Parallels, VirtualBox and Amazon EC2.  Easy way to get up and running with Liferay!
  • The Liferay Greek Community is sporting a new logo.  If you are an organized community group and are interested in having your own, leave comments below!
  • Recent Wiki updates: WSRP, Resin 4, Logical Architecture, FAQ, Eclipse, Chat, Weblogic.
  • Recent blogs: Eclipse Helios and Liferay IDE, Mounting Multiple CMIS Repositories, People who use Liferay: UAE Government and Careers at Total, Secure RSS Feeds, AlloyUI and YUI3 Console.

That's it for now.  See you on the forums!

Liferay Issue Tracker (JIRA) Upgrade

Staff Blogs 2011/03/02 投稿者 James Falkner Staff

Recently Liferay completely an upgrade of the software that powers issues.liferay.com.  We upgrade to JIRA 4.2.4, which is around 5 years newer than the one before!  As you can imagine, there have been a lot of improvements and new features, so I wanted to illustrate a few that I use and that you may find useful.  A full list of features is listed on the Atlassian site (some of which are only available to the site administrators), but the following should be accessible to all.

Labels

I've wanted these for a long time!  Labels are arbitrary tags associated with issues, which serve as handy filters and just-in-time categorization.  These are normally added when you are creating an issue, but for issues you own (or if you are an admin or Liferay staff), you can update these as needed.  While we don't have a taxonomy for these labels, I will be using it for the 100 PaperCuts program, and we may in the future use this as an additional way to categorize issues.

Autocomplete

Many fields in JIRA will now be auto-completed as you type.  For example, selecting Components for a new issue. This also works with Labels, Affected Versions, and others.  Whee!

Dashboards and OpenSocial Gadgets

A very cool new feature, this allows you to construct personal dashboards very similar to Liferay pages, dragging and dropping OpenSocial gadgets onto the page and arranging them as desired.  JIRA comes with a bunch of pre-defined gadgets, but you can also develop your own (and if its generally useful, let us know and we'll check it out and possibly deploy it on the main server!). You can generate lots of interesting graphs and quick links, as shown below:

The data/visualization graphs work against either a full project (like LPS), or against a pre-configured filter you or someone else defined.

Activity Streams

You can now construct customized activity streams, and subscribe to them via RSS.  Handy to keep abreast of the latest goings-on at issues.liferay.com.  Activity Streams are also shown on each project's default landing page:

Internationalization

JIRA supports many languages, so you can manage issues in whatever local language you desire.

Rich Text Comments

Now, your comments can come to life using wiki-like markup to make code snippets standout, quote text, or highlight various phrases.

Image Gallery

For issues that have attached images (screenshots, etc), a handy browser is included so you don't have to visit each image individually, or download them.

It even works for introspecting into .zip file attachments:

Remote RPC

Our JIRA instance can also be queried and interacted with through remote services (SOAP and XML-RPC).  While this was possible in the past, there were numerous bugs that prevented its use.  Seems to have been fixed!  The WSDL URL is http://issues.liferay.com/rpc/soap/jirasoapservice-v2?wsdl .

Greenhopper Plugin

Greenhopper is a plugin for JIRA that supports Agile Program Management practices.  We have been using this for a while but now it is open to the wider community.  It can be accessed via the "Agile" dropdown.

That's all for now. I'm sure there are many other new features just waiting to be discovered.  Have fun!

100 PaperCuts: Sprint 1 Review

Staff Blogs 2011/02/17 投稿者 James Falkner Staff

Yesterday we wrapped up the first sprint of the Liferay Community's 100 Paper Cuts program, and at at 80% solution rate, I'm calling it a success!  A big round of applause goes to the volunteers of this program: Szymon, Rafal, Juan, Milan, Deb, Boubker, Corne, Tomas, Maarten, and our newest member, Hitesh!

Of the 10 papercuts identified, 8 have been resolved.  In the next sprint, we'll take 10 more and do it again.  We learned several things that we will use to improve the next round:

  • I initially identified the 10 papercuts and randomly assigned them to people.  This does not work as everyone has a different skill set, and so there was some shuffling around of issues at the beginning.  In the next sprint, we'll identify a set of issues (a backlog) and individuals can pick and choose which ones to tackle.
  • One of the issues (LPS-12988) turned out not to be a papercut at all, requiring some in-depth investigation and non-trivial changes.  We'll try to avoid that next time.
  • A couple of the issues were fixed by the team, but the original submitter was no longer around to Accept Contribution.  This is a hole in our JIRA workflow (to be sorted after the JIRA upgrade).  Rest assured, the issue is assumed to be in the Community Resolved state now and can be handled by Liferay Staff.
  • A couple of issues turned out to be already fixed.  Not a huge deal, and clearing/closing them is an added benefit of this program.

Sprint 2 will begin next week, and between now and then we'll be identifying papercuts to fix.  If you want to participate, leave a comment below.  This program is a great way to immerse yourself in the code, learn a lot about enterprise-class web development, and give a little back to the community, all at the same time.  Requires a couple of hours of work every 2 weeks, so if you've got the motivation and like a (relatively easy) challenge, join up!

Community Roundup

Staff Blogs 2011/02/11 投稿者 James Falkner Staff

Time for another installment of Community Roundup!  I enjoy taking a tour of the Liferay Community in these blog posts, I hope you find them useful.  Lots of items of interest in the last 2 weeks, so let's get started.

That is all for now.  Looking forward to seeing many of you at ECS!

Liferay East Coast Symposium 2011: Make Your Connection

Staff Blogs 2011/02/09 投稿者 James Falkner Staff

Liferay East Coast Symposium 2011

Liferay's third annual East Coast Symposium (ECS) will be held in beautiful Leesburg, Virginia on May 10-11, 2011.

ECS 2011 will be a great opportunity for learning, knowledge sharing and networking with blah, blah, and more blah.  Let's talk about how this impacts the community and why you should attend.

Why Should I Attend?
As a community member, you are probably wondering "Is this for me?  Why should I spend the time and money to attend a Liferay Symposium?"  Well, you know all those forum posts, and JIRA issues, and community contributions, and innovative solutions and technologies that swirl around in the Liferay Community?  Those are all backed by real people, just like you, with a passion for excellence and desire to achieve something useful.  What's even more exciting is that everyone has different interests, skills, backgrounds, and roots in various cultures.

Throw all of those people together, entwined with a common thread (Liferay), and amazing things can happen.  Individuals are guaranteed to learn something new, or create the Big New Idea, or even just get to know their fellow community members in way that's not possible over a virtual link.  Companies in attendance will (re-)discover the benefits of using open source for their mission critical apps, and see firsthand how Liferay and its community blend together to make a great product.  When you go back to your desks after the symposium, you will have a newfound respect for the individuals you interact with, along with the knowledge gained by being in a high-bandwidth environment (i.e. face-to-face) for a couple of days.

For those that want to give a little back, this year, for the first time, we are asking the wider community to present their ideas, give their talks, and host their workshops.  This is your chance to showcase your World of Liferay to a dedicated, captivated audience (though we cannot lock the doors for fire safety ). Visit the call for papers site to learn more about this unique opportunity.

Finally, if ECS is too far to travel, or your schedule doesn't permit your attendance, keep in mind that we have many other events throughout the year where you can get together with fellow Liferay'ers.  Check out the events page for more details.  And if you are brave enough, why not host your own Liferay event?  We can help with coordination, and you can get lots of interested people in the same room (your room) to showcase your technology.

Thanks for reading, and hope to see you in May at ECS!

Calling All Filmmakers! Win an Amazon Kindle!

Staff Blogs 2011/02/03 投稿者 James Falkner Staff

Make a great video and win an Amazon Kindle! Liferay is calling on YOU, the Liferay Community, to showcase your world with Liferay. Participate in Liferay's first ever community video contest, make a video, upload it anywhere, and tell us about it!

We're looking for compelling stories about how Liferay positively affects you, your company or organization, or your users and customers. Be as creative as you want in telling your story. Some examples:

  • Music video
  • Combination of screencast and live video
  • Animation
  • Series of Stills with captions or voice narration
  • Documentary Style - interviews with people, formal or informal
  • Creative intros/outros for your video
  • Any other format you can create!

Any format is acceptable, just make it fun, creative, and compelling! It's simple to enter!  Here are the basic requirements:

  • Length: 5 minutes or less
  • Audio: Spoken English, with or without music or other effects
  • Keep it clean! (no profanity, etc)
  • Uploaded to one of many video sharing sites (YouTube, Vimeo, blip.tv, etc)
  • Share the URL on this forum thread or email it to us.
  • Submit no later than Monday, February 28th at 11:59pm US/Pacific.
  • Optional, but highly recommended: Put identifying information at the beginning or end of your video, so we know who you are!
  • Winner wins an Amazon Kindle or a Flip Video Camcorder (your choice)!  2nd place gets a Flip Video Camcorder! Woohoo!!

Detailed contest information can be found on the forum post.  Have fun!

Community Roundup

Staff Blogs 2011/01/25 投稿者 James Falkner Staff

Howdy folks, time for another community roundup.. It's been an action-packed couple of weeks at Liferay.  The first month of 2011 is almost over, and we're chugging along nicely.  Lots of items to mention, plus some upcoming events and community programs to discuss.

That is all for now.  As my friend Mahipal likes to say, "Enjoy Your Day!"

 

 

Community Roundup

Staff Blogs 2011/01/13 投稿者 James Falkner Staff

Hi all, welcome to another installment of Community Roundup!  The last one was 2 weeks ago (been busy with a few other initiatives, see below), so I've got a lot to share.  Once the community landing page is complete (it's pretty much done now, just doing some final tweaks to make it magical), you can get even more up-to-date community news there, but I'll continue to post a digest for your reading pleasure.

  • JIRA is a nice issue tracker, but it can be confusing to newcomers (even outside of Liferay).  I updated the JIRA wiki page to document all those oddball fields and give some usage tips for Liferay'ers.
  • Earlier I announced two new community initiatives: 100 PaperCuts and Liferay Community Leadership Program.  Even if you don't want to participate in the programs, we still need your help.  Vote for your favorite issues at issues.liferay.com and make get your bucket list shorter!  Here's the current ticket list sorted by votes.  The timer runs out Wednesday, March 2nd for the first community sprint.
  • Robert signed up for the Translation Team to work on Romanian translations.  Very cool!
  • For our Spanish speakers, José is doing a webinar on Integrating JSF and Liferay next Wednesday. "Os prometo que una vez veáis las ventajas y la sinergia entre ICEfaces y Liferay, empezaréis a usar esta combinación."
  • Kefa gives a nice step-by-step Linux install guide, including MySQL configuration and demo content removal.
  • Capella University recently chose Liferay Portal for its new university experience.  Check it out.  Along the same lines, Osaka Gas is also using Liferay.
  • If you missed Ray's Liferay Live: Staging session yesterday, don't worry, all of the previous session recordings are being prepped to be posted for playback.  Will let you know as soon as they are posted.
  • Speaking of Liferay Live, I'm hosting a session next Wednesday (Janary 19) at 10am PST on our wonderful community and its roadmap.  Register for free, and hope to see you there!
  • Ray also discusses the current buzz around NoSQL, and demonstrates Liferay on MongoDB.  "There seems to be an overwhelming desire to find scallability solutions that don't seem to be addressed with an RDBMS. What is Liferay to do?"
  • Newsletters Portlet for LiferayHiding homepages. More great content from eo Networks!
  • Is there any difference between taglib-used JSPs and the normal ones? Find out here. (hint: it's a performance thing)
  • Using Orbeon and Form Runner on Liferay? Want to deploy and manage them them separately?  Now you can.
  • Recent Wiki page updates: Performance, Javadoc Guidelines, JAAS, OpenLDAP+JAAS, JIRA, Flash, CAS on Liferay 6

Whew, that is all for now.  Don't forget to vote for your "favorite" issues!

Liferay Community Programs

Staff Blogs 2011/01/11 投稿者 James Falkner Staff

Thanks to all of you, Liferay has an active and growing community that has made a lot of contributions in 2010.  However, with that growth comes inevitable growing pains!

One of the areas in which we have experienced continued pain is in the reporting, analysis, and resolution of tickets via Liferay's issue tracking system (based on Atlassian's JIRA).  The problem, as I've mentioned in the past, is that due to the sheer volume of tickets, sometimes tickets get lost in the noise.  In many cases, tickets that cause annoying bugs, yet are simple one-liner fixes, don't get resolved for months.  There are also existing tickets that have been in various stages of workflow, tickets that have a high community impact but have not been touched for months, or have been resolved as part of another fix, but are still open.  This should not happen!

With this in mind, I am announcing two new community initiatives that start today, focusing on community contributions to Liferay, and how you can help.  With these programs, it is my hope that the community will benefit in multiple ways:

  • Tickets are evaluated and resolved quicker
  • Users get important fixes faster, and with higher quality
  • Community Members that wish to "go above and beyond" can do so
  • Existing tools are used, so no real learning curve

1. One Hundred Paper Cuts


Szymon recently suggested that we should start our own program modelled after Ubuntu's "One Hundred Paper Cuts" program.  It's a simple, yet effective way for the community to reduce the backlog of "annoying" yet easy to fix bugs.  It will work like this:

  • Starting today we will have a 3-week time period in which to vote for bugs you feel should be fixed sooner rather than later.  Voting for Liferay bugs via JIRA helps the community prioritize tickets.  This is one of the easiest ways you can contribute to the betterment of the Liferay project.  Vote counts are also used when determining what goes into new releases.  For example, for the next Community Edition, those bugs with higher votes will be included.  Every community member gets 1 vote per issue.  To vote, you must be logged into issues.liferay.com.  Don't have an account?  Register for freeHere's a search showing the top community-voted issues as of now.
  • After the 3 week voting period, the top 10 "annoying yet easy to fix" tickets will be chosen, and assigned to community members who are participating in the project, in the form of a 2-week "community sprint" to get these issues resolved (into the "Community Resolved" state).
  • After those 2 weeks are up, repeat the process.  The time taken to vote and/or fix issues will be tuned based on performance on the first sprint.

If you want to participate, leave your desires in the comments section below. 

Aside from being recognized as a valued and active community member participating in the program, there will be prizes as well :)

 

2. Community Leadership Program

 

You've been working with Liferay for a couple of years now, and have contributed many fixes and are active on the forums, and have a genuine desire to see the Liferay community grow and prosper.  You may even have ideas on how to fix certain parts of the community.  What better way to recognize your achievements than being part of the Community Leadership Program!  This new program promotes and rewards key community individuals who have  demonstrated such qualities, and gives them additional privileges (and also responsibilities) for helping guide and manage the community.

One of the first actions for members of this program will be in JIRA, as a Community Verifier. Historically, community members have had limited permissions in our issue tracking system.  Members can create and comment on issues, and submitters may mark their bugs as "Contributed Solution" or "Community Resolved".  Sometimes issues go to this state, and rot over time.  Many of these issues have been resolved (esp. in the Liferay 6 release), but haven't been updated.  Some issues are still real issues, or are only partially resolved.  Some issues simply aren't issues at all!  We need leaders who can help us work through this backlog, and are willing to learn and teach the wider community how to properly mark issues, including which releases it is applicable to, which component it affects, or declaring a bug is not a bug so that it can be closed or otherwise resolved.

Over the coming weeks and months, additional roles and responsibilities will be defined as part of this program. With your help and mentorship, we can improve the quality of not only the project, but of the community as well. If you are interested in participating in this program, leave a comment below!

Community Roundup

Staff Blogs 2010/12/30 投稿者 James Falkner Staff

Greetings from the next-to-last day of 2010.  Hope everyone has a safe and happy holiday season!  Liferay is working hard to bring you another great year of technology, innovation, and fun.  I for one am looking forward to the huge number of community events that we have planned (and some that aren't yet planned).  I hope you can make it to one of them (either virtually or in person!).  Now, onto the links!

  • Clustering Liferay has been made easier than ever in Liferay 6.  Sachin shows us how easy it is with GlassFish v2.  Nice.
  • Gaurang's Liferay-centered blog has lots of useful bits of knowledge.  Thanks for sharing!  Now, if only it was hosted on Liferay
  • Rich posted another chapter in his excellent Liferay in Action book.  This chapter includes great stuff like Liferay's asset system, workflow, tags and categories, ratings and commenting, and Service Builder tips.
  • Want to speed up Liferay with simple tweaks?  Check out Sheikh Mohammad Sajid's tips on speeding up page loading.
  • If you're like me, you love to see products come to market built specifically on or around Liferay.  Recently MyOffice24x7 released a very useful Survey Application to the community.  It's these kinds of community activities and products that will really benefit from the coming Liferay Marketplace.
  • Speaking of community, did you happen to catch the thread around linking web content to different pages?  Jorge, Szymon, Grzegorz, Ahmed, Mauro, Orin, Jonas and Ray demonstrated what the community is all about.  Nice to see such engaging activity!
  • The 2011 Liferay Symposium series dates are almost finalized.  The first one out of the gate will be the East Coast Symposium, in Philadelphia, in May.  This year, we are opening up the sessions to the entire community!  Watch for a Call For Papers in early January, and be thinking about topics you'd like to present.
  • Liferay Live is set to start back up January 12 with a session on Staging.  Following that are: Liferay Community, OpenSocial, and others. Don't miss these!  Free to attend!
  • If you plan on hosting your own Liferay event, please let everyone know by registering on the Community Events page.  The more awareness the better!
  • Work on the community landing page is almost done!  The goal is to make this page appear in every community member's bookmark list, and on your daily speed-dial pages to visit (or read via a Feed).  Features include community announcements, links to popular JIRA tickets, community polls, dynamic activity maps, community buzz feeds, release documentation, concentrated developer docs, and other goodies I hope you'll find useful on a daily basis.

That's all for now, see you in 2011!

Community Roundup: Holiday Edition

Staff Blogs 2010/12/21 投稿者 James Falkner Staff

Happy Holidays from Liferay and our community!  Hope everyone has a safe and fun-filled holiday season.  Liferay had an exciting and fun-filled 2010.  Partner program expansion, new Liferay offices throughout the world, the Liferay 6 release, new documentation, developer tools, magic quadrants, symposiums, the Liferay LIVE program, community development, and tons of events added up to a great year of strengthening our community ties.  We have lots of stuff planned for next year, so here's to a successful 2010, and onward to an even better 2011.  Now, onto the links...

  • Everone knows how to do SSO for authentication, but what's more fun is integrating with Liferay's flexibile authorization and permissioning system.  Bert wrote up a nice post in his Liferay Services series on securing your Liferay service using PermissionChecker.
  • Juan has done an awesome job revitalizing the French community, reorganizing and categorizing the 500+ threads, and adding new forum categories.  He is actively seeking moderators so if you're interested, contact him.
  • We asked you to participate in the 2010 Open Source CMS Market Survey - and you responded en masse to make your voice heard.  Liferay was recognized as the leading open source Java CMS in this year's survey (here too).  Thanks to all that participated!! An even bigger thanks goes to the community developers and users that made this possible!
  • The Liferay Roadshow series visited Brazil back in October, and Sea Tecnologia took some video (in Portugese, but still fun to watch even if you don't speak the language).  Great to see our community members connecting in real life.
  • Community rank titles have been updated thanks to your input.  You are no longer a Jedi Council Member, now you are a Liferay Legend .
  • Work on the community landing page is proceeding, and we hope to have this live by end of January.  The goal is to make this page appear in every community member's bookmark list, and on your daily speed-dial pages to visit (or read via a Feed).  Features include community announcements, links to popular JIRA tickets, community polls, dynamic activity maps, community buzz feeds, release documentation, concentrated developer docs, and other goodies I hope you'll find useful on a daily basis.

Next year promises to be the year of the Liferay platform, and I hope to see continued community involvement. First off will be a series of online Liveray LIVE events (including one concentrating on our community), followed by regular community contests (with excellent prizes), and our worldwide Liferay Symposiums will be back (watch out for a call-for-papers for next year, open to the entire community).  See you out there!

Community Roundup

Staff Blogs 2010/12/10 投稿者 James Falkner Staff

Finally returned from Liferay's annual employee retreat!  It was a fun-filled week and it's great to put a face with all the names I interact with on a daily basis.  I hope to see many of our community members in the coming weeks and months as well at our various events.  If you have a chance to attend a Liferay event (Symposium, Roadshow, or any other event), I highly encourage you to do so, because our employees and community members are some of the most kind, smart, and humble folks I've ever met in my 13 years of being in this industry.  Now.. on to the links!

I don't usually post on Fridays, because half the world has checked out from their professional lives by the time I post, but I missed the last week due to the Liferay retreat, so..

Community Roundup

Staff Blogs 2010/11/30 投稿者 James Falkner Staff

Despite the US Holiday, we have lots of items in this week's roundup!

  • Finalist and Proteon hosted the fist ever Dutch Liferay Community Event on November 23 and it was a huge success. Go check out the Wrap-up and pics!
  • The Community Translation Team deserves continued credit and support. One of a great community is the transcendence across culture, language, and geography. Without the continued support of the team, Liferay would not be the leading global open source portal it is today.
  • Milan created several wiki pages for Liferay 6.x properties (like this one). While not groundbreaking, it's nice to have an indexed page with all the properties for easy reference. Thanks Milan!
  • I'm putting out another call-for-presentations for Liferay Live! Any community member can present on any topic of interest to the wider community. There are slots available on Wednesdays in February, March, and beyond. Let me know if you are interested. Upcoming events include Hudson+Liferay+Selenium (tomorrow, December 1st, 1pm US/Eastern), AlloyUI (December 8), and Staging (December 15).
  • If you're in Boston, don't miss Paul Hinz as he discusses Open Source Enterprise Portal's recent resurgence at the Gilbane Conference.
  • Gnaniyar posted an excellent and detailed overview of standard IPC mechanisms. Nice!
  • From the "I did not know that!" department:You can migrate Wordpress blogs into Liferay.
  • Nice community contribution by Boubker:Accessibility Portlets and Theme. The purpose of this project is to provide a set of plugins to enhance the accessibility experience on Liferay portal.
  • Lastly, I did a quick search on Liferay on Youtube.  I was surprised at all the relevant hits.  Even though Liferay, Inc. has its own channel, there are many other community-generated videos. Check them out!

I am heading to Liferay's annual retreat this week and part of next week, but will try and keep in touch. Can't wait to meet the many faces of Liferay and talk about our great community!

該当件数: 85 件中 61 - 80
ページごとのアイテム数 20
/ 5