13 July, 2007

Portlets and GWT

I've tried to search any useful samples of development Portlets with GWT.
But I found only one such sample: Developing_Portlets_with_GWT.

Now I'm investigating possibility of development JSR168 Portlets together with Google Web Tolkit (I'm using JBoss Portal 2.6 and GWT 1.4).

This subject too interesting for me, and, I think, for many other developers, because:
  • GWT is very useful stuff for development AJAX- based web applications.
  • JBoss Portal is powerful and popular platform for this.

If these technologies work together, it will be great!

But there are problems and questions present on this way. I'm going to say about these problems in next posts.
This post about simple portlet based on GWT:


This portlet only receives time from server every second and repaints this time.
There are some source code presented below (I think it helps for someone).



Project files

./build.properties
./build.xml
./local.properties
./src
./src/WEB-INF
./src/WEB-INF/portlet-instances.xml
./src/WEB-INF/portlet.xml
./src/WEB-INF/web.xml
./src/WEB-INF/xlam-object.xml
./src/xqx
./src/xqx/web
./src/xqx/web/xlam
./src/xqx/web/xlam/client
./src/xqx/web/xlam/client/rpc
./src/xqx/web/xlam/client/rpc/XLamService.java
./src/xqx/web/xlam/client/rpc/XLamServiceAsync.java
./src/xqx/web/xlam/client/Time.java
./src/xqx/web/xlam/client/XLam.java
./src/xqx/web/xlam/public
./src/xqx/web/xlam/public/XLam.html
./src/xqx/web/xlam/server
./src/xqx/web/xlam/server/XLamServiceImpl.java
./src/xqx/web/xlam/XLam.gwt.xml
./src/xqx/web/xlam/XLamPortlet.java


portlet-instances.xml

<?xml version="1.0" standalone="yes"?>
<deployments>
<deployment>
<instance>
<instance-id>XLamPortletInstance</instance-id>
<portlet-ref>XLamPortlet</portlet-ref>
</instance>
</deployment>
</deployments>


portlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
version="1.0">

<portlet>
<portlet-name>XLamPortlet</portlet-name>
<portlet-class>xqx.web.xlam.XLamPortlet</portlet-class>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>VIEW</portlet-mode>
<portlet-mode>EDIT</portlet-mode>
<portlet-mode>HELP</portlet-mode>
</supports>
<portlet-info>
<title>XLam Portlet</title>
</portlet-info>
</portlet>
</portlet-app>


web.xml

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

<servlet>
<servlet-name>XLamService</servlet-name>
<servlet-class>
xqx.web.xlam.server.XLamServiceImpl
</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>XLamService</servlet-name>
<url-pattern>/XLamService</url-pattern>
</servlet-mapping>

</web-app>


xlam-object.xml

<?xml version="1.0" encoding="UTF-8"?>
<deployments>
<deployment>
<if-exists>overwrite</if-exists>
<parent-ref>default</parent-ref>
<page>
<page-name>XLam</page-name>
<window>
<window-name>XLamPortletWindow</window-name>
<instance-ref>XLamPortletInstance</instance-ref>
<region>left</region>
<height>0</height>
</window>
</page>
</deployment>
</deployments>


XLamService.java

package xqx.web.xlam.client.rpc;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import xqx.web.xlam.client.Time;

public interface XLamService extends RemoteService {

Time getTime();

public static class App {
private static xqx.web.xlam.client.rpc.XLamServiceAsync ourInstance = null;

public static synchronized xqx.web.xlam.client.rpc.XLamServiceAsync getInstance() {
if (ourInstance == null) {
ourInstance = (xqx.web.xlam.client.rpc.XLamServiceAsync) GWT.create(XLamService.class);
((ServiceDefTarget) ourInstance).setServiceEntryPoint(GWT.getModuleBaseURL() + "XLamService");
}
return ourInstance;
}
}
}


XLamServiceAsync.java

package xqx.web.xlam.client.rpc;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface XLamServiceAsync
{
void getTime(AsyncCallback callback);
}



Time.java

package xqx.web.xlam.client;

import com.google.gwt.user.client.rpc.IsSerializable;

public class Time implements IsSerializable
{
private String time;

public String getTime() { return time; }

public Time(String message) { this.time = message; }

public Time() { }
}


XLam.java

package xqx.web.xlam.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import xqx.web.xlam.client.rpc.XLamService;

public class XLam implements EntryPoint
{
private Timer timer;
private Label label = new Label("Wait...");

private class CallBack implements AsyncCallback
{
public void onFailure(Throwable caught)
{
timer.cancel();
Window.alert(caught.getMessage());
}

public void onSuccess(Object result)
{
label.setText(((Time) result).getTime());
}
}

private CallBack callBack = new CallBack();

public void onModuleLoad()
{
RootPanel.get("uid").add(label);

timer = new Timer()
{
public void run()
{
XLamService.App.getInstance().getTime(callBack);
}
};
timer.scheduleRepeating(1000);
}
}


XLam.html (dummy)

<html>
<head>
<title>XLam</title>
</head>
<body>
<script language='javascript' src='xqx.web.xlam.XLam.nocache.js'></script>
<div id='uid'></div>
</body>
</html>


XLamServiceImpl.java

package xqx.web.xlam.server;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import xqx.web.xlam.client.Time;
import xqx.web.xlam.client.rpc.XLamService;

import java.text.DateFormat;
import java.util.Date;

public class XLamServiceImpl extends RemoteServiceServlet implements XLamService
{
public Time getTime()
{
String out = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.FULL).format(new Date());
return new Time(out);
}
}


XLamPortlet.java

package xqx.web.xlam;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletSecurityException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class XLamPortlet extends GenericPortlet
{
protected void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, PortletSecurityException, IOException
{
renderResponse.setContentType("text/html");
PrintWriter writer = renderResponse.getWriter();
writer.println("<script language='javascript' src='" + renderRequest.getContextPath() + "/xqx.web.xlam.XLam.nocache.js'></script>");
writer.println("GWT time:");
writer.println("<div id='uid'></div>");
writer.close();
}

protected void doHelp(RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, PortletSecurityException, IOException
{
renderResponse.setContentType("text/html");
PrintWriter writer = renderResponse.getWriter();
writer.write("Help");
writer.close();
}

protected void doEdit(RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, PortletSecurityException, IOException
{
renderResponse.setContentType("text/html");
PrintWriter writer = renderResponse.getWriter();
writer.println("Edit");
writer.close();
}
}


XLam.gwt.xml

<module>
<inherits name='com.google.gwt.user.User'/>
<entry-point class='xqx.web.xlam.client.XLam'/>
<servlet path="/XLamService" class="xqx.web.xlam.server.XLamServiceImpl"/>
</module>




Build, deploy and view (http://localhost:8080/portal/auth/portal/default/XLam)

For building purpose you may use this Ant file:

build.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<project name="xlam" default="war">

<property file="local.properties"/>
<property file="build.properties"/>

<property name="build" value="build"/>

<property name="jarfile" value="${project.name}.jar"/>
<property name="warfile" value="${project.name}.war"/>

<path id="classpath">
<fileset dir="${jbossportal.lib}" includes="**/*.jar"/>
<fileset dir="${gwt.home}" includes="**/*.jar"/>
<fileset file="${jarfile}"/>
<pathelement location="src"/>
</path>

<target name="clean">
<delete dir="${build}"/>
</target>

<target name="jar" depends="clean">
<mkdir dir="${build}/bin"/>
<javac srcdir="src" destdir="${build}/bin" classpathref="classpath" debug="true"/>
<jar basedir="${build}/bin" jarfile="${build}/${jarfile}"/>
</target>

<target name="war" depends="jar, gwt">
<copy todir="${build}/war">
<fileset dir="${build}/www/${project.package}.${project.name}">
<exclude name="**/*cache.xml"/>
<exclude name="**/history.html"/>
</fileset>
</copy>

<copy todir="${build}/war/WEB-INF">
<fileset dir="src/WEB-INF"/>
</copy>

<copy todir="${build}/war/WEB-INF/lib">
<fileset file="${build}/${jarfile}"/>
<fileset file="${gwt.home}/gwt-servlet.jar"/>
</copy>

<jar basedir="${build}/war" jarfile="${build}/${warfile}"/>
</target>

<target name="gwt">
<mkdir dir="${build}/www"/>
<java classname="com.google.gwt.dev.GWTCompiler" fork="true" dir="${build}" classpathref="classpath">
<arg line="-out www ${project.package}.${project.name}"/>
</java>
</target>

<target name="shell" depends="jar">
<delete dir="${build}/www"/>
<mkdir dir="${build}/www"/>
<java classname="com.google.gwt.dev.GWTShell" fork="true" dir="${build}" classpathref="classpath">
<arg line="-out www ${project.package}.${project.name}/${project.name}.html"/>
</java>
</target>

<target name="undeploy">
<delete file="${jbossportal.node}/deploy/${warfile}"/>
</target>

<target name="deploy" depends="undeploy, war">
<copy file="${build}/${warfile}" todir="${jbossportal.node}/deploy"/>
</target>
</project>


build.properties

project.name=XLam
project.package=xqx.web.xlam
project.sources=xqx/web/xlam


local.properties (* You should replace paths with your real data)

jdk.home=E:/devenv/tools/jdk-1.5.0_04

jbossportal.home=E:/devenv/tools/jboss-portal-2.6
jbossportal.node=${jbossportal.home}/server/default
jbossportal.lib=${jbossportal.node}/deploy/jboss-portal.sar/lib

gwt.home=E:/devenv/tools/gwt-windows-1.4.10
gwt.docs.index=${gwt.home}/doc/html/gwt.html






You may download these sources from Xantorohara.blogspot.com samples

17 comments:

Ganesh Prasad said...

Hello,

Thanks for a very detailed and informative post. I created a demo exactly as you listed, with only changes to "local.properties" as required for my environment. The portlet side of the demo works perfectly. The View, Edit and Help modes and the Maximised, Minimised and Normal states are all working fine. But the GWT part of the demo doesn't work. No system time is being displayed, just a blank after "GWT Time:". What could be the problem? Any tips on what to look at or change when debugging?

Thanks in advance.
Ganesh Prasad

Xantorohara said...

Big thanks to Ganesh.

When I converted my source codes to html, I lost one string (which contain html tags) in file "XLamPortlet.java".

I corrected this post.

Ganesh Prasad said...

That fixed it, thanks :-).

I was beginning to suspect the same thing. I was wondering how the portlet would know about GWT without a reference to the JavaScript file, and just at that moment, you submitted your post! Spooky.

Cheers,
Ganesh

JeanV said...

Great tutorial. Anyway to pass user credential info to the GWT app from the JBoss Portal?

JeanV said...

I'm trying to deploy a Portlet+GWT app and I keep getting this error:

11:37:56,553 INFO [WebappClassLoader] validateJarFile(/Applications/dev/java/jboss-portal-2.6.1.GA/server/default/./tmp/deploy/tmp46341WebApplication25-exp.war/WEB-INF/lib/gwt-dev-mac.jar) - jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class
11:37:56,555 INFO [WebappClassLoader] validateJarFile(/Applications/dev/java/jboss-portal-2.6.1.GA/server/default/./tmp/deploy/tmp46341WebApplication25-exp.war/WEB-INF/lib/gwt-user.jar) - jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class

Do I need to modify any of the jars to avoid this conflict?

Xantorohara said...

I see that you include gwt-user.jar and gwt-dev-mac.jar into your application. But i think, you should use gwt-servlet.jar.
...
I didn't test my application on the Mac platform.

Gardeep said...

Thanks for all your help. Is there anyway to pass info about locale being used by the user to the GWT application. Let's say the portal user is using locale 'fr' - How would we get this info and set GWT to use this particular locale?

JeanV said...

Are you available for consulting work? Some small GWT + Portlet projects. Thanks

Santhi swaroop said...

Thanks a lot for ur project.It helped us very much... But we couldn't deploy our own application which has some images and some urls.We have deployed sample example "dynatable".But it didn't work

Anonymous said...

Nice post, but what about integrating to liferay instead of JBoss portal, especially the context resolution path, since JBOSS and Liferaye have slightly different ways of creating the Portlet.java class.

kedar said...

hi, thanks for detail post.
I am trying to integrate gwt with LIFERAY,i have followed same stapes,but getting blank after "GWT time:" .
cam you tell me what the problem is

maykel said...

Hello, I try to develop a portlet with GWT, I have an ask? what do you use for this, Netbeans??

Xantorohara said...

Hi, I use Vim (with enabled syntax highlightings and auto-completions) under Linux and IntelliJ IDEA under Windows. Both good for me...

Anonymous said...

Could you explain how the GWT RPC service is protected (from unauthorized access)?

Grant said...

This was one of the best, detailed listing of how to use GWT in a portlet I found... superb job I was able to take my code and make it work with JSR286 portlets in Pluto!

Erron said...

Grant,
What changes were necessary for to make 286 compatible? I assume your RPC calls are going through the portal.

tong123123 said...

cannot download the source from https://sites.google.com/site/xantorohara/xantorohara.blogspot.comsamples