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.

Tuesday, February 8, 2011

'noticeEl' is null or not an object

So this JavaScript error:

'noticeEl' is null or not an object

started popping up after we upgraded a couple of our servers to run Service Pack 5 for Liferay's Enterprise Edition. Apparently this same problem exists now for Liferay 6 as well. The fix here works equally well in Liferay 5.2.5 (Enterprise Edition) so definitely use it.

NOTE: You folks using Liferay 5.2.3 (Community Edition) or those who are on SP3 don't have to worry about this fix.

The problem is that they're recommending the use of a hook to fix this. Great, but what if you don't know HOW to create a hook?

It gets even more fun when you look into the Liferay 5.2 SDK and realize there's no create.sh (or create.bat for you Windows folks) for creating hooks. (Strangely, this seems to coincide with the lack of an "Install More Hooks" button in the plugins installation section of Liferay's Control Panel.)

All is not lost, friends. We can beat this thing together!

Let's suppose you want to modify the functionality of a particular JSP in your Liferay (like the bottom.jsp mentioned in the linked thread above). You might be tempted to just replace the JSP in the installed Liferay structure.

This is not a good idea.

For one thing, it means if you ever have to re-install Liferay you're going to lose that page unless you remember to copy it into a backup first. It also means screwing up any hope of keeping track of versions unless you want to dump your WHOLE Liferay portal into your repository.

Hooks really are easier, and provide a nice way to keep the changes and modifications separate from the original source.

So, here's how we do it.

Go into \liferay-plugins-sdk-5.2.3\hooks and create a folder to house your hook. Let's call it mywickedawesomehook. Now, normally there'd be a build.xml in here for you like there is in the portlet and theme folders, but there's not. (Don't ask me why.) That means you have to create it. (If this were a portlet or theme we could just run an Ant create command to handle all this. If you want to write your own Ant script, go for it!)

So inside the mywickedawesomehook folder create your build.xml and put this in it:

<?xml version="1.0"?>
<project name="hook" basedir="." default="deploy">
<import file="../build-common-hook.xml" />
</project>

This will allow Ant to build your .war file later when you're done.

Now, to start creating the meat of the hook. Let's call it the meathook.

Ok let's not.

Now add some folders so your directory structure looks like this:

mywickedawesomehook/docroot/WEB-INF

Now, in that WEB-INF we need two configuration files. The first is liferay-hook.xml and it should look something like this inside:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 5.2.0//EN" "http://www.liferay.com/dtd/liferay-hook_5_2_0.dtd" >
<hook>
<custom-jsp-dir>/WEB-INF/jsps</custom-jsp-dir>
</hook>

The second is liferay-plugin-package.properties and it should contain:

name=mywickedawesomehook
module-group-id=liferay
module-incremental-version=1
tags=
short-description=This hook is so awesome it blows my mind.
change-log=
page-url=[http://www.liferay.com]
author=Dr. Gaius Baltar
licenses=MIT
required-deployment-contexts=

Now here's where some decisions have to be made. If you're just changing the functionality of a JSP page then in your WEB-INF folder create this structure:

/WEB-INF/jsps/html/

What we're doing here is we're replicating the folder structure in the Liferay portal source code itself. So using the example from the fix above where you modify your bottom.jsp file, you'd find it in the Liferay source here:

portal-web/docroot/html/common/themes/bottom.jsp

So your folder structure in the hook should match

/WEB-INF/jsps/html/common/themes

where the html folder is the point where they converge.

(You DID download the Liferay 5.2.3 source code, right? If not, you can get it here. Fair warning... If you're using this to fix the noticeEl problem then you're going to need to get the bottom.jsp file from your Liferay 5.2.5 install or source. The link I'm providing here is for the Community Edition and doesn't apply to the noticeEl problem.)

And of course, continuing that example, bottom.jsp would reside in that themes folder.

When you've made the changes you want and are ready to try it out, just run the ant deploy command in the home folder of your hook (in this case, the mywickedawesomehook folder).

Yeah, I know. You Maven guys want some love too.

If you're using Maven in Eclipse you can use it to build the Liferay 5.2.3 hook structure automatically IF you've added the archetype to it. It creates a nifty little folder structure for you to use as a STARTING point. (Maven's not gonna make it THAT easy for you.)

It'll be up to you to create the folder structure to match what's described above. Also, Maven expects there to be a web.xml file in the WEB-INF folder whether you're doing anything with it or not, so I just stick a blank one in there to keep it happy.

...ok well it isn't actually blank. You do need to follow at least the minimal dtd.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
</web-app>

Now, once you've built this thing and want to install it you just drop it into the deploy folder the same as any other plugin. Don't have access to the file system? You can upload it via the site interface, but strangely Liferay doesn't provide a button for uploading your own hooks per se. Not to worry. Just use the Portlet Install button.

Seriously.

When you go to install a theme or a portlet by uploading it all Liferay is doing is automatically taking that file and dropping it into the deploy folder for you, just as if you were on the file system doing it yourself. It doesn't care if you use the Install More Portlets button to upload a theme or a hook.

Friday, February 4, 2011

CVS could not find desired version blues

Using CVS as your source repository integrates pretty well with Eclipse Ganymede but sometimes you'll make some changes to a file and when you try and commit it...

cvs [update aborted]: could not find desired version

Somewhere along the way the version numbers got out of sync and now you can't commit all the fabulous changes you've made to the file.

What do you do?

Well the first thing you do, as a way of creating yourself a safety net, is to copy the contents of the file and paste them into a blank text document. That way, if you completely screw up your files you won't lose your hard work.

With that safely aside, you can start fixing your CVS problem.

1: Right click the file, and select Replace with --> Latest from HEAD

This will load the version from CVS and straighten out the version number by forcing Eclipse to match. It only loads into your editor however, and isn't changing the file on your local.

2: Right click the file again and select Replace with --> Previous from Local History

This will replace the contents in your editor with the file you actually saved in your file system with all your changes.

3: Right click the file and select Team --> Commit

It will successfully commit your changes.

This happened to me with a pom.xml TWICE a couple of weeks apart. The first time we fixed it by completely blowing away not only the file on my local, but also the version in the CVS repository. Then, we added the file back in. It made it possible to commit he changes, but now the unbroken file history was... well, broken. Obviously, the problem came back again later anyway. Time will tell if the procedure above will be permanent, and I'll post any further developments on it.

Wednesday, February 2, 2011

SEVERE: Error listenerStart

Yeah, this is a fun one.

SEVERE: Error listenerStart

goes right along with

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

You've built yourself a fabulous portlet, you've gotten it to build but it refuses to deploy. Your Liferay output tells you it's available for use but then turns right around and unregisters it.

Liferay 5.2.3

You've checked your jars, you've checked your versions, you're fighting your way through jar hell and are beating your head against the wall.

I know because that's how I spent my morning.

Do yourself a favor. Check the portlet name you've defined in your configuration files. Check portlet.xml. Check liferay-display.xml. Check web.xml. Compare that against your .war filename.

We recently modified our portlets to be Maven-buildable and while we were at it we also started using a portlet naming convention which tacks "-portlet" on the end of the .war file names. (So uberportletofdoom.war would instead be uberportletofdoom-portlet.war) I'd made this change in the pom.xml but not in any of my WEB-INF configuration files.

*KABOOM*

Liferay expects the .war file to be named consistently with your configuration file names.

Now go deploy your portlet and start debugging it.

(If that doesn't fix it, go on to this post.)