Tuesday, June 21, 2011

Button Action Not Firing in JSF

We see this one a LOT.

The action listener in JSF isn't complicated at all and yet it seems like half the time something as simple as clicking a commandButton can cause problems.

Today's Example:

Liferay 6.0.6
Tomcat 6
Windows 7
ICEfaces 1.8.1
Sun JSF 1.2

Some code from the view.xhtml page:

<ice:selectManyMenu style="width:150px;" value="#portletBacking.currentsite}">
<f:selectItems id="siteList" value="#{sites.sites}" />
</ice:selectManyMenu>
<ice:commandButton actionListener="#{portletBacking.add}"
name="addButton" type="submit" value="Add"/>

And the add() method from the backing bean:

public void add(ActionEvent e){
System.out.println("Adding...");
//A bunch of processing...
}

Clicking the button did NOTHING. It didn't even print the "Adding..." message to standard output. Why? Was the action method set up properly? Yes. Was the backing bean registered in faces-config.xml? Yes.

What happens sometimes is that JSF gets stalled when there's an incorrect return type on a control. One way to figure out which control is causing the problem is to comment out the page controls and add them back in, one at a time, until the problem happens again. That's what I did, and in this case the flaw was in the ice:selectManyMenu control. Here's the code that was linked to the value:

public void setCurrentsite(String currentsite) {
this.currentsite = currentsite;
}

public String getCurrentsite() {
return currentsite;
}

See the problem?

The ice:selectManyMenu doesn't take a String value. It takes a String[]. If I had been working with an ice:selectOneMenu this would have been correct. All I had to do was fix the code to use a String[] instead:

public void setCurrentsite(String[] currentsite) {
this.currentsite = currentsite;
}

public String[] getCurrentsite() {
return currentsite;
}

And voila'! The action fired when I clicked the button.

So if you're having this problem and everything else seems right, start taking a closer look at the other controls on the page.

If that doesn't fix it, take a look at this.

Monday, June 13, 2011

Creating a Liferay Portlet From Google Web Toolkit

Yes, you can do it.

Now, I'm still in the process of refining the method but here's a quick and dirty approach. You just have to remember:
  • GWT applications "compile" Java source code into JavaScript.
  • Liferay Portlets have their JavaScript source files defined in the liferay-portlet.xml
Keep that in mind and you'll have no problems.

The Environment:
Liferay 6.06
Tomcat 6.0.29
Windows 7
Google Web Toolkit

*This should work on Liferay 5 as well but I haven't tried it.

First, if you're an experienced GWT developer then you already know how to build and compile your code. If you aren't, complete this tutorial first.

Once you've "compiled" your GWT project you'll need an empty Liferay portlet project to transplant your GWT project into. Go ahead and create one using the Liferay SDK.

Now that you have your empty Liferay portlet and your compiled GWT project, you have everything you need.

Suppose our project is called SuperPortlet.

  • Copy your war/WEB-INF/appengine-web.xml and war/WEB-INF/logging.properties files into the empty portlet's WEB-INF folder.
  • Copy your /SuperPortlet folder into the empty portlet's /docroot folder.
  • Copy your SuperPortlet.css and SuperPortlet.html files into the portlet's docroot folder.
  • Rename SuperPortlet.html to view.jsp
  • In your view.jsp file, there are a few tags that need to go away. In portlets we don't need <html>, <body>, <title> or <head> tags. Blow them away.
  • You don't need the <link> or <script> tags either. We're going to specify our .css and .js files elsewhere.
  • Add this to the top of your view.jsp file:

<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>

<portlet:defineObjects />

  • Now, open your liferay-portlet.xml file.
  • In the <header-portlet-css> tag, add the path to the .css file. In this case the line would look like this:
<header-portlet-css>/SuperPortlet.css</header-portlet-css>

*Note: This assumes the .css file is in the /docroot folder. If you have a subfolder with your .css just put it there and be sure the path is defined correctly in the above tag.

  • This is the tricky part... The auto-generates portlet defines a <footer-portlet-javascript> tag. If you put the path to your SuperPortlet.nocache.js file in there, it won't work. You need to change "footer" to "header."
<header-portlet-javascript>/SuperPortlet/SuperPortlet.nocache.js</header-portlet-javascript>

Again, adjusting that file path depending on how you organize your portlet.

Be sure to copy over any images or other .css and .js files you have (defining each in its own separate tag in the liferay-portlet.xml file) and you should be ready to rock & roll. I did this in Eclipse and exporting the project to a .war file gave me a deployable .war that worked great.

Friday, June 10, 2011

java.lang.ClassCastException: [Ljava.lang.String; cannot be cast to java.lang.String

If you see this error:

java.lang.ClassCastException: [Ljava.lang.String; cannot be cast to java.lang.String When using HttpServletRequest getParameters()

and you're using Liferay 6 with ICEfaces 1.8, then maybe this will help you.

The environment:

Liferay 6.06
Tomcat 6.0.29
Windows 7
ICEfaces 1.8.1 on JSF 1.2

The error is the result of having the out-of-the-box Chat portlet running at the same time as an ICEfaces portlet. I haven't worked out yet exactly why, but what's hapening is the PollerServlet in Liferay is having its getContent() method called and the pollerRequestString is giving it indigestion.

Liferay expects to be able to take the pollerRequestString, deserialize it, and cast it into a Map. For whatever reason, when there's an ICEfaces 1.8 portlet present, it gets a string it can't handle. Since Liferay didn't check the cast in the code for this Servlet, it throws the above error every 60 seconds. This can make for a very full log file in a very short period of time.

The offending line of code:

Map<String, Object>[] pollerRequestChunks =
(Map<String, Object>[])JSONFactoryUtil.deserialize(
pollerRequestString);


What to do?

Well, one suggestion on the Liferay forum for this issue is to remove the Chat portlet. That will make the errors go away, but it isn't much of a solution when you want to actually use the Chat portlet.

What I did was a quick & dirty "fix" using the Ext plugin system Liferay 6 provides.

Now, the Liferay In Action book from Manning has a pretty good section on setting up an Ext plugin and a good overall explanation of how Ext plugins work, but for some reason he seems to be in love with using Struts examples. He even sort of hangs a lampshade on that by saying that its 99% likely that if you're using the Ext plugin it's because you want to modify the Struts behavior.

Well I guess this is that 1% when we're not. What now?

We go in and use brute force, of course!

The author also strongly encourages the reader to extend, not replace, the classes in Liferay. The logic is that if you replace a class, then Liferay subsequently performs an update it will break your code. Well, he also acknowledges that your Ext plugin will probably break anyway so I don't see this as a major risk beyond what we're already doing. Further, all we're doing here is a simple fix, not changing any big functionality, so I'm going ahead and replacing.

Using either your Eclipse Helios (with Liferay plugin) or Liferay 6.0 SDK create a new Ext plugin project.

Know how?

You should have the Liferay 6 SDK set up on your machine. For details on that, you can check out one of my earlier posts. From there, if you're not using Eclipse, you can navigate into the ext folder and type

create uberext "Uber Extension"

(Substitute your own names)

That creates the plugin structure for you in a new folder in the ext folder.

In Eclipse, it's the same idea and it'll even create the plugin folder in your SDK ext folder, exactly as if you'd done it the command line way. (Assuming you've set Eclipse to use your SDK. If not, you need to do that first.)

To set Eclipse to use your SDK:
  1. Open Eclipse
  2. Click the dropdown arrow next to the Create New Server button. It should be right under "Search" on your Menu bar and it's the icon that looks a little like Oscar the Grouch's Trash Can. (Make of that what you will.)
  3. Select "New Liferay SDK"
  4. Follow the prompts to add the new SDK to your Eclipse environment.
Now you can use Eclipse to create your plugins and use the supplied Ant build scripts from the SDK.

Now to create your new Ext project:
  1. Either click the Liferay icon that looks like a power plug or go to File >> New >> Liferay Project
  2. Choose a project name and Display name, entering them in the appropriate fields.
  3. Under Plugin Type, select Ext
Eclipse will, behind the scenes, run the command that creates the project in the ext folder just as if you'd done it there yourself. It also displays the project in the navigation pane at the left.

So, to fix the above error:

In the source code for the plugin, under the docroot/WEB-INF/ext-impl/src folder, create your version of the com.liferay.portal.poller package. Inside it, create your PollerServlet class.

Now copy/paste the code from the original source to your version.

What I did (and this is a quick and dirty solution, I leave a more elegant approach to you) was to take the offending line of code above and replace it with this:

Map<String, Object>[] pollerRequestChunks;

try{
pollerRequestChunks =
(Map<String, Object>[])JSONFactoryUtil.deserialize(
pollerRequestString);
}catch (java.lang.ClassCastException e){
return null;
}

This handles the exception being thrown by the bad cast. Would it be better to check the input and handle it more gracefully? Yes it would, especially because this will still result in a log entry every minute of "Current URL /poller/send generates exception: null" as the method is returning a null value, but the idea here is to show how to do a simple Ext plugin that isn't related to Struts. Besides, it's still better than a stack trace dump every minute.

Now to create the deployable war...

The Liferay SDK has an ant deploy command that you can run from the command line in the folder for your ext project or you can double click it in the Ant window in Eclipse. Either way, if you've configured your SDK environment properly the result will be an Ext plugin that's been compiled, built as a .war file, and copied to the deploy folder in your Liferay instance.

Liferay will install it, but because we're making changes to the compiled code we will need to restart Liferay to make the changes take effect.

This is the quickest, simplest and dirtiest way to build an Ext plugin. Normally we'd do it with more finesse than that but this should be enough to get you started.

Thursday, June 9, 2011

Can't find bundle for base name javax.faces.Messages, locale en_US

This is another one of those error messages that, if you Google it, will bring back a LOT of message board posts and Jira entries and most of them won't help.

This post will talk specifically about times when this error message is thrown and the first two lines look like this:

java.util.MissingResourceException: Can't find bundle for base name javax.faces.Messages, locale en_US java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:1427)

This one is what you get when you try and submit a JSF form.

I'm using:

Liferay 6.0.6
ICEfaces 1.8.1
Windows 7

In my case, I was modifying a portlet originally built for Liferay 4 and ICEfaces 1.7 to make it run on Liferay 6.

What was happening is the message-override.properties file wasn't being found. Now, a lot of people have a lot of different ideas on what folder that file goes in, and yet seldom mention the other half of the problem. You see, you have to tell JSF about this file, and that means going into the faces-config.xml.

<message-bundle>messages-override</message-bundle>

And make sure that goes in your <application> block.

And where should that file be? In WEB-INF/classes.

"So, how do you get that in Maven?"

Getting this messages-override.properties file into WEB-INF/classes isn't hard, but it isn't intuitive either if you're not used to dealing with Maven. In your project file structure, create a folder called resources under main. Your file goes there. When Maven is building your .war file, it'll take whatever is in resources and put it in WEB-INF/classes for you.