I don't want to migrate from Liferay to Sharepoint

Staff Blogs 7 février 2013 Par Ray Augé Staff

Ok, so don't. This doesn't mean you can't gain advantage of Liferay.

Also, what follows is not just a story about Sharepoint. If you have any HTML site of any kind you can gain advantages from using Liferay.

Liferay has this crazy simple mechanism called "widgets". Yup widgets!

The term has been used, we know, but Liferay has seriously had this feature since at least version 5.1.0 (possibly earlier) and yet I NEVER hear anyone talking about, or taking advantage of it.

Sure you can use Liferay as a WSRP provider and/or use other thick integration technologies like Web Services. You could even build Open Social apps on Liferay as full fledged gadget server (and also a client on whatever site you want to ingrate into).

However; "widget" are dead simple! AND they require only an HTML client! Nothing else!

Q: "Where do I sign up???"

It's extrelemly simple. Let's build an example.

Imagine you have a Liferay portal running somewhere (this is of course required).

Now, suppose on that portal you have a portlet on a page, and let's say the page is:

http://host.my.tld/web/site/page

and on this page is portlet named: XYZ

It even happens to be instanceable, so the fully instanced portlet id is: XYZ_INSTANCE_1234

Wait! It also happens to be a plugin, it's not a native Liferay portlet, it's in a war called: XYZ_portlet.war (and so the context is XYZ_portlet), and so the fully full instanced, plugin portletId is: XYZ_portlet_WAR_XYZ_INSTANCE_1234

Ok, so given the page url, and the portletId above, we change the url to:

http://host.my.tld/widget/web/site/page/-/XYZ_portlet_WAR_XYZ_INSTANCE_1234

If everything is working as it should what you should see is a HTML page with ONLY the portlet in question; no headers, no footers (you can even tune the portlet's "look and feel" to show the portlet in borderless mode (Show Portlet Borders: uncheck, Save) so it integrates more smoothly with the host site).

You can even navigate around the portlet, clicking links, performing actions, and again if everything is working as it should, the portlet should remain in widget mode throughout. Even non-native (or plugin) portlets!!!

Now, back to this Sharepoint server you have, or the legacy website you can't, for whatever reason, get out of using. But you want to plug in the shinny new portlet you created (or existing one you want to use 'cause it's already designed and doing what you need/want).

On this host site, all you need is an iframe! That's it! Just an iframe which references the widget url you created earlier.

(In fact, you could probably even get out of using an iframe with some clever js which loads the content of an ajax request to the same url into some HTML container on the page. Adding a url or button event listener to handle the clicks via ajax requests can round out a pretty darn smooth integration.)

Now, you want to have that portlet behave in context of the user logged in from this other site? Sure thing!

Just plug in Liferay to whatever SSO you are using on the host site and BAM! you have integration!

I hope that helps someone out there.

Liferay OSGI and Shell Access Via Gogo Shell

Staff Blogs 4 février 2013 Par Ray Augé Staff

Liferay has been slowly integrating an OSGi framework into it's core; not in the sense that Liferay will nessecarily run "within" an OSGi runtime itself (there are arguments for doing that, and it's possible to a certain extent but the gain just isn't in doing that just yet), more that Liferay will "host" and OSGi runtime within itself that Liferay will utterly control.

Why do this? Well, as I've stated many times before, Liferay is a complex beast with an intricate network of features, so many features in fact that the ocassionaly have such indistinct lines such as finding the where one feature ends and another begins can be difficult.

OSGi will hopefully play a part in 1) providing us with a lot of re-use of features we had ourselves partially implemented but for which there existed a fully specified implementation our in the larger java eco system. The savings those existing features can bring is one of many benefits. 2) A specified lifecycle for "modules" (a.k.a. bundles) also is a huge benefit, one we can simply use. 3) Dependency resolution and management is another piece we had started to implement but it was clear to me that this had already been done much more effectively than Liferay alone could implement, again as part of an OSGi specification.

The number of benefits is almost too great to list. However, one of the greatest advantages can't be discussed enough: Modularity

Modularity is something that Liferay is now learning to embrace. Hopefully in the future we'll see many more examples like the one I'm about to illustrate.

One of the things I spend a lot of time doing is running and debugging code. There are a number of challenges to doing this with such a large application as Liferay. It takes time to get up and running, the vast number of features sometimes makes it tricky to isolate just the right service calls in order to test some specific interaction. I'm most often running the portal in debug mode executed from my IDE (Eclipse). A favourite thing to do is, place a break point and then use the context evaluation to invoke some specific code (using the Display view). However, my main purpose for doing this is to be able to fire some operation often unrelated with the actual breakpoint position. Effectively I just want to execute some arbitrary code in the JVM.

What would be ideal is if I had a command line from which I could do the same thing.

Enter Gogo

The OSGi echo system provides that out there somewhere a bunch of smart people have specified some behaviors, how to build properly interacting bits of code, and provided I have the appropriate runtime for those bits, I can interconnect them with my own bits and compose a greater feature.

Gogo is an OSGi (hence java) command shell implemented by the Apache Felix community.

"Apache Felix Gogo is a subproject of Apache Felix implementing the OSGi RFC 147, which describes a standard shell for OSGi-based environments."

Installing this shell with Liferay (due to Liferay's new interated OSGi runtime) is almost too simple.

Step 1)

First we'll download the necessary bundles:

 
Place these jars in a common, convenient location (it need not be within the Liferay install).

(Note: We could have even avoided this pre-download step, but due to a small logical bug in Liferay's integration, we'll have to download the bundles ahead of time. I'll be fixing this bug in the days to come.)

Step 2)

Enable Liferay's Module Framework

Place the following in your portal-ext.properties (or equivalent props file):

module.framework.enabled=true

Step 3)

Configure some initial bundles to start when the OSGi runtime starts in Liferay (still in  portal-ext.properties):

extra.bundles=file:/downloaded/bundles

module.framework.initial.bundles=\
    felix-fileinstall.jar@start,\
    ${extra.bundles}/org.apache.felix.gogo.command-0.12.0.jar@start,\
    ${extra.bundles}/org.apache.felix.gogo.runtime-0.10.0.jar@start,\
    ${extra.bundles}/org.apache.felix.gogo.shell-0.10.0.jar@start,\
    ${extra.bundles}/org.eclipse.equinox.console_1.0.0.v20120522-1841.jar@start
 

Step 4)

Set the telnet port on which we'll access the shell (still in  portal-ext.properties):
 
module.framework.properties.osgi.console=11311
 

Step 5)

Start Liferay
 

Step 6)

Connect to the telnet service from the command line (or from your favourite telnet client):
 
]$ telnet localhost 11311

You should see something like the following:

We have sheel access!

But what can we do with this?

The first thing I wanted to do was investigate how the shell works and how to customize the configuration.

Passing System Properties to OSGi bundles

One thing I should add is that OSGi bundles often have system properties specific to them, and in order to isolate these from potentially other osgi runtimes in the same JVM, Liferay provided a means of passing these as namespaced portal properties.

An example of this is passing a Gogo specific system property called gosh.home. And so I added that property to my portal-ext.properties (prefixed by module.framework.properties.):

e.g.

module.framework.properties.gosh.home=${module.framework.base.dir}

This places the Gogo shell's home directory within Liferay's osgi directory inside ${liferay.home}.

Gogo is then trained to look inside an /etc directory within it's gosh.home for configuration files.

In this folder I added two files.

gosh_profile (think bash_profile):

https://gist.github.com/rotty3000/4712530#file-gosh_profile

motd (a.k.a. Message of the day):

https://gist.github.com/rotty3000/4712530#file-motd

Now a restart and my shell appears like:

Awesome!

Executing commands

The shell offers many build in commands. Executing help will reveal many of the extra commands namespaced by providers. However these are executable without the namespace (the namespace is used for conflict resolution of two namespaces provide commands of the same name).

 

A few choice commands are:

ss (short bundle status)

diag (diagnose a problem bundle)

 

However, it's possible to add commands dynamically as well as use closures. This is where the real power begins.

You may note that we added a custom set of commands based on the java.lang.System class in the gosh_profile:

addcommand system (loadclass java.lang.System)

What this does is loads the class and assigns the public methods of that class under the system prefix. Executing:

system:getproperties

outputs all the system properties. In a similar fashion any class can be added as a set of shell commands.

Conclusion

Modularity via OSGi allowed me to easily add functionality not previously available in Liferay. I can now build out some commands that will enable me to quickly take actions which previously required me to find round about ways to achieve.

 

Well, that's all for today. It was simple and fun to extend Liferay with shell access, I solved my problem. It also demonstrates the power of OSGi since we didn't have to write a single line of code in order to benefit from it's well defined modularity and lifecycle specifications.

 

I'll talk more about shell commands, closures, and how to add and use custom Liferay commands in more detail in a future blog post. I'll also start using this shell to show how to start building small Liferay extensions using OSGi.

Eventually, more of the Module Framework features (such as the web extender) that Miguel and I have been working on will reach master and we'll start to discuss those as well.

 

Why OSGi and Liferay?

Staff Blogs 11 octobre 2012 Par Ray Augé Staff

 

I had planned to speak at this years North American Symposium about our ongoing work with OSGi. However, sadly due to several different factors that didn't happen.

It's too bad because as it turns out at least two dozen people asked me:

  • What happened to the OSGi plans?

  • Was it dead?

  • When would it come?

  • Was there a talk on it disguised as something else?

  • some variation thereof


 

Likewise, I was asked:

  • What does OSGi and Liferay together even mean?

  • What is the relation?

  • What is the value?

 

Clearing the air

 

1. The plan

Liferay's OSGi plans are not dead. My current focus, as far as development is concerned, is almost 100% OSGi. A usable form of OSGi integration will arrive in 6.2. There was no talk on OSGi at NAS, so you didn't miss anything.

2. The reasoning (why OSGi?)

Liferay is a large, complex application which implements it's own proprietary plugin mechanisms. While Liferay has managed well enough dealing with those characteristics over it's history, it's reached a point on several fronts where these are becoming a burden which seem to bleed into every aspect of Liferay: development, support, sales, marketting, etc.

(Disclaimer: I'm not part of support, sales or marketting. However, as a senior architect I see how each of those teams deal with the limitations imposed by those previously mentioned characteristics and how they impact the effectiveness of each team, and at times their level of frustration.)

How can OSGi make such a broad impact? you ask.

The impact doesn't actually come from OSGi at all. The impact comes from the outcomes resulting from designing a system using a modular approach.

"Modularity is the degree to which a system's components may be separated and recombined." - Wikipedia

Further to this general definition, you might consider that regardless of which context is used for obtaining a more specific one, it becomes quickly apparent that "modularity" is a benefit rather than impediment.

However, when we do look at a specific definition in the context of software design we see how it clearly applies and might relate to the aspects above:

"In software design, modularity refers to a logical partitioning of the "software design" that allows complex software to be manageable for the purpose of implementation and maintenance. The logic of partitioning may be based on related functions, implementation considerations, data links, or other criteria.” - Wikipedia

Plainly, a modular system allows that the interal details each module be altered without affecting other modules in the system. Imagine if this were true for Liferay on a wider scale.

  • simpler delivery of hot fixes for bugs and/or security vulnerabilities

  • more frequent delivery of improved or new features

  • less bugs in critical modules due to higher degree of focus

  • potentially smaller deployment footprint due to ability to limit functionality to desired modules

  • greater robustness and resilience due to higher degree of care required in designing for consumption and re-use

 

OSGi is simply the best existing implementation of a Java modularity framework. So many of the less obvious concerns which become inherent needs in modular systems are already implemented in OSGi. There exists a number of very good implementations. They are widely adopted. They are heavily supported. The number of supported libraries grows daily, while even unsupported libraries are easily fixed.

 

In short, the benefits far outweigh the costs. There will surely be difficulties along the way, the largest being the fact that learning to implement in a modular way is often counter to how many of us have worked for so long.

 

I'd be very interested in any feedback that people have about this topic, so please let yourselves be heard.

Hadoop Dynamic File System as Liferay Store

Staff Blogs 11 octobre 2012 Par Ray Augé Staff

At this year's Liferay North American Symposium I did a talk on Big Data.

The goal of the talk was to illustrate some cases where Liferay and the broad notion of "Big Data" intersect.

Most importantly I showed an example which stored Documents & Media portlet files in Hadoop's extremely scallable and fault tolerant Dynamic File System (a.k.a. HDFS).

The same example code also demonstrates how you might tee off the indexed result of these documents to store a copy in a separate location in HDFS so that it might be consumed as input for some MapReduce calculation in order to extract insight from it.

It demonstrates how to use the MapReduce API from a java client all the way from creating the job and sending it to submitting it for processing and then (basically) monitoring it's progress. The actual logic applied in the example is trivial, however the most important part is actually showing how you could use the APIs in making Liferay and Hadoop talk.

The code is on github as a standalone Liferay SDK (based on trunk but easily adaptable to earlier versions):

https://github.com/rotty3000/liferay-plugins-sdk-hadoop

Please feel free to fork it or use it as an example.

[update] I should also add a few links to resources I used in setting up hadoop in hybrid mode (single cluster node with access from a remote client, where my Liferay code is assumed to be a remote client):

Liferay North American Symposium 2012 - Another whirlwind succes

Staff Blogs 10 octobre 2012 Par Ray Augé Staff

I'm almost back home from San Francisco after yesterday's completion of Liferay's 2012 North American Symposium. I'm quite exhausted but what a great event to be a part of. For this small town Canadian boy, travelling to historic big cities to meet with old friends I rarely get to see face to face is an experience I will never take for granted. Every turn through the crowd there seemed to result in either heartfelt handshake and a great "Nice to see you again!" with a community member or client I'd met once or twice or even, dare I say, in a big hug from someone I've been working with for years but have really only met in person a dozen times, perhaps less. And the pride that comes from seeing what people have done and dream to do with the humble project I began participating in and was later allowed to earn a living from those many years ago is sometimes... overwhelming. But you can't help but stay grounded when you see the deep intensity and passion people bring. You want to make sure that doesn't get lost, and between the two there seems to be little room for pretentiousness. I'm honoured to be given the opportunity to do the work I do and I hope to keep doing it for a long time. Once again it was great to see you all. To those I already knew, I wish you continued good health and happiness. To those I met for the first time, I wish you luck in your endeavors and hope to see you again at other Liferay events or even elsewhere and happy. Thanks for having me.

Liferay 6.2 Targets Java 6 and Ant 1.8

Staff Blogs 23 août 2012 Par Ray Augé Staff

In order to maintain its extensive deployment matrix, Liferay up to 6.1 has been locked to Java 5 APIs.

However, Java 5 started its EOL cycle on April 8th, 2007 and completely it on October 8th of 2009.

Furthermore the last app server that still required Java 5 has finally been EOL'd itself at the end of 2011.

This means that Liferay 6.2 is now free to adopt Java 6.

It's true that Java 6 itself is almost EOL (extended through Feburary 2013), but due to the transition period lasting an estimated 2 years for clients with support contracts, it means that many enterprise applications will remain Java 6 dependent untill the very end of the transition period which also demands that Liferay remain complient if it wants to retain those applications as part of the extensive deployment matrix it currently enjoys.

This of course in no way prevents 3rd parties from developing plugins/extensions against Java 7 or later as long as they accept the limitation this will have on deployment.

It was also suggested that the community should be informed (as the documents have not yet been updated) that building Liferay 6.2+ requires Ant 1.8 or greater. This is a minor point but can cause frustration if one is not aware.

Faceted Search in Liferay 6.1

Staff Blogs 9 mars 2012 Par Ray Augé Staff

It's been so long since I've written anything, I've been fealing rather guilty. Luckly recently we've been undertaking a huge effort to document features of Liferay, old and new.

One interesting but highly understated feature of Liferay 6.1 is the new Faceted Search support that I was lucky enough to get to work on. As I finished the first round of documentation (for my more eloquent peers to turn into a more polished and finished product) I thought that this would be a great bunch of info to place here for comment. It's a little more formal than a blog post should be, and probably much longer as well (there is even a toc!!!).. but what the hey!

 

Definitions

Before going through features, let us outline a set of definitions that are commonly used in discussion Faceted Search (or search in general).

indexed field: When we store documents in a search engine, we classify aspects of the document into fields. These fields represent the metadata about each document. Some typical fields are: name, creation date, author, type, tags, content, etc.

term : A term is a single value that can be searched which does not contain any whitespace characters. Terms may appear more than once in a document or appear in several documents, and are typically considered atomic units of search. Within the search engine, each indexed field (for example name) will have a list of known terms found within all the documents having that particular indexed field.

phrase: A phrase is a series of terms separated by spaces. The only way to use a phrase as a term in a search is to surround it with double quotes (").

multi value field: Some fields store more than one term at a time. For instance the "content" field may in fact contain hundreds of unique terms. Such fields are often referred to as "text" or "array" fields.

single value field: In contrast to multi-value fields, we have to logically assume that there is such a thing as single value field. Such fields always only contain a single term. These fields are often referred to as "token" or "string" fields.

frequency: The frequency value indicates how many times a term appears within a set of documents.

facet: A facet is a combination of the information about a specific indexed field, it's terms and their frequency. Facets are typically named by the field in question.

term result list: When a facet displays it's data, we call this the term result list.

frequency threshold: Some facet have a property called frequency threshold. This value indicates the minimum number for frequency of terms we want to show. If the frequency threshold of a facet is set to 1, a term appearing 1 or more times will appear in the term result list.

max terms: Some facet have a property called max terms. This value indicates the maximum number of terms that will be included in the term result list regardless of how many actual matching terms are found for the facet. This is done to keep the user interface under control and not to overwhelm the user with too much information.

order: The order property determines the default ordering used for the term result list. There are two possible modes: Order Hits Descending, or Order Value Ascending. The first, Order Hits Descending , means that results will be ordered by frequency in a descending order. The second, Order Value Ascending, means that the results will be ordered by value (i.e. "term") in ascending order. Both modes will fall back to the other mode as a secondary sort order when there are duplicates. (i.e. many terms with the same frequency will always be sorted by "value").

range: A range defines an interval within which all the matching terms' frequencies are summed. This means that if a facet defines a term range for the "creation time" field between the year 2008 to 2010, and another for 2011 to 2012, all matching documents having a creation time within one of these specified ranges will be returned as a sum for that range. Thus you may find 7 documents in the first range, and 18 documents in the second range. Ranges cannot be used with multi-value fields.

For End Users and Portal Administrators

Faceted search is a new feature in Liferay 6.1 (although some of the APIs were first introduced in Liferay 6.0 EE sp2). As such there is little relevance with previous versions other than in direct comparison with the old implementation of the Search Portlet which had no facet capabilities of any kind.

Although the new Faceted Search APIs are used transparently throughout the portal, primary exposure is surfaced through the new Search Portlet implementation.

What follows is a list of features provided by Faceted Search via the Search Portlet.

  1. Aggregation of assets into a single result set: Results from all portlets are returned as a single set and the relevance is normalized among the entire set, regardless of type (i.e. the best results among all types will be at the top). Searching has a more linear cost due to the fact that only a single query is performed. Searching is therefore faster, more intuitive, and more relevant.

    In previous versions of the portal, each Portlet implemented it's own search and returned a separate result set which resulted in several issues:
    • Each portlet invoked it's own query, and each portlet was called in turn resulting in a single portal request generating potentially N queries to the index each with it's own processing time. This lead to increased time to produce the final view.
    • Depending on the order of how portlet searches were called, the results near the bottom may be the most relevant and due to positioning could appear to have less value than those of portlets positioned physically higher up on the page. i.e. the relevance of results was not normalized across all the total results of all portlets.
  2. Default facets: Asset Type, Asset Tags, Asset Categories, and Modified Time range facets are provided by default. These defaults make finding content on the most common facets simple and powerful. Facets details are displayed in the left column of the search portlet and provide information in context of the current search.
    • Asset Type: Performing a search for the term "htc" may return Asset Type facet details which appear as follows:



      The value in parenthesis is the frequency with the term appearing on the left.

      You may notice that as you perform different searches, the Asset Type terms may disappear and re-appear. When a term does not appear it means; a) it was not be found among the results, b) it did not meet the frequency threshold property, or c) it was beyond the maxTerms property (these properties will be discussed more later).
    • Asset Tags: If tags have been applied to any document which appear in the result set, they may appear in the Asset Tag facet:



      Note: Not all tags may appear. In the example above, there are many more than the 10 tags that are listed, but the default configuration for this facet is to show the top 10 most frequently occuring terms as set by it's maxTerms property.
    • Asset Categories: If categories have been applied to any document which appear in the result set, they may appear in the Asset Categories facet:



      Note: Not all categories may appear. In the example above, there are many more than the 10 categories that are listed, but the default configuration for this facet is to show the top 10 most frequently occuring terms as set by it's maxTerms property.
    • Modified Time: All documents appearing in the result set should have an indexed field called "modified" which indicates when the document was created (or updated). The Modified Time facet is a range facet which provides several pre-configured ranges as well as an option for the user to specify a range. All results in the subsequent query should then fall within this range.
  3. Drill down: The next feature allows refining results by selecting terms from each facet thereby adding more criteria to the search to narrow results (referred to as "drilling down" into the results).

    Clicking on terms adds them to the search criteria (currently only one term per facet). They are then listed in what is known as "token style" just below the search input box for convenience and clarity. Clicking the any token's X removes it from the currently selected criteria.

    e.g. Selected the tag "liferay":


    e.g. Additionally, selected the type "Web Content":
  4. Advanced operations: These are supported directly in the search input box. Most of the advanced operations supported by Lucene are supported with only slight variations.

    For a full description of the Lucene syntax see: http://lucene.apache.org/core/old_versioned_docs/versions/3_0_3/queryparsersyntax.html

    Note: Many of the descriptions bellow are copied (almost word for word) from the above reference to account for the similarities but also to highlight the slight variations found between the two.
    • Searching in specific fields: By default, searches are performed against a long list of fields (this is different from Lucene which searches a single specific field by default). Sometimes you want results for a term within a particular field. This can be achieved using the field search syntax:

      <field>:<term> title:liferay

      Searching for a phrase within a field requires surrounding the term with double quotation marks:

      content:"Create categories"

      Note:The field is only valid for the term that it directly precedes, so the query

      content:Create categories will search for the term "Create" in the content field, and the term "categories" will be searched in "all" the default fields.
    • Wildcard Searches: The Search Portlet supports single and multiple character wildcard searches within single terms not within phrase queries.

      To perform a single character wildcard search use the "?" symbol.

      To perform a multiple character wildcard search use the "*" symbol.

      The single character wildcard search looks for terms that match that with the single character replaced. For example, to search for "text" or "test" you can use the search:

      te?t Multiple character wildcard searches looks for 0 or more characters. For example, to search for test, tests or tester, you can use the search:

      test* You can also use the wildcard searches in the middle of a term.

      te*t Note: You cannot use a "*" or "?" symbol as the first character of a search.
    • Fuzzy Searches : Search supports fuzzy searches based on the Levenshtein Distance, or Edit Distance algorithm. To do a fuzzy search use the tilde, "~", symbol at the end of a single word term.

      For example to search for a term similar in spelling to "roam" use the fuzzy search:

      roam~ This search will find terms like foam and roams.

      An additional (optional) parameter can specify the required similarity. The value is between 0 and 1, with a value closer to 1 only terms with a higher similarity will be matched. For example:

      roam~0.8 The default that is used if the parameter is not given is 0.5.
    • Range Searches: Ranges allow one to match documents whose field(s) values are between the lower and upper bound specified by the range. Ranges can be inclusive or exclusive of the upper and lower bounds. Sorting is done lexicographically.

      modified:[20020101000000 TO 20030101000000] This will find documents whose modified fields have values between 2002/01/01 and 2003/01/01, inclusive.

      Note: Liferay's date fields are always formatted according to the value of the property index.date.format.pattern. The format used should be a sortable pattern. The default date format pattern used is yyyyMMddHHmmss. So, when comparing or searching by dates, this format must be used.

      You can also use ranges with non-date fields:

      title:{Aida TO Carmen} This will find all documents whose titles are between Aida and Carmen, but not including Aida and Carmen.

      Inclusive range queries are denoted by square brackets. Exclusive range queries are denoted by curly brackets.

      Note: Ranges can only be applied to single value fields.
    • Boolean Operators: Boolean operators allow terms to be combined through logical operations. The Search Portlet supports AND, "+", OR, NOT and "-" as Boolean operators.

      Note: Boolean operators must be ALL CAPS.
      • The OR operator is the default conjunction operator. This means that if there is no Boolean operator between two terms, the OR operator is used. The OR operator links two terms and finds a matching document if either of the terms exist in a document. This is equivalent to a union using sets. The symbol || can be used in place of the word OR.

        To search for documents that contain either "liferay portal" or just "liferay" use the query:

        "liferay portal" liferay or

        "liferay portal" OR liferay
      • The AND operator matches documents where both terms exist anywhere in the text of a single document. This is equivalent to an intersection using sets. The symbol && can be used in place of the word AND.

        To search for documents that contain "liferay portal" and "Apache Lucene" use the query:

        "liferay portal" AND "Apache Lucene"
      • The "+" or required operator requires that the term after the "+" symbol exist somewhere in a field of a single document.

        To search for documents that must contain "liferay" and may contain "lucene" use the query:

        +liferay lucene
      • The NOT operator excludes documents that contain the term after NOT. This is equivalent to a difference using sets. The symbol ! can be used in place of the word NOT.

        To search for documents that contain "liferay portal" but not "Apache Lucene" use the query:

        "liferay portal" NOT "Apache Lucene" Note: The NOT operator cannot be used with just one term. For example,the following search will return no results:

        NOT "liferay portal"
      • The "-" or prohibit operator excludes documents that contain the term after the "-" symbol.

        To search for documents that contain "liferay portal" but not "Apache Lucene" use the query:

        "liferay portal" -"Apache Lucene"
    • Grouping: Search supports using parentheses to group clauses to form sub queries. This can be very useful if you want to control the boolean logic for a query.

      To search for either "liferay" or "apache" and "website" use the query:

      (liferay OR apache) AND website This eliminates any confusion and makes sure that website must exist and either term liferay or apache may exist.
    • Field Grouping: Search supports using parentheses to group multiple clauses to a single field.

      To search for a title that contains both the word "return" and the phrase "pink panther" use the query:

      title:(+return +"pink panther")
    • Proximity Searches and Term Boosting are not supported.

Portlet Configuration

[Updated: Oct 16 2012] Search portlet configurations are currently scoped to the site page, which means that all Search Portlets used in the same site will have the same settings, regardless of their location or position on different pages will have their own configurations; this also includes any instances of the portlet embedded in themes, or other templates.

Display Settings:

  • Basic : This represents the most basic way of controlling the visible facets.

    Display Asset Type Facet: Display or not.
    Display Asset Tags Facet: Display or not.
    Display Asset Categories Facet: Display or not.
    Display Modified Range Facet: Display or not.
  • Advanced: This mode gives ultimate control over the display of facets and is where the true power lies in the Search Portlet. However, it is not for the faint of heart and requires creating a configuration in JSON format. (Future versions of Liferay will include a user friendly user interface for configuration of facets.)

    In it's default configuration, the Search Portlet configuration would equate to the following JSON text:
    {"facets": [
    	{
    		"className": "com.liferay.portal.kernel.search.facet.AssetEntriesFacet",
    		"data": {
    			"frequencyThreshold": 1,
    			"values": [
    				"com.liferay.portlet.bookmarks.model.BookmarksEntry",
    				"com.liferay.portlet.blogs.model.BlogsEntry",
    				"com.liferay.portlet.calendar.model.CalEvent",
    				"com.liferay.portlet.documentlibrary.model.DLFileEntry",
    				"com.liferay.portlet.journal.model.JournalArticle",
    				"com.liferay.portlet.messageboards.model.MBMessage",
    				"com.liferay.portlet.wiki.model.WikiPage",
    				"com.liferay.portal.model.User"
    			]
    		},
    		"displayStyle": "asset_entries",
    		"fieldName": "entryClassName",
    		"label": "asset-type",
    		"order": "OrderHitsDesc",
    		"static": false,
    		"weight": 1.5
    	},
    	{
    		"className": "com.liferay.portal.kernel.search.facet.MultiValueFacet",
    		"data": {
    			"maxTerms": 10,
    			"displayStyle": "list",
    			"frequencyThreshold": 1,
    			"showAssetCount": true
    		},
    		"displayStyle": "asset_tags",
    		"fieldName": "assetTagNames",
    		"label": "tag",
    		"order": "OrderHitsDesc",
    		"static": false,
    		"weight": 1.4
    	},
    	{
    		"className": "com.liferay.portal.kernel.search.facet.MultiValueFacet",
    		"data": {
    			"maxTerms": 10,
    			"displayStyle": "list",
    			"frequencyThreshold": 1,
    			"showAssetCount": true
    		},
    		"displayStyle": "asset_tags",
    		"fieldName": "assetCategoryTitles",
    		"label": "category",
    		"order": "OrderHitsDesc",
    		"static": false,
    		"weight": 1.3
    	},
    	{
    		"className": "com.liferay.portal.kernel.search.facet.ModifiedFacet",
    		"data": {
    			"ranges": [
    				{
    					"range": "[past-hour TO *]",
    					"label": "past-hour"
    				},
    				{
    					"range": "[past-24-hours TO *]",
    					"label": "past-24-hours"
    				},
    				{
    					"range": "[past-week TO *]",
    					"label": "past-week"
    				},
    				{
    					"range": "[past-month TO *]",
    					"label": "past-month"
    				},
    				{
    					"range": "[past-year TO *]",
    					"label": "past-year"
    				}
    			],
    			"frequencyThreshold": 0
    		},
    		"displayStyle": "modified",
    		"fieldName": "modified", 
    		"label": "modified",
    		"order": "OrderHitsDesc",
    		"static": false,
    		"weight": 1.1
    	}
    ]}
    
    The base definition consists of a JSON object with a field of type array named "facets":
    {"facets": []}
    This array must contain elements (which in JSON are called Objects) having the following mandatory structure:
    {
    	"className": ...,
    	"data": ...,
    	"displayStyle": ...,
    	"fieldName": ...,
    	"label": ...,
    	"order": ...,
    	"static": ...,
    	"weight": ...
    }
    
    • "className": This field must contain a string value which is the FQCN (fully qualified class name) of a java implementation class implementing the Facet interface. Liferay provides the following implementations by default:
      "com.liferay.portal.kernel.search.facet.AssetEntriesFacet"
      "com.liferay.portal.kernel.search.facet.ModifiedFacet"
      "com.liferay.portal.kernel.search.facet.MultiValueFacet"
      "com.liferay.portal.kernel.search.facet.RangeFacet"
      "com.liferay.portal.kernel.search.facet.ScopeFacet"
      "com.liferay.portal.kernel.search.facet.SimpleFacet"
      
    • "data": This field takes an arbitrary JSON "Object" (a.k.a. {}) for use by a specific facet implementation. As such, there is no fixed definition of the data field. Each implementation is free to structure it as needed.
    • "displayStyle": This field takes a value of type string and represents a particular template implementation which is used to render the facet. These templates are normally JSP pages (but can also be implemented as Velocity or Freemarker templates provided by a theme if the portal property theme.jsp.override.enabled is set to true). The method of matching the string to a JSP is simply done by prefixing the string with /html/portlet/search/facets/ and appending the .jsp extension.

      e.g. "displayStyle": "asset_tags"

      maps to the JSP

      /html/portlet/search/facets/asset_tags.jsp Armed with this knowledge a crafty developer could create custom display styles by deploying custom (new or overriding) JSPs using a JSP hook.
    • "fieldName": This field takes a string value and indicates the indexed field on which the facet will operate.

      e.g. "fieldName": "entryClassName"

      indicates that the specified facet implementation will operate on the entryClassName indexed field.

      Note: You can identify available indexed fields by checkmarking the Search Portlet's Display Results in Document Form configuration setting and then expanding individual results by clicking the [+] to the left of their title.
    • "label": This field takes a string value and represents the language key that will be used for localizing the title of the facet when rendered.
    • "order": This field takes a string value. There are two possible values:

      "OrderValueAsc" This tells the facet to sort it's results by the term values, in ascending order.

      "OrderHitsDesc" This tells the facet to sort it's results by the term frequency, in descending order.
    • "static": This field takes a boolean value (true or false). A value of true means that the facet should not actually be rendered in the UI. It also means that, rather than using inputs dynamically applied by the end user, it should use pre-set values (stored in it's "data" field). This allows for the creation of pre-configured result domain. The default value is false.

      Image Search Example: Imagine you would like to create a pre-configured search that returns only images (i.e. the indexed field "entryClassName" would be com.liferay.portlet.documentlibrary.model.DLFileEntry and the indexed field "extension" should contain one of bmp, gif, jpeg, jpg, odg, png, or svg). We would need two static facets, one with "fieldName": "entryClassName" and another with "fieldName": "extension". This could be represented using the following facet configuration:
      {
      	"displayStyle": "asset_entries",
      	"static": true,
      	"weight": 1.5,
      	"order": "OrderHitsDesc",
      	"data": {
      		"values": [
      			"com.liferay.portlet.documentlibrary.model.DLFileEntry"
      		],
      		"frequencyThreshold": 0
      	},
      	"className": "com.liferay.portal.kernel.search.facet.AssetEntriesFacet",
      	"label": "asset-type",
      	"fieldName": "entryClassName"
      },
      {
      	"displayStyle": "asset_entries",
      	"static": true,
      	"weight": 1.5,
         	"order": "OrderHitsDesc",
      	"data": {
      		"values": [
      			"bmp", "gif", "jpeg", "jpg", "odg", "png", "svg"
      		],
      		"frequencyThreshold": 0
         	},
      	"className": "com.liferay.portal.kernel.search.facet.MultiValueFacet",
      	"label": "images",
      	"fieldName": "extension"
      }
      
      			
    • "weight": This field takes a floating point (or double) value and is used to determine the ordering of the facets in the facet column of the search portlet. Facets are positioned with the largest values at the top (yes it's counter intuitive and perhaps should be reversed in future versions).

Other Settings

  • Display Results in Document Form: This configuration, if checked, will display each result with an expendable section you can reach by clicking the [+] to the left of the result's title. In Document Form, all of the result's indexed fields will be shown in the expandable section. This is for use in testing search behavior.

    Note: Even if enabled, for security reasons this ability is only available to the portal Administrator role because the raw contents of the index may expose protected information.
  • View in Context: This configuration, if checked, will produce results which have links that target the first identifiable application to which the result is native.

    For example, a Blog entry title will link (or attempt to link) to a Blogs Admin, Blogs, or Blogs Aggregator portlet somewhere in the current site. The exact method of location is defined by the result type's AssetRenderer implementation.
  • Display Main Query: This configuration, if checked, will output the complete query that was used to perform the search. This will appear directly bellow the result area, like this:

  • Display Open Search Results: In previous versions of the portal, the Search Portlet was implemented as a collection of com.liferay.portal.kernel.search.OpenSearch implementation classes which were executed in series. Due to the subsequent re-design of the Search Portlet, the portal itself no longer relies on these implementations for it's primary search. However, third party plugin developers may yet have Open Search implementations which they would like to continue to use. This configuration, if checked, will enable the execution of these third party Open Search implementations and results will appear bellow the primary portal search.

    Note: It is highly recommended that third parties re-design their search code to implement com.liferay.portal.kernel.search.Indexer or more simply to extend com.liferay.portal.kernel.search.BaseIndexer. Thus it will be possible to aggregate custom assets with native portal assets.

For Developers

Key Classes

When implementing a customized search, many of following API classes are important:

com.liferay.portal.kernel.search.SearchContext
com.liferay.portal.kernel.search.SearchContextFactory
com.liferay.portal.kernel.search.facet.config.FacetConfiguration
com.liferay.portal.kernel.search.facet.config.FacetConfigurationUtil
com.liferay.portal.kernel.search.facet.util.FacetFactoryUtil
com.liferay.portal.kernel.search.facet.Facet
com.liferay.portal.kernel.search.Indexer
com.liferay.portal.kernel.search.IndexerRegistryUtil
com.liferay.portal.kernel.search.BaseIndexer
com.liferay.portal.kernel.search.FacetedSearcher
com.liferay.portal.kernel.search.SearchEngineUtil
com.liferay.portal.kernel.search.Hits
com.liferay.portal.kernel.search.Document
com.liferay.portal.kernel.search.facet.collector.FacetCollector
com.liferay.portal.kernel.search.facet.collector.TermCollector

We'll briefly go through the general organization of the above to understand where each class fits into the greater scheme.

SearchContext

The first thing required is to setup a context within which to perform a search. The context defines things like company instance to search, the current user invoking the search, etc. This task is handled by the com.liferay.portal.kernel.search.SearchContext class. Since this class has a wide variety of context properties to deal with, the most effective way to get one is to call the getInstance(HttpServletRequest request) method of the com.liferay.portal.kernel.search.SearchContextFactory class.

SearchContext searchContext = SearchContextFactory.getInstance(request);

Context Properties

Once you have a SearchContext instance, we then can populate values like the pagination style, start and end:

searchContext.setAttribute("paginationType", "more");
searchContext.setEnd(mainSearchSearchContainer.getEnd());
searchContext.setStart(mainSearchSearchContainer.getStart());

There are number of other SearchContext properties that can be set. See the javadocs for a complete list.

Setting up Facets

After we have setup all the appropriate SearchContext properties, we are ready to add the Facets for which we want to collect information. We can add Facets either programatically or through configuration. Programatically adding facets allows the developer to tightly control how the search is used. The following example shows how to add two facets using some provided Facet classes:

Facet assetEntriesFacet = new AssetEntriesFacet(searchContext);
assetEntriesFacet.setStatic(true);
searchContext.addFacet(assetEntriesFacet);

Facet scopeFacet = new ScopeFacet(searchContext);
scopeFacet.setStatic(true);
searchContext.addFacet(scopeFacet);

Note: The above two Facet implementations are not re-usable in that they always operate on specific indexed fields; entryClassName, and groupId (and scopeGroupId) respectively. Other implementations can be re-used with any index fields as demonstrated previously in the Image Search Example.

As shown previously, facets can also be setup using a JSON definition. Using a JSON definition allows for the highest level of flexibility since the configuration can be changed at run-time. These definitions are parsed by the static method load(String configuration) on the com.liferay.portal.kernel.search.facet.config.FacetConfigurationUtil class. This method reads the JSON text and returns a list of com.liferay.portal.kernel.search.facet.config.FacetConfiguration instances.

List<FacetConfiguration> facetConfigurations = FacetConfigurationUtil.load(searchConfiguration);

for (FacetConfiguration facetConfiguration : facetConfigurations) {
	Facet facet = FacetFactoryUtil.create(searchContext, facetConfiguration);

	searchContext.addFacet(facet);
}

Facets as Filters

It should be noted that Facets are always created with reference to the SearchContext. Since facets also behave as the dynamic filter mechanism for narrowing search results, having the SearchContext allows a Facet implementation to observe and react to context changes such as looking for specific parameters which affect it's behavior.

Indexer Implementations

The next step involves obtaining a reference to an indexer implementation. The implementation obtained determines the type of results return from the search.

With respect to searching, there are two categories of Indexer implementations: Asset Specific Searchers and Aggregate Searchers.

Asset Specific Searchers

As the name implies, Asset Specific Searchers always deal with only one specific type of asset. These are the implementations that are provided by developers when creating/designing custom Asset types. Liferay provides the following Asset Specific Searchers:

com.liferay.portal.plugin.PluginPackageIndexer
com.liferay.portlet.blogs.util.BlogsIndexer
com.liferay.portlet.bookmarks.util.BookmarksIndexer
com.liferay.portlet.calendar.util.CalIndexer
com.liferay.portlet.documentlibrary.util.DLIndexer
com.liferay.portlet.journal.util.JournalIndexer
com.liferay.portlet.messageboards.util.MBIndexer
com.liferay.portlet.softwarecatalog.util.SCIndexer
com.liferay.portlet.usersadmin.util.OrganizationIndexer
com.liferay.portlet.usersadmin.util.UserIndexer
com.liferay.portlet.wiki.util.WikiIndexer

A developer tells the portal about Indexer implementations by declaring them in their liferay-portlet.xml file.

<indexer-class>com.liferay.portlet.calendar.util.CalIndexer</indexer-class>

Any number of such implementations may be provided.

Aggregate Searchers

Obtaining a reference to an Asset Specific Indexer requires calling either the getIndexer(Class<?> clazz) or getIndexer(String className) methods on the com.liferay.portal.kernel.search.IndexerRegistryUtil class.

Indexer indexer = IndexerRegistryUtil.getIndexer(PluginPackage.class);

Aggregate Searchers can return any of the asset types in the index according to the SearchContext and/or facet configuration. Liferay only provides a single aggregate searcher implementation:

com.liferay.portal.kernel.search.FacetedSearcher

Obtaining a reference to this searcher simply involves calling the static getInstance() method of the same class.

Indexer indexer = FacetedSearcher.getInstance();

Note<: When implementing Indexers it is highly recommended to extend the com.liferay.portal.kernel.search.BaseIndexer class.

SearchEngineUtil

Internally each Indexer will make calls to the SearchEngineUtil which handles all the intricacies of the engine implementation. For the purpose of this document, we won't delve into the internals of SearchEngineUtil. But suffice it to say that all traffic to and from the search engine implementation passes through this class, and so when debuging problems it is often beneficial to enable debugging level logging on this class.

Performing the Search

Once an Indexer instance has been obtained, searches are performed by calling its search(SearchContext searchContext) method.

Hits & Documents

The result of the search method is an instance of the com.liferay.portal.kernel.search.Hits class.

Hits hits = indexer.search(searchContext);

This object contains any search results in the form of an array (or list) of com.liferay.portal.kernel.search.Document instances.

Document[] docs = hits.getDocs();

OR

List<Document> docs = hits.toList();

The results display typically involves iterating over this array. Each Document is effectively a hash map of the indexed fields and values.

Facet Rendering

Facet rendering is done by getting Facets from the SearchContext after the search has completed and passing each to a template as defined by the FacetConfiguration:

Map<String, Facet> facets = searchContext.getFacets();
List<Facet> facetsList = ListUtil.fromCollection(facets.values());
facetsList = ListUtil.sort(
	facetsList, new PropertyComparator("facetConfiguration.weight", false, false));

for (Facet facet : facetsList) {
	if (facet.isStatic()) {
		continue;
	}

	FacetConfiguration facetConfiguration = facet.getFacetConfiguration();
	request.setAttribute("search.jsp-facet", facet);

%>

	<liferay-util:include page='<%= "/html/portlet/search/facets/" + facetConfiguration.getDisplayStyle() + ".jsp" %>' />

<%
}

Facet Details (Terms and Frequencies)

A Facet's details are obtained by calling it's getFacetCollector() method which returns an instance of com.liferay.portal.kernel.search.facet.collector.FacetCollector class.

FacetCollector facetCollector = facet.getFacetCollector();

The primary responsibility of this class is to in turn provide access to TermCollector instances primarily by calling the getTermCollectors() method, but also by getting a TermCollector by term value using the getTermCollector(String term) method. There will be a TermCollector for each term that matches the search criteria, as well as the facet configuration.

List<TermCollector> termCollectors = facetCollector.getTermCollectors();

OR

TermCollectorterm termCollector = facetCollector.getTermCollector(term);

And finally, the com.liferay.portal.kernel.search.facet.collector.TermCollector class provides the getFrequency() method.

<%= termCollector.getTerm() %> <span class="frequency">(<%= termCollector.getFrequency() %>)</span>

Rendered facet views (i.e. non-static facets) should result in UI code which allows dynamically passing facet parameters the interpretation by the implementation (see Facets as Filters). There are a number of examples in the /html/portlet/search/facets folder of the Search Portlet.

============================================================

Well, I hope that was useful information.

At the core of all content management lies search and so I'm really excited about the potential of this new search API. As we work on 6.2 and introduce even more innovative new search features, I hope to see Liferay become the most feature rich and extensible search integration platform available in the market. 

Embedding portlets in themes on Liferay

Staff Blogs 21 octobre 2011 Par Ray Augé Staff

Liferay has long had the ability to embed portlets in themes. This is convenient for implementors to get highly functional design into the look and feel of a site. In my years at Liferay I've seen and heard many different attempts at doing this with various levels of success. There are a number of things to consider when embedding portlets in the theme and the same method does not apply in all cases.

The original motive behind embedded portlets was for integrating WCM content or simple functional features such as Search or Language selection. As such the complexity of these portlets was understood to be low and without significant performance costs either due to not having any direct service calls (search & language selection portlets don't have any initial service calls), or having service calls which were highly cached (such as is the case with web content).

There is more than one technique for embedding portlets into the theme, and several different issues to consider when choosing any of those.

Method One: Using $theme.runtime()

This is by far the most common method. There are of course a few gotchas with this method.

1) it renders the portlet synchronously in the theme regardless of the ajaxable settings on the portlet ( via liferay-portlet.xml).

This means if the portlet is expensive to render because it a) has lots of data to process on render, b) uses synchronous web service calls, or c) calculates the fibonacci sequence up to a million, DON'T EMBED IT IN THE THEME! You're just killing the performance of your portal whenever that theme is on any page.

Rule of Themes #1: This is a good rule of thumb to follow regardless of embedded portlets or not: "Themes should be SUPER FAST! They should be the fastest component of your portal view at any given time."

If you don't follow this rule how will you expect the experience to be once you start actually putting more things on the page? It's a different topic entirely, but you should generally performance test your theme with no portlets on the page. Once you know it's blazing fast then proceed to test your portlets' performace.

But I digress! Back to portlets embedded in the theme.

2) portlets rendered in the theme don't have their js/css resources loaded at the top of the page with all the rest of the portlets. Liferay doesn't yet implement "streaming portlet" mode (this is optional in the spec, this doesn't mean Liferay is not fast, it's just a name they chose for the feature of calling the portlet's header altering methods separately from the rendering methods). So the issue with this is that if you embed a portlet that uses something like JSF dynamic javascript features and there happens to be more than one of this type of portlet on the page, the ordering of these dynamic resources may get fouled up and cause general mayhem to their behavior.

On the other hand, I would argue that such portlets are already, by their very nature, TOO complex and expensive to be embedded in the theme. They are quickly diverging from the rule above that the theme should be SUPER FAST!

Rule of Themes #2: "Only use $theme.runtime() to embed portlets that are extremely fast. If they make expensive service calls, MAKE SURE those are not calls that happen all the time, and MAKE SURE they use great caching."

 

Method Two: Ajax / Iframe!

This is a "currently" seldom used method, but one that I would HIGHLY recommend using! Why?

1) It's Asynchronous with the rendering of the theme. This is HUGE! It means that regardless how slow your portlet(s) is(are), the general experience of the portal will remain fast!

2) It means that you can still put that fairly complex portlet into the theme without killing the overall performace.

3) It doesn't suffer from the limitation of the resource loading that the $theme.runtime() method suffers from.

So how do I do it?

Ok, so generally what you do to embed a portlet via ajax or iframe is to create a portlet url (either server side or client side) and then request for it, placing it somewhere in the page. With an iframe you can of course interact with the portlet in place. If you use an ajax call and embed the output of the portlet directly it will of course cause page refresh.

Here is the code for doing it with the iframe:

 

One thing you'll note with this code is that it's making the assumption that the portlet is at the current page! But it's not on the current page yet!

There are a couple of ways of handling this.

1) The portlet is specifically designed to be in the theme and should be visible to anyone seeing the page.

In this case I recommend setting these attributes in your liferay-portlet.xml file:

These will allow your portlet to safely added to any page in the portal automatically (otherwise you may get permission issues because the user viewing the portlet may not have permission and all that jazz.)

2) You can't do as above because you still want to control permissions of the portlet using roles.

In this case, you'll have to add the portlet to something like a hidden page, from which you can manage it's permissions. When creating the url in the code above, you'll then use the page "plid" of that target page instead of the current one.

 

Well, I hope that helps!

I hope that I hear far less talk about $theme.runtime()issues and Liferay performance problems that end up being directly related to expensive operations embedded in the theme.

 

And please heed these words:

"Embed portlets with impunity! Just make sure to do it carefully!"

Introducing Arkadiko, a bridge between Spring and OSGi

Staff Blogs 18 octobre 2011 Par Ray Augé Staff

 

Spring's dependency injection framework is the picture of ubiquity in the java world. As such it aims to drive developers toward declarative, component driven design and lose coupling. But, it is largely a static container. The class space is flat and once started there is little means for dynamic change.
 
OSGi is "the" modularity framework for java. It supports dynamic change via strict classloader isolation and packaging metadata. It also aims to drive developers toward modular design. But although it is very powerfull it is also complex and learning it's intricacies can be daunting.
 
These two paradigms have recently undergone a merge or sorts in such that within OSGi frameworks one can use the declarative nature of Spring (via Spring DM and more recently standardised as Blueprint) to configure and wire together the interdependencies of components as they come and go from the OSGi framework.
 
So how does this project, Arkadiko come into play?
 
First allow me to explain briefly the current state of the world as I understand it (this is my personal assessment).
 
Given the vast number of Spring based projects and the most recent up-surge in the desire to benefit from OSGi's dynamic modularity it has become clear that there is a understandable difficulty in moving projects of any complexity to OSGi from a pure Spring architecture. The issue is that there are some design changes that must be made in moving traditional java applications, including Spring based ones, to OSGi. OSGi has a puritanical solution to a vast number of problems caused by the traditional architectures, but in order to gain from those solutions a considerable amount of redesign has to be done.
 
What to do?
 
There are several well known projects and individuals promoting methodologies and best practices to help undertake the considerable amount of work that such a migration could potentially involve.
 
A recent presentation by BJ Hargrave (IBM) and Peter Kriens (aQute) (http://www.slideshare.net/bjhargrave/servicesfirst-migration-to-osgi) defines the concept of "services first" migration methodology. This methodology suggests that the first steps in the migration is to re-design the existing architecture such that it's based on a services oriented design. They offer some insight into how that is accomplished paraphrased over several different articles about μservices (micro-services) and I won't go into detail here about all that. Suffice it to say that once this has been accomplished it becomes far simpler to
isolate portions/groupings of code that form logical components into modules which use or publish services in well defined manner to subsequently be turned into OSGi bundles.
 
Also, a core developer of the Apache Felix project, Karl Pauls, has recently released a library called PojoSR (http://code.google.com/p/pojosr/) based on some of the Apache Felix project code that itself does not implement a full OSGi framework container, but essentially provides an active registry which scans the class space for recognizable "services" and effectively tries to wire those together or a the very least provide a central access point for those services.
 
I have no in-depth knowledge on either of those two topics but I highly suggest taking some time to review both because they present options for anyone entertaining the notion for undertaking such a migration to OSGi, and several
options are always welcome.
 
Finally we come to Arkadiko!
 
Arkadiko is a small bridge between Spring and OSGi.
 
Arkadiko is an attempt to provide an new migration option, and borrows ideas from both of the above and tries to marry into those the concept of "quick wins" (such as immediate access to OSGi features) and "time to evolve". 
 
Quick Wins: Often the light at the end of the tunnel seems awfully far away. When reviewing the scope of the migration, it may seem like an eternity before the benefits will pay off. So, Arkadiko gives you OSGi right now! How does it do
that? It simply provides a means to wire an OSGi framework into your current spring container. But that in itself doesn't help and so it also dynamically registers all your beans as services and at the same time registers a OSGi ServiceTracker for each one. It does this so that if matching services are published into the OSGi framework they are automatically wired in place of those original beans. You get OSGi right away! It also means that the OSGi framework has access to all your beans and can use those as regular services.
 
Time to Evolve: The other benefit is that you now have time to evolve your platform into OSGi as you see fit, and as time allows, moving components slowly from outside the OSGi framework, into the OSGi framework as re-design is completed by component. This way you gain the benefit where you can get to it quickly. Also, those nasty libraries which have yet to be ported or are still known to not live happily inside of OSGi framework can remain outside thr framework, consumed and wired in from within the container, until such a time as they evolve their own OSGi solutions.
 
Arkadiko is very small and very simple! It only comprises 5 classes in total (one is an exception, one is constants, one is a util class, the real work is done by two classes).
 
Adding Arkadiko to your existing spring configurations is as simple as adding a single bean:
<bean class="com.liferay.arkadiko.BridgeBeanPostProcessor">
    <property name="framework">
        <!-- some factory to get an instance of org.osgi.framework.launch.Framework -->
    </property>
</bean>

For more details and to see the code please go to https://github.com/rotty3000/arkadiko.
 
Feedback is very appreciated!

 

Random Liferay Code Snippets

Staff Blogs 12 octobre 2011 Par Ray Augé Staff

I've taken to using github.com's gist service quite alot recently and so I wanted to just spread the word that there are some handy nuggets appearing there.

Here's the url https://gist.github.com/rotty3000.

Examples:

From others:

 

I hope those prove useful. I'll continue putting things there as I come across them.

Also, there is an RSS feed of those if you want as well . (won't include ones I add by others)

Liferay Portal & OSGi

Staff Blogs 3 septembre 2011 Par Ray Augé Staff

Over the past several months I've been working on integrating an OSGi framework into Liferay Portal. The reasons for doing so are varied but include solutions to app server/servlet container dependency, library version conflicts, runtime module lifecycle, advanced extensibility, and so on.

If you are not familliar with OSGi, I recommend visiting the OSGi web site, and in particular reading the Benefits of Using OSGi.

The first publicly available code of this integration is available as an OSGi branch on my fork of the liferay-portal project on github.

The url of the branch is: https://github.com/rotty3000/liferay-portal/tree/OSGi

I'd like to encourage anyone having an interest in this work to clone the branch and send me feature improvements, bug fixes, etc, via git pull request, or even to just send me comments here.

 

Things I've done to date:

  • I'm using Eclipse Equinox 3.7 (but it works with any 4.1 or greater implementation of OSGi with no code changes, I tested Knopflerfish 3.2.0 and it worked very well, I'm sure Felix would work equally well).
  • Published all the services and beans of the portal's spring context as services in the framework so they can be used by bundles.
  • Developed a management portlet available in the control panel where the portal administor can add/remove/start/stop/update bundles, see all the bundle headers, all the service registrations and all service references of any bundle. (I'd like to eventually add bundle dependency graphing as well).
  • Make sure logging works (this can be tricky in OSGi due to libraries not due to OSGi).
  • Setup an auto deploy listener that will inspect any jar/war file dropped into the portal deploy folder that are OSGi bundles and automatically install them into the framework (and try to start them).
  • Setup a bridge Servlet which is wired into the OSGi HttpService to provide HttpServlet request dispatching for any servlets or jsps registered in the framework. (This feature depends on a bundle I created which I'm going to release in the comming days, in an OSGi branch of my fork of the liferay-plugins repository. The bundle depends on the eclipse equinox http servlet package which is available in the Equinox 3.7 zip.)
  • I've successfully tested various code modification scenarios such as replacing a service implementation and replacing a Util implementation, with all the virtues of OSGi bundle lifecycle goodness.
  • I've successfully deployed and used the recently released Gemini Blueprint implementation of the OSGi Blueprint specification (which you might call Spring for OSGi). It's a simple matter of deploying the 3 main bundles.
  • I've added BND lib to the portal so that it can be used for various OSGi packaging tasks.

Things I'd like to do next:

  • Make some changes to the Plugins SDK so that plugins can (optionally) be generated as OSGi bundles.
  • Support deploying the various bundle-ized plugins into the framework.
  • Support all the spring wiring of ServiceBuilder services found in plugins correctly.
  • Setup a portal packaging system so that we can bundle as set of bundles/plugins so there is no need to assemble and install them manually.
  • Modify the Liferay repository mechanism so that it supports bundles and implements either auto update or auto detectiong of possible updates for bundles in the repository (and also resolves/downloads dependencies).

 

Here are a couple of images of the OSGi Admin portlet.

List of bundles:

View of a Bundle:

 

I'll be presenting this work in a workshop at the upcoming West Coast Symposium in Anaheim on 21-22 of September.

I hope to hear your thoughs on this and maybe to discuss it with you at WCS.

Theme Settings and New Advanced Controls

Staff Blogs 25 août 2011 Par Ray Augé Staff

To continue a recent string of theme related posts, I'm going to add another on the recently added feature allowing addition of advanced input controls and behaviors to theme settings.

Themes are the whip cream that bring flavour and sweetness to our portal fruit salads. Without beautiful and clever theme designs our fruit salad may taste healthy but can be bland and leave us wanting other treats.

To make sure this doesn't happen, Liferay tries its hardest, each generation, to bring new and/or improved features and richness to the theme APIs in order to empower designers to create master pieces.

One of the tools in the deisgners chest which was added in 6.0 and which added pretty significant flexibility to theme developers was the Settings API.

As the name implies, the Settings API is a mechanism that allows a designer to add configuration settings to their theme creations. These settings can then be used within the theme logic to do things like toggle features on or off, provide lists of options for display behaviors, provide boiler plate content fragments, etc. Using this API the designer can, with a single theme, attempt to provide a mixture of options that can potentially meet many more requirements and appeal to more users.

Using the Settings API

In order to use the settings API the designer must include a liferay-look-and-feel.xml file in their theme. The path should be:

<theme>/docroot/WEB-INF/liferay-look-and-feel.xml

As you might have guessed this is an xml file and a boiler plate file for 6.1 could be as trim as the following:

<?xml version="1.0"?>
 <!DOCTYPE
    look-and-feel PUBLIC
    "-//Liferay//DTD Look and Feel 6.1.0//EN"
    "http://www.liferay.com/dtd/liferay-look-and-feel_6_1_0.dtd">

<look-and-feel>
    <compatibility>
        <version>6.1.0+</version>
    </compatibility>
    <theme id="hotel" name="Hotel Theme" />
</look-and-feel>

Plainly enough this file defines the compatibility the theme has with different portal versions (in this case, since we're using features from 6.1 DTD, we're limiting our compatibility to 6.1+). It defines the theme element which contains the id of the theme as well as the name that will appear in the UI during theme selection/configuration.

In order to add settings we're going to add an element as a child of the <theme> element:

<?xml version="1.0"?>
 <!DOCTYPE
    look-and-feel PUBLIC
    "-//Liferay//DTD Look and Feel 6.1.0//EN"
    "http://www.liferay.com/dtd/liferay-look-and-feel_6_1_0.dtd">

<look-and-feel>
    <compatibility>
        <version>6.1.0+</version>
    </compatibility>
    <theme id="hotel" name="Hotel Theme" >
        <settings>
        </settings>
    </theme>
</look-and-feel>

Each setting is defined using a single <setting> elements with two main attributes, key (the name by which the setting is handled) and value (in the case of configurable setting, this will be the default value).

e.g.

...
<settings>
   <setting key="show-breadcrumb" value="true" />
</settings>
...

There are three optional attributes (added in 6.1):

  • configurable (whether or not the setting is static or editable from the UI)
  • type (the data type for the field: checkbox, select, text, or textarea, default is text if not provided)
  • options (in the case of type="select", options should contain a comma delimited list of values available to the selection)

Therefore a slightly more advanced setting might be:

...
<settings>
    <setting configurable="true" key="show-breadcrumb" type="checkbox" value="true" />
 </settings> 
...

So, how would this be used from the theme?

The usage is pretty simple and is handled by a single method on the ThemeDisplay object; getThemeSetting(String key). The ThemeDisplay object you say? Do not fear, this object is already in the context of both Velocity and Freemarker. In Velocity it is available as both $themeDisplay, as well as $theme_display. In Freemaker it's available as themeDisplay as well as theme_display.

Using the setting we created above, in your theme's portal_normal.vm (in a Velocity theme) you might do the following:

#set ($show_breadcrumb = $theme_display.getThemeSetting('show-breadcrumb'))
 
#if ($show_breadcrumb == 'true')
    <nav class="site-breadcrumbs" id="breadcrumbs">
        <h1>
            <span>#language("breadcrumbs")</span>
        </h1>
        #breadcrumbs()
    </nav>
#end

Checkbox type fields have string values of either "true" or "false".

Advanced controls

So far I have not talked about the Advanced Controls I referred to in the title of the post.

So you have controls, fine! Checkboxs, text fields, textareas, and selects are great but we can't exactly call this a great collection of "rich" controls. These are the lowest common controls that you'd probably expect. We're talking about theme developement here, so we want some POP and PIZZAZ (yes PIZZAZ!!!).

To solve this limitation without trying to provide for every scenario imaginable we added the ability for a setting definition to contain a block of javascript code which would be inlined immediately with the input field. This javascript would allow the designer to take the basic field types and turn them into a more advanced control.

For the following example I'm going to bind a color picker tool to a regular text field.

First the base xml for the definition:

...
<setting 
    configurable="true"
    key="portal-main-color"
    type="text"
    value="#EEF0F2">
<![CDATA[
]]>
</setting>
...

Now, I'm going to use the Alloy UI color-picker component and some other simple code for making the field read-only so users can't input faulty data:

...
<setting 
    configurable="true"
    key="portal-main-color"
    type="text"
    value="#EEF0F2">
<![CDATA[ 
    AUI().ready(
        'aui-color-picker-base',
        function(A) {
            var target = A.one('#[@NAMESPACE@]portal-main-color');
            target.attr('readonly', 'readonly');
            var currentValue = target.val();
 
            target.ancestor(
                '.aui-field-element').append(                 "<div id='[@NAMESPACE@]PortalMainColorPicker'></div>");
 
            setTimeout(function() {
                var [@NAMESPACE@]PortalMainColorPicker = new A.ColorPicker(
                    {
                        hex: currentValue
                    }
                ).render('#[@NAMESPACE@]PortalMainColorPicker');
 
                [@NAMESPACE@]PortalMainColorPicker.on(                     'colorChange',
                    function(event) {
                        var hex = [@NAMESPACE@]PortalMainColorPicker.get("hex");
 
                        target.val('#' + hex);
                    }
                );
            }, 0);
        }
    );
]]> 

</setting> ... 

If you read through the javascript you will see that we get an element using the A.one() alloy call. This is akin to document.getElementById() but it works by using css selectors. In this case we're getting the setting field by using the key of the field prefixed by the # symbol, which in CSS means that it's the ID of an element. The key is also prefixed by the token [@NAMESPACE@]. This token is used to avoid namespace collisions with other applications that may be floating around the page and is replaced at runtime with the actual namespace of the theme configuration app (in a portal we always need to be conscious that we may be co-existing with other apps). Use the token whenever creating public variables or dom objects.

Once we have the field, we can manipulate it however we like.

The Advanced Control Result

We've added the control, so what does it look like?

Here is the settings panel with the new field and the control:

With the control active:

Moving the cursor around the color-picker while left button pressing on the mouse will move the color-picker cursor and update the value in the setting text field until you release the button.

The Possibilities

Given the multitude of advanced controls we can create using javascript and the simple usage of the Settings API, the possibilities are quite limitless.

I hope that this takes your theme development to a whole new level of creativity and richness.

I'll be at WCS doing a couple of talks and a workshop which I'll be talking about shortly. So if you have questions regarding any of the things I've writen about recently, or about any other topic actually, and you're attending WCS, please bring those along with you.

See you at WCS!

Update: I wanted to add that the Theme Settings feature was originally implemented and donated to Liferay as part of a community contribution by José Ignacio Huertas (LPS-12468). I'd like to thank José for his contribution. This is a very good example of how a small contribution by the community can become a very cool feature. I would like to encourage others to participate by donating their feature implementations, suggestions, bug reports, documentation skills, or translation skills. This is the key way that Liferay has become the rich platform that it is.

Theme JSP Overrides

Staff Blogs 22 août 2011 Par Ray Augé Staff

Ok, so in the last post I talked about JSP Include Buffer.

That post talked about how you'd override JSPs from Liferay's core in a maintainable way. Now I'd like to introduce you to a Liferay Theme feature, new in 6.1, that will grant you much more power when it comes to manipulating portal views from your theme using that plus this additional feature.

From your theme you can now include templates (following the same path maning as they are found in the core) in your themes which override those of the core, provided they have a template extension matching that of your themes. They will override the default view of the portatl but only where and when your theme is applied.

Now think about this! When you apply a theme onto any page of your portal, you can alter the view of any portlet on just those pages!

Let me illustrate with an example. I'll use the exact same example that was intriduced in the previous post just so it easier to follow along.

Assuming for the moment that our theme is a Freemarker theme, we add the following template to our theme:

<SDK_ROOT>/themes/my-theme/docroot/_diffs/templates/html/taglib/ui/page_iterator/start.ftl

In this template we place the following code (using the buffer pattern):

<#assign liferay_util = PortalJspTagLibs["/WEB-INF/tld/liferay-util.tld"] />

<@liferay_util["buffer"] var="html">
    <@liferay_util["include"] 
        page="/html/taglib/ui/page_iterator/start.jsp" 
        strict=true 
        useCustomPage=false 
    /> 
</@> 
 
<div style="border: 4px solid red; padding: 4px;">
    ${html}
</div>

So, you should note two things.

First, unlike in the previous post I don't have to use the .portal encoded name for the jsp because it's still where it started, the portal didn't relocate it since my override is in the theme (it only does that when using hook plugins).

Second, the attribute strict=true. As I mentioned before, but will again, this is to prevent the portal from doing a lookup for overrides when making the call to the underlying JSP (this is to avoid infinite recursion since we're in an override right now).

Ok, I can noew enhance or alter this view no problem without having to reproduce the whole of the original logic and I'm doing it from my theme no less!

What more could you ask for?

Well it turns out there IS more that could be asked and since it was asked for, we made it reality (that's what we do at Liferay, we make wishes come true).

The question was "Could you override a JSP, but only for a particular portlet?"

Since overriding a blog JSP only in blogs doesn't really make sense, nor does overriding a blog JSP only for message boards, the main case here is with taglib JSPs. Liferay has a vast number of tags and their view logic are largely implemented with JSPs.

So, the answer to that is, "Yes, now you can override a JSP in your theme for only specific portlets!"

How do you do it?

Simple! All you need to do is add the portlet Id of the target portlet, between the file name of the original JSP and the extension of the template like so:

<SDK_ROOT>/themes/my-theme/docroot/_diffs/templates/html/taglib/ui/page_iterator/start.19.ftl

Using a similar template containing (note the green border instead):

<#assign liferay_util = PortalJspTagLibs["/WEB-INF/tld/liferay-util.tld"] />

<@liferay_util["buffer"] var="html">
    <@liferay_util["include"]
        page="/html/taglib/ui/page_iterator/start.jsp"
        strict=true 
        useCustomPage=false 
    />
</@>

<div style="border: 4px solid green; padding: 4px;"> 
    ${html} 
</div>  

Now, you'll note that we have two new tempalate files in our theme:

_diffs/templates/html/taglib/ui/page_iterator/start.ftl
_diffs/templates/html/taglib/ui/page_iterator/start.19.ftl  

These two files do not conflict, and the precendence will be:

1. portlet specific template
2. non portlet specific template
3. (no file) default

If your template is trying to override the JSP for a portlet that is in a plugin, make sure to use the fully qualified portletId (which is encoded using the pattern portletid_WAR_plugincontextname).

Now if you place two portlets on the page while this theme is in effect on it, and each of those portlets use the liferay-util:page-iterator tag, and one of those portlets is the Message Boards Portlet (19) then you should see both red and green boxes around those parts of the page.

 

Oh, and remember that you CAN do the same from a Velocity theme:

_diffs/templates/html/taglib/ui/page_iterator/start.vm
_diffs/templates/html/taglib/ui/page_iterator/start.19.vm

The template (using the buffer pattern) would look more like this:

#set ($bufferTagClass = $portal.class.forName("com.liferay.taglib.util.BufferTag"))
#set ($includeTagClass = $portal.class.forName("com.liferay.taglib.util.IncludeTag"))
 
#set ($bufferTag = $bufferTagClass.newInstance())
#set ($V = $bufferTag.setPageContext($pageContext))
#set ($V = $bufferTag.setParent(null))
#set ($V = $bufferTag.setVar('html'))
 
#if ($bufferTag.doStartTag() == 2)
    #set ($V = $bufferTag.setBodyContent($pageContext.pushBody()))
    #set ($V = $bufferTag.doInitBody())
    #set ($includeTag = $includeTagClass.newInstance())
    #set ($V = $includeTag.setPageContext($pageContext))

    #set ($V = $includeTag.setPage('/html/taglib/ui/page_iterator/start.jsp'))

    #set ($V = $includeTag.setStrict(true))
    #set ($V = $includeTag.setUseCustomPage(false))
    #set ($V = $includeTag.runTag())
    #set ($V = $bufferTag.doAfterBody())
    #set ($V = $pageContext.popBody())
    #set ($V = $bufferTag.doEndTag())
#end
 
<div style="border: 4px solid red; padding: 4px;">
    ${pageContext.findAttribute('html')}
</div>

Now, I hope you find more uses for these features that colourful boxes. Good luck!

JSP Include Buffer

Staff Blogs 22 août 2011 Par Ray Augé Staff

There are plenty of times when you might want to alter the default views of the portal. I'm not going to go into the why since that is hugely subjective. What I do want to show is the how you can do this in the most maintainable way possible.

The largest concern with core JSP modification in projects is of course maintainability. JSPs have no "extensability" feature of their own and so when taking modified core JSPs into your project you run the risk of causing yourself a tone of heartache during future upgrades.

JSPs don't have any contract that allows for cleanly identifying when something has changed or gone wrong between versions. At least with pure source code you can take a leap and just try compiling against the latest version and although you likely run into many errors, they are quite literally spelled out for you. With JSPs you don't have this luxery and it's quite possible to go weeks or months with JSPs that seem to work after an upgrade but that suddenly start to exhibit bad behavior as your system starts to hit those edge cases or outright fail for the same reason. This is typically because of some unforseen change in the core logic.

Developers often try to mitigate this by doing things like using some complex or just "obvious" comments explaining changed code, hoping they are still around to review the changes on future upgrades...

What's likely to happen is that there ends up being so many JSPs changed and/or that the same developer is not around for the next upgrade, a complete and thorough review of those changes simply doesn't get done, risks are run and the ensuing issues become costly.

So goes the story of "JSP Hell"!

Meanwhile JSPs are still the best performing RAD view techology in java, so they are still used quite heavily.

What to do?

Liferay has long experienced this scenario and though we knew that changing from JSP was either unlikely or unreasonable considering the alternatives, we still had to come up with a good solution to the problem.

The solution is quite simple, the JSP Include Buffer.

The basic principle is to simply buffer, then extend the result that is output from the original JSP. Thus we can change only the parts we care about, and latter if there is a change in the original, it will either cause our extension to fail, or it will be seamlessly integrated, exactly the way a source code change would (even though it won't be a build time error, it will still be far more evident than the previous alternative).

There is one limitation, the JSP that you want to perform this operation on must NOT be a jsp fragment (a fragement is a JSP that you include using the low level <%@ include file="****.jsp" %> directive.

Rather it should be one using an include tag, such as <jsp:include /> or in the case of Liferay <liferay-util:include />, i.e. one that is invoked through a request dispatcher.

The examples I'm going to show will be using the <liferay-uitl:include /> tag.

From a JSP Hook

Liferay offers a plugin type called a "Hook" that allows you to define JSPs that should override (not overwrite) JSPs in the core, and on deploy these jsps replace those in the core. BUT the core JSPs are not lost (remember override not overwrite). The core JSPs are simply re-located, thus they are still accessible.

The core JSPs are relocated to a new name which injects the keyword .portal between the file name and extension. Thus a core JSP having the name start.jsp after being "Hooked" would be located under the name start.portal.jsp (at the exact same path).

How does this help us with the above problem?

Simply it means that we have the original to work with and so we don't need to completely re-implement or reproduce the complete code in our "extended" implementation. We can still call the original, buffer it's output and then manipulate the result to our needs.

Here is a very simple example or wrapping the output of the original with a simple black box (you could do this via CSS but what's the fun in that, and this example could serve many more uses that simple css styling can ever achieve).

<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util" %>

<liferay-util:buffer var="html">
    <liferay-util:include
        page="/html/taglib/ui/page_iterator/start.portal.jsp"
        useCustomPage="<%= false %>"
    />
</liferay-util:buffer>

<div style="border: 4px solid red; padding: 4px;">
    <%= html %>
</div>

From a FreeMarker Template

Can the same be achieved from Freemarker you ask? Certainly! And here is the same example as above.

 <#assign liferay_util = PortalJspTagLibs["/WEB-INF/tld/liferay-util.tld"] />

<@liferay_util["buffer"] var="html">
    <@liferay_util["include"]
        page="/html/taglib/ui/page_iterator/start.jsp"
        strict=true
        useCustomPage=false
    />
</@>

<div style="border: 4px solid red; padding: 4px;">
    ${html}
</div>

From a Velocity Template

Hold up! You're kidding right? Velocity can't do jsp tags...

Well it can! It's just not pretty. Why would you do this from a Velocity anyway? Well, you never know what people want to do in their themes and the next blog post I write about how to override JSPs from theme templates will make this here feature all the more interesting for theme developers, AND by supporting Velocity, won't force all those developers to convert their existing themes to Freemarker just so they can override the odd JSP.

So, here goes (remember I don't promise that this is beautiful, only that it works and works well).

#set ($bufferTagClass = $portal.class.forName("com.liferay.taglib.util.BufferTag"))
#set ($includeTagClass = $portal.class.forName("com.liferay.taglib.util.IncludeTag"))

#set ($bufferTag = $bufferTagClass.newInstance())
#set ($V = $bufferTag.setPageContext($pageContext))
#set ($V = $bufferTag.setParent(null))
#set ($V = $bufferTag.setVar('html'))

#if ($bufferTag.doStartTag() == 2)
    #set ($V = $bufferTag.setBodyContent($pageContext.pushBody()))
    #set ($V = $bufferTag.doInitBody())

    #set ($includeTag = $includeTagClass.newInstance())
    #set ($V = $includeTag.setPageContext($pageContext))

    #set ($V = $includeTag.setPage('/html/taglib/ui/page_iterator/start.jsp'))

    #set ($V = $includeTag.setStrict(true))
    #set ($V = $includeTag.setUseCustomPage(false))
    #set ($V = $includeTag.runTag())
    #set ($V = $bufferTag.doAfterBody())
    #set ($V = $pageContext.popBody())
    #set ($V = $bufferTag.doEndTag())
#end

<div style="border: 4px solid red; padding: 4px;">
    ${pageContext.findAttribute('html')}
</div>

And there you have it!

Now you'll notice that I bolded a coupled of lines above in the template examples. These lines, which set the field strict on the include tag to true, are VERY important. They tell the underlying tag NOT check if there are overrides for the specified JSP include (otherwise you may end up with infinite recursion and likely stack overflow errors ;) ).

 

I hope this can help some of you to biuld more maintainable solutions on Liferay with less fear of the upgrade process.

Opening a portlet in dialog from web content

Staff Blogs 11 août 2011 Par Ray Augé Staff

Recently I was asked how to do this! Well, it's rather simple (providing you understand the limitations of accessing portlets without permission, and portlets that are not allowed to be loaded dynamically; a.k.a. add-default-resource, and so on).

Here goes!

First I created a simple web content structure/type with the following definition:

<root>
    <dynamic-element name='portlet-id' type='text' index-type='' repeatable='false'></dynamic-element>  
    <dynamic-element name='parameters' type='text' index-type='' repeatable='true'>         
        <dynamic-element name='value' type='text' index-type='' repeatable='false'></dynamic-element>
    </dynamic-element>
</root>

Then I created the following web content template:

 

<script type="text/javascript" charset="utf-8">
    AUI().ready('aui-dialog','aui-dialog-iframe','liferay-portlet-url', function(A) {
        var url = Liferay.PortletURL.createRenderURL();
        url.setPortletId("${portlet-id.data}");
        url.setWindowState('pop_up'); 

        #foreach ($parameter IN $parameters.getSiblings())
            url.setParameter("${parameter.data}", "${parameter.value.data}");
        #end  

        window.myDialog = new A.Dialog(
            {
                title: 'My Dialog',
                width: 640,
                centered: true
            }
        ).plug(
            A.Plugin.DialogIframe,
            {
                uri: url.toString(),
                iframeCssClass: 'dialog-iframe'
            }
        ).render();
    });
 </script>

 

As you can see there is a place for setting the portletId to craete an URL for, as well as a repeated section for assigning parameters to the url.

Creating an article with the Portlet Id value of 3 results in a dialog opening with the search portlet.

 

You can expand this simple example into whatever you like. The Alloy Dialog plugin is very rich and the Liferay.PortletURL library provides a complete API for creating all of the PortletURL types defined by the portlet spec.

 

Enjoy!

Secure RSS Feeds

Staff Blogs 16 février 2011 Par Ray Augé Staff

All secure RSS feeds now transparently support BASIC  Authentication.

The behavior is such that when you're logged in, the feeds will simply work as expected if you open them directly in the browser. If you log out, you'll suddenly be promted for BASIC  Authentication. If you use the url with an external RSS client and that client supports BASIC Authentication then simply give your credentials and you should be good to go.

Pleae note that BASIC Authentication transfers your passwords over the internet/network in plain text, so make sure that you have SSL enabled if you care about such things.

An alternative is also to enabled DIGEST Authentication (just add an init parameter for "digest_auth" in the filter declaration and restart).

Note of you're using an SSO of some kind, and the client making the request speaks in your SSO's toung, then of course you don't have to worry about any of this.

 

Enjoy!

(Update: The issue was resolved as of LPS-12308 r73243.)

Advanced Web Content Example with AJAX

Staff Blogs 2 février 2011 Par Ray Augé Staff

This example demonstrates several advanced features of Liferay's Web Content Management provided when taking full advantage of Liferay's Web Content Template processing engine. It'll demonstrate implementing PHASES of the portlet lifecycle, performing AJAX calls, using Alloy Javascript Library, using Liferay's SearchEngine, converting java objects to JSON for passing back as AJAX response body all from within our templates.

It starts with a structured piece of content we'll call a Widget, defined by the following Web Content Structure:

<root>
  <dynamic-element name='name' type='text' index-type='text' repeatable='false'>
  <meta-data>
			<entry name="displayAsTooltip"><![CDATA[true]]></entry>
			<entry name="required"><![CDATA[false]]></entry>
			<entry name="instructions"><![CDATA[Enter plain text only.]]></entry>
			<entry name="label"><![CDATA[Widget Name]]></entry>
			<entry name="predefinedValue"><![CDATA[]]></entry>
		</meta-data>
</dynamic-element>
  <dynamic-element name='description' type='text_box' index-type='text' repeatable='false'>
  <meta-data>
			<entry name="displayAsTooltip"><![CDATA[true]]></entry>
			<entry name="required"><![CDATA[false]]></entry>
			<entry name="instructions"><![CDATA[Prefer to use plain text here, at worst use only simple HTML tags like strong, em, etc.]]></entry>
			<entry name="label"><![CDATA[Description]]></entry>
			<entry name="predefinedValue"><![CDATA[]]></entry>
		</meta-data>
</dynamic-element>
  <dynamic-element name='image' type='document_library' index-type='keyword' repeatable='false'>
  <meta-data>
			<entry name="displayAsTooltip"><![CDATA[true]]></entry>
			<entry name="required"><![CDATA[false]]></entry>
			<entry name="instructions"><![CDATA[]]></entry>
			<entry name="label"><![CDATA[Widget Image]]></entry>
			<entry name="predefinedValue"><![CDATA[]]></entry>
		</meta-data>
</dynamic-element>
  <dynamic-element name='document' type='document_library' index-type='keyword' repeatable='false'>
  <meta-data>
			<entry name="displayAsTooltip"><![CDATA[true]]></entry>
			<entry name="required"><![CDATA[false]]></entry>
			<entry name="instructions"><![CDATA[Link a PDF document preferably.]]></entry>
			<entry name="label"><![CDATA[Widget Document]]></entry>
			<entry name="predefinedValue"><![CDATA[]]></entry>
		</meta-data>
</dynamic-element>
</root>

Make sure that each field of the structure is marked as "Searchable" as either text or token otherwise later on we won't be able to individually access those fields. The template for this structure might look like this (but it's not overly important for this example):

<h3>$name.data</h3>

<p><img style="float: left;" src="$image.data" alt="$name.data"/> $description.data</p>

<a href="$document.data">Spec Sheet</a>

Now given that we have a few pieces of content using this structure, our goal is to create an enhanced view that leverage AJAX in order to be cool... er fullfill our business requirements!

So, we're going to create a structure to act as the settings schema for a simple Web Content app:

<root>
  <dynamic-element name='number-of-items' type='text' index-type='' repeatable='false'>
  <meta-data>
			<entry name="displayAsTooltip"><![CDATA[true]]></entry>
			<entry name="required"><![CDATA[false]]></entry>
			<entry name="instructions"><![CDATA[How many items to retrieve with ajax.]]></entry>
			<entry name="label"><![CDATA[Number of items]]></entry>
			<entry name="predefinedValue"><![CDATA[]]></entry>
		</meta-data>
</dynamic-element>
</root>

Finally, we get to the good part which is the template of the Web Content app. I've implemented this example using Velocity just because it's the one I'm most comfortable with. The template implements both the front end logic as well as the backend that will handle our AJAX request. Remember when writing templates that implement request handling to unckeck "Cacheable".

#set ($ns = $request.portlet-namespace)
#set ($companyId = $getterUtil.getLong($request.theme-display.company-id))
#set ($scopeGroupId = $getterUtil.getLong($request.theme-display.scope-group-id))
#set ($numberOfItems = $getterUtil.getInteger($number-of-items.data))

#if ($request.lifecycle == 'RESOURCE_PHASE')

	## This phase will handle the ajax request.

#else

	## This phase (default is 'RENDER_PHASE') will handle the view.

#end

Let's look at the view logic and the AJAX call that drives it! It uses Liferay's very own Alloy Javascript Library to perform the AJAx call.

...
#else
	<table class="taglib-search-iterator">
		<thead>
			<tr class="portlet-section-header results-header">
				<th>
					Widgets
				</th>
			</tr>
		</thead>
		<tbody class="${ns}results-container">
			<tr class="portlet-section-body results-row last">
				<td>
					No Widgets
				</td>
			</tr>
		</tbody>
	</table>

	<script type="text/javascript">
	AUI().use(
		'aui-base', 'aui-io',
		function(A) {
			var search = function(eventType) {
				A.io.request(
					'${request.resource-url}',
					{
						dataType: 'json',
						on: {
							success: function(event, id, obj) {
								var instance = this;

								var hits = instance.get('responseData');
								
								var resultsContainer = A.one('.${ns}results-container');
						
								if (!hits && !hits.docs) {
									return;
								}

								resultsContainer.empty();
								
								for (var i = 0; i < hits.docs.length; i++) {
									var doc = hits.docs[i];

									console.log(doc);
								
									var title = doc.fields.map['web_content/name'].value || doc.fields.map.uid.value;
									var description = doc.fields.map['web_content/description'].value;
									var image = doc.fields.map['web_content/image'].value;
									var document = doc.fields.map['web_content/document'].value;
									
									var position = ' portlet-section-body';
									
									if (i % 2 == 1) {
										position = ' portlet-section-alternate alt';
									}
									
									if (i == 0) {
										position += ' first';
									}
									else if (i == hits.length - 1) {
										position += ' last';
									}
		
									resultsContainer.append(
										[
											'<tr class="results-row' + position + '">',
												'<td>',
													'<h3>',
														title,
													'</h3>',
													'<p>',
														'<img style="float: left;" src="',
															image,
															'" alt="',
															name,
															'"/>',
														//description,
													'</p>',
													'<a href="',
														document,
														'">Spec Sheet</a>',
												'</td>',
											'</tr>'
										].join('')
									);
								}
							}
						}
					}
				);
			}

			search();
		}
	);		
	</script>
#end

You'll notice that the bulk of the code lies in javascript processing the results into the table. I'm not the greatest of javascript developers so take my code with a grain of salt.

Also notice that the target of the AJAX call is a url generated from the request and is in fact one which invokes the current portlet in the "RESOURCE_PHASE". If you aren't familiar with portlets, the "RESOURCE_PHASE" is one which allows portlets to return output without the wrappings and trappings of the surrounding portal. Effectively the OutputStream or PrintWriter used by the portlet is not touched or altered in any way by the portal. This allows the portlet to do things like handle AJAX requests, or generally server any type of "static" resource, like images.

Finally, see how the value of each individual structure field is retrieved from the JSON object: doc.fields.map['web_content/name'].value. Each field that is marked as "Searchable" when creating the structure can be retrieved from the SearchEngine result prefixed by web_content/. The prefix exists so that dynamically created fields don't collide with fields of the actual Web Content Article object when indexed.

 

The final step is getting our content! To do that we'll invoke and query Liferay's SearchEngine from within the template and convert the results into JSON format which we will return as the response body of the request.

...
#if ($request.lifecycle == 'RESOURCE_PHASE')
	#set ($portalBeanLocator = $portal.getClass().forName('com.liferay.portal.kernel.bean.PortalBeanLocatorUtil'))

	#set ($searchEngine = $portalBeanLocator.locate('com.liferay.portal.kernel.search.SearchEngineUtil'))
	#set ($queryFactory = $portalBeanLocator.locate('com.liferay.portal.kernel.search.BooleanQueryFactoryUtil'))
	#set ($sortFactory = $portalBeanLocator.locate('com.liferay.portal.kernel.search.SortFactoryUtil'))
	#set ($jsonFactory = $portalBeanLocator.locate('com.liferay.portal.kernel.json.JSONFactoryUtil'))

	#set ($fullQuery = $queryFactory.create())
	#set ($contextQuery = $queryFactory.create())
	
	#set ($V = $contextQuery.addRequiredTerm('companyId', $companyId))
	#set ($V = $contextQuery.addExactTerm('entryClassName', 'com.liferay.portlet.journal.model.JournalArticle'))
	#set ($V = $contextQuery.addRequiredTerm('groupId', $scopeGroupId))
	#set ($V = $contextQuery.addRequiredTerm('structureId', '10628'))
	#set ($V = $fullQuery.add($contextQuery, 'MUST'))
	
	#set ($sorts = $sortFactory.getDefaultSorts())
	
	#set ($hits = $searchEngine.search($companyId, $fullQuery, $sorts, 0, ))

	$jsonFactory.serialize($hits)
#else
...

What I've done here is create a SearchEngine query which limits the results to a specific structureId. This limitation is only imposed because I chose to limit the logic of the example to only support this one structure. It is entirely possible to query for arbitrary content types (even beyond just Web Content) as long as you are willing to implement the view logic to handle those.

Here's an image of what it might look like.

The complete template follows:

#set ($ns = $request.portlet-namespace)
#set ($companyId = $getterUtil.getLong($request.theme-display.company-id))
#set ($scopeGroupId = $getterUtil.getLong($request.theme-display.scope-group-id))
#set ($numberOfItems = $getterUtil.getInteger($number-of-items.data))

#if ($request.lifecycle == 'RESOURCE_PHASE')
	#set ($portalBeanLocator = $portal.getClass().forName('com.liferay.portal.kernel.bean.PortalBeanLocatorUtil'))

	#set ($searchEngine = $portalBeanLocator.locate('com.liferay.portal.kernel.search.SearchEngineUtil'))
	#set ($queryFactory = $portalBeanLocator.locate('com.liferay.portal.kernel.search.BooleanQueryFactoryUtil'))
	#set ($sortFactory = $portalBeanLocator.locate('com.liferay.portal.kernel.search.SortFactoryUtil'))
	#set ($jsonFactory = $portalBeanLocator.locate('com.liferay.portal.kernel.json.JSONFactoryUtil'))

	#set ($fullQuery = $queryFactory.create())
	#set ($contextQuery = $queryFactory.create())
	
	#set ($V = $contextQuery.addRequiredTerm('companyId', $companyId))
	#set ($V = $contextQuery.addExactTerm('entryClassName', 'com.liferay.portlet.journal.model.JournalArticle'))
	#set ($V = $contextQuery.addRequiredTerm('groupId', $scopeGroupId))
	#set ($V = $contextQuery.addRequiredTerm('structureId', '10628'))
	#set ($V = $fullQuery.add($contextQuery, 'MUST'))
	
	#set ($sorts = $sortFactory.getDefaultSorts())
	
	#set ($hits = $searchEngine.search($companyId, $fullQuery, $sorts, 0, $numberOfItems))

	$jsonFactory.serialize($hits)
#else
	<table class="taglib-search-iterator">
		<thead>
			<tr class="portlet-section-header results-header">
				<th>
					Widgets
				</th>
			</tr>
		</thead>
		<tbody class="${ns}results-container">
			<tr class="portlet-section-body results-row last">
				<td>
					No Widgets
				</td>
			</tr>
		</tbody>
	</table>

	<script type="text/javascript">
	AUI().use(
		'aui-base', 'aui-io',
		function(A) {
			var search = function(eventType) {
				A.io.request(
					'${request.resource-url}',
					{
						dataType: 'json',
						on: {
							success: function(event, id, obj) {
								var instance = this;

								var hits = instance.get('responseData');
								
								var resultsContainer = A.one('.${ns}results-container');
						
								if (!hits && !hits.docs) {
									return;
								}

								resultsContainer.empty();
								
								for (var i = 0; i < hits.docs.length; i++) {
									var doc = hits.docs[i];

									console.log(doc);
								
									var title = doc.fields.map['web_content/name'].value || doc.fields.map.uid.value;
									var description = doc.fields.map['web_content/description'].value;
									var image = doc.fields.map['web_content/image'].value;
									var document = doc.fields.map['web_content/document'].value;
									
									var position = ' portlet-section-body';
									
									if (i % 2 == 1) {
										position = ' portlet-section-alternate alt';
									}
									
									if (i == 0) {
										position += ' first';
									}
									else if (i == hits.length - 1) {
										position += ' last';
									}
		
									resultsContainer.append(
										[
											'<tr class="results-row' + position + '">',
												'<td>',
													'<h3>',
														title,
													'</h3>',
													'<p>',
														'<img style="float: left;" src="',
															image,
															'" alt="',
															name,
															'"/>',
														//description,
													'</p>',
													'<a href="',
														document,
														'">Spec Sheet</a>',
												'</td>',
											'</tr>'
										].join('')
									);
								}
							}
						}
					}
				);
			}

			search();
		}
	);		
	</script>
#end

Here it is again as XSLT!

<?xml version="1.0"?>

<xsl:stylesheet version="1.0"
	xmlns:BooleanQueryFactoryUtil="xalan://com.liferay.portal.kernel.search.BooleanQueryFactoryUtil"
	xmlns:JSONFactoryUtil="xalan://com.liferay.portal.kernel.json.JSONFactoryUtil"
	xmlns:BooleanQuery="xalan://com.liferay.portal.kernel.search.BooleanQuery"
	xmlns:SearchEngineUtil="xalan://com.liferay.portal.kernel.search.SearchEngineUtil"
	xmlns:SortFactoryUtil="xalan://com.liferay.portal.kernel.search.SortFactoryUtil"
	xmlns:str="http://exslt.org/strings"
	xmlns:xalan="http://xml.apache.org/xalan"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	exclude-result-prefixes="xalan"
	extension-element-prefixes="BooleanQueryFactoryUtil JSONFactoryUtil BooleanQuery SearchEngineUtil SortFactoryUtil str xalan">

	<xsl:output method="text" omit-xml-declaration="yes" />
	
	<xsl:param name="groupId" />

	<xsl:variable name="request" select="/root/request" />
	<xsl:variable name="ns" select="$request/portlet-namespace" />
	<xsl:variable name="companyId" select="$request/theme-display/company-id" />
	<xsl:variable name="scopeGroupId" select="$request/theme-display/scope-group-id" />
	<xsl:variable name="numberOfItems" select="/root/dynamic-element[@name='number-of-items']/dynamic-content" />

	<xsl:template name="out" match="@*|node()">
		<xsl:value-of select="local-name()"/><xsl:text> = </xsl:text>
		<xsl:copy>
			<xsl:apply-templates select="@*|node()"/>
		</xsl:copy>
	</xsl:template>

	<xsl:template match="/">
		<xsl:choose>
			<xsl:when test="$request/lifecycle = 'RESOURCE_PHASE'">
				<xsl:variable name="fullQuery" select="BooleanQueryFactoryUtil:create()" />
				<xsl:variable name="contextQuery" select="BooleanQueryFactoryUtil:create()" />
				<xsl:variable name="void1" select="BooleanQuery:addRequiredTerm($contextQuery, 'companyId', $companyId)" />
				<xsl:variable name="void2" select="BooleanQuery:addExactTerm($contextQuery, 'entryClassName', 'com.liferay.portlet.journal.model.JournalArticle')" />
				<xsl:variable name="void3" select="BooleanQuery:addRequiredTerm($contextQuery, 'groupId', $scopeGroupId)" />
				<xsl:variable name="void4" select="BooleanQuery:addRequiredTerm($contextQuery, 'structureId', '10628')" />
				<xsl:variable name="void5" select="BooleanQuery:add($fullQuery, $contextQuery, 'MUST')" />

				<xsl:variable name="sorts" select="SortFactoryUtil:getDefaultSorts()" />
				
				<xsl:message>
					<xsl:value-of select="$numberOfItems" />
				</xsl:message>
				
				<xsl:variable name="hits" select="SearchEngineUtil:search(number($companyId), $fullQuery, $sorts, number(0), number($numberOfItems))" />
				
				<xsl:value-of select="JSONFactoryUtil:serialize($hits)" />
			</xsl:when>
			<xsl:otherwise>
				<xsl:text disable-output-escaping="yes"><![CDATA[
					<table class="taglib-search-iterator">
						<thead>
							<tr class="portlet-section-header results-header">
								<th>
									Widgets
								</th>
							</tr>
						</thead>
						<tbody class="]]></xsl:text><xsl:value-of select="$ns" /><xsl:text disable-output-escaping="yes"><![CDATA[results-container">
							<tr class="portlet-section-body results-row last">
								<td>
									No Widgets
								</td>
							</tr>
						</tbody>
					</table>]]></xsl:text><xsl:text disable-output-escaping="yes"><![CDATA[
					<script type="text/javascript">
					AUI().use(
						'aui-base', 'aui-io',
						function(A) {
							var search = function(eventType) {
								A.io.request(
									']]></xsl:text><xsl:value-of disable-output-escaping="yes" select="$request/resource-url" /><xsl:text disable-output-escaping="yes"><![CDATA[',
									{
										dataType: 'json',
										on: {
											success: function(event, id, obj) {
												var instance = this;

												var hits = instance.get('responseData');
								
												var resultsContainer = A.one('.]]></xsl:text><xsl:value-of select="$ns" /><xsl:text disable-output-escaping="yes"><![CDATA[results-container');
						
												resultsContainer.empty();
								
												if (!hits && !hits.docs) {
													return;
												}

												for (var i = 0; i < hits.docs.length; i++) {
													var doc = hits.docs[i];

													var title = doc.fields.map['web_content/name'].value || doc.fields.map.uid.value;
													var description = doc.fields.map['web_content/description'].value;
													var image = doc.fields.map['web_content/image'].value;
													var document = doc.fields.map['web_content/document'].value;
									
													var position = ' portlet-section-body';
									
													if (i % 2 == 1) {
														position = ' portlet-section-alternate alt';
													}
									
													if (i == 0) {
														position += ' first';
													}
													else if (i == hits.length - 1) {
														position += ' last';
													}
		
													resultsContainer.append(
														[
															'<tr class="results-row' + position + '">',
																'<td>',
																	'<h3>',
																		title,
																	'</h3>',
																	'<p>',
																		'<img style="float: left;" src="',
																			image,
																			'" alt="',
																			name,
																			'"/>',
																		//description,
																	'</p>',
																	'<a href="',
																		document,
																		'">Spec Sheet</a>',
																'</td>',
															'</tr>'
														].join('')
													);
												}
											}
										}
									}
								);
							}

							search();
						}
					);		
					</script>]]></xsl:text>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>
</xsl:stylesheet>

Here's hoping someone finds this useful!

Liferay Expando MongoDB Hook Finished Review

Staff Blogs 17 janvier 2011 Par Ray Augé Staff

Last week I wrote about a hook we wrote for Expandos using MongoDB.

I just wanted to let you know that it just finished review and is ready for consumption.

It's been renamed to mongodb-hook in case we ever want to persist more services to it.

Have a look and enjoy!

Expandos III (Liferay, NoSQL, and MongoDB)

Staff Blogs 8 janvier 2011 Par Ray Augé Staff

Update: The expando-mongodb-hook plugin is now committed to SVN and available from trunk.

Over the past few months the hype around NoSQL type databases had really been heating up the tech news and blog feeds. 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?

Could Liferay support some form of NoSQL integration? I think so, and I surely couldn't go long without doing something to draw attention to the fact that Liferay is a prime candidate as a viable platform for scalling dynamically via a NoSQL backend.

The most obvious way I could see to leverage a NoSQL solution was with perhaps the most dynamic aspect of the portal, Expandos (and by association Custom Fields).

In order prove the concept of NoSQL with Liferay we decided to write an adapter (using a Liferay Hook pattern) to build a backend for Expando on MongoDB. I had no real idea how long it would take to accomplish but we decided to try. As it turns out it was not too difficult. We now have a fully functional adapter to store all of Liferay's dynamic Expando data in a highly scalable MongoDB. But note that Expandos still support all Liferay permissions. And Custom Fields are still indexed along with the entities anywhere they would be normally. This is a fantastic demonstration of just how extensible Liferay portal really is.

I tested against the version of mongodb that was readily available for Ubuntu 10.04 (1:1.2.2-1ubuntu1.1). I also tried to make sure to support cluster configurations. So check out the portlet.properties file in the plugin as well as the mongodb driver javadocs for what and how to set that up.

I did several small usage tests (none of which were load testing, since this was an informal design) to see that everything was working the right way. I created several Custom Fields on several different entites and tested CRUD opperations to make sure that the data was landing (as well as being removed/updated) where I wanted it, in MongoDB.

Meanwhile, I was also using the Mongo DB command line client mongo to make sure that everything was working from that end. I added a custom field called test to the User entity, and for the first user in the system, I set the value to test value . Here is an example of what we see via mongo:

 

[rotty@rotty-desktop  expando-mongodb-hook]$ mongo
MongoDB shell version: 1.2.2
url: test
connecting to: test
type "exit" to exit
type "help" for help
> show dbs
admin
local
lportal_0
lportal_10135
> use lportal_10135
switched to db lportal_10135
> db.getCollectionNames()
[
	"com.liferay.portal.model.User#CUSTOM_FIELDS",
	"com.liferay.portlet.blogs.model.BlogsEntry#CUSTOM_FIELDS",
	"com.liferay.portlet.documentlibrary.model.DLFileEntry#CUSTOM_FIELDS",
	"system.indexes"
]
> db.getCollection("com.liferay.portal.model.User#CUSTOM_FIELDS").count()
1
> db.getCollection("com.liferay.portal.model.User#CUSTOM_FIELDS").find()
{ "_id" : ObjectId("4d28f318fcfcc08a7855ebe4"), "companyId" : 10135, "tableId" : 17205, "rowId" : 10173, "classNameId" : 10048, "classPK" : 10173, "valueId" : 17207, "test" : "test value" }
> 

So far so good! As you can see the data is landing nicely into the Mongo DB database.

 

While that was a good test I also wanted to make sure that other use cases would work just as well. I decided to revive the First Expando Bank example to see how that would work.

I first had to make a few small API changes in the Velocity template. The updated template is attached. See this article and the follow up for more information on that topic.

After adding some accounts into the First Expando Bank app, the mongo console results looked like this:

 

> db.getCollectionNames()
[
	"AccountsTable#AccountsTable",
	"com.liferay.portal.model.User#CUSTOM_FIELDS",
	"com.liferay.portlet.blogs.model.BlogsEntry#CUSTOM_FIELDS",
	"com.liferay.portlet.documentlibrary.model.DLFileEntry#CUSTOM_FIELDS",
	"system.indexes"
]
> db.getCollection("AccountsTable#AccountsTable").count()
3
> db.getCollection("AccountsTable#AccountsTable").find()
{ "_id" : ObjectId("4d29292abda2c08a05e35e67"), "companyId" : 10135, "tableId" : 17320, "rowId" : 1294543146642, "classNameId" : 17313, "classPK" : 1294543146642, "valueId" : 17336, "balance" : 55, "firstName" : "Ray", "lastName" : "Auge", "modifiedDate" : "Sat Jan 08 2011 22:19:06 GMT-0500 (EST)" }
{ "_id" : ObjectId("4d292945bda2c08a06e35e67"), "companyId" : 10135, "tableId" : 17320, "rowId" : 1294543173086, "classNameId" : 17313, "classPK" : 1294543173086, "valueId" : 17337, "balance" : 120, "firstName" : "Daffy", "lastName" : "Duck", "modifiedDate" : "Sat Jan 08 2011 22:19:33 GMT-0500 (EST)" }
{ "_id" : ObjectId("4d292958bda2c08a07e35e67"), "companyId" : 10135, "tableId" : 17320, "rowId" : 1294543192848, "classNameId" : 17313, "classPK" : 1294543192848, "valueId" : 17338, "balance" : 300, "firstName" : "Mickey", "lastName" : "Mouse", "modifiedDate" : "Sat Jan 08 2011 22:19:52 GMT-0500 (EST)" }
> 

Excelent! It would appear that all our use cases are covered from automatic Custom Fields via the UI to programmatic use in a CMS template.

I'd love to get your feedback about it! Please note that there is currently no rich way to perform queries (à la MongoDB). But with a little enginuity we could probably make that possible.

Liferay Nomination to the JCP

Staff Blogs 25 octobre 2010 Par Ray Augé Staff

The JCP Election is just around the corner and Liferay is on the ballot for the first time ever. Being that it is the first time, you may want to know what motivates Liferay's desire for a seat on the JCP?

Frankly it's not a political one. It's purely pragmatic! Liferay would like to see things get done, to see the JCP process in the most effective state it can be. Liferay is not new to the java world and Liferay knows open source. It knows it well as a provider as well as a consumer being itself one of the largest consumers of open source java projects in the industry. On the other hand Liferay is aware of the need to drive revenue whether as an individual or as an enterprise. So, we like to not waste time and get things done. There is no political engine at work. There is only reality which demands that solutions arrive in a timely fashion; and yet Liferay is keenly aware that standardization is nessecary and can take time. Adressing the needs of a wide audience can be difficult and filled with roadblocks beyond just the technical ones. To that end Liferay wants to bring it's pragmatic know how to bare and drive innovation at it's heart. There is no better venue than the JCP.

So then, the key techincal concerns for Liferay are primarly to preserve an open, unfragmented, ubiquitous Java platform, to see improved development agility, to strive for increased modularity and to reach those goals sooner than later. The future looks bright if the JCP's focus can stay on track and Liferay would like to help make that happen.

Affichage des résultats 1 - 20 parmi 69.
Items 20
de 4