Sunday, November 15, 2009

SEO with liferay - required features

Liferay 5.2 came with a couple of improvements regarding SEO (Search Engine Optimization) For example

  • default friendly URLs
  • automatically generated META tags
  • unique titles
That is really good news. And more importantly: To me it shows, that liferay has understood the the future doesn't only lie in intranet-portals but as well in webpages / business models for the internet.
And well - we all know that SEO is key there.

So here are some additional points that are really required from my point of view to catch up with the other big CMS/Portal solutions:

  1. Themes that are SEO friendly by default. This implies usage of headers (H1, H2 etc) - instead of limited meaningfull elements like "Div"
  2. "Related links" portlet. This would allow smart intra-site linking of pages
  3. Pimping of the "Blog" portlets (better customization possibilites) - as this is one of the main means for user generated content
  4. Default google-friendly 404 handling (Not so critical as it can be fixed as described in this blog entry about the 404 customization)

I'm confident, that liferay will pick up some of these ideas. What do you think?

PS:
Talking about catching up with the big cms systems for the internet - like wordpress - a nice "tag cloud" portlet for liferay would be cool. :)

Satisfy google - Create 404 error message to verify page for webmastertools

Now this was a long fight. Pew.

Quite some time ago google changes their rules for page "verification" for their webmastertools which you probably know. Now they ban the usage of redirects when a page is not found - google want's a nice "404" error message.

How does that work with liferay? By default liferay redirects all requested URLs that doen't have a corresponding page to the homepage (e.g. /web/guest/home). That's exactly what google does not like. :o/

There are now two different cases that should be distinguished:

  1. Wrong URLs "behind" the path of the homepage (e.g. www.foo.com/web/guest/hehjoifjal)
  2. Wrong URLs directly at the root path (e.g. www.foo.com/hehjoifjla )
(This difference has been pointed out by Chris Kauffman in the liferay forums - thanks!)

For 1) there are seetings within liferay that customize that behaviour. These work fine - but just for this use case:

layout.show.http.status=true
layout.friendly.url.page.not.found=/errors/my_404error.htm

For 2) This has to be adjusted on the app-server level (for my: tomcat from the bundle)

Two options:
The proper way - change the routing in the web.xml of the liferay application (in tomcat-5.5.27\webapps\ROOT\WEB-INF). There you change the entry for "error-page.

Or you do it quick and dirty :)
The "error-page" tag points to /errors/404.jsp
You can modify this file directly to turn of the redirection. That is you have to remove:
<body onload="javascript: location.replace('<%= homeURL %>')">


and related.

Now you should be friends with google again. :)

Sunday, August 23, 2009

Issue: Blog entry disappears

Final note for today:

Recently I had a strange problem with the Blog feature of liferay (which I heavily use for my intened web 2.0 features - especially letting "Guest" users write content)

"Blog posts" done by the users (with my own EasyBlog portlet) did not appear in the Blog portlets - but could be found with the admin portlets.
Changing the display time helped! Somehow the timezones of the three different timezone-settings
  1. operating system (ubuntu)
  2. virtual machine (set by the statical portal.propoerties at startup of liferay)
  3. liferay portal
got out of synch - maybe during some "daylight saving" change.

After some research on the topic I had to do a complete restart of my "Plesk" environment anyway - and the problem disappeared. :)

But next time when filling time-related parameters in the liferay API I would by carefull filling the Calendar objects with something from System - but rather search for an according (helper) API by liferay..
Eventually. :)

Apache (reverse) proxy for liferay

...by the way (as my last post just described some additional apache tuning) here my full vhost.conf file per each virtual liferay host.

Because there is one additional thing that might be interesting: I use the proxy (actually: Reverseproxy) from apache to map the port 80 to 9080 - and that in a virtual host environment.

So the full vhos.conf looks as follows:

LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so


RewriteEngine On
RewriteOptions Inherit
RewriteCond %{HTTP_HOST} !^www\.traumtube\.de [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http://www.traumtube.de/$1 [L,R,NE]



Order deny,allow
Allow from all


ProxyPass / http://localhost:9080/
ProxyPassReverse / http://localhost:9080/
ProxyPreserveHost on

Virtual hosts with liferay - handling of "subdomain" www

As you might have noticed there were no major changes lately. But yesterday at least I did a lot of cleanups with the team. Most of them were content related - but one basic thing could be interesting for you as well: Fine tuning virtual hosting.

We are using the "virtual host" feature of liferay a lot.
(Control panel -> Server -> Portal instances)

Unfortunately you can just enter one single virtual host ID here. Usually the domain plus the www subdomain/prefix. (e.g. www.traumtube.de )
If a user now enters the URL without the subdomain (here: http://traumtube.de) he would not get to the intended page but to the first (default) virtual host. Reason is: Liferay doesn't match the HTTP_HOST as it doesn't match including the subdomain.

Finally I came up with a solution using Apaches mod_rewrite to achieve the desired flexibility:

RewriteEngine On
RewriteOptions Inherit
RewriteCond %{HTTP_HOST} !^www\.traumtube\.de [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http://www.traumtube.de/$1 [L,R,NE]
Whoopie - that helped.

Hope that helps somebody with the same issues.
Cheers!

Sunday, May 17, 2009

Liferay deployment problem: "Deployment will start in a few seconds" ....

One more thing I learned today:

After migrating to the 5.2.2 and from JBoss to tomcat (see my migration entry ) I noticed, that I could not deploy portlets anymore.
(Why did I not fix that earlier? In the beginning I used the workaround to copy the full unzipped folders to the server and restarted the server :) )

So as we started a big "Theme" project today (Note: Watch out for upcoming blog entries about themes! :) ) that problem got annoying.

The result I always got was:

... was copied successfully. Deployment will start in a few seconds.
What I finally found out:

The new control panel is great. Really! Also good to install plugins. (Control Panel->Server->Plugin Installation)
What I did forget though: I still had the good old "Plugin Installer" portlet up and running - from the good old JBoss days.

And there it was. The setting:
Plugin Installer -> Configurations -> Destination Directory

still contained old JBoss setting. (Of course - they were still in the DB).
Removing that column solved my problem - deploying works perfectly now.

Cheers!

Hibernate problem: "MESSAGE: Broken pipe"

Hi all,

short update - after a long while of running smoothly, the portlet with the hibernate DB connection had a small problem (again).

This time the error was:
04:12:22,792 ERROR [JDBCExceptionReporter:101] Communications link failure due to underlying exception:

** BEGIN NESTED EXCEPTION **

java.net.SocketException
MESSAGE: Broken pipe

STACKTRACE:

java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:2744)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1612)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1723)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3256)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1313)
at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1448)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76)
at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:208)
at org.hibernate.loader.Loader.getResultSet(Loader.java:1808)
at org.hibernate.loader.Loader.doQuery(Loader.java:697)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.doList(Loader.java:2228)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2125)
at org.hibernate.loader.Loader.list(Loader.java:2120)
at org.hibernate.hql.classic.QueryTranslatorImpl.list(QueryTranslatorImpl.java:935)
at org.hibernate.engine.query.HQLQueryPlan.performList(HQLQueryPlan.java:196)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1148)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
at com.liferay.portal.dao.orm.hibernate.QueryImpl.list(QueryImpl.java:78)
at com.liferay.portal.dao.orm.hibernate.QueryImpl.list(QueryImpl.java:73)
A short webresearch pointed out a problem with transaction handling and hinted at two possible solutions:
  1. Wrong coding regarding "commit" of transactions
  2. Usage of a parameter to "autocommit"
(Sources:
http://www.mikeschubert.com/archives/2006/08/javanetsocketex.html
http://forums.mysql.com/read.php?39,196323,198960#msg-198960)
)

Trying not to focus on hibernate problems but rather on other topics I'm trying solution 2 at the moment.
I will let you know how it worked out. :)

Sunday, May 10, 2009

JSF: Refresh Bean AND get different Language bundle per host (Properties)

Hi again,

two small gadgets for you:

1. Get and default bean attributes for different websites or different languages

Sometimes you have the same portlet-code for different websites. I use multiple properties files and select the same within the bean with the following code:

private void initBeanData() {
FacesContext fCtx = FacesContext.getCurrentInstance();

ResourceBundle bundle =
ResourceBundle.getBundle( this.getBundleByHost() , fCtx.getViewRoot().getLocale());
_name = bundle.getString("name_default");
_entryText = bundle.getString("text_default");
}




FacesContext fCtx = FacesContext.getCurrentInstance();
String host = ((PortletRequest)fCtx.getExternalContext().getRequest()).getServerName();
if (host.contains("localhost"))
return "LocalBundle";
...



2. Refresh (backing) Bean

After submitting some input I asked myself how to refresh the backing bean? After some research I found a LOT of people having the same problem.
That seems pretty funny in the meanwhile to me as I realized now how easy that is. Maybe there are some people like me that didn't get it the first time. ;)

Here is the code:

public void reset(ActionEvent actionEvent) {

this.initBeanData();
}


(For the init method: see above :) )

Then of course you have to call the method as always from the view (here: JSP):

Saturday, May 9, 2009

Portal default languange (spanish)

Short note:

There still seems to be a bug at setting the default language for a specific instance/company.

The known trick (thanks to luca) is to change the entry

"languageId"


in the database table

users_


for the user with the flag

defaultUser = 1


In addition I just learned that for spanish the code "es_ES" does not work - however the code "es" does work.

Maybe it helps somebody.

Friday, May 1, 2009

Error at uploading logo: Caused by: java.io.IOException: Unable to retreive rendered image from input stream with type na

Small update:

Today I tried to use the standard "logo" functionality of liferay (Control Panel-> Communitiey-> manage pages -> settings -> logo)

Strangely I got the following confusing errors after uploading a logo (JPG - created with Adopbe Photoshop):


com.liferay.portal.SystemException: java.io.IOException: Unable to retreive rendered image from input stream with type na
...
Caused by: java.io.IOException: Unable to retreive rendered image from input stream with type na


If I remember correctly I used JPG before (created by GIMP) and it worked. Anyway - I changed the format to GIF and then it worked.

If anybody has a better advice / explanation please let me know. :)

Wednesday, April 29, 2009

Migration to liferay 5.2.2.: ServiceContext, JAVA_HOME, 7cogs, Cannot allocate memory

Migration to 5.2.2 continued:

Ok friends - here are some of the pitfalls I encountered. I hope it helps some of you who read this.

1. JAVA_HOME

Coming from JBoss it was quite a surprise to me to get the good old "JAVA_HOME not defined" error message. So I had to search where to set point it to in my linux (ubuntu 6.06) based virtual private server environment.

Finally I found out that the following path seemed to be a good one:
JAVA_HOME=/usr/lib/jvm/java-1.5.0-sun

(Note: The full command to set the parameter is "export JAVA_HOME=/usr/lib/jvm/java-1.5.0-sun" )

2. java.io.IOException: java.io.IOException: Cannot allocate memory

Also an old friend. The main trick was just to lower the memory that java would get.
So I set in the setenv.sh file the following:

JAVA_OPTS="$JAVA_OPTS -Xmx192m -XX:MaxPermSize=128m -Xms128m -Dfile.encoding=UTF8 -Duser.timezone=GMT -Djava.security.auth.login.config=$CATALINA_HOME/conf/jaas.config -Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false"
(Note: The little linux command free -m helped a lot identifying the problem)


3. The new ServiceContext approach

Finally after the server started running I realized some errors in my own code (yes - the great JSF portlets :) ) due to simplifications of the liferay API.

Liferay has moved some repetitive context related attributes into an own ServiceContext class

In detail I changed my code from

BlogsEntryLocalServiceUtil.addEntry(liferayUser.getUserId(),
themeDisplay.getPlid(), this.getName(),
this.getEntryText(), now.get(Calendar.MONTH), now
.get(Calendar.DAY_OF_MONTH),
now.get(Calendar.YEAR), now.get(Calendar.HOUR), now
.get(Calendar.MINUTE), false, true, trackbacks,
tags, true, true, themeDisplay);
to the following:

ServiceContext serviceContext = ServiceContextFactory.getInstance(BlogsEntry.class.getName(), req);

BlogsEntryLocalServiceUtil.addEntry(liferayUser.getUserId(), this.getName(), this.getEntryText(),
now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.YEAR), now.get(Calendar.HOUR), now.get(Calendar.MINUTE),
false, true, trackbacks, serviceContext);


4. Removal of 7cogs example data

As you might have noticed the new version of liferay comes along with some example data. That is a great idea - especially for beginners. Also it shows the new features of the WOL-portlet. ("World of Liferay" :) )
Unfortunately it is a bit tricky to remove the sample data afterwards. Even after removing the hooks - the data stays in the DB of course. And unfortunately it overwrites your default "instance" (that is your first website / company id)

So what I did is:
  1. Restore my VPS backup :)
  2. Remove the 7cogs folder before the first server start

I hope my migration posts help you. As always: I'm happy about all feedback and follow up questions.

"Migration" from 5.1.0 (JBoss 4.2) to 5.2.2 (Tomcat 5.5) - general steps

Hello everybody,

finally I did it: I managed the "migration" from 5.1.0 to 5.2.2.

As some of you might now the change to 5.2. is quite a major change for liferay.
Funnily the final reason for me to migrate was a quite small change: In 5.2.2. finally Blog-Entries get a HTML Title and a friendly URL. This is quite important from an SEO perspective.
(By the way: Big thanks to the liferay community - I triggered this feature by myself by writing a forum post about it - and it has been implemented quite quickly afterwards!)

Why do I write "Migration" in quotationmarks?
After some bad experiences while trying to migrate from 4.3.5 to 5.1.0 (over 4.4.2) I came up with a new "migration" strategy: I install a fresh new bundle and make the necessary changes in the file system. This seems to work with much less problems to me.

Here are the general steps I use to follow:

Step 1: The new installation

  1. point to database (+ potentially install driver) - now in portal-ext.properties
  2. config mail service - now in portal-ext.properties
  3. configure the start script (for tomcat: setenv.sh)

Step 2: Do the post installation steps

  1. Transport additional webcontent (In my case I had some html and applets in a folder. This needs to go into the ROOT folder)
  2. Finalize the portal-ext.properties file (Normally you have to take everything that is in the _legacy_ properties provided by liferay. This goes especially for authorizations - you can't login if you are migration from an old version due to some algorithm changes)
  3. (optional) In my case I have to change the port to 9080 - so I have to modify the web.xml file of tomcat
  4. provide "authentication"files for google or yahoo webmastertools

Said and done. :)

No honestly: Of course it cost my about two days again - but this time especially due to some tomcat specific things I wasn't used to - and some changes in 5.2.2.
But I will describe this in the next post.

Tuesday, March 3, 2009

Problem: Could not initialize class HibernateUtil / org.slf4j.LoggerFactory not found

Hi all,

after testing the "upgrade" to the long awaited version 5.2.2 (I can describe the procedure as well if you are interested) I got some nasty problems with hibernate.

For the portlet "EasyComments" hibernate3 worked fine so far - but suddenly I got the following errors:

Could not initialize class HibernateUtil
....
at HibernateUtil.closeSession(session);
Annoyingly there was not more to take from the standard out of tomcat (nor from the logs). Only an additional catch blog in the HibernateUtil class showed the real problem:

org.slf4j.LoggerFactory not found

and later:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder"

So it seems that hibernate is using slf4j internally.
Adding the following jars solved the problem:

slf4-api-1.5.6.jar
slf4-simple-1.5.6.jar

The files can be found here:
www.slf4j.org

And finally all worked again.


Saturday, January 31, 2009

By the way: [DriverManagerConnectionProvider:41] Using Hibernate built-in connection pool (not for production use

.... one addition to my last hibernate post:

The main reason why I tried the c3p0 solution first (besides me not being an experienced mysql admin) was the following message in the output of my local Tomcat test-instance:

[DriverManagerConnectionProvider:41] Using Hibernate built-in connection pool (not for production use!)

(See my last post how to configure c3p0 for liferay portlets:

Portlets for liferay portal with java jsf: Meanwhile: Hibernate / mySQL Problems: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes....)

Meanwhile: Hibernate / mySQL Problems: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes....

Hi all,

we're almost there.
My "user generated content" portlets are running (EasyBlogEntry and EasyComments) - but as expected: There are unforeseen problems. :)

Since I deployed the Hibernate3 based portled EasyComments I get the following exception always after the liferay server is running for some time - roughly every second day. (I use mySQL 5.0.22-0ubuntu6.06.5)


10:53:20,430 ERROR [jsp] java.lang.Exception: org.hibernate.exception.JDBCConnectionException: could not execute query
...
Caused by: org.hibernate.exception.JDBCConnectionException: could not execute query
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:74)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
...
Caused by: com.mysql.jdbc.exceptions.MySQLNonTransientConnectionException: No operations allowed after connection closed.Connection was implicitly closed due to underlying exception/error:


** BEGIN NESTED EXCEPTION **

com.mysql.jdbc.CommunicationsException
MESSAGE: Communications link failure

Last packet sent to the server was 1442 ms ago.

STACKTRACE:


com.mysql.jdbc.CommunicationsException: Communications link failure

Last packet sent to the server was 1442 ms ago.
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1070)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2873)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2763)
...
Caused by: java.io.EOFException: Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2332)

at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2774)


After quite some research in the web I got different clues about which jdbc driver to use, what mysql parameters to set and how to configure hibernate.

At first I harmonized my mySQL drivers (liferay portal had mysql-connector-java-5.0.8-bin.jar and in my portlet mysql-connector-java-5.1.5-bin.jar for some reason. (I should use the one shared library here anyway - well next time..))
Anyway: I didn't help. (I could image that it took longer until the error - but it still kept coming)

As suggested by the mySQL forum I tried now to use c3p0 as the connection pool.
I achieved this by:

1. configuring the hibernate.cfg.xml file

simply add the following to your file:

<!-- Sessions and transactions -->
<!-- Use the C3P0 connection pool provider -->
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">3000</property>
<property name="current_session_context_class">thread</property>



As I did here:



Additionally you have to add the c3p0.jar to your portlet! I took the one from the sample portlet sample-dao-portlet-5.1.1.1.war from liferay. (Again you should by able to add it to your server and reference it via the liferay-plugin-package.properties file - but I just put in the /lib folder at first for the sake of ease.)

Unfortunately I have to wait now for at least three days to see if it really helps. Of course I will update you folks with the result. So come back and find out. :)

Sunday, January 18, 2009

Meanwhile: different JSF Versions on JBoss.. (Exception regarding org.jboss.web.jsf.integration.config.JBossJSFConfigureListener)

Hi,

before finishing my post on the user generated contend I want to put something very technical in between.

While deploying my final solution for the anonymous comments (EasyComment-Portlet) I stumpled for the tenth time about an issue regarding the various JSF Versions - and how compatible they are with the different Containers.

So once and for all I want to persist this topic here. :)

As you might be aware JBoss (at least version 4.2) brings its own JSF libraries - and dislikes the deployment of other JSF implementation libraries within an .war file. However as not all versions seem to harminize with liferay properly (I plan to publish a matrix on this one soon if there is interest) its sometimes easier to just bundle the JSF implementation of choice.

In my case I use the myfaces JSP (JSF 1.1) - based on the example portlet "Sample JSF 1.1 (MyFaces JSP)"

So as some times before I got the following error at deployment:
2009-01-17 12:58:29,454 WARN [org.jboss.web.jsf.integration.config.JBossJSFConfigureListener] MyFaces JSF implementation found! This version of JBoss AS ships with the java.net implementation of JSF. There are known issues when mixing JSF implementations. This warning does not apply to MyFaces component libraries such as Tomahawk. However, myfaces-impl.jar and myfaces-api.jar should not be used without disabling the built-in JSF implementation. See the JBoss wiki for more details.
2009-01-17 12:58:29,908 FATAL [javax.enterprise.resource.webcontainer.jsf.config] null MessageFactory
2009-01-17 12:58:29,908 ERROR [STDERR] java.lang.ClassCastException: org.apache.myfaces.renderkit.html.util.ExtensionsPhaseListener
2009-01-17 12:58:29,909 ERROR [STDERR] at com.sun.faces.config.ConfigureListener.configure(ConfigureListener.java:825)


2009-01-17 12:58:29,927 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[localhost].[/EasyComments-portlet]] Exception sending context initialized event to listener instance of class org.jboss.web.jsf.integration.config.JBossJSFConfigureListener
javax.faces.FacesException: java.lang.ClassCastException: org.apache.myfaces.renderkit.html.util.ExtensionsPhaseListener
at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:387)
at org.jboss.web.jsf.integration.config.JBossJSFConfigureListener.contextInitialized(JBossJSFConfigureListener.java:69)

(It was especially annoying because in my local version the portlet worket (on tomcat) - whereas on my productive server (JBoss) it didn't...)

After some time I remebered what this error was about: I had to tell JBoss to take my war included libraries.

So I set the according parameter in the Web.xml of my portlet as shown below:


Here the parameter again:


<context-param>

<param-name>org.jboss.jbossfaces.WAR_BUNDLES_JSF_IMPL</param-name>

<param-value>true</param-value>

</context-param>

I hope this helps all the people that find this.