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.

Friday, May 6, 2011

JSF/ICEfaces Validation Messages and Liferay Portlets

Turning this:





Into this:






Getting JSF and Liferay to play nice together in the sandbox is sometimes an adventure. Even things that should be straightforward sometimes... aren't.


Take validation. JSF 1.2 has a pretty nice validation component. It's easy to use and has some built-in validators with custom validators easy to handle.

There are some things to watch out for, however, especially when using this functionality in your portlets.

Our environment today:
Liferay 5.2
ICEfaces 1.8.1 (built on JSF 1.2)

Let's say we have 3 fields... two of them are for inputting a name (first and last separately) and one is for an E-mail address. All 3 fields are required and we're going to use a regex to validate the E-mail address is properly formatted.

The text fields are simple.

<ice:inputText id="lname"
label="Last Name"
partialSubmit="false"
required="true"
value="#{backingBean.lname}"/>

Notice the required="true" attribute. That tells JSF to use its built-in required field validator to check this field during the Process Validation phase of the JSF lifecycle. (That isn't just technical mumbo-jumbo, boys and girls. It's going to matter later.)

We do the same for first name.

<ice:inputText id="fname"
label="First Name"
partialSubmit="false"
required="true"
value="#{backingBean.fname}"/>

Now, for E-mail address we don't just want it to be required. We also want to check to be sure the format is correct.

<ice:inputText id="email"
label="E-mail Address"
partialSubmit="false"
required="true"
validator="#{backingBean.validateEmail}"
value="#{backingBean.email}"/>

Notice that we used the required="true" attribute, but we're ALSO adding our custom validator with the attribute validator="#{backingBean.validateEmail}"

Now, sometimes it's better to create a separate validator class to define a bean whose sole purpose is validation. In this case, we're only defining a single custom validator so there's no need to create a separate class just to handle it. We certainly could have, and in a more complex form with more custom validators we would.

The custom validator method we're calling here is pretty straightforward.

public void validateEmail(FacesContext context, UIComponent toValidate, Object value) {
String email = (String) value;
String regex = "regexstring";

if(!email.matches(regex)){
((UIInput)toValidate).setValid(false);
FacesMessage message = new FacesMessage("Invalid Email Adddress");
context.addMessage(toValidate.getClientId(context), message);
}
}

I left out the actual regex string, replacing it in the code with just regexstring. I did this because there are a lot of different ways to do this and you may have a favorite you'd prefer. Either way, I stink at writing regex strings so anything I put there would be written by somebody else and I'm not gonna plagiarize. Here's a nice regex library where you can find all sorts of user uploaded regex strings as well as a regex tester.

So here's what's happening...

We take 3 parameters in our validator method. The FacesContext object, the UI component that's triggering the validation, and the Object that is the subject to be validated. We're casting that Object to a String and comparing it to our regex. If it matches, life goes on as normal. if it doesn't, the UI component is marked as being invalid and we add a new FacesMessage to the FacesContext object.

So if any is not valid, the JSF lifecycle bypasses all but the Render Response phase.

Nice, huh?

Now all we need is to display the validation messages back to the user. There are two ways to do this. The first way is to have a message for each control, so that it can be placed right next to the control. This is done by means of an <ice:message/> tag with the for attribute set to the control being validated. You can also display all of the validation messages in one place with the<ice:messages/> tag which has attributes for setting the display layout and so on.

We're not done yet, friends.

Here's the problem. FacesMessage objects have a severity associated with them. By default, the built-in required field validator is of SEVERITY_ERROR. In our custom validator we didn't set a severity, did we? Well the default is not SEVERITY_ERROR. That means it's going to be different from the required validators.

Why is that a problem?

Because Liferay assigns a different look and feel to the way it displays these messages based on severity.

Normally, we could just set the severity level of the message when we create it in our custom validator so it matches. At least then they'd all look the same. Personally, I don't really like the big red box look. The default severity level is handled by Liferay as a SEVERITY_INFO which is a much more pleasant blue box.

Liferay also sometimes tacks on a lot of extra, very user unfriendly info like the uid for the portlet instance and we don't want all that displaying in the error message.

Note: it only does this for the built-in JSF validators. When we create our own, it's no problem.

That means in order to get them all the same, we need to override the messages created by the built-in validators.

I prefer to do this right after the Process Validation phase.

That means creating a phase listener.

public void afterPhase(PhaseEvent pe) {
if (pe.getPhaseId() == PhaseId.PROCESS_VALIDATIONS) {
FacesContext facesContext = FacesContext.getCurrentInstance();
Iterator messages = facesContext.getMessages();
while(messages.hasNext()){
String control, detail;
FacesMessage message = messages.next();
message.setSeverity(FacesMessage.SEVERITY_INFO);
reformatMessage(message);
}
}
}

So in our phase listener (Which you did remember to register in the faces-config.xml, right?) we get the messages in the FacesContext. We then turn each of them to SEVERITY_INFO for that nicer blue look. Then we reformat each message...

private void reformatMessage(FacesMessage message){
String[] messageElements = message.getSummary().split(":");
if(messageElements.length > 1){
message.setSummary(messageElements[messageElements.length - 3] + " field is blank. Please enter a value.");
}
}

As you can see from the image at the top of this post, the exact length of the messages isn't the same so we need a little flexibility in our String handling here. We also want it to ignore our custom messages so we have it ignore the messages that don't contain that ":" in them.





Thursday, April 21, 2011

SEVERE: Error listenerStart Part II

Sometimes they come back...

SEVERE: Context [/mybrokenportlet] startup failed due to previous errors

Liferay 5.2

Oh yeah, so I had this one come back and give me the proverbial love bite in the derrière.

Checked all the jars: Good.
Checked all the configuration names matched the .war file name: Good.

What's left?

The scenario:

I was making a copy of an existing portlet project in order to keep the existing version which was in use in a production server and modify the copy to form a new portlet with similar functionality.

So I just took the old portlet "myoldportlet-portlet" (Names being fictionalized here to protect the innocent) and went through all the java files, all the config files, the pom.xml and updated the names using the good ol' find/replace feature.

...being oh, so careful to keep track of uppercase/lowercase.

(Pay attention, boys and girls. This is foreshadowing.)

And guess what happened when I went to deploy my new cloned portlet?

portlet was unregistered due to previous errors.

Long story short, I converted this entry in my faces-config.xml

com.my.uber.portletapp.MyOldPortletPhaseListener

to

com.my.uber.newportletapp.mynewportletPhaseListener

Yeah. Go figure Tomcat choked on it.

Case sensitivity is nothing to play with.


(If you're still having trouble, try here.)

Monday, April 11, 2011

JavaScript in Liferay Portlets! How?

So as much as I personally detest using JavaScript and prefer to handle my AJAX through ICEfaces, there are occasionally times when one absolutely must use some JavaScript. I also grudgingly admit that there are plenty of people out there for whom using JavaScript is a delight.

This post is for you.

Using JavaScript class files with Liferay portlets is easy, but it seems unusually difficult to get a simple, straightforward answer to the question "How do I link my .js files to my portlet?"

Now, the answer I've heard around the water cooler is "Stick it in the HEAD tag on your page, of course." Well if you're following best practice, your portlet has no business including a HEAD or BODY tag. Not only that, but by doing it that way you're asking for all sorts of interesting conflicts. There is a better way.

Your Liferay portlet includes, in your WEB-INF folder, a liferay-portlet.xml file, does it not?

It had better.

That's where you define your .js and .css class files for the portlet.

Inside the <portlet> element, you should have these two settings:

<header-portlet-css>
/xmlhttp/css/mysuperawesomestyle.css
</header-portlet-css>

<header-portlet-javascript>
/js/theworldsgreatestjavascript.js
</header-portlet-javascript>

With those paths and filenames being relative to the portlet's own application directory.

Have more than one file to link? Use multiple tags and define each one individually.