This is one of those things that seems really intimidating when you first start, and then even more intimidating when you start Googling it and realize that there isn't much out there to help you, and what there is... Isn't very helpful. Once again, those resources all take for granted that the reader knows everything there is to know (which leaves me wondering what the purpose of the resource is).
Novices need love too.
So here's the scenario: You need to stand up a Hudson system to handle your portlets (or whatever Liferay plugins) and have never used Hudson before.
Fear not.
It's actually not difficult at all.
Our setup here:
Server: CentOS
Portal: Liferay 6.1
Hudson: Eclipse Edition
Code Repo: GitHub
The first thing you must do is to get the latest installation guide from Hudson. I found The Hudson Book to be pretty good and easy to read.
FOLLOW THOSE INSTRUCTIONS TO THE LETTER. I'm not kidding about that. Do it. It's not complex. In the case of my team, I installed it on a CentOS box as a service. That gets you your basic Hudson installed.
Now, if this thing is living on the same box as your Liferay server, you'll need to tell Hudson to use a port other than the one your Liferay will be using. In my case, I set it to port 8585 by doing the following:
Go to /etc/sysconfig and open the "hudson" file in your text editor.
Change HUDSON_HOME from port 8080 to port 8585
Save it.
Now, in my case, I had a problem where Hudson wouldn't start because it was pointing to a Java 1.5 JRE on my system and it required at least 1.6. My JAVA_HOME variable was set to a Java 1.7 JDK but Hudson didn't use that. Beware. It looks in /usr/bin for your Java. Our quick fix was to put a symbolic link in there to make it point to the correct Java version.
Now, I'm going to assume here that you either already have Liferay installed on your server, or that you know how. There's plenty of info on that so go ahead and get that done then come back here.
All done? Ok. Next, you'll need the Liferay SDK on that machine. Install it and configure it, using Liferay's documentation, so that the SDK is pointing to the instance of Liferay on your server.
Here's what's going to happen. When Hudson pulls code from the repo, it's going to put it in the SDK and run the Ant command to use the build script that comes with Liferay to build and deploy the plugin. That means that the hudson user needs to have the correct permissions on the SDK. In our case, we simply changed the ownership of the SDK to a user group that the hudson user was a part of. Keep in mind this also applied to the deploy folder in Liferay, as the Hudson user needs to be able to copy the .war file into it, and whatever user you're running Liferay under needs to be able to pull the file from that folder and delete it.
Now you'll need to go into Hudson and start setting up jobs.
In the Hudson interface, click "New Job"
Give the job a name and make sure "Build a free-style software job" is selected, then hit Ok
Most of these settings are a matter of preference and tuning, so you won't really know how they should be until after you run this for a while. The ones you need in order to get Husdon working are the ones I'll mention.
Under Advanced Job Options (You may have to click the "Advanced" button to see this) check "Use custom workspace" and fill in the directory path where you want the code to go. This should be in your Liferay SDK. For example: "/opt/liferay/liferay-plugins-sdk-6.1.1/portlets/welcome-portlet" in the case of a portlet named "welcome-portlet."
Under Source code management, configure it to point to the project in your repo. In our case, that's Github, so I selected Git and gave it the URL to the project repo (remember to include the .git extension at the end).
If you want Hudson to automatically perform a build when the code rep is updated, you'll want to choose the Poll SCM trigger under "Build Triggers." The schedule field takes a basic cron expression. (For example, "0 * * * *" tells it to run every hour on the hour.)
Under the "Build" section, you'll need to use Ant. This is the default for how Liferay projects are built. (You'll need Ant installed on your server.) Select the Ant version you're using and in the "Targets" field simply enter "deploy." This tells Hudson to run the deploy target on Ant just as you would in the command line if you were building the portlets manually. When that happens, the plugin will be built and copied by Ant into the deploy folder and the process from Hudson's perspective is done.
Thursday, September 19, 2013
Friday, April 5, 2013
My ManagedProperty is null!
I've actually been told by a developer or two that I shouldn't use Managed Properties because "they don't work right."
The scenario:
JSF 2.0
Tomcat 6.0.32
Java 1.6
Imagine the following Managed Bean, which requires a value from another Managed Bean:
@ManagedBean(name = "downloadBacking")
@ViewScoped
public class DownloadBacking implements Serializable {
private static final long serialVersionUID = 4778576272893200307L;
@ManagedProperty("#{userModel.username}")
private String userID;
public DownloadBacking(){
System.out.println("UserID is: " + userID;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
}
What would the output be from the above code when the object is instantiated, if the value of userModel.username is "Magnus the Red"?
A) "UserID is: Magnus the Red"
B) Casting error
C) NullPointerError
D) "Ahriman had better watch out..."
The correct answer is C. This is why I'm told that ManagedProperty doesn't work right.
The truth is that ManagedProperty works just fine, if you understand the order in which things are happening. The problem here is that JSF used the setUserID() method to inject the value of userModel.username into the object. Well, that can't happen until the object has been instantiated, right? Well if the reference to the ManagedProperty is taking place in the constructor, how can it already have the value of the ManagedProperty in it if the object isn't even completely instantiated yet?
The solution here is to NOT put references to a ManagedProperty in your constructor. Instead, use the @PostConstruct annotation with an initialize() method to run your code AFTER the value has been injected into the newly created bean.
@ManagedBean(name = "downloadBacking")
@ViewScoped
public class DownloadBacking implements Serializable {
private static final long serialVersionUID = 4778576272893200307L;
@ManagedProperty("#{userModel.username}")
private String userID;
@PostConstruct
public void initialize(){
System.out.println("UserID is: " + userID;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
}
Now the output will be A).
The scenario:
JSF 2.0
Tomcat 6.0.32
Java 1.6
Imagine the following Managed Bean, which requires a value from another Managed Bean:
@ManagedBean(name = "downloadBacking")
@ViewScoped
public class DownloadBacking implements Serializable {
private static final long serialVersionUID = 4778576272893200307L;
@ManagedProperty("#{userModel.username}")
private String userID;
public DownloadBacking(){
System.out.println("UserID is: " + userID;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
}
What would the output be from the above code when the object is instantiated, if the value of userModel.username is "Magnus the Red"?
A) "UserID is: Magnus the Red"
B) Casting error
C) NullPointerError
D) "Ahriman had better watch out..."
The correct answer is C. This is why I'm told that ManagedProperty doesn't work right.
The truth is that ManagedProperty works just fine, if you understand the order in which things are happening. The problem here is that JSF used the setUserID() method to inject the value of userModel.username into the object. Well, that can't happen until the object has been instantiated, right? Well if the reference to the ManagedProperty is taking place in the constructor, how can it already have the value of the ManagedProperty in it if the object isn't even completely instantiated yet?
The solution here is to NOT put references to a ManagedProperty in your constructor. Instead, use the @PostConstruct annotation with an initialize() method to run your code AFTER the value has been injected into the newly created bean.
@ManagedBean(name = "downloadBacking")
@ViewScoped
public class DownloadBacking implements Serializable {
private static final long serialVersionUID = 4778576272893200307L;
@ManagedProperty("#{userModel.username}")
private String userID;
@PostConstruct
public void initialize(){
System.out.println("UserID is: " + userID;
}
public String getUserID() {
return userID;
}
public void setUserID(String userID) {
this.userID = userID;
}
}
Now the output will be A).
Tuesday, August 7, 2012
Including External jars in your Liferay Extension
This is one that I've seen asked about a lot online, and the solution is so painfully simple that I can only conclude that the people who know the answer aren't bothering to help out those who are asking.
That's what this blog is for.
The environment:
Liferay 6.0
Apache Ant
You're trying to use the ant deploy command either from within Eclipse or on the command line but you have a dependency on another .jar file you need to include in the compilation. If you do this in Eclipse, and include the .jar file in your project build path in Eclipse, the IDE will add a line to your any .classpath file pointing to this dependency.
The problem is that doesn't help your ant find the file.
If you need to include external .jar resources in your Ext, just drop a copy of the .jar you need in your Project/docroot/ext-lib/global folder. Your ant deploy target should now be able to use it fine.
That's what this blog is for.
The environment:
Liferay 6.0
Apache Ant
You're trying to use the ant deploy command either from within Eclipse or on the command line but you have a dependency on another .jar file you need to include in the compilation. If you do this in Eclipse, and include the .jar file in your project build path in Eclipse, the IDE will add a line to your any .classpath file pointing to this dependency.
The problem is that doesn't help your ant find the file.
If you need to include external .jar resources in your Ext, just drop a copy of the .jar you need in your Project/docroot/ext-lib/global folder. Your ant deploy target should now be able to use it fine.
Thursday, April 26, 2012
Phase Listener Not Running
This is another one of those times when becoming complacent with IDE tools will turn around and bite you.
Hard.
The environment:
IDE: Eclipse Helios SR2
Portal: Liferay 6.0.6
Development: JSF 2.0 (Mojarra)/ICEfaces 2.0
The Phase Listener I included in my portlet absolutely did nothing. No errors... No crashes... But it simply didn't respond in any way, shape or form.
Yes, it was configured in the faces-config.xml.
Yes, it implemented PhaseListener.
The problem turned out to be that I had allowed Eclipse to automatically add the unimplemented methods to the class when I was creating my PhaseListener. The problem was in this method:
@Override
public PhaseId getPhaseId() {
// TODO Auto-generated method stub
return null;
}
See the problem?
It should have said
@Override
public PhaseId getPhaseId() {
// TODO Auto-generated method stub
return PhaseId.ANY_PHASE;
}
I hadn't paid any attention to the method after it was auto-generated because I didn't need to modify it, so I didn't notice the problem.
It's a nice feature in Eclipse, but always verify that when it does things to save you some time, that it does them correctly.
Hard.
The environment:
IDE: Eclipse Helios SR2
Portal: Liferay 6.0.6
Development: JSF 2.0 (Mojarra)/ICEfaces 2.0
The Phase Listener I included in my portlet absolutely did nothing. No errors... No crashes... But it simply didn't respond in any way, shape or form.
Yes, it was configured in the faces-config.xml.
Yes, it implemented PhaseListener.
The problem turned out to be that I had allowed Eclipse to automatically add the unimplemented methods to the class when I was creating my PhaseListener. The problem was in this method:
@Override
public PhaseId getPhaseId() {
// TODO Auto-generated method stub
return null;
}
See the problem?
It should have said
@Override
public PhaseId getPhaseId() {
// TODO Auto-generated method stub
return PhaseId.ANY_PHASE;
}
I hadn't paid any attention to the method after it was auto-generated because I didn't need to modify it, so I didn't notice the problem.
It's a nice feature in Eclipse, but always verify that when it does things to save you some time, that it does them correctly.
Thursday, March 22, 2012
RichFaces Error: filterStart
RichFaces has recently moved into version 4, providing compatibility with JSF 2.0. Pretty cool.
The problem is that it can be a bit confusing when upgrading from version 3, or when following some of the slightly outdated documentation on the site.
If you've dutifully followed the getting started instructions for RichFaces when working with version 4, you probably added these lines to your web.xml file:
<filter>
<display-name>RichFaces Filter</display-name>
<filter-name>richfaces</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>richfaces</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
And yet you're still getting that horrible filterStart error, aren't you? No problem, my friends. Here's how to fix it:
Delete those lines from your web.xml.
Seriously.
The JSF 2.0 spec handles those configurations already. By adding those lines into your web.xml you're just going to confuse the poor thing.
The problem is that it can be a bit confusing when upgrading from version 3, or when following some of the slightly outdated documentation on the site.
If you've dutifully followed the getting started instructions for RichFaces when working with version 4, you probably added these lines to your web.xml file:
<filter>
<display-name>RichFaces Filter</display-name>
<filter-name>richfaces</filter-name>
<filter-class>org.ajax4jsf.Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>richfaces</filter-name>
<servlet-name>Faces Servlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
And yet you're still getting that horrible filterStart error, aren't you? No problem, my friends. Here's how to fix it:
Delete those lines from your web.xml.
Seriously.
The JSF 2.0 spec handles those configurations already. By adding those lines into your web.xml you're just going to confuse the poor thing.
SEVERE: Error listenerStart Part IV
By now you've gone through the other possible solutions on this site to get your JSF Portlet to deploy and are still seeing this evil, horrible, unholy error message and you're seriously contemplating changing careers...
It hit me this afternoon that there is yet another possible cause for this error that might catch the new developer off guard.
Which version of JSF are you using?
No, I don't mean the version number. I mean which implementation? Don't know? Better find out, because there are two very popular ones.
The first is Mojarra, which is the JSF implementation developed by Sun. It's the one I've used most often in my own JSF Portlets in Liferay. The other is MyFaces from Apache. They both comply with the JSF standard, and are usually interchangeable...
BUT
You have to make sure you know which one you're dealing with when you're editing your web.xml. When you configure your listener, you need to know which implementation you're working with so that you can configure it to call the correct class.
If you're using Mojarra, then your listener should look like this:
<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
If you're using Apache MyFaces, then it should look like this:
<listener> <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class> </listener>
Make sure you're configuring for the right implementation!
It hit me this afternoon that there is yet another possible cause for this error that might catch the new developer off guard.
Which version of JSF are you using?
No, I don't mean the version number. I mean which implementation? Don't know? Better find out, because there are two very popular ones.
The first is Mojarra, which is the JSF implementation developed by Sun. It's the one I've used most often in my own JSF Portlets in Liferay. The other is MyFaces from Apache. They both comply with the JSF standard, and are usually interchangeable...
BUT
You have to make sure you know which one you're dealing with when you're editing your web.xml. When you configure your listener, you need to know which implementation you're working with so that you can configure it to call the correct class.
If you're using Mojarra, then your listener should look like this:
<listener>
<listener-class>com.sun.faces.config.ConfigureListener</listener-class>
</listener>
If you're using Apache MyFaces, then it should look like this:
<listener> <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class> </listener>
Make sure you're configuring for the right implementation!
Tuesday, March 6, 2012
CILogon and Liferay Part 6: Signing Out
This is part 6 of a set of blog posts detailing a procedure for setting up CILogon to provide authentication for a Liferay Portal.
So by now you've got your Liferay signing you in using your CILogon provider and all is well. Your boss is pleased, and says "Can I try?" Of course, you say, and slide the demo laptop over to him. He clicks the "Sign Out" link in the upper right corner of the page and...
...you're still signed in. He slides it back over to you, smile fading as you start clicking that link over and over, but Liferay's got you in an iron grip and ain't letting go.
Time to go back to your extension.
See, Liferay has a Struts driven LogoutAction object that clears the session and kills all the Liferay cookies... But it's not killing your CILogon cookies because, well, you made them, not Liferay. We need to modify that class to include the cookies your CILogonAutoLogin module keeps using to log you back in.
Grab the following file from the Liferay source code: com.liferay.portal.action.LopgoutAction.java and put it into your ext plugin in the corresponding folder.
About halfway down the file is a series of lines of code adding cookie objects to CookieKeys. Right before those lines is a good place to add this code:
Cookie cILogonEmailCookie = new Cookie(
"CILOGON-USER_EMAIL", StringPool.BLANK);
if (Validator.isNotNull(domain)) {
cILogonEmailCookie.setDomain(domain);
}
cILogonEmailCookie.setMaxAge(0);
cILogonEmailCookie.setPath(StringPool.SLASH);
cILogonEmailCookie.setSecure(false);
Cookie cILogonNameCookie = new Cookie(
"CILOGON-USER_NAME", StringPool.BLANK);
if (Validator.isNotNull(domain)) {
cILogonNameCookie.setDomain(domain);
}
cILogonNameCookie.setMaxAge(0);
cILogonNameCookie.setPath(StringPool.SLASH);
cILogonNameCookie.setSecure(false);
CookieKeys.addCookie(request, response, cILogonNameCookie, false);
CookieKeys.addCookie(request, response, cILogonEmailCookie, false);
What's happening here is that we're replacing the cookies we created in SuccessServlet with new ones that will expire immediately. That way when the user comes back around in the request the CILogonAutoLogin module won't see the cookies and log the user back in.
Here's an important note... We're using the domain String object from the line
String domain = CookieKeys.getDomain(request);
earlier in the class. This is not the full string you may expect. For example, if this code is running on machine.mydomain.com, the domain that's returned here will be ".mydomain.com" so when you're setting this string in your SuccessServlet (back in part 2 of this series we called that variable TARGET_DOMAIN) make sure it matches!
I'm serious. It took me a day and a half to figure out why Liferay was able to find the cookie, and yet wasn't overwriting the old cookie with the new one. For this to work, the cookie domains must match exactly!
One quick note about setting that cookie back in SuccessServlet... I didn't go into specific detail but remember we NEVER hard code values! If your domain is hard coded in that servlet for testing purposes fine... But as soon as possible move that string value into a config file or database entry or something. Don't leave it in the compiled code.
--------------------------------------------
This project incorporates tools whose development was funded in part by the NIH through the NHLBI grant: The Cardiovascular Research Grid (R24HL085343)
Subscribe to:
Posts (Atom)