Tag: native
Not only is it possible to write an ALUI Identity Service in Ruby on Rails, it’s remarkably easy. I was able to do the entire authentication part in fewer than 15 lines of code! However, I ran into problems on the synchronization side and ended up writing that part in Java. Read on for all the gory details.
As part of building the suite of social applications for BEA Participate 2008, we’re designing a social application framework in Ruby on Rails and integrating it with ALI 6.5. Not being a big fan of LDAP, I decided to put the users of the social application framework in the database (which is MySQL). Now, when we integrate with ALI, we need to sync this user repository (just as many enterprises do with Active Directory or LDAP).
So I set out to build an IDS to pull in users, groups and memberships in Ruby on Rails.
It’s pretty obvious that Ruby on Rails favors REST over SOAP for their web service support. However, they still support SOAP for interoperability and it mostly works. I did have to make one patch to Ruby’s core XML processing libraries to get things humming along. I haven’t submitted the patch back to Ruby yet, but at some point I will. Basically, the problem was that the parser didn’t recognize the UTF-8 encoding if it was enclosed in quotes (“UTF-8”). This patch suggestion guided me in the right direction, but I ended up doing something a little different because the suggested patch didn’t work.
I changed line 27 of lib/ruby/1.8/rexml/encoding.rb as follows:
enc = enc.nil? ? nil : enc.upcase.gsub('"','') #that's a double quote inside single quotes
Now that Ruby’s XML parser recognized UTF-8 as a valid format, it decided that it didn’t support UTF-8! To work around this, I installed iconv, which is available for Windows and *nix and works seamlessly with Ruby. In fact, after installation, all the XML parsing issues went bye-bye.
Now, on to the IDS code. From your rails project, type:
ruby script/generate web_service Authenticate
This creates app/apis/authenticate_api.rb. In that file, place the following lines of code:
class AuthenticateApi < ActionWebService::API::Base api_method :Authenticate, :expects => [{:Username => :string}, {:Password => :string}, {:NameValuePairs => [:string]}], :returns => [:string] end
All you’re doing here is extending ActionWebService
and declaring the input/output params for your web service. Now type the following command:
ruby script/generate controller Authenticate
This creates the controller, where, if you stick with direct dispatching (which I recommend), you’ll be doing all the heavy lifting. (And there isn’t much.) This file should contain the following:
class AuthenticateController < ApplicationController web_service_dispatching_mode :direct wsdl_service_name 'Authenticate' web_service_scaffold :invoke def Authenticate(username, password, nameValuePairs) if User.authenticate(username, password) return "" else raise "-102" #generic username/password failure code end end end
Replace User.authenticate with whatever mechanism you’re using to authenticate your users. (I’m using the login_generator gem.) That’s all there is to it! Just point your AWS to http://localhost:3000/authenticate/api
and you’re off to the races.
Now, if you want to do some functional testing (independently of the portal), rails sets up a nice web service scaffold UI to let you invoke your web service and examine the result. Just visit http://localhost:3000/authenticate/invoke
to see all of that tasty goodness.
There you have it — a Ruby on Rails-based IDS for ALUI in fewer than 15 lines of code!
The synchronization side of the IDS was almost just as simple to write, but after countless hours of debugging, I gave up on it and re-wrote it in Java using the supported ALUI IDK. Although I never could quite put my finger on it, it seemed the problem had something to do with some subtleties about how BEA’s XML parser was handing UTF-8 newlines. I’ll post the code here just in case anyone has an interest in trying to get it to work. Caveat: this code is untested and currently it fails on the call to GetGroups because of the aforementioned problems.
In app/apis/synchronize_api.rb:
class SynchronizeApi < ActionWebService::API::Base api_method :Initialize, :expects => [{:NameValuePairs => [:string]}], :returns => [:integer] api_method :GetGroups, :returns => [[:string]] api_method :GetUsers, :returns => [[:string]] api_method :GetMembers, :expects => [{:GroupID => :string}], :returns => [[:string]] api_method :Shutdown end
In app/controllers/synchronize_controller.rb:
class SynchronizeController < ApplicationController web_service_dispatching_mode :direct wsdl_service_name 'Synchronize' web_service_scaffold :invoke def Initialize(nameValuePairs) session['initialized'] = true return 2 end def GetGroups() if session['initialized'] session['initialized'] = false groups = Group.find_all groupNames = Array.new for group in groups groupNames << "<SecureObject Name=\"#{group.name}\" AuthName=\"#{group.name}\" UniqueName=\"#{group.id}\"/>" end return groupNames else return nil end end def GetUsers() if session['initialized'] session['initialized'] = false users = User.find_all userNames = Array.new for user in users userNames << "<SecureObject Name=\"#{user.login}\" AuthName=\"#{user.login}\" UniqueName=\"#{user.id}\"/>" end return userNames else return nil end end def Shutdown() return nil end end
Comments
Comments are listed in date ascending order (oldest first)
bdg is hosting an AquaLogic (Plumtree) Training Class in Washington, DC the week of September 24th.
We’ll be sticking with the format we used last time:
Monday, Tuesday and Wednesday: ALI Administration
Wednesday, Thursday and Friday: ALI Portlet Development
Wednesday will be a “double up” day when we do advanced administration for admins and introductory administration for developers.
Training will run 9AM-5PM each day with a break for lunch. The location will be as follows:
ALI Administration (Mon, Tues, Wed)
- ALI Architecture
- Basic Troubleshooting of the ALI Environment
- Configuring the Automation Server and Jobs
- Performing Routine Portal Maintenance
- Creating an Administrative Hierarchy
- Configuring Portal Access and Permissions
- Implementing Advanced Object Security
- Community Fundamentals
- Creating a Community
- Advanced Community Topics
- Building Subportals
- Configuring ALI Web Services
- Importing Users and Groups
- Extending the User Profile
- Adding Content to the Portal
- Maintaining the Knowledge Directory
- ALI Enterprise Planning Role Play
- Using PTSPY and the Migration Utility
- Using the Admin Hierarchy
- Creating a Community
- Creating a Subportal
- Registering an Authentication Source
- Registering a Profile Web Service
- Managing the Knowledge Directory
- Register Automation Servers
- Create, run and troubleshoot Jobs
- Perform routine Portal maintenance
- Use PTSPY and the new Migration Wizard
- Create Users and Groups in the Portal Set up Activity Rights for users
- Manage permissions using Access Control Lists
- Create Communities
- Create Projects in Collaboration Server
- Create Experience Definitions and understand their use
- Configure Web Services
- Configure an AD Authentication Web Service
- Configure a User Profile Web Service
- Understand all of the content management objects in the Portal
- Create a Crawler to allow access to external content through the Portal
- Maintain Portal content and its search index
- Implement Portal best practices
ALI Portlet Development
- Introduction to ALI, the MPPE, Portlets & C#/.NET or Java
- Demonstration of ALI Portal and Integration Products
- Install a Java IDE and come up with an idea for a new Portlet
- Design an ALI Portlet and create a associated objects; add to MyPage and Community
- HTTPGP/MPPE & the Gateway
- Yahoo! Search Portlet: Design a portlet that allows the user to issue a search in Yahoo!
- TCP Tracing: Use a free utility to trace TCP activity on port 80 (HTTP); observe CSP in action
- Interaction Development Kits
- Gateway Specific Configuration
- Design an ALI Portlet that uses a Gateway Setting
- Introduction to Settings
- Change Font Color: Use a Portlet Setting to allow the user to personalize the Portlet with a font color
- My Bookmarks: Design an ALI Portlet that displays an end-user customizable list of bookmarks to web sites
- Settings Review
- Community Bookmarks: Design an ALI Portlet that allows the community manager to set a customizable list of bookmarks to web sites
- Introduction to Portlet Frameworks and Database-driven Portlet Design
- Database Schema: Design database schema for a Data
- Entry and Browse Framework Portlet
- Administrative Settings Review
- Administrative Settings: Design the Administrative Preferences page for a Data Entry and Record Browse Framework Portlet
- Data View/Entry Form: Design Data View/Entry Form for a Data Entry and Record Browse Framework Portlet
- Record Browsing: Design Record Browsing for a Data Entry and Record Browse Framework Portlet
- Portlet Caching, Performance and Scale-ability
- Caching: Add ETAG/IF-NONE-MATCH caching to Data Entry and Record Browse Framework Portlet
- Enhance Record Browsing with Pagination, Sorting and Filtering
- Internationalization/Localization
- Create a Localized Portlet
In his 1947 speech to the House of Commons, Winston Churchill quipped, “It has been said that democracy is the worst form of government except all those other forms that have been tried.”
I’m not nearly as pithy as Sir Winston (nor as portly — at least not yet), but yet I feel the same way about passwords being used to protect web sites or other enterprise systems. In many ways, they’re the worst form of security out there except for everything else that’s been tried. Part of this has something to do with what I’ve coined Bucchere’s Axiom of Strong Passwords, which is a derivative of Murphy’s Law (which states that whatever can go wrong will). It goes something like this: the stronger a password is, the easier it is to hack. Why? Because if you force users into using a strong password, they’re more likely to write it down. And writing a password down defeats its purpose entirely.
The bottom line: passwords suck. But they’ve become the de-facto standard because they’re easier and cheaper than everything else we’ve tried, including PKI certs, biometrics (e.g. fingerprints, retina-scans), CAC cards, RSA secure IDs, etc. (Even for a cert-based authentication scheme, you still need a key to generate your cert, which is essentially just a glorified password.)
Just because passwords are the de-facto standard for authentication does not mean that we should quit trying to use other, ostensibly better forms of security, especially if 1) you’re protecting particularly sensitive data, 2) you’re open to the internet and 3) you have the resources (e.g. $$$) to invest in more robust forms of security. And I’m not talking about just buying an SSL cert from Verisign and continuing to have your users write down their passwords on post-it notes attached to their monitors. (Note to self: remove the post it note on your monitor with your password on it when you get back to the office.) I’m talking about using some sort of “soft” cert (e.g. PKI) or “hard” cert (e.g. CAC) to protect your system and your data.
Now if your system is ALI (formerly known as Plumtree Foundation or Plumtree Portal), you’re in luck, because the eggheads at what was once known as Plumtree have made this particularly easy to do. In fact, the hardest part is just getting the user’s identity out of the cert (see below the code snippet for some suggestions). Once you’ve done that, just drop a class into a jar that implements the ISSOProvider
interface. (For those of you running on Windows, please don’t ask me to “port” this to C# — just take the Java code, drop it into Visual Studio.NET and then fix the syntax errors.)
But wait, SSO stands for “Single Sign On,” right? And what you’re really doing here is passing credentials from a cert to Plumtree and that has little or nothing to do with SSO. That’s a true statement. The subtlety here is that ISSOProvider
, while it contains the letters SSO in its name, can be used for pretty much any form of authentication, whether you are using an SSO product or not.
CertIntegration.java
package com.bdgportal.alui.auth;
import com.plumtree.openfoundation.util.*;
import com.plumtree.openfoundation.web.*;
import com.plumtree.portaluiinfrastructure.sso.*;
public class CertIntegration implements ISSOIntegration {
private XPHashtable settings;
public CertIntegration() {
;
}
public boolean Initialize(XPHashtable settings) {
this.settings = settings;
//String exampleSetting = ((XPArrayList)settings.GetElement("SettingName")).GetElement(0);
}
public String GetSSOProductName() {
return "My Favorite Cert Integration";
}
/**
* Gets the username from the cert and returns it to Plumtree. This will fail if the username
* does not have a matching account in Plumtree. This can be a Plumtree database user or a user
* imported from an authentication source, in which case you need to include the auth source
* prefix in the username, e.g. "MyAuthSource/cbucchere"
*
* @param request The wrapped HttpServletRequest
from the web container.
* @return The object passed back to Plumtree for authentication with the portal.
*/
public SSOLoginInfo GetLoginInfo(IXPRequest request) {
String userName = ((XPRequest)request).GetUnderlyingObject().getUserPrincipal().getName();
return new SSOLoginInfo(userName);
}
public String[] GetSecureCookies() {
return null;
}
public String[] GetSecureHeaders() {
return null;
}
public boolean OnLogout(IXPResponse response, String returnURI) {
return false;
}
}
The hardest part about all this, as I said above, is getting the user name out of the PLI cert/CAC card/retina scan/etc. In the example above, I made MANY assumptions. First, I assumed that your portal is running on Weblogic, which understands and correctly implements Principal, which is a Java Servlet’s way of knowing who’s using it. Weblogic lets you plug custom implementations of the Principal class into its security infrastructure. All you need to do is extend java.security.Principal
and then walk through a bunch of magical configuration steps to enable it.
Speaking of magical configuration, I neglected to mention that there are two small configuration steps that you need to perform in order to get your shiny new ISSOIntegration
working in ALI. In portalconfig.xml
, you need to set the value of SSOVendor
setting to 100 (or greater) and then set the CustomSSOClass
to the fully qualified name of the class you wrote that implements ISSOIntegration
. For our Java example above, that would be com.bdgportal.alui.auth.CertIntegration
and for .NET, it would the the name of your C# class.
Speaking of .NET . . . as many of you know, it is an entirely different animal with its own way of provisioning security to web applications (e.g. System.Web.Security
).
Regardless of your platform, you need to get the user name out of whatever authentication method you’re using. Once you’ve accomplished that, just drop the code above into your project and replace the getUserPricipal().getName()
with whatever mechanism you can find for getting your users’ names.
Assuming you trust your authentication mechanism to return the appropriate user name, you’ll have users getting logged into the portal via pretty much however you would like — CAC, PKI, biometrics, etc.
If only implementing a democracy were this easy . . . .
Comments
Comments are listed in date ascending order (oldest first)
- This is wonderful article. How ever I’ve researched for a long time but still can not figure out what to do with Bea Weblogic to use Costom Identify Assertion. I wish this artical to have link to the document of how to “do the magical configuration steps”.
Posted by: minh.tran on January 9, 2007 at 9:04 AM
- This article was intended to be application server independent, but if you’re using BEA WebLogic, there’s a great article on how to set up custom identity providers which should work with this ALUI SSO solution.
Posted by: bucchere on January 10, 2007 at 6:44 PM
There’s been some recent activity on this very old thread in the newsgroups regarding displaying the help link in a portlet. Until G6, this could only be done with native code AFAIK. But, if you supress the portlet title bar, there really aren’t many places where you can put native code in a portlet.
Enter G6 and the extensible taglib support, a quiet little feature that (without any fanfare or marketing by BID) has seriously changed my life.
The source speaks for itself. It look 15 minutes to write. (Granted, I already had my ALUI development environment all set up.)
HelpURL.java:
package com.bdgportal.alui.taglibs; import com.plumtree.openfoundation.util.*; import com.plumtree.portaluiinfrastructure.tags.*; import com.plumtree.portaluiinfrastructure.tags.metadata.*; import com.plumtree.server.*; import com.plumtree.xpshared.htmlelements.*; public class HelpURL extends ATag { public static final ITagMetaData TAG; public static final RequiredTagAttribute PORTLET_ID; public static final RequiredTagAttribute ID; public static final OptionalTagAttribute SCOPE; static { TAG = new TagMetaData("helpurl", "Puts the help URL for this portlet into the variable specified by the ID attribute."); PORTLET_ID = new RequiredTagAttribute("portletid", "The portlet ID.", AttributeType.INT); ID = new RequiredTagAttribute("id", "The name of the variable in which the help link should be stored.", AttributeType.STRING); SCOPE = new OptionalTagAttribute("scope", "The scope used to store the the help link.", AttributeType.STRING, Scope.PORTLET_REQUEST.toString()); } public HTMLElement DisplayTag() { ((IXPList)GetState().GetSharedVariable(GetTagAttributeAsString(ID), Scope.GetScope(GetTagAttributeAsString(SCOPE)))).Add( ((IPTWebService)((IPTSession)GetEnvironment().GetUserSession()).GetWebServices() .Open(((IPTGadget)((IPTSession)GetEnvironment().GetUserSession()).GetGadgets() .Open(GetTagAttributeAsInt(PORTLET_ID), false)).GetWebServiceID(), false)) .GetProviderInfo().ReadAsString("PTC_HTTPGADGET_HELPURL")); return null; } public ATag Create() { return new HelpURL(); } }
To deploy this code, see the excellent section on edocs about creating custom Adaptive Tags.
To use this code in a portlet, do the following.
myportlet.htm:
<span xmlns:pt='http://www.plumtree.com/xmlschemas/ptui/'> <pt:mytaglibns.helpurl pt:portletid="234" pt:id="helplink"/> <pt:core.html pt:tag="a" href="$helplink">Help</pt:core.html> </span>
I didn’t test this, so YMMV. Have fun!
Comments
Comments are listed in date ascending order (oldest first)
- That’s slick, Chris – that’ll be handy for porting between devstageprod where objectids may be different 🙂
Posted by: ewwhitley on September 13, 2006 at 6:20 AM
- Hi, This code makes ten database requests just to get the the IPTWebService object for given portlet. Is there any better way to do this?
Posted by: Piotr Dudkiewicz on May 18, 2007 at 6:48 AM
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!
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
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 inWEB-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 inWEB-INF/classes
and all your.class
files inside jars inWEB-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 thecom.plumtree.remote.portlet
package (meaning you have the linepackage com.plumtree.remote.portlet;
at the top of your source files and your.java
files live incom/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
Hello world! This is my first post on my shiny new dev2dev blog and needless to say, I’m very pleased to be blogging here and to be given the opportunity to contribute to this prestigious and active community.
That being said, I hope you’ll find my contributions worthwhile. I plan to address mostly technical topics pertaining to AquaLogic User Interaction or ALUI (pronounced ah-LOO-eee by those in the know) with the occasional foray into the business side of ALUI and portals in general. I’ve been working with the ALUI (formerly Plumtree) products now for almost 10 years, but I also know a bit about Weblogic and I want to learn more about the other products in the AquaLogic family. So watch this space for news about all your favorite BEA products!
Okay, on to the topic at hand — navigation. Once upon a time (pre Plumtree 5.0), UI customization meant hacking through layers of ASP or JSP includes to find the right file and then changing this and that and trying not to break anything. And then when upgrade time came, you somehow had to merge all your changes out of the old UI code and into the new. It was a black art at best, a total goat rodeo at worst.
Enter Plumtree 5.0, which introduced the concept of header, footer and content canvas portlets along with “pluggable navigation.” For the first time, you could code your navigation against a supported and documented API and bundle it into a separate DLL or JAR file. When upgrades came along, you simply installed the new code and everything in your old DLL or JAR just worked. The only problem was that writing a pluggable navigation was really hard.
So, in ALUI G6 (the current version), you can easily make a navigation using a remote portlet and few lines of HTML, CSS along with a few handy XML tags. However, the old pluggable navigation model is still in the product, which begs the question: how do I know what form of navigation to use (native or remote)?
A lot of times you’ll hear me say, “it depends” i.e. it depends on your goals or it depends on your architecture. But, in this case, with 100% certainty, I can say that you want to go with remote navigation. Why? Because it’s about 1000 times easier to code, maintain and deploy, you can change it on the fly and in a breeze, you can include Javascript and CSS just like you would in a normal web page, you don’t have to work with confusing HTMLElement classes, you don’t have to know Java or C# . . . need I say more?
If you’re not sold yet on the concept of remote, XML tag based navigation over native pluggable navigation, consider the following example. Say I want to get all the mandatory with tab communities and display them in an unordered list.
Here’s the native code for that:
CListURLMediator mediator = new CListURLMediator(m_asOwner, m_model.GetCategoryLinks(NavCategoryType.MANDATORYTABS, false)); HTMLList ul = new HTMLList(1); while (mediator.Next()) { HTMLListItem li = new HTMLListItem(); li.AddInnerHTMLElement((HTMLAnchor)mediator.GetEntry()); ul.AddInnerHTMLElement(li); } return ul;
And here’s the remote code:
<ul xmlns:pt='http://www.plumtree.com/xmlschemas/ptui/'> <pt:ptdata.mandtabcommsdata pt:id='mandtabcomms'/> <pt:logic.foreach pt:data='mandtabcomms' pt:var='c'> <li><pt:core.html pt:tag='a' href='$c.url'><pt:logic.value pt:value='$c.title'/></pt:core.html></li> </pt:logic.foreach> </ul>
The code speaks for itself. (If you never have to learn what a CListURLMediator
is, consider yourself lucky.) Not to mention that you have to worry about the NavType
and deployment issues and that you have to restart the portal any time you make a change to your navigation code.
So, is remote, tag-based portlet navigation the solution to all your navigation needs? Not exactly. With pluggable navigation, you can control six navigation areas: the top bar, above the header, below the header, the left side, the right side and above the footer. It’s easy enough to suppress the top bar and replace the first three areas with your header portlet. Same goes for the footer. But what about the left and right sides? You can’t really replace those with portlets unless you use the two narrow columns, which leaves you with only a single portlet column for content.
So you’re in a pickle — you read this post and you’re now the world’s biggest remote navigation fan — but you can’t use them in left and right navigation. There is a solution. If you read this post in the newsgroups, you’ll see that it’s possible to write some code that will load a remote portlet into the left and/or right columns. (The only thing I would change about this code is make sure you put the portlet ID in a VarPack instead of hardcoding it.) Using this fairly simple approach, you can have your cake and eat it too.
Comments
Comments from BEA dev2dev are listed in date ascending order (oldest first)
- were doing UI customization to modify the top nav section, but would like to move to remote portlet with tags instead for reasons you mention.
If i understand correctly, we have to do this in header portlet. How do we enforce that that the same top nav is used across the portal if the community managers are able to change the tag code in the header portlet?
thanks
steph
[email protected]
Posted by: sviau on July 9, 2006 at 7:04 AM
- That’s a great question and I’m glad you asked it. First let me clear something up: community managers cannot and should not change the tag code. However, they can change which header portlets are being used for the communities they manage (and thus choose a whole new set of tags or no tags at all).
Fortunately, there’s a easy way to prevent this from happening using community templates. There’s a check box on the “Header and Footer” page of the community template that says “Force Community to use Header and Footer from Experience Definition.” You’ll want to check this box. Then, as the portal administrator, you can mandate which community templates your community managers can use. Set it up so that your community managers are forced to use the community template with this box checked and you should be good to go.
Posted by: bucchere on July 10, 2006 at 2:38 AM
- Hopefully this is applicable here…I am trying to brand (or customize) the title bar of a portlet(s). I have determined how to change the text on the title bar (PortletResponse.setTitle) but cannot find nothing on changing colors or adding colors to the title bar. Can this even be done? Is there a CSS class somewhere or can it be done programmatically? Thanks for your assistance.
Posted by: jayparker on January 2, 2007 at 12:28 PM
- This should do the trick:
.platportletHeaderBg { background-color: red; }
Posted by: bucchere on January 10, 2007 at 7:13 PM
- is the remote code supported in 6.1 mp1? I attempted to run it and nothing is generated. Has mandtabcommsdata gone away? I notice it also is absent from the current edocs http://edocs.bea.com/alui/devdoc/docs60/Portlets/Adaptive_Portlets/Using_Adaptive_Tags/PlumtreeDevDoc_Portlets_Adaptive_Navigation.htm
Posted by: geoffgarcia on October 2, 2007 at 11:26 AM
I can’t even begin to count the number of times I’ve been asked how to write a query filter in Plumtree. Yesterday, I had to write one for myself and lo and behold, I was confused. I’ve come to the conclusion that they’re just hard to write. Despite this, they’re incredibly useful when trying to lookup objects in the Plumtree API.
(Note: this post does not apply to writing portlets or other integrations. It’s only useful when writing Pluggable Navigation, PEIs, or other customizations to the UI.)
To use a query filter, you’ll first need access to a Plumtree user’s session. Let’s assume that you have one in the variable plumtreeSession
and that it’s already connected as a user who has at least read-level access to some objects. This query will work for any object, but just for the sake of example, let’s use Groups. Also, I wrote this in Java, but it works almost exactly the same way in C#.
First you need to get an IPTObjectManager
as shown here:
IPTObjectManager groups = plumtreeSession.GetUserGroups();
Next, we’ll build up the query filter, which is always an Object[][]
.
Object[][] filter = new Object[3][1]; filter[0][0] = new Integer(PT_PROPIDS.PT_PROPID_NAME); filter[1][0] = new Integer(PT_FILTEROPS.PT_FILTEROP_EQ_NOCASE); filter[2][0] = "Everyone";
Let’s go through that line by line. First of all, why did I choose 3,1 for my array dimensions? All query filters are 2D arrays. The first dimension always has three items:
- The name of the property on which you’re filtering
- The operator (equals, less than, greater than, etc.)
- The value of the property you’re evaluating
Since it’s an Object[][]
, we can’t use primatives, so we must box our constants in Integer
objects because the constants themselves are primatives.
When using the Query
method that supports a query filter, you also need to specify an ordering attribute. You can only order ascending, but you can order by up to three properties which will be applied in the order they are put in the array, as shown below:
int[] orderBy = {PT_PROPIDS.PT_PROPID_NAME, PT_PROPIDS.PT_PROPID_OBJECTID};
In the code above, I’m asking to order first by name, then by object ID. (Since no two names can be the same in Plumtree, this dual ordering doesn’t really make much sense, but oh well.)
Now, finally, it’s time to run the query:
IPTQueryResult group = groups.Query(PT_PROPIDS.PT_PROPID_OBJECTID + PT_PROPIDS.PT_PROPID_NAME, -1, orderBy, 0, -1, filter);
Let’s break down those parameters one by one. The first, PT_PROPIDS.PT_PROPID_OBJECTID + PT_PROPIDS.PT_PROPID_NAME
is a bitmask of properties you want to include in your results.
The second, -1
, means query all folders in the admin directory. (If you want to restrict your query to a single folder (and all folders below it), use the folder id here. If you want to query down two distinct folder trees, you’ll need to run two queries.
The third, orderBy
is your ordering attribute(s), explained above.
The fourth, 0
is your starting row.
The fifth, -1
is the number of rows to return (-1
means all rows). This parameter, along with the starting row, can be very useful for pagination.
The sixth and final parameter is your beloved query filter.
We did it! Now that wasn’t so bad, eh? Iterating through the results set is trivial, so I won’t cover it here, but if you have you have a question, feel free to make a comment here or post on the developer forums.