Pluto portlet container notes
Pluto is the Apache Software Foundation's reference implementation of the Portlet container API JSR-168 http://jcp.org/aboutJava/communityprocess/final/jsr168/index.html.
This page contains some notes from my attempts to install Pluto, and to compile and run the Pluto tests using Eclipse.
Contents:
Contents
1. Pluto 1.0.1-rc4 installation and test
The Pluto project uses Maven, and comes ready with a project.xml file. However, the Maven configuration dows not work with maven 1.1 or 2.0, so use Maven 1.02:
Install Maven 1.0.2, and update MAVEN_HOME and PATH variables. (See MavenNotes)
Optionally, create a personalized build.properties in your home directory. (This will override anything specified in a project.) Running on Windows, I prefer to not store everything in my "home" directory, so I have used this to use a common directory for my "personalized" maven options (e.g. C:\DEV\Maven). Remember to use '/' or '\\' for filename path separators in the property file. (Again, see MavenNotes)
Download and unzip the Pluto source distribution (start [here http://portals.apache.org/pluto/mirrors.cgi]) into a working directory.
Create a file build.properties in the Pluto base directory, specifying the Tomcat installation directory (or, this may be specified in the home directory build.properties mentioned above.
- Run "maven fullDeployment" from the Pluto working copy base directory. First time this is run, it causes many files upon which Pluto depends to be downloaded from a public maven repository. (NOTE: doing this without creating build.properties causes new top-level directories to be created (common, shared, webapps).)
- Now start Tomcat.
First time I did this, I got errors relating to log4j.jar. I'm not sure of the exact cause, though I suspect a hangover from a previous uPortal installation, which installed its own version of Pluto to be run by Tomcat. In the event, I completely removed Tomcat, deleted all files from the Tomcat installation directory, reinstalled Tomcat and the Tomcat admin package, and re-ran the "maven fullDeployment" from the Pluto source kit base directory. This time, Tomcat started cleanly without log4j problems.
Browse to http://localhost:8080/pluto/portal. This a page is displayed with a number of test options. Select each of the tests in turn.
Initially, I tried browsing to http://localhost:8080/testsuite/jsp/, tried to run some of the test JSPs, and got all sorts of errors. Moral: RTFM!
Somewhere, it says that the "Application Scoped Session Attributes Test" requires the Tomcat conf/server.xml file to me modified with emptySessionPath="true". I edited conf/server.xml to contain:
<Connector port="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" emptySessionPath="true" />But this didn't work either.
- Log in to Pluto (or Tomcat?) for the Security mapping test to pass.
- Click "Dispatcher Render Parameter Test" in portlet 2 - test passes; then "Simple Parameter Test" in portlet 1 - this test passes, but the portlet 2 display changes to failed. Is this a software bug or a test bug?
2. Compile and run Pluto 1.0.1-rc4 in Eclipse
These instructions presume that Eclipse is has been installed and set up. I am using Eclipse version 3.2, with JDK 1.5.0-04
I found that first running "Maven fullDeployment" (see above) to ensure that all the dependencies were downloaded made it easier to import the project into Eclipse. This may have been an artefact of previous errors, and unnecessary, so YMMV.
- In the Pluto source base directory, run "maven eclipse".
First time, an error is displayed indicating a plugin cactus-maven cannot be downloaded (or something like that)
Google for cactus-maven yields: http://www.ibiblio.org/maven/cactus/plugins/. Locate the latest version of the jar file there and download to plugins subdirectory of Maven installation. (cf. appendix in Maven: A developer's notebook [O'Reilly].)
Now, re-running "maven eclipse" works, downloading a number of additional files in the process.
Start Eclipse, create MAVEN_REPO variable referencing the maven repository directory (Window -> Preferences -> Java -> Build path -> Classpath variables).
Import the project into Eclipse (File -> Import/Export -> Existing projects into workspace).
The project now exists in Eclipse, but the source directories and build path do not appear to be configured. Looking at http://maven.apache.org/reference/plugins/eclipse/properties.html there's reference to a configuration option for the Eclipse workspace; I'm using a non-standard location with my Eclipse, hence edit $USER_HOME/build.properties to include:
# Location for Eclipse workspace files maven.eclipse.workspace=D:/Cvs/Eclipse
And try re-building the eclipse project. Still no joy. Multiprojects are different. Googling for "maven eclipse multiproject" turns up some more info. The maven command is:maven -Dgoal=eclipse multiproject:goal
Which creates Eclipse project descriptions for all the sub-projects. Make sure that any .project and .classpath files left in the Pluto project root directory are removed. Now start Eclipse and import the projects (note: there are 5-6 of them) from the Pluto directory tree; there should be 5 of them.
Sub-project "pluto-descriptors", failed with this error: "The project was not built since its build path is incomplete. Cannot find the class file for org.apache.xml.serialize.OutputFormat. Fix the build path then try building this project". This problem was fixed by including "C:\Program Files\Java\jdk1.5.0_04\jre\lib\endorsed\XerxesImpl.jar" in the Java build path for this eclipse sub-project. (Project -> Properties -> Java build path -> L:ibraries -> Add external jars)
(Why doesn't Eclipse just pick up this lib/endorsed directory? It's part of the JRE specification http://java.sun.com/j2se/1.5.0/docs/guide/standards/index.html.)
Now I try to run unit tests under Eclipse. The only one that shows any sign of working is pluto-descriptors, which reports a number of failures (as well as some successes, so there's some progress). I think one problem may be a failure to read portlet.xml, possibly due to an incorrect working directory, but I couldn't be sure.
- Adding src/conf to the pluto-descriptors source path in Eclipse allows that test to pass. It turns out the problem was the castor mapping files not being on the classpath. The mapping files are apparently used for mapping Java objects when marshalling/unmarshalling, and would appear to be:
src/conf/org/apache/pluto/descriptors/portlet/castor-portlet-xml-mapping.xml src/conf/org/apache/pluto/descriptors/servlet/castor-web-xml-mapping.xml
- Adding src/conf to the pluto-descriptors source path in Eclipse allows that test to pass. It turns out the problem was the castor mapping files not being on the classpath. The mapping files are apparently used for mapping Java objects when marshalling/unmarshalling, and would appear to be:
I also needed to add $MAVEN_REPO/junit/jars/junit-3.8.1.jar to Java build path for modules "testuite" and "pluto-deploy", and now get "No tests found" when attempting to run the JUnit tests. This is looking like a dead-end.
Subsequently, in email discussion with Pluto developers, I found that the only unit tests in the Pluto source kit are those in package pluto-descriptors, so the above results are exactly what should happen.
3. Pluto 1.1 installation and test
My goal is to use Pluto and its test code to create a test environment for WSRP4J. The Pluto developers recommended that I use Pluto 1.1 for this purpose, since it has been refactored to make the code easier to follow. (I certainly found the Pluto 1.0 code seemed rather hard to follow in places.)
But Pluto 1.1 is new code, and as I write I am still struggling to get it to install on my system. Pluto 1.1 also uses Maven 2 for build and installation, so this must also be installed. Note that alpha- or beta- releases of Maven 2.0 don't work: the final release is required.
2005-11003: I have succeeded in getting Pluto 1.1 to install and run (with a few acknowledged errors in the test suite). I have encountered problems using Tomcat 5.5.12, so for not I'm using Tomcat 5.5.9, which seems to work OK. Here is the procedure I used for a clean install from source:
Prerequisites:
- Ensure Java is installed. I'm using version 1.5.0_04, and I have both JRE and JDK installed.
Install Tomcat 5.5.9 base package. On Windows XP, I use the Windows installer program (http://archive.apache.org/dist/tomcat/tomcat-5/archive/v5.5.9/bin/jakarta-tomcat-5.5.9.exe). I am installing to C:\Dev\Apache Software Foundation\Tomcat 5.5.9. I have configured Tomcat to use the JDK 1.5.0_04 installation. Check the installation by starting Tomcat and browsing to http://localhost:8080/.
Install the Tomcat admin utility (http://archive.apache.org/dist/tomcat/tomcat-5/archive/v5.5.9/bin/jakarta-tomcat-5.5.9-admin.zip) by unzipping it into a local directory, then moving the conf and server subdirectories to the Tomcat installation directory. Restart Tomcat and check the admin tools are installed by browsing to http://localhost:8080/admin.
Now to build and install Pluto:
- Retrieve Pluto 1.1 source from SVN. As I write, I'm at repository version 330525.
The repository is at: https://svn.apache.org/repos/asf/portals/pluto/trunk.
- Ensure the release version of Maven 2.0 is installed and accessible on the path. E.g., in a command window:
{{{>mvn -version
Maven version: 2.0 }}}
- In a command window, go to the base directory of the Pluto source code, and issue the commands:
{{{$> cd <PLUTO-1.1-HOME>
$> mvn install $> mvn pluto:install -DinstallDir="C:/Dev/Apache Software Foundation/Tomcat 5.5.9" }}} The directory in the last command should be replaced with the actual Tomcat installation location. (Note: at the time of writing, there is an error in the cd command in the instructions at http://portals.apache.org/pluto/1.1/getting-started.html).
- (On my system, I have environment variables defined for ANT_HOME, M2_HOME (Maven), CATALINA_HOME (Tomcat) and JAVA_HOME, but I don't think any of these are needed for the Pluto installation.)
Now restart Tomcat, and browse to http://localhost:8080/testsuite/. See "Hello world!" displayed. browse to http://localhost:8080/pluto or http://localhost:8080/pluto/portal, and see a page of test links. Click on the various links to test various aspects of the Portal. Some require manual inspection to determine whether or not they are working properly.
This completes the installation of Pluto 1.1.
4. Compile and run Pluto 1.1 in Eclipse
These notes made using Eclipse 3.2 (development stream stable release M2) on Windows XP with JDK 1.5.0_04.
There is a "Guide to using Eclipse with Maven 2.x" at http://maven.apache.org/guides/mini/guide-ide-eclipse.html.
Before creating an Eclipse project, I run MVN install to ensure that all relevant jars and other dependencies have been downloaded into the Maven repository.
- Go to the Pluto source base directory, and create an Eclipse project descruiption; e.g.
{{{$> CD D:\Work\OxfordCS\Pluto-1.1>
$> mvn eclipse:eclipse }}}
Eclipse .project files are created in the various subdirectories, but not in the root directory. (Eclipse doesn't grok nested projects in the same way as Maven.)
- Fire up Eclipse. I have chosen to create a new workspace for this work, to keep it well-separated from other unrelated Eclipse projects (there seems to be a problem if the workspace directory is a parent of the project directory, so create a new subdirectory outside the Pluto project tree. I have also chosen to stop Tomcat to free up system, resources for Eclipse.
Within Eclipse, use the command File -> Import/Export... to start the Import/Export wizard. Select General -> Existing projects into workspace. Select the Pluto source root directory as the import root directory. A list of pluto projects is displayed, with all selection boxes checked.
- If this message is seen: {{{Invalid project description.
D:\Work\OxfordCS\Pluto-1.1\maven-pluto-plugin overlaps the workspace location: D:\Work\OxfordCS }}}
- check that the Eclipse workspace is not a parent of the source directory tree.
The Pluto projects will load, and initially show many errors. Eclipse needs to be told where the Maven repository is located. To do this, select command Window -> Preferences -> Java -> Build path -> Classpath variables, and define a new variable M2_REPO whose value is the location of the Maven repository on the current system. (Mine is C:/DEV/M2/repository.) Eclipse will ask if a full rebuild should be performed: allow this to proceed; the build may take a few monutes, but when done the error indicators by all the Pluto projects should disappear (though many warnings may remain).
The following packages have JUnit test suites that may be run by selecting the package in Eclipse's package explorer window, then issuing the command Run -> Run as -> JUnit test. All these test suites should show "all green".
- pluto-container
- pluto-descriptor-impl (The absence of pluto-testsuite from this list is because that package is not a JUnit test suite, but a suite of test portlets that are installed and deployed by the standard Pluto installation.)
4.1. Updating Pluto 1.1 Eclipse projects
Sometimes, the project structure or details may change between Pluto source releases. To obtain the latest version, and get it to build in eclipse, perform the following:
- Update the SVN source respository
- Run the maven installation procedure
{{{$> cd <PLUTO-1.1-HOME>
$> mvn install $> mvn pluto:install -DinstallDir="C:/Dev/Apache Software Foundation/Tomcat 5.5.9" }}}
- Re-build the Eclipse project files:
- {{{mvn eclipse:eclipse
}}}
Start Eclipse, and allow (or force) all projects to rebuild. If errors are shown, select each of the failed projects and perform a refresh operation (or press key F5). Eventually, all errors should be flushed out.
Updating the Pluto software may mean that the configuration files change. My unit tests use a selective copy of the Pluto driver/container/portlet configuration data, and it may be necessary to update this before the tests can be successfully run.
5. Request flows in Pluto 1.1
The details here are based on the test portal driver and test suite provided with the Pluto 1.1 distribution, and installed by the instructions given above, but my hope is that the general elements of the request flow are more generally applicable.
With Pluto installed and configured into Tomcat, and with Tomcat running, viewing page http://localhost:8080/pluto/portal arrives at a call into PortalDriverServlet, by the following route:
conf/localhost/pluto.xml
pluto-portal.war/index.jsp
redirect to http://localhost:8080/pluto/portal/
web.xml <servlet-mapping>
servlet PlutoPortalDriver
web.xml <servlet> <servlet-name>
org.apache.pluto.driver.PortalDriverServlet.
Class PortalDriverServlet is the point of entry into the portal system:
Once it receives a request, it gathers configuration information, processes any requested actions, and determines which resource is used to generate content for that page (pageConfig.getUri()). A request dispatcher is then retrieved for that resource, and the request is handed off.
The configuration information that populates pageConfig.getUri() resides in WEB-INF/pluto-portal-driver-config.xml. This config file (amoung other things) defines the Pages and their backing URIs which can be used to generate content. From this file, one can determine that "Test Page" is rendered by /WEB-INF/fragments/portlet.jsp.
pluto-portal/src/main/webapp/WEB-INF/fragments/portlet.jsp contains:
{{{<c:set var="include" scope="request" value="portlet-page.jsp"/>
<jsp:forward page="template.jsp"/> }}}
pluto-portal/src/main/webapp/WEB-INF/fragments/template.jsp contains:
{{{<TABLE>
<TR><TD>
<jsp:include page='<%=(String)pageContext.findAttribute("include")%>'/> </TD></TR>
</TABLE> }}}
- (noting that variable "include" was previously set to "portlet-page.jsp").
pluto-portal/src/main/webapp/WEB-INF/fragments/portlet-page.jsp contains:
<TABLE> <c:forEach var="portlet" varStatus="status" items="${currentPage.portletIds}"> <c:if test="${status.index % 2 == 0}"> <TR> </c:if> <TD valign="top"><c:set var="portlet" value="${portlet}" scope="request"/><jsp:include page="portlet-skin.jsp"/></TD> <c:if test="${status.index % 2 != 0}"> </TR> </c:if> </c:forEach> </TABLE>
pluto-portal/src/main/webapp/WEB-INF/fragments/portlet-skin.jsp contains:
<pluto:portlet portletId="${portlet}"> <TABLE class="portlet" border="1"> <TR class="banner"><TD><pluto:title/></TD> <TD><A href="<pluto:window windowState="minimized">">min</pluto:window></TD> <TD><A href="<pluto:window windowState="maximized">">max</pluto:window></TD> <TD><A href="<pluto:window windowState="normal">">nor</pluto:window></TD> <TD><A href="<pluto:window portletMode="help">">help</pluto:window></TD> <TD><A href="<pluto:window portletMode="edit">">edit</pluto:window></TD> <TD><A href="<pluto:window portletMode="view">">view</pluto:window></TD></TR> <TR><TD colspan="7"> <pluto:render/> </TD></TR> </TABLE> </pluto:portlet>(so this appears to generating no more than the option links across the top of the test portlet window and then calling <pluto:render/>, which presuably invokes the test portlet which in turn generates all the "Test" links that are seen).
referring back to pluto-portal/src/main/webapp/WEB-INF/pluto-portal-driver-config.xml, we see:
{{{<render-config default="Test Page">
<page name="Test Page" uri="/WEB-INF/fragments/portlet.jsp">
<portlet context="/testsuite" name="TestPortlet1"/> <portlet context="/testsuite" name="TestPortlet2"/>
</page> <page name="Secondary Page" uri="/WEB-INF/fragments/portlet.jsp">
<portlet context="/testsuite" name="TestPortlet1"/> <portlet context="/testsuite" name="TestPortlet2"/>
</page> <page name="About Pluto" uri="/WEB-INF/fragments/about.jsp"/>
</render-config> }}} TO BE CONFIRMED -- which suggests that the content rendered by <pluto:render> is obtained from portlets (servlets?) named TestPortlet1 and TestPortlet2.
Looking at testsuite/WEB-INF/web.xml (in the running Tomcat directory $CATALINA_HOME/webapps, we see that servlets named TestPortlet1 and TestPortlet2 are configured as using java class org.apache.pluto.core.PortletServlet.
File pluto-container/src/main/java/org/apache/pluto/core/PortletServlet.java contains the code:
- {{{PortletAppDD appDD = portletContext.getPortletApplicationDefinition();
List dds = appDD.getPortlets();
PortletDD dd = null; for (int i = 0; i < dds.size(); i++) {
- PortletDD pd = (PortletDD)dds.get(i); if (pd.getPortletName().equals(portletName)) {
- dd = pd; break;
} }}}
- which appears to lookup the portlet name in a configuration file, and activate a servlet using information obtained there.
The correspondimng configuration file appears to be pluto-testsuite/src/main/webapp/WEB-INF/portlet.xml (why? is this hardwired?), and contains:
{{{<portlet>
<description>TestSuiteDescription</description> <portlet-name>TestPortlet1</portlet-name> <display-name>Test Portlet #1</display-name>
<portlet-class>org.apache.pluto.testsuite.TestPortlet</portlet-class>
<init-param>
<name>config</name> <value>/WEB-INF/testsuite-config.xml</value>
</init-param>
- :
</portlet> }}}
This in turn refers to file pluto-testsuite/src/main/webapp/WEB-INF/testsuite-config.xml, which contains the required information for creating and accessing the various test portlet pages; e.g.
{{{ <testsuite-config>
<name>Dispatcher Parameter Test</name> <class>org.apache.pluto.testsuite.test.DispatcherRenderParameterTest</class> <display-uri>/jsp/test_results.jsp</display-uri>
</testsuite-config>
<testsuite-config>
<name>Simple Attribute Test</name> <class>org.apache.pluto.testsuite.test.SimpleAttributeTest</class> <display-uri>/jsp/test_results.jsp</display-uri>
</testsuite-config>
}}}
The portal.xml file also references the class contained in pluto-testsuite/src/main/java/org/apache/pluto/testsuite/TestPortlet.java, which appears to read the configuration file, and do some magic with it which could well be rendering the test links and displaying the pages. I can see calls of doTest, which are implemented by a class called AbstractReflectivePortletTest.
The test portlet front page is rendered by a JSP in file portal/WEB-INF/fragments/template.jsp, which is forwarded from portal/WEB-INF/fragments/portlet.jsp, which in turn is selected by (where?).
6. Creating Pluto 1.1 unit tests
My goal in what follows is to create a number of unit tests that drive test cases in the Pluto test portlet in the context of JUnit test rather than via HTTP from a browser. As well as avoiding the overhead of running via a web application server, this also makes it easier to trace the portal and portlet code using a debugger such as that which is part of the Eclipse IDE.
My strategy has been to use ServletUnit, a component of HttpUnit, to replace the Tomcat servlet engine, and to use simulated web requests scripted and tested as JUnit test cases that call the simulated container.
Unfortunately, HttpUnit ships with a version of servletapi.jar that is not compatible with Pluto and/or Tomcat 5.5. (As far as I can tell, HttpUnit is using the Servlet API version 2.2 or 2.3, but Tomcat 5.5 uses servlet API version 2.4 which, among other things, has the JSP 2.0 API separated from the servlet API). Running the Pluto portal driver with the Tomcat 5.5 JSP libraries causes a java "Method not found" error: class javax.servlet.jsp.tagext.TagAttributeInfo has an additional constructor in JSP 2.0 that is used by the Tomcat JSP compiler. For now, I have overcome the problem by dint of some surgery on the servlet.jar file that is part of the Httpunit distribution, removing all the JSP classes so that they can be picked up from a separate jar file that implements the JSP 2.0 API ($M2_REPO/jspapi/jsp-api/2.0/jsp-api-2.0.jar).
(I tried swapping the later servlet-api.jar file into the HttpUnit class path, but that resulted in a compilation error to the effect that a required abstract base class method was not implemented by one of the Httpunit classes.)
I made another change to the ServletUnit code, in file /httpunit/src/com/meterware/servletunit/ServletRunner.java, adding a new constructor for the ServletRunner class, thus:
/**
* Constructor which expects an input stream containing the web.xml for the application,
* a File indicating a context directory, and a string indicating a context path.
*
* [[[GK]]] Added to allow circumvention of file: URI creation bug in javax.xml.parser
*
* @param webXml the web.xml input stream
* @param contextDir a file object corresponding to the context directory
* @param contextPath the context path
*
* TODO: refactor other constructors to use this and eliminate duplicate code
**/
public ServletRunner( InputStream webXML, File contextDir, String contextPath )
throws IOException, SAXException
{
_application = new WebApplication( HttpUnitUtils.newParser().parse( new InputSource( webXML ) ),
contextDir, contextPath );
completeInitialization( contextPath );
}This constructor allows me to specify the working directory for the servlet code (contextDir) separately from the context URI (contextPath). Within the test harness driver code Testcase class instance, the ServletRunner instance is created thus:
protected void setUp() throws Exception {
super.setUp();
PropertyConfigurator.configure("src/test/resource/log4j.properties");
File webxmlfile = new File("src/test/resource/WEB-INF/web.xml");
File contextfile = new File("src/test/resource");
String contextpath = "/pluto";
InputStream webxmlstr = new FileInputStream(webxmlfile);
runner = new ServletRunner(webxmlstr, contextfile, contextpath);
runner.registerServlet("plutoPortalDriver",
PortalDriverServlet.class.getName());
client = runner.newClient();
}These changes allow the Pluto portal driver to initialize itself in the ServletUnit environment, but are not enough to allow invocation of a portlet. Portlet invocation initially appears to work, but when the response is analyzed, the rendered data from the portlet is replaced by a Java exception and stack trace. (Data rendered by the portal itself is fine: it is just the portlet-rendering that is not correct. The cause for this appears to be that Servletunit does not support multiple applications, and when the portal driver attempt to retrieve a context for the portlet servlet, it receives a null pointer instead (see ServletUnitServletContext.getServletContext). Also, there is some additional configuration data in webapps/testuite/WEB-INF/web.xml that maps portlet invoker context paths to the testsuite servlets; but it is not obvious how to configure Servletunit to handle multiple wab application paths and configurations.
I observe that the ServletUnit implementation of ServletContext uses a WebApplication value to determine the location of a resource for getResourceAsStream, suggeting that multiple WebApplications must be created, and a common directory of path->ServletContext values be maintained in order to serve different servlets from different context directories. I created a static Hashtable value in WebApplication could serve this purpose, and modified ServletUnitServletContext.getServletContext to access the corresponding WebApplication, and then use this map returning a context value.
With this change, the application ran a good deal further, but the portlet code running in a separate servlet is unable to access the tag library code portlet.tld. I am unable to see how the location of the TLD files should be passed to the portlet, so I coped them into the test suite web application area (testsuite/WEB-INF/tld/), and modified the test suite configuration file (testsuite/WEB-INF/web.xml) to reflect this location, thus:
<!-- Copied from pluto/WEB-INF/web.xml, together with tld directory -->
<taglib>
<taglib-uri>http://java.sun.com/portlet</taglib-uri>
<taglib-location>/WEB-INF/tld/portlet.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://portals.apache.org/pluto</taglib-uri>
<taglib-location>/WEB-INF/tld/pluto.tld</taglib-location>
</taglib>With these changes, I am able to render the portlet test cases in the unit test environment, and to test the results thus generated.
7. References and links
http://portals.apache.org/pluto/mirrors.cgi: Pluto source distribution.
https://svn.apache.org/repos/asf/portals/pluto/trunk: Pluto 1.1 SVN repository.
http://www.developer.com/java/web/article.php/3563411: Embedding Apache Pluto gives an overview of how Pluto 1.1 is constructed, and how to incorporate the portlet container into a Portal application, thus providing a high-level overview of how the Pluto software is organized.
http://www.developer.com/java/web/article.php/3554396: Developing Portlets with Apache Pluto, article covers configuration and deployment of portlets.
http://httpunit.sourceforge.net/: Httpunit and Servletunit testing software.
http://www.doc.ic.ac.uk/~mo197/portlets/: Some more notes about Portlets and Pluto.
-- GrahamKlyne 2005-11-03 11:52:40

