Categories
bdg dev2dev

Searching Intrinsic Properties in .NET

I won’t waste your time with any introductory drivel — if you want that, you can read the other post. Let’s get right into the code.

Plumtree.Remote.PRC.Search.IntrinsicPortalField.cs:

using System;
using com.plumtree.remote.prc.search.xp;
using com.plumtree.remote.prc.search;

namespace Plumtree.Remote.PRC.Search
{
 public class IntrinsicPortalField : Field
 {
  private IntrinsicPortalField(IntrinsicXPPortalField xpField) : base(xpField)
  {
   ;
  }

  public static readonly IntrinsicPortalField EMAIL_ADDRESS = new
   IntrinsicPortalField(IntrinsicXPPortalField.forID(26));
 }
}

com.plumtree.remote.prc.search.xp.IntrinsicXPPortalField.cs:

using System;

namespace com.plumtree.remote.prc.search.xp
{
 public class IntrinsicXPPortalField : XPField
 {
  private IntrinsicXPPortalField(String name, bool isSearchable, bool isRetrievable) :
   base(name, isSearchable, isRetrievable)
  {
   ;
  }

  public static IntrinsicXPPortalField forID(int propertyId)
  {
   return new IntrinsicXPPortalField("ptportal.propertyid." + propertyId, true, true);
  }
 }
}

Enjoy!

Categories
dev2dev Featured Posts Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

A Slick Alternative to pt:standard.openerlink

For one reason or another your bosses (we all have more than one, don’t we?) have told you that the look-n-feel of the common object opener in ALUI just doesn’t cut it. Even though it’s powerful, scalable and pretty nice-looking and it includes a myriad of options (e.g search, browse, single vs. multi-select, set previously selected, etc.), they just want something different. Perhaps they don’t want a pop-up window. Perhaps they don’t like how many clicks it takes to get down to an object. Perhaps they’re just being difficult.

Regardless, you’ve been asked to come up with a clean, fast, in-place object selector that still shows a hierarchical view. (For the purposes of this discussion, I’m going to use the example of communities from here on out instead of just talking about “objects.”) So naturally, as a portlet developer, you turn to the IDK. Unfortunately, if you want to get portal metadata, you have to use the PRC/SOAP server. There goes fast. So maybe you can write it in native code or using database calls. There goes clean.

Your best bet here — and really the only good way to accomplish this — is to develop a custom taglib. Custom taglibs are quickly becoming my favorite new feature in G6. (BTW, if you aren’t on G6, upgrade ASAP — it’s worth the effort.) So, for your benefit, I decided to try my hand at writing a taglib to present a nice hierarchy of communities. Here’s what I discovered.

First off, let’s talk about the HTML I want to display in my custom tag for a moment. Whoever came up with the concept of select boxes and optgroup elements was a complete goofball. Why develop something that’s naturally suited for a hierarchy and then limit the hierarchy to a depth of one?

Here’s an example:

So I had to throw my initial idea of using nested option elements out the window simply because you can’t nest an optgroup within an option. Bummer.

So here’s the display I settled on:

There’s still a hierarchy here, it’s just flattened and there’s essentially a “breadcrumb” for each community. In the example I have, bdg is a top level community and services is a subcommunity of bdg. Consulting, development, integration and training are all subcommunities of services.

Alrighty then, so how to you construct this nice select box? And BTW, make it easy, clean and fast. Here you go:

package com.bdgportal.alui.taglib

import com.plumtree.openlog.OpenLogService;
import com.plumtree.openlog.OpenLogger;
import com.plumtree.portaluiinfrastructure.tags.*;
import com.plumtree.portaluiinfrastructure.tags.metadata.*;
import com.plumtree.xpshared.htmlelements.*;
import com.plumtree.server.*;

public class CommunitySelector extends ATag
{
   private static OpenLogger log = OpenLogService.GetLogger(
    OpenLogService.GetComponent("UI_Infrastructure"),
    "com.bdgportal.alui.taglib.CommunitySelector");

 public static final ITagMetaData TAG;
 public static final RequiredTagAttribute SELECT_ID;
 public static final RequiredTagAttribute SELECT_NAME;
 public static final OptionalTagAttribute SELECT_CLASS;
 public static final RequiredTagAttribute ROOT_FOLDER_ID;

 static
 {
  TAG = new TagMetaData("communityselector",
    "Displays a community selector.");

  SELECT_ID = new RequiredTagAttribute("id",
    "The id of the select box.",
    AttributeType.STRING);

  SELECT_NAME = new RequiredTagAttribute("name",
    "The name of the select box.",
    AttributeType.STRING);
 
  ROOT_FOLDER_ID = new RequiredTagAttribute("rootfolderid",
    "The root folder. All communities in this folder and below " +
    "will be displayed.",
    AttributeType.INT);

  SELECT_CLASS = new OptionalTagAttribute("class",
    "The CSS class of the select box.",
    AttributeType.STRING, "objectText");
 }

 public HTMLElement DisplayTag()
 {
  HTMLSelect comms = new HTMLSelect(
    GetTagAttributeAsString(SELECT_NAME),
    GetTagAttributeAsString(SELECT_ID));  

  comms.SetStyleClass(GetTagAttributeAsString(SELECT_CLASS));
  recursiveAddComms(((IPTSession)GetEnvironment().GetUserSession())
   .GetCommunities(), ((IPTSession)GetEnvironment()
   .GetUserSession()).GetAdminCatalog(), comms,
   GetTagAttributeAsInt(ROOT_FOLDER_ID), "");
 
  return comms;
 }

 public ATag Create()
 {
  return new CommunitySelector();
 }

 public TagType GetTagType() {
  return TagType.NO_BODY;
 }
 
 private void recursiveAddComms(IPTObjectManager commObjMgr,
  IPTAdminCatalog adminCatalog, HTMLSelect comms,
  int folderId, String prefix) {
 
  //CAB: add the communities at this level, if any
  IPTQueryResult commsToAdd = commObjMgr.SimpleQuery(folderId,
   PT_PROPIDS.PT_PROPID_NAME);
  for (int i = 0; i < commsToAdd.RowCount(); ++i) {
   comms.AddOption(new HTMLOption(
    Integer.toString(commsToAdd.ItemAsInt(i, PT_PROPIDS.PT_PROPID_OBJECTID)),
    prefix.substring(0, prefix.length() - 3)));
  }
 
  IPTAdminFolder adminFolder = adminCatalog.OpenAdminFolder(folderId, false);
  if (0 == adminFolder.QuerySubfoldersCount()) {
   return; //CAB: base case
  } else {
   IPTQueryResult subFolders = adminFolder.QuerySubfolders(
    PT_PROPIDS.PT_PROPID_OBJECTID + PT_PROPIDS.PT_PROPID_NAME
    + PT_PROPIDS.PT_PROPID_FOLDER_FOLDERTYPE,
    0,
    PT_PROPIDS.PT_PROPID_NAME,
    0,
    -1,
    null);
 
   //CAB: recurse into each subfolder
   for (int i = 0; i < subFolders.RowCount(); ++i) {
    recursiveAddComms(commObjMgr, adminCatalog, comms,
     subFolders.ItemAsInt(i, PT_PROPIDS.PT_PROPID_OBJECTID),
     prefix + subFolders.ItemAsString(i, PT_PROPIDS.PT_PROPID_NAME)
     + " : ");
   }
  }
 }
}

I think the code pretty much speaks for itself, but if you want further explanation, let me know by posting a comment.

Comments

Comments are listed in date ascending order (oldest first)

  • Iiiinteresting. This is very cool, Chris. I might be forced to highjack this and turn it into a pt:data tag 🙂 You have any thoughts on how / where you might approach caching with this? Have you seen the EOD sample tag?

    Posted by: ewwhitley on August 31, 2006 at 9:41 AM

  • Hmmm . . . caching. It’s so fast OOTB that I didn’t think about caching it. 🙂 Plus, I used it on a project where we have fewer than 100 communities, so I didn’t have any problems. I mean, we’re not using the PRC, right? I guess if you wanted to cache it you could doink around with the shared variables in the tag library (session scope), but you’ve got to worry about clearing the cache after a fixed interval of time.

    Posted by: bucchere on August 31, 2006 at 10:31 AM

  • Awesome! Thanks Chris. I just spent about 2 hours last night trying to do almost the exact same thing. This is great!

    Posted by: jturmelle on May 17, 2007 at 7:48 AM

Categories
dev2dev Featured Posts Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Searching Intrinsic ALI Properties Using the PRC

There’s a problem with the IDK PRC API for search that’s tripped up users in the dev2dev forums and that stymied me for the first time today while coding up a custom search application for one of our customers.

The problem is that there’s a hardcoded limitation in the IDK that prevents you from calling PortalField.forID if the passed in object ID is less than 100. This prevents you from searching on some really useful properties, including e-mail address! For the life of me, I can’t figure out why this limitation was imposed.

The good news is that I found a workaround. It involves a quick two-file IDK patch that entails subclassing two classes. The only catch is that you need to put the child classes in the same package as the IDK (because the parent classes have package-private constructors).

Here’s the source code that does the trick.

com.plumtree.remote.prc.search.IntrinsicPortalField.java:

package com.plumtree.remote.prc.search;

import com.plumtree.remote.prc.search.PortalField;
import com.plumtree.remote.prc.search.xp.*;

public class IntrinsicPortalField extends PortalField {
  private IntrinsicPortalField(IntrinsicXPPortalField xpField) {
    super(xpField);
  }

  public static final IntrinsicPortalField EMAIL_ADDRESS;

  static {
    EMAIL_ADDRESS = new IntrinsicPortalField(IntrinsicXPPortalField.forID(26));
  }
}

com.plumtree.remote.prc.search.xp.IntrinsicXPPortalField.java:

package com.plumtree.remote.prc.search.xp;

import com.plumtree.openfoundation.util.XPIllegalArgumentException;

public class IntrinsicXPPortalField extends XPPortalField {

  private IntrinsicXPPortalField(String name, boolean isSearchable, boolean isRetrievable) {
    super(name, isSearchable, isRetrievable);
  }

  public static IntrinsicXPPortalField forID(int propertyId) throws XPIllegalArgumentException {
    return new IntrinsicXPPortalField("ptportal.propertyid." + propertyId, true, true);
  }
}

I used e-mail address (ID = 26) as an example, but you can put any properties in there that you want. Then, when you’re setting up your search filter, just use IntrinsicPortalField instead of PortalField. For example:

IFilterClause filter = searchFactory.createOrFilterClause();
filter.addStatement(IntrinsicPortalField.EMAIL_ADDRESS, Operator.Contains, searchQuery);

Since IntrinsicPortalField is a subclass of PortalField, the PRC has no problem with it. I’ve tested this with e-mail address and it works flawlessly. I’m sure other properties will work perfectly well too.

Enjoy!

Comments

Comments are listed in date ascending order (oldest first)

  • Thank you, Chris 🙂 Now that Chris has kindly posted a workaround, any possibility of having this put into an IDK hotfix?

    Posted by: ewwhitley on August 27, 2006 at 2:39 PM

  • I second Eric’s opinion. Can you guys just remove the artificial restriction of 100 from the IDK in the next release? Seems to work fine without it and it would obviate the need for my silly patch.

    Posted by: bucchere on August 30, 2006 at 2:31 PM

  • Here is an e-mail I received from a person who attempted to use my patch:

     

    I found your blog because I am having the same problem you describe with searching Intrinsic properties. However, I am now having trouble actually “patching” the IDK. How exactly would I go about repackaging everything with these new java files included? Thank you very much for your time and help.

    Here was my response:

    Thanks for your note. Assuming that you’re building a Java web application, all you need to do is compile the patch along with all your other application code. You can put the class files for the patch in WEB-INF/classes or you can make a jar (e.g. myapp.jar) and put the class files for the IDK patch there and then drop the jar in WEB-INF/lib. You can then put everything into a .war or .ear (or not).

    The magic of the Java classloader is that all the .class files in WEB-INF/classes and all your .class files inside jars in WEB-INF/lib all end up loaded into the same memory. That means that if you have two class files in two different jars, but they’re both in the com.plumtree.remote.portlet package (meaning you have the line package com.plumtree.remote.portlet; at the top of your source files and your .java files live in com/plumtree/remote/portlet), then they’ll act like they’re in the same package. This means that you’ll have access to all package private member methods, which the patch needs in order to compile.

    Posted by: bucchere on August 30, 2006 at 7:23 PM

  • Hi mate, I think this is very helpfull but I was wondering where can I find corresponding ID’s for all standart and custom user properties, when I’m using

    forID(26) method? Thanks in advance!

    Posted by: ggeorgiev on September 19, 2006 at 1:16 AM

  • This gets you the standard (intrinsic) ones:
    select objectid, name from plumdbuser.ptproperties order by objectid where objectid < 200;
    1 Name
    2 Description
    3 Object Created
    4 Object Last Modified
    5 Open Document URL
    6 Content Type ID
    7 Plumtree Document Image UUID
    8 Content Language
    9 Content Tag
    26 Email Address
    50 Full Text Content
    60 Document Submit Content Source
    61 Document Upload Repository Server
    62 Document Upload DocID
    71 Related Communities
    72 Related Folders
    73 Related Portlets
    74 Related Experts
    75 Related Content Managers
    80 Snapshot Query Reference
    101 Keywords
    102 Subject
    103 Author
    104 Created
    105 Document Title
    106 URL
    107 Category
    111 Comments
    112 Modified
    152 Phone Number
    153 Title
    154 Department
    155 Manager
    156 Company
    157 Address
    158 Postal Code
    159 State or Province
    160 Country
    161 Employee ID
    162 City
    163 Address 2
    

    For the custom ones, change the where clause to >= 200.

    Posted by: bucchere on September 23, 2006 at 6:53 PM

Categories
bdg dev2dev Featured Posts Software Development

Everyone likes a friendly URL

As part of our BEA World strategy for this year, we’re revamping our corporate web site, http://www.bdg-online.com. You should expect an unveiling in the upcoming weeks.

While there will be some revised and some additional content, this is primarily an infrastructure upgrade, including moving to more powerful virtual hosts and upgrading the backend from ASP to ASP.NET (yes, I know, it’s about time).

One of things that really bugs me about ASP and ASP.NET is the failure to include built-in support for friendly URLs. By friendly I mean something that doesn’t end in .asp, .htm, .aspx or some other extension and naturally also doesn’t have a querystring (?foo=bar&boo=moo . . . etc.). For example, http://www.bdg-online.com/customers is a lot more friendly than http://www.bdg-online.com/customers.asp and definitely more friendly than something like http://www.bdg-online.com/content.aspx?p=/customers.

Java provides a nice facility for this in the form of servlet mappings. Since a lot of people are using MVC these days, you are probably going to set up servlet mappings anyway. Here’s an example from a sample web.xml file:

<servlet>
 <servlet-name>customers</servlet-name>
 <servlet-class>com.bdg-online.www.Customers</servlet-class>
</servlet>

<servlet-mapping>
 <servlet-name>customers</servlet-name>
 <url-pattern>/customers/*</url-pattern>
</servlet-mapping>

But what about ASP or ASP.NET? No one cares about ASP any more, so I didn’t bother to research that. But for .NET, I came up with a simple and elegant solution to the friendly URL problem. All you need to do is add the following code (or something like it) to your Global.asax.cs file:

protected void Application_BeginRequest(Object sender, EventArgs e)
{
 if (!Request.RawUrl.EndsWith("htm") &&
     !Request.RawUrl.EndsWith("css") &&
     !Request.RawUrl.EndsWith("ico") &&
     !Request.RawUrl.EndsWith("jpg") &&
     !Request.RawUrl.EndsWith("js") &&
     !Request.RawUrl.EndsWith("gif"))
 {
   if ("".Equals(Request.RawUrl) || "/".Equals(Request.RawUrl))
   {
     Context.Server.Transfer("default.aspx");
   }
   else
   {
     Context.Server.Transfer(Request.RawUrl + ".aspx");
   }
 }
}

You’ll note that I forward requests to the corresponding aspx page, as long as the request isn’t for static content (images, css, etc.).

Of course you also need to do two things:

  1. Configure an .aspx page for every friendly URL you want resolved
  2. Add a wildcard mapping in IIS for * (files without extensions) to the asp.net ISAPI filter

The process for #2 is a little involved. It’s also different for IIS 5 (XP) and IIS 6 (2003). I don’t feel like posting screen shots right now, but if anyone wants to give this a trial run and can’t figure out how to do #2, just e-mail me and I’ll walk you through it.

I just came up with this a couple of hours ago and I haven’t put it through much testing, so YMMV.

Categories
dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Handy Startup Script for ALUI G6 on *nix

I just wrote a nice little startup script for ALUI G6 on *nix. If you want to use it, place a file in /etc/init.d called “plumtree” with the following contents:

#!/bin/sh

. /opt/plumtree/pthome.sh

case "$1" in
 'start')
   $PT_HOME/ptportal/6.0/bin/automationserverd.sh start
   $PT_HOME/ptws/6.0/bin/apiserviced.sh start
   $PT_HOME/ptdr/6.0/bin/drserverd.sh start
   $PT_HOME/ptsearchserver/6.0/bin/searchserverd.sh start
   $PT_HOME/ptupload/6.0/bin/plumtreefileuploadd.sh start
   /opt/httpd/bin/apachectl.pt start
   /opt/tomcat5/bin/startup.sh
   ;;

 'stop')
   $PT_HOME/ptportal/6.0/bin/automationserverd.sh stop
   $PT_HOME/ptws/6.0/bin/apiserviced.sh stop
   $PT_HOME/ptdr/6.0/bin/drserverd.sh stop
   $PT_HOME/ptsearchserver/6.0/bin/searchserverd.sh stop
   $PT_HOME/ptupload/6.0/bin/plumtreefileuploadd.sh stop
   /opt/httpd/bin/apachectl.pt stop
   /opt/tomcat5/bin/shutdown.sh
   ;;
esac

Now make the script executable:

%>chmod +x /etc/init.d/plumtree

To run all the ALUI components, type:

/etc/init.d/plumtree start

To stop them, use

/etc/init.d/plumtree stop

If you want to start ALUI automatically when the server comes up, do this (assuming you want ALUI to start at run level 3):

ln -s /etc/init.d/plumtree /etc/rc3.d/S50plumtree

Of course you need to make sure that Oracle is started first if you go this route. If you’re not running all the ALUI services on the same machine (not a recommended configuration, but good for development), you’ll need to edit the script above to start and stop only the components you have installed on each server. Enjoy!

Categories
dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

ALI Install Issues on Fedora Core 5

Note: I don’t think this is officially supported, so YMMV.

I encountered a problem installing ALUI G6 on Fedora Core 5. Just for the record, it’s not a problem with ALUI — it’s a problem with InstallAnywhere, the software that BEA uses to install ALUI.

Here are the errors I got:

[plumtree@bdg-chris]# ./PlumtreeFoundation_v6-0
Preparing to install...
Extracting the JRE from the installer archive...
Unpacking the JRE...
Extracting the installation resources from the installer archive...
Configuring the installer for this system's environment...
awk: error while loading shared libraries: libdl.so.2:
cannot open shared object file: No such file or directory
dirname: error while loading shared libraries: libc.so.6:
cannot open shared object file: No such file or directory
/bin/ls: error while loading shared libraries: librt.so.1:
cannot open shared object file: No such file or directory
basename: error while loading shared libraries: libc.so.6:
cannot open shared object file: No such file or directory
dirname: error while loading shared libraries: libc.so.6:
cannot open shared object file: No such file or directory
basename: error while loading shared libraries: libc.so.6:
cannot open shared object file: No such file or directory
hostname: error while loading shared libraries: libc.so.6:
cannot open shared object file: No such file or directory

Launching installer...

grep: error while loading shared libraries: libc.so.6:
cannot open shared object file: No such file or directory
/tmp/install.dir.17585/Linux/resource/jre/bin/java: error while loading
shared libraries: libpthread.so.0: cannot open shared object file:
No such file or directory

I resolved these errors by doing the following:

[plumtree@bdg-chris]# cat PlumtreeFoundation_v6-0 | sed
"s/export LD_ASSUME_KERNEL/#xport LD_ASSUME_KERNEL/" >
PlumtreeFoundation_v6-0.new

[plumtree@bdg-chris]# chmod a+x PlumtreeFoundation_v6-0.new

[plumtree@bdg-chris]# ./PlumtreeFoundation_v6-0.new

This may work to get ALUI installed on other unsupported flavors of *nix, although I’ve never tried it, so again, YMMV.

Comments

Comments are listed in date ascending order (oldest first)

  • once you got it installed how did it run on core 5?

    Posted by: phil- on April 13, 2007 at 12:30 PM

  • It ran pretty well aside from the occasional JVM segfault, which I could have probably fixed by upgrading to the latest 1.4.2 JVM and plugging that into Tomcat 5, rather than running the OOTB bundled Tomcat/JVM supplied by BEA.

    Instead, I’ve switched to Ubuntu (with Oracle XE), which is a great platform because, for the most part, it just takes care of itself. It’s like having RHEL with a lifetime subscription to RHN, but without actually paying for it! 😉

    Posted by: bucchere on April 14, 2007 at 6:17 AM

Categories
dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Time to switch search keywords?

The data below are from overture.com. They state how many times the following terms are currently being used in search queries. (I’m not sure if the data collected are only from Google or if they include multiple search engines.)

680 plumtree
67 plumtree software
46 plumtree portal

305 aqualogic
31 aqualogic bea

According to these data, which show that more than twice as many people are searching for “plumtree” than for “aqualogic,” it’s not quite time to insert AquaLogic keywords into bdg-online.com and/or re-brand the bdg Plumtree blog as the bdg AquaLogic blog.

Adoption of the new names takes time, but we’re getting closer.

Categories
dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

ALUI Portlet Pagination Cookbook

A coworker asked me how to write Plumtree portlet pagination (i.e. showing records in a UI and allowing the user to move from page to page, n records at a time) the other day and the ensuing discussion made me rethink how I’ve always done this and consider some new options. In this post, I attempt to shed some light on a very simple concept that turns out to be quite interesting in terms of implementation.

First, let’s consider some of the ways developers typically add pagination to standard web applications, forgetting about portals and portlets for a moment. Let’s call n your page size, i your page number and t the total number of records. On the first page, you might see n records laid out with alternating row colors and a 1-n of t marker to show you were you are, e.g. Now Showing Records 1-5 of 45. There’s probably also a next button and a back button (grayed out for now), a first page button (also grayed out), a last page button and maybe even a “fast-forward” button to move forward several pages at a time.

A very easy way to implement this in a standard (non-portal) MVC Java/J2EE Web application would be to carry some state, say i (the page index), on the querystring. For example, say you have a record viewer servlet called “RecordView” running on a Java-enabled Web application container such as Tomcat. You could have something like http://bdg-plumtree:8080/bdg-plumtree-context/RecordView as your URL. Your servlet code snippet might look something like this:

import javax.servlet.*;
import javax.servlet.http.*;

...

private Model model;

protected void doPost(HttpServletRequest request,
                      HttpServletResponse response)
                        throws IOException {

    //if you didn’t specify a page argument, display the
    //first page (pageIndex = 0)
    int pageIndex = request.getParameter("i") == null ? 0 :
      Integer.parseInt((String)request.getParameter("i"));

    //make a call into the data model to get the i-th page
    //and put the results in the request
    request.setAttribute("results", model.getResults(pageIndex));

    //forward to your view (JSP)
    request.getRequestDispatcher("view.jsp").forward(request, response);
}

In the view, you would simply print out the results, line-by-line, perhaps alternating row colors to make it easier to read. Then you need to display the little marker that tells you what page you’re on and the buttons to get next/back/to the end/etc. The logic to figure out what buttons to display and which buttons to gray out is a little involved, but it’s mundane enough that I don’t think I need to cover it here. The important part for this discussion is what’s in the links that actually take you forward and (soon) back. The answer there is simple enough — just create links that append the appropriate querystring and you’re all set. Here’s an example:

<a href="http://bdg-plumtree:8080/bdg-plumtree-context/RecordView?i=<%=i + n%>">Next</a>

So far so good.

The problem is that when you try to do this in an ALUI portlet, you don’t have direct access to the querystring, so you can’t use this approach. You need to store the variable i using some kind of state mechanism. Here are your options:

  1. The HTTP session
  2. A portlet setting
  3. A session setting (G6 and up only)

There are tradeoffs between #1 and #2 but #3 offers a good compromise. Let me explain.

If you use the HTTP session, your users’ page setting (i), will only persist for the life of the session, which is probably desirable. But, if you’re using the Plumtree caching model for portlets (such as expires or last-modified/etag caching), you can’t cache this portlet at all, which is definitely not desirable. The reason is that every page, regardless of the value of i, will have the same cache key.

To implement session-based pagination, you only need to change two lines of code:

request.getParameter("i")
becomes request.getSession().getAttribute("i") and your anchors that control the moving from page to page now need to point to a different controller servlet. (Remember, you don’t have control over the query string any more in portletland).

<a href="http://bdg-plumtree:8080/bdg-plumtree-context/ChangePage?i=<%=i%>">Next</a>

The ChangePage servlet simply sets the session attribute and then calls return to portal as shown here:

request.getSession().setAttribute("i",
    request.getParameter("i"));
PortletContextFactory.createPortletContext(request,
    response).getResponse().returnToPortal();

The only way to unique-ify the cache key, therefore caching your portlet appropriately, is to go with approach #2, the portlet setting. Now, when users advance i, they will be creating a new cache entry for each value of i (since settings are always part of the cache key). The drawback is that the users’ page settings (i) will persist longer than the life of the session. In other words, they could be browsing page 5, then they could leave the portal for several days, come back, and still be on page 5!

For your view:

PortletContextFactory.createPortletContext(request,
  response).getRequest().getSettingValue(SettingType.Portlet, "i");

And for your ChangePage servlet:

PortletContextFactory.createPortletContext(request,
  response).getResponse().setSettingValue(SettingType.Portlet, "i", request.getParameter("i"));
PortletContextFactory.createPortletContext(request, response).getResponse().returnToPortal();

G6 offers a nice compromise: the session setting. (If I had to guess, I would say the session setting was designed expressly for pagination.) With a session setting, you get the best of both worlds: a page setting that lasts only for the duration of the session but also a unique cache key so that you can effectively cache your portlet.

For your view:

PortletContextFactory.createPortletContext(request,
  response).getRequest().getSettingValue(SettingType.Session, "i");

And finally, for your ChangePage servlet:

PortletContextFactory.createPortletContext(request,
  response).getResponse().setSettingValue(SettingType.Session, "i", request.getParameter("i"));
PortletContextFactory.createPortletContext(request,response).getResponse().returnToPortal();

All of these methods have one drawback — they refresh the entire page on every portlet pagination click. So . . . stay tuned for an upcoming post on AJAX-based portlet pagination.

Comments

dev2dev comments are listed in date ascending order (oldest first)

  • This is a good start for everyone on Java. If you are like me and are a .NET developer at heart, you will be glad to know the .NET Web Control Consumer supports the use of a MS data grid control. All that is needed is for the developer to drag one of these objects onto the form, hook it up to a data source, and presto, you get pagination, storability, edit, and any other thing you always wanted from a table.

    Andrew Morris – [email protected]

    Posted by: drews_94580 on August 15, 2006 at 2:03 PM

  • It’s true — in many ways, .NET is way ahead of Java. I have only a little experience with the .NET Web Controls and the Control Consumer, but from what I’ve seen, there’s a lot of power and flexibility there. My post was making the “I want to roll my own pagination in Java” assumption. 🙂

    Posted by: bucchere on August 16, 2006 at 8:30 PM

Categories
bdg dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Mingle & the PHP EDK moved to dev2dev’s CodeShare

Just in case you’re looking for our two opensource projects that were once accessible via the Plumtree Portal, you can now find them on BEA dev2dev’s CodeShare. Here are the links:

Mingle: https://mingle.projects.dev2dev.bea.com
PHP EDK: https://plumtree-php-edk.projects.dev2dev.bea.com

Enjoy!

Categories
dev2dev Featured Posts Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Caveat Emptor: Using Varpacks in Pluggable Navigation

I got burned by this today, so I thought I would share it with you all. I had a perfectly good and working pluggable navigation that loaded a remote portlet in the left navigation pane. The only problem was that I had hardcoded the portlet ID and I wanted to make it configurable. So naturally I put the portlet ID and some other settings in a varpack, which is a reasonable thing to do. I then called my varpack from the constructor of my pluggable navigation class that implements IView.

Then I spent the next hour banging my head against the keyboard.

I kept getting a nasty Invocation Target Exception coming out of the reflection methods used to pre-load pluggable navigations. Eventually, using the handy space=MemoryDebug trick, I was able to ascertain that my varpack XML syntax was wrong and my varpack was looking empty to ALUI. So I fixed that and still, I got an ITE.

Then I looked at a PTSpy log and I discovered that the loading order of objects in ALUI was to blame. The portal loads built-in varpacks first, then it loads pluggable navigations, then it loads custom varpacks. So you can’t use the varpack from the IView constructor. I moved it to the Display method and everything started working again. Phew. 😐

So, shame on me for trying to use a custom varpack before it was loaded. Make sure not to make the same mistake!