Tuesday, December 14, 2010

ICEfaces User Session Timeout Aggravation

"User Session Timeout" is an ugly thing when you're trying to work your ICEfaces controls on a Liferay portlet.

The vitals:
ICEfaces 1.8.1
Liferay 5.2
Tomcat 6

Your portlet renders fine but when you try and use one of the controls, like changing the value of an ice:selectOneMenu control for instance, you get the dreaded session timeout.

As with so many of these issues, there seems to be an endless variety of solutions out there. In our case, we're talking specifically about a situation where Liferay is NOT installed as the ROOT context on your Tomcat.

To fix this, you need only go into your [Tomcat Home]/conf/server.xml file and find the relevant Connector setting. That's usually the one that says Connector port="8080" protocol="HTTP/1.1"...

Add the following attribute:

emptySessionPath="true"

This is something of a workaround, but it is the accepted solution. For more info, click here.


Monday, November 29, 2010

The Ghost EL of JSF and Liferay

Yes, this one is a nightmarish tale of woe, of pain, of suffering and of a desk with a vague dent in it shaped roughly like a forehead...

The scenario:

You're deploying a portlet into Liferay with Tomcat that's built with some flavor of JSF or another and when you watch the output during the deploy you see a message that says something like this:

java.lang.LinkageError: loader constraint violation: when resolving interface method "javax.servlet.jsp.JspApplicationContext.getExpressionFactory()Ljavax/el/ExpressionFactory;" the class loader (instance of org/apache/catalina/loader/WebappClassLoader) of the current class, com/sun/faces/config/ConfigureListener, and the class loader (instance of org/apache/catalina/loader/StandardClassLoader) for resolved class, javax/servlet/jsp/JspApplicationContext, have different Class objects for the type javax/el/ExpressionFactory used in the signature

Now, in Googling this beast of an error you will see a lot of very helpful advice that usually revolves around "Take the el-api.jar file out of your WEB-INF/lib folder in your project."

See, the conflict arises when Tomcat already has an el-api.jar file in its lib folder so when you try to bring yours to the party it causes a collision.

"But wait!" You say. "I did that! I went into my pom.xml and made sure to exclude that dependency so now it doesn't show up in my .war file and still I get the error!!!!!!!111111111"

So you've verified that there's no sign of that el-api.jar file in your .war file's WEB-INF/lib folder? You sure?

Yes? ok then here's what you do next:

Go to your webapps folder in Tomcat. see your portlet folder in there? Open it. Now go into the WEB-INF/lib folder inside it.

Yeah. You see it, don't you?

See, back when you deployed your portlet the first time and put that el-api.jar file there, it caused the initial problem. Later, when you re-deployed your project without that .jar, it got deployed but it didn't completely blow away the old installation. That's why it's best to make sure it's gone when you re-deploy. How? Just delete the portlet's folder from webapps and wait a minute or two. Liferay will notice it's gone then unregister the portlet. You can now safely deploy your portlet again.

Thursday, November 18, 2010

That Video is Too Small!

So your portlet that shows videos works great, you keep replaying it over and over 'cause you're proud of it, and your boss thinks it's pretty cool except...

"... it's so small... Can't really even see those Flash controls. Any way to have the user be able to make it bigger, like maybe full screen sized as when they click to maximize the portlet? That way it can be small and out of the way when they aren't watching it!"

Short answer: Yes.

In Liferay you can maximize a portlet to make it easier to see or more usable. The problem is maximizing the portlet makes the PORTLET bigger but that Flash video isn't going to grow with it. What you need is code that will cause the video to grow with the portlet.

Fortunately, this IS possible. It's easy, too.

First, take a look at your HTML code for your object. (In this case, it's an ice:outputMedia control.) If you don't have it already you're going to need a style attribute to set the width and height of your video. Usually it looks something like style="width: 300px; height: 300px;"

If you don't have that, add it. If you have it, change it to look like this:

style="width: #{videoDisplay.width}px; height: #{videoDisplay.height}px;"

Yes, we're going to do this with JSF/ICEfaces. If you're not using that tech then just substitute whatever JSP tag you want that'll make those values dynamic.

Next, you're going to need to add some members to your backing bean.

private int width, height;
private boolean maximized;
private static int MAX_WIDTH = 900;
private static int MAX_HEIGHT = 900;
private static int MIN_WIDTH = 300;
private static int MIN_HEIGHT = 300;

and some setters and getters for those non-static ints.

public void setMaximized(boolean maximized) {
if(maximized){
width = MAX_WIDTH;
height = MAX_HEIGHT;
}
else{
width = MIN_WIDTH;
height = MIN_HEIGHT;
}
this.maximized = maximized;
}

public boolean isMaximized() {

return maximized;
}

public void setWidth(int width) {
this.width = width;
}

public int getWidth() {
return width;
}

public void setHeight(int height) {
this.height = height;
}

public int getHeight() {
return height;
}

"But wait!" You say. "There's actual processing being done in your setter! Isn't that bad practice?"

Some say it is, some say it isn't. Know what I say? Whatever makes the code cleaner and simpler. You'll see the method (pardon the pun) to my madness in a moment.

Make sure you initialize your width and height too, by the way. Either put it in your backing bean's constructor or init() method or wherever you set things up.

if(this.isMaximized()){
width = MAX_WIDTH;
height = MAX_HEIGHT;
}
else{
width = MIN_WIDTH;
height = MIN_HEIGHT;
}

Now, the tricky part is that we want the maximization to be determined before the page renders. That means you're going to need to build a phase listener class if you don't have one already. Here's a basic one:

package com.myproject.listener;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.render.ResponseStateManager;
import javax.portlet.PortletPreferences;
import javax.portlet.RenderRequest;
import javax.portlet.WindowState;

public class VideoPhaseListener implements PhaseListener {

private static final long serialVersionUID = 1L;

public void afterPhase(PhaseEvent pe) {
//some implementation
}

public void beforePhase(PhaseEvent pe) {

VideoDisplay videoDisplay = (VideoDisplay) facesContext
.getApplication().getELResolver().getValue(
facesContext.getELContext(), null, "videoDisplay");

if (pe.getPhaseId() == PhaseId.RENDER_RESPONSE) {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
RenderRequest renderRequest = (RenderRequest) externalContext
.getRequest();
PortletPreferences prefs = renderRequest.getPreferences();
videoDisplay.setMaximized((renderRequest.getWindowState().equals(WindowState.MAXIMIZED)));
}
}

public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
}

See what's happening? Before the Render Response phase we're getting the portlet state from the Render Request. So if the user has clicked the maximize button, the state is MAXIMIZED and we want the backing bean to use the maximized constant values we set up earlier. Otherwise we want the minimums.

See now why our setMaximized() setter had that bit of processing? When we change the value of maximized in the backing bean we want to immediately change the width and height to match. Could I have used the setWidth() and setHeight() methods here and set them all from the Phase Listener? Well yeah I could have but this way is cleaner and makes the backing bean (videoDisplay) manage itself. It isn't the job of the Phase Listener to handle the details of what goes on in the backing bean. Its job here is to report the state of the portlet. Period.

One last step: Make sure you configure your new listener in faces-config.xml.

<lifecycle>
<phase-listener>
com.myproject.listener.VideoPhaseListener
</phase-listener>
</lifecycle>

So when the page response is rendered, it'll use the values appropriate for the window state.

Now, you could take this a step further and have those static width and height values come in from some sort of configuration and avoid hard coding, but you get the idea.

Monday, November 15, 2010

I Want to Watch Videos in Liferay! Part II

So you've uploaded your videos into the Liferay 5.2 Document Library but now what? How to view them?

Well, one way is to build a portlet.

A little caution here. The video format you choose is going to impact what happens when you try and view it. Different browsers handle this stuff in different ways. One browser might play your content in the designated video viewer while another treats it like a file download. Your best bet is to use a viewer component that plays Flash videos, and stick to those.

So what now?

Choose a component that will display your video. The simplest way is to embed the video object like this:

<object type="application/x-shockwave-flash" style="width: 200px; height: 200px;" data="http://someurl"/>

And there you go.

You can also get a little fancier and use a control like ICEsoft's outputMedia control. It's a little more complicated but you can do a lot more with it programmatically. You can see how it works by going to the ICEfaces website and looking at the Component Showcase.

Then your view page will have markup that looks a little like this:

<ice:outputMedia
id="outputMedia"
player="flash"
source="#{videoDisplay.source}">
<f:param name="play" value="true"/>
</ice:outputMedia>

Again, be aware of your browser and format environment. Choose "windows" as your player to view .wmv files and it'll work great in Internet Explorer, but try it in Chrome and you'll get a download instead. As I said before I strongly recommend Flash here.

The problem is, of course, to know what to put in that source URL. Notice in this second example instead of a hard-coded URL for the source I'm using a JSF reference to a String stored in my backing bean. Why? Because I'm generating the source URL dynamically, based on some configuration options selected by the user. You could hard code a URL in there if you wanted. Personally, I despise hard coding anything. (You could also use a JSF reference for the simpler embedded video object above.)

So how to get at that file?

When you request a document from a Liferay Document Library the URL has to be of a specific structure. Here's an example:

http://localhost:8080/c/document_library/get_file?uuid=67874de5-d480-45d6-82dd-610e9d1a783e&groupId=10136

If you're interested, you can find this value by going into your Document Library portlet, clicking the "Actions" button, and selecting "View." It will display the actual URL of that file.

You can generate this URL programmatically by using the Liferay Service Layer. Here's a simple method to grab those details:

private String getUrl(long selectedFileId){

DLFileEntry file = com.liferay.portlet.documentlibrary.service.DLFileEntryLocalServiceUtil.getFileEntry(selectedFileId);

return "http://" + hostname + "/c/document_library/get_file?" +
file.getUuid() + "&groupId=" +
file.getGroupId();
}


Notice the hostname isn't hard coded either. That should be a configuration option unless you plan to deploy this portlet on only one server, ever.

"Great!" You say. "But how do I get the file id?"

Well, one way is to browse the folders in the Document Library. Suppose we wanted to choose a video file from a folder in the Document Library named "Previews"

List folderSet;
long selectedFolderId;
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
RenderRequest renderRequest = (RenderRequest) externalContext .getRequest();

long companyId = PortalUtil.getDefaultCompanyId();
(Make sure this goes in a try-catch block.)

folderSet = com.liferay.portlet.documentlibrary.service.DLFolderLocalServiceUtil.getFolders(companyId);
folders = new ArrayList();
for(DLFolder folder : folderSet){
if(folder.getName() .equals("Previews")){
selectedFolderId = folder.getFolderId();
}


Now that you've got the folder you want, you can get the details from each file in it. Let's say we know the name of our video file is "trebuchet.swf" and it's in the folder we found above.

(Make sure this goes in a try-catch block too.)

String videoName = "trebuchet.swf";

DLFileEntry video = com.liferay.portlet.documentlibrary.service.DLFileEntryLocalServiceUtil.getFileEntry(selectedFolderId, videoName);

now you can use that DLFileEntry object to get the file Id to pass into the method we created above:

getUrl(video.getFileEntryId());

And there you have it. Make sure your component can access it through your backing bean. Maybe initialize your bean with all that code and finish with something like this:

this.source = getUrl(video.getFileEntryId());

Now, I'm not touting this as the very best possible way to do it, and if anybody out there has a better idea please, pass it along. This works, though. So it'll get you results.

In my video portlet, I used ice:selectOneMenu controls to let the user browse the folders in the Document Library, then choose the video file they'd like to view.

Wednesday, November 10, 2010

I Want to Watch Videos in Liferay! Part I

So you want to create a portlet that runs in Liferay 5.2 that will play videos stored in your Document Library.

There are a series of hurdles that you'll have to overcome to be able to do this, however. Nothing too horrible, but it's easy to underestimate what a pain it can be.

First, Liferay is kind of picky about what kinds of files can be uploaded into it. By default it won't accept videos of certain formats and the file size is fairly limited anyway. Luckily, this is an easy fix.

Do you have a portal-ext.properties file in your Liferay home directory? No? Create one. If you don't know what this file is for, it's a configuration file that will override the settings that are in Liferay by default. Create the empty file, name it portal-ext.properties, and add the following line to it:

dl.file.max.size=3072000

This will give you more maneuvering room for uploading videos. You can set that to whatever value is best for your project.

Next, you have to tell Liferay what file formats to accept. Add this line:

dl.file.extensions=.bmp,.css,.doc,.dot,.gif,.gz,.htm,.html,.jpg,.js,.lar,.odb,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.ppt,.rtf,.swf,.sxc,.sxi,.sxw,.tar,.tiff,.tgz,.txt,.vsd,.xls,.xml,.zip,.avi,.mov,.wmv

Make sure you have no carriage returns in that file if you're copying and pasting this! If you wind up with newlines in that parameter list it'll ignore everything after it.

Now restart Liferay.

You should now be able to upload video files with the above formats into your Document Library.

Not too bad, eh?

Monday, November 8, 2010

JavaScript errors in IE with ICEfaces and Liferay.

JavaScript errors in Internet Exploerer?!?!?!? Surely not!

Yes, friends. It is my sad duty to report that yes indeed, Internet Explorer may have JavaScript issues when using ICEfaces in Liferay when Firefox and Chrome do not.

But then, if you found this blog because you Googled that, you already knew it, and now you'd like me to stop gabbing and tell you the solution.

Oh I get it.

So here's the scenario:
Liferay 5.2
ICEfaces 1.8.1

You create some nifty little portlet that installs into Liferay fine and displays fine and... uh oh... some of the functionality doesn't work and you get that irritating little icon in the bottom left corner of IE telling you there's a JavaScript error on the page. Clicking it to show the error gets you nothing but a message telling you "Error: Object expected" and what line the error occurred on.

Useless, ain't it?

So here's the thing: According to this post:

http://www.liferay.com/community/forums/-/message_boards/message/5441603#_19_message_3413693

Liferay and ICEfaces compress .js files and this does not make Internet Explorer happy. If you want your portlet to run in IE, you have to disable this feature.

Go into your web.xml file and add the following:

<context-param>
<param-name>com.icesoft.faces.compressResources</param-name>
<param-value>false</param-value>
</context-param>

And try your app again. That hideous little error should be gone.

Sunday, October 31, 2010

Googled this one yet? "Data truncated for column 'createDate' at row 1"

This is an interesting case study.

So you're facing a problem in your Liferay 5.2 install (running on Apache Tomcat) where you can't add threads to the message boards, reply to threads, add or edit WIki pages... nada. Everytime you try you get the error message saying the portlet is temporarily unavailable.

Looking in the Catalina.out log you see that an error was logged, preceding a series of exceptions. The error: "Data truncated for column 'createDate' at row 1"

The createDate field in question is in the table SocialActivity in the lportal database.

The backstory:

A Liferay 5.2.3 portal site was to be upgraded to Liferay 6. The new portal was configured to point to the old portal database and the upgrade process began. For whatever reason the upgrade failed so the old 5.2 portal was restarted until the problem could be fixed. Mysteriously, people could suddenly no longer add any social content, like message board threads or Wiki pages.

Here's the problem. In Liferay 5.2 the SocialActivity table's createDate column is a DATETIME field. In Liferay 6 it's a BIGINT. When the upgrade process was running it changed the column type. By going back to Liferay 5.2 and trying to add social content, we're asking the portal to store a DATETIME value into a BIGINT field.

KABOOM.

So that field type needs to be changed back to DATETIME or a backup of the database needs to be restored. (You did backup the database, right?)

Wednesday, October 27, 2010

My Portlets Won't Register in Liferay.... Why is this?

Yeah here's another one that will astound you with the sheer variety of possible answers out there on the web. Head over to Liferay.com and search the forums and watch the show.

The reason is, I suppose, because this is one of those problems that really can be caused by a number of different things and maybe a different approach really does work for different people.

The problem:

You drop a portlet .war file into your Liferay 5.2.3 hot deploy folder. (Running in an Apache Tomcat 6 server bundle.) The output console indicates that the file was copied successfully and that portlet installation will begin in a few seconds...

...

...

...

...and after a while those few seconds have turned into several minutes, and still no further activity from Liferay. Oh, everything else seems to work okay but you won't be able to use that portlet. It hasn't been registered.

It looks like the cause of this is somewhere in the myriad of temoporary files and caches something's making Liferay stumble. Your mission, if you choose to accept it: Find the block and eliminate it. I admit this is a somewhat shotgun-like approach but hey, it's all good for the server anyway, right?

Browse on over to [Tomcat Home Folder]/conf/Catalina/localhost/

See an xml file in there named after the portlet you're trying to deploy? Delete. (Making "pew-pew" sounds as you do it will relieve some tension here. Ignore the reactions of your co-workers. You're on a mission.)

OK now head back up to [Tomcat Home Folder]/temp/

Any folders in there? Destroy them. "pew-pew."

Now go into [Tomcat Home Folder]/webapps/

Any folders in there corresponding to any of the portlets you've been having trouble with? "pew-pew."

Now start your Liferay back up.

Once it's up, go into Control Panel and on down to Server Administration. Do some regular maintenance stuff like Clearing the cached content, reindex your serach indexes, run garbage collector.

Now try that hot deploy again.

Epilogue: This fix was only temporary. It allowed me to deploy some pre-existing portlets but my ICEfaces enabled portlet project still brought it down again. I suspect it's because I changed my build path in that portlet (and if you've been reading my blogs you know what that'll do to a server joined to Eclipse) anyway. Suddenly it can't find resources etc etc etc.

I blew away that Liferay install and started up a fresh Liferay install and hot deployed my custom portlet. It worked fine this time.

Update: 12/8/2010 If you follow the above steps and top it all off by going into the Liferay Control Panel and Reindexing the Search Indexes, you can get it to hot deploy your portlet, but you may have to go through this process each time you want to re-deploy unless you can just re-install Liferay.

Monday, October 25, 2010

It's a Layout Template Not a Portlet!!!!!

This was an interesting one so I thought I'd share.

This morning I was building some layout templates for Liferay 5.2.3 with the plugins sdk and a simple text editor. (Wordpad, if you must know. Yes, I'm in Windows these days.) I made the changes I needed, zipped it up into a .war file and dropped it into my hot deploy folder.

I opened up Liferay on my localhost to witness the majesty that was my layout template and... Nothing. It didn't appear in the list of choices.

So I re-opened the files and made sure everything was correctly formatted and that I hadn't missed anything in my configuration or code. Hot deploy again and...

Nothing.

So I opened up the terminal window where the Tomcat output was running and yes indeed, it had successfully imported, installed and made my portlet available for use.

Yes, I said portlet.

Now that's interesting... Just for giggles I clicked "Add Application" from the Dock and in the filter box started typing the name of my awesome layout template... And there it was.

Interesting.

So Liferay thinks my layout template is a portlet. I wondered... what if I clicked "add..."

It added a portlet. It added a portlet with the text "The requested resource (/3_columns_i_5-layouttpl/3_columns_i_5layouttpl/invoke) is not available" in it.

Yeah I know, right?

So I started to compare the liferay-plugin-package.xml file with that from a known (and tested) working template I downloaded from Liferay. All the same. Code in the individual files... all the same. WTH???

Now some of you, by now, may have figured out what I did wrong. For those that haven't go ahead and take a guess. I haven't really given enough information to make it blatantly obvious since I wanted to save it as a surprise but try anyway. I'll wait right here.

Back already, eh? Ok I'll tell you.

When I created my .war file I included the docroot directory in the structure. (This is what I get for trying to be fast and slick.) The proper contents of a layout template .war file is what's inside the docroot folder.

Apparently when Liferay 5.2.3 sees docroot in a .war file it thinks you're deploying a portlet and runs with it.

Oh, and for those of you who don't know the quick and dirty way to generate a .war file... Just put your stuff in a .zip file and change the extension to .war.

Just don't include docroot if it's a layout template...

Friday, October 22, 2010

ContextEventRepeater Can Serve as a 4-letter Word

I've seen a LOT of people have trouble with this one. You get the dreaded NoClassDefFoundError and it's pointing right at com.icesoft.faces.util.event.servlet.ContextEventRepeater.

There's a variety of forum posts on this monster over at www.icesoft.org but the causes of this particular error seem to be wide and varied, and even after checking all the things they tell you to check you can still wind up slamming your head against the keyboard until the spacebar is smeared with blood and hair.

I've been tooling around with Eclipse Ganymede (That's version 3.4.2 if you care) with ICEfaces 1.8.1. I got this hideous error myself today and the cause, in my case, is absolutely bizarre.

I'd been building my little app and getting some other, unrelated, strange errors when I decided that what I needed to do was to move my build output from the default /build to /WEB-INF/classes. Sounds peachy, right?

Well, wouldn't you know, that triggered the hideous error. At first I didn't think there could POSSIBLY be a connection so I did the search/read/headbang thing for about 2 hours before I decided to try to switch that output folder back.

Guess what?

That fixed the error.

Normally I try to follow up these little nuggets with an explanation of why that happened but I have absolutely no idea. I presume it has something to do with how Eclipse tells the server where the .jars are but I really am stumped. If anybody would like to E-mail me with that or post a comment on why, it would be very much appreciated.

Tuesday, October 5, 2010

Making MySQL Install - or - WHY CAN'T I FIND THE PACKAGE?!?!?!?!?!?

So you're looking to install MySQL on your Ubuntu machine because you know how much MySQL rocks or because you want to link it up with Liferay or both. (Or neither. I dunno.)

There's a plethora of advice on the web on how to do this. "Easy!" They tell you "All you have to do is use the command

sudo apt-get install mysql-server

and you're good to go!"

So you happily get on your terminal, type in that command, enter your root password and all that and...

E: Can't find package mysql-server

*sigh*

So you restart your machine because one guy was able to get the packages to appear in the Synaptic Package Manager after doing that and that doesn't help. So you search for it in Synaptic and that doesn't help either. The fate of the world rests in you getting MySQL installed and suddenly that wonderful apt-get feature ain't looking too hot.

Well don't start the manual installation just yet, friends.

See, the problem is that MySQL is (as of the time I write this) on version 5.1 and that's included in the package name. No, you might not see it in Synaptic either. Use this command:

sudo apt-get install mysql-server-5.1

And watch the magic happen.

Friday, September 17, 2010

New OWA Users Can't Sign In. WHY?!?!?!?!?!?!?!

Ok so this one isn't really web development related but I'm putting it in here anyway. It's not like they're charging me by the blog post, right?

Right?

Er.. yeah so anyway here's another example of a time when you just want a simple answer to a simple problem and Googling for it is like asking for a repair manual for a car when all you need is to know where the oil dipstick is.

Here's the scenario. You're the friendly, happy sysadmin for your company running an Exchange 2003 E-mail server. You've been asked to create a new user account for some lucky new employee who will be accessing his mail using OWA. You create the account, set the password, open up your web browser to test and here's what happens:

You enter the credentials when prompted but it shrugs that off and asks again. You check to make sure you didn't fat finger anything and enter the credentials again. It shrugs that off too so you try one more time and all you get is an empty web page that says "Error: Access is Denied."

So you close the browser and re-open it and try again. Same thing.

So you go back into Exchange and reset the password to cover that base and you try logging in again. Same thing.

What's the problem?

Well you Google that and you'll get a laundry list of protocols, permissions, settings and inheritance to check over. Well that's great but what if everybody else's account is working and this new guy's isn't? Isn't there something simple you can check?

Yes there is.

Open up the properties for that user and click the "Account" tab. Look under "Account Options." See all the checkboxes? See the one on top? "User must change password at next logon" is checked, isn't it?

Yeah. Thought so. Uncheck it.

See, depending on how your protocols are set up, your server may be unable to prompt a user through the browser to change his or her password when they're trying to authenticate. If your user absolutely must login for the first time using OWA, they're going to have to change that password manually once they're logged in.

Tuesday, August 24, 2010

Eclipse Ganymede, Portlets and Weird Errors.

Ok so you're doing some of the training exercises from Liferay and you're all excited about working with Liferay 6. (You are, aren't you? Isn't everyone?) You're doing the classic Pitcher-Catcher portlets to practice with inter-portlet communication to show off the Portlet 2.0 specs and everything's going great when...

...You're getting weird errors in Eclipse Ganymede and can't seem to fix them.

The first one is in your view.jsp files where this line

taglib uri = "http://java.sun.com/portlet_2_0" prefix = "portlet"

Is erroring out. Specifically, it says "Cannot find the tag library descriptor for "http://java.sun.com/portlet_2_0"

And nothing you've done has made that go away. You've tried making sure you have the right util-taglib.java file in your build path and nevertheless it won't go away. What's wrong?

Blame Eclipse.

Now, I love Eclipse. I think it's the cat's meow and I don't see myself ever using anything else, but Ganymede isn't perfect and it has a few warts. This is one of them. The solution:

Ignore it. It's a bug but it won't mess up your ability to build and deploy it with Ant.

"But wait!" You say. "I have another error in my jsp. in the line

String pitch = (String)renderRequest.getParameter("pitch");

it has an error saying 'renderRequest cannot be resolved' so how do I fix that?"

Your portlet.jar file is where it's supposed to be, right?

"Right!"

Then ignore that one too. Run your Ant deploy. See, Eclipse is noticing that in that .jsp file there's no declaration for renderRequest so, in its neurotic wisdom, it has assumed that you simply failed to declare the variable and it errors it. That won't stop it from building your project since Java code in .jsp files doesn't get compiled until the first time the page is requested, and Tomcat's got the definition it needs for renderRequest. It'll be fine.

Batter up!

Thursday, July 15, 2010

Liferay 6 Plugins SDK and the Deploy Path

So I'm excited to start creating and deploying portlets in the new Liferay 6 portal, so even though I've built portlets for Liferay 5 already I wanted to go step by step in version 6 as if I'd never done it before, in case there were differences.

I'm doing this in Linux Ubuntu, although that shouldn't matter in this case.

Well as always, getting the SDK set up is pretty easy but when we say "easy" what we really mean is "Done in very few steps." It still assumes an awful lot about your situational awareness, as it were, as well as the adaptability of the tools you're working with.

This is Liferay Plugins SDK 6.0.2 by the way, and the Liferay itself is bundled with Tomcat 6.

When setting up your environment the best practice, according to the manual written by Richard Sezov, Jr (which I'm enjoying very much, by the way), is to have a folder structure that looks a bit like this:

[Code Home]/bundles/[Liferay Bundle]
[Code Home]/plugins

Now, this seems pretty clear, doesn't it? Just drop your unzipped Liferay Portal bundle into /bundles then alongside that your unzipped SDK. Simple, right? Well yeah, and no.

This notation is a bit confusing. When I set up a Liferay portal bundle on my development machine I like to keep the name of the original unzipped folder. The reason for this is at this moment I actually have 3 different Liferay bundles on my development computer, any one of which I may need to use at any time, and by keeping the original folder names I keep them straight and avoid confusion. This is what my structure looks like:

[Code Home]/liferay-portal-6.0.2/tomcat-6.0.26
[Code Home]/liferay-plugins-sdk-6.0.2

There is a side effect to this, you're going to have to fix your build properties. You see, the default configuration in the build.properties file in your SDK is assuming that your Liferay bundle is in a folder called /bundles and the actual tomcat home folder is inside that. In other words, it thinks you're renaming /liferay-portal-6.0.2 to /bundles. You could do that if you want to, but I prefer to keep my development structure consistent so I changed the default. To do that:

Create a file in the SDK directory and name it build.[your username].properties. In this file add the line:

app.server.dir=${project.dir}/../liferay-portal-6.0.2/tomcat-6.0.26

This overrides the line in build.properties that sets the app.server.dir variable to ${project.dir}/../bundles/tomcat-6.0.26.

Nice, huh?

Now when you run the create.sh to start building your new portlet it will follow the structure you've created.




Friday, May 21, 2010

Where is that EntityManagerFactory?

Ok so how much do you hate this error:

java.lang.NoClassDefFoundError: Ljavax/persistence/EntityManagerFactory;

Yeah, on a scale of 1 to 10 I'd rank that one about a 12. You're using Eclipse Ganymede, you've checked your Java build path and there are your jars. (Mine are in a User Library I named Eclipselink) You've checked the /bin folder in your Tomcat and there they are. You go to run your app and

KABOOM

EntityManagerFactory eludes you and your application.

By this point much of your hair may be in clumps on the floor around your chair, much to the disgust of the weekly cleaning crew. People are avoiding your desk at all costs because you're radiating frustration like the outer layers of a star going nova and still no solution presents itself.

And now for the solution:

Eclipse knows you want to use those particular jars, but it may have forgotten where they are. In the Project Explorer view expand your user library and right click your persistence jar. Select Properties. In the new window that pops up the dominant feature will be a text field labeled "Location path:" and if it's empty, BINGO that's your problem.

Click the "External File..." button and browse to the actual jar. Click "OK." Do this for each of the jars in your user library while you're at it.

Don't worry, the hair will grow back.

Thursday, April 22, 2010

Making JPA Play Nice With a DataTable

It can be done!

No... really... it can!

Lesson learned for today... The following two statements:

Query query = em.createQuery("Select p from Chapters p");
Query query = em.createNativeQuery("Select * from chapters");

are NOT interchangeable.

I'm serious.

don't believe me? Go ahead and try it. Do a

query.getResultList();

and then feed that output to a JSF DataTable and watch what happens.

The first one will work. The second: KABOOM.

You see, the getResultList() method returns a List. Simple, right? Nah. Not really. You see, at issue here is what, exactly that List contains.

Using the EclipseLink SQL-like language in the createQuery does two things: First, it abstracts the database so that the command will run no matter what kind of backend you're using. Second, it returns a List of objects of the type specified in the query. In the first statement, the createQuery method, the word 'Chapters' is capitalized because it refers to a class not a table. It will return a List of type Chapters (in this case) and when we wire that up later to the DataTable component it will happily cast the objects as Chapters objects (As specified in the faces-config.xml) and display them.

On the other hand, running that createNativeQuery method is asking for trouble. It's straight SQL, and in this example would run fine in prettymuch any database, but not all databases are created equal and the more complex your query is, the more committed you are to the database you originally wrote it for. Know what the other problem is? Yep, by now you should have guessed it... you'll get back a List of Objects.

Good luck trying to cast those as Chapters objects.

The EL isn't going to be much help so a list of generic Objects gets passed to your poor, unsuspecting DataTable and KABOOM! You'll be Googling this: "java.lang.NumberFormatException: For input string:"

See, when this big list of generic objects gets sent to the DataTable it doesn't know how to interpret these things, the EL is relying on the Chapters bean for guidance on finding the fields but these aren't Chapters objects. They're generic lumps of data that came from a database. It appears the EL is trying to stick the index of the elements into the first field you've defined in your table. That's an int. Bad mojo.


Wednesday, April 21, 2010

persistence.xml and the Errors Who Love it.

Have you ever done a Google search for a problem you're having and all of the replies you find in various blogs, forums and tutorials say basically the same thing?

Yeah, me too.

That's what I did when looking for the reason why my EclipseLink JPA project wasn't behaving itself as a web app.

Search for this string sometime:

javax.persistence.PersistenceException: No Persistence provider for EntityManager named

And you'll get a fascinating myriad of responses, most of which will tell you where to put your persistence.xml file (META-INF under WEB-INF/classes in the case of a web app, by the way, although typically your IDE will do that for you.) but what if you've done that already?

Some more will tell you to make sure you include the line

org.eclipse.persistence.jpa.PersistenceProvider

in your persistence.xml file and still others will tell you to verify that you're using the same Persistence Unit name in

persistence-unit name="myJPAname"

and the line in code where you're instantiating your EntityManager

emf = Persistence.createEntityManagerFactory("myJPAname");

But what if you've done all of that?

What to do?

Ready for the silly, stupid facepalm solution?

Make sure it's in your servlet container/web server lib folder.

For example, my Tomcat 6.0 has a folder in:

/home/mylogin/Portals/training_tomcat/apache-tomcat-6.0.20/lib

Yeah. Make sure your eclipselink and persistence jar files are in there too.

Eclipse Generates it, you fix it.

Lesson learned...

So as I continue my mad scientist like exploration of JPA, specifically EclipseLink, I have learned that while the Eclipse API offers a great deal of automation and management, you still have to watch it closely unless you want to spend time ripping your hair out.

Take the example I ran across this morning.

OS: Linux Ubuntu 9.10
Database: MySQL 5.1
API: Eclipse Ganymede
JPA Provider: EclipseLink

This was a simple console app designed to play around with columns while learning the EclipseLink SQL-ish query language. I created a table called "chapters" in my database designed to hold some simple data related to a wargame. (You Warhammer 40,000 fans will like this.) Columns include chapter_name, parent_legion, primarch... obviously all VARCHAR type columns.

One of my columns is called loyalist. It's a BIT column. Hold that in your mind for now...

Now, Eclipse has a nice little feature that allows you to automatically generate entities from your database. Very cool. Run the utility and watch that entity class grow. Nifty.

2 problems:

The first problem is that EclipseLink is going to assume your table name is in ALL CAPS. This is easy to get confused on because the select statement in a case like this reads:

Query query = em.createQuery("Select p from Chapters p");

That syntax is correct because "Chapters" here refers to the entity Chapters, not the table. That means that you're not directly controlling the name of the table the actual SELECT command will be run against. So if your table is named in all lowercase, like mine, running this code will blow up your app.

The solution: Pretty simple, actually. You just have to use an annotation to override the table name. Just add something like this under your @Entity annotation:

@Table(name="chapters")

Good to go. Now EclipseLink will run a SELECT command against table name "chapters" and not "CHAPTERS."

The second problem is a little easier to solve and yet I find it more annoying. When Eclipse generated the entity and tripped across that BIT column, it created a matching member in my class and made it type byte.

KABOOM.

So I manually changed it to boolean in my entity code and now it's happy as a clam.

Tuesday, March 23, 2010

And so it begins...

For the first few years of my time as a Web Developer I lived in the world of .NET. C# was my main language and still my favorite language to program in to date. I originally learned to program in Java during my time as a Computer Science major at the University of Maryland. These days I'm in Java again and have had several crash courses in new technologies in the last few months.

The purpose of this blog is to take what I'm working on as I tailor the information and technologies for the environment and procedures I'm using now, and share my findings with the world. I'll be using a great deal of detail since not everybody who goes searching for this information will necessarily be a guru. I know when I'm researching a new technology my greatest frustration is articles written with the assumption that you're already familiar enough with it that they can skip a lot of detail. This blog will be my remedy for that.

Stay tuned!