Sunday, March 15, 2009

ALUI Publisher: Easy bug fix with portlet templates…

I know ALUI Publisher is on the down slope, but until then, it does not mean I should not share some of my findings…Lately, I found something in publisher that does not make sense to me. It has to do with the creation of portlets based on publisher portlet templates. Let me explain the problem…and then the quick fix…

The problem:

Out of the box, when you create a portlet based on a publisher portlet template (i.e. announcement, news, etc…), there is a screen where you are asked to choose a publisher folder where the portlet publisher content should be…When clicking on the “chose publisher folder”, a publisher tree pops up, and you can pick the publisher folder. The tree picker will only show you the folders where you have “producer” access…until now it makes sense since only the “producer” role and above can create folders in publisher.

But what if a user does have “producer” access to a subfolder Z located in the tree structure at X > Y > Z…but does not have producer access to the parent folder X and Y?? Then the tree picker would simply stop showing the tree at X, hence not showing the subfolder to which the user has actually access to…hence problem.

A perfect (and probably common) use case is where you have various publisher sites (i.e. an intranet site, and internet site, etc…) and within each of these, you have various “community-related” publisher folders… (folders that contain the web content of each community). You will want your community administrators (in the portal) to also have “producer” or “folder administrator” role in their “community-related” publisher folder…but not have these roles in the parent publisher folders…they should have “reader” access on these parent folders…

The solution:

By reverse engineering publisher one more time, I found out that this problem can be fixed really easily…basically the publisher tree picker is opened with the following url:

“../folderpicker_frame.jsp?sid="+sessionId+"&showItemCategory=0&itemId="+parentFolderId+"&isMultiSelect=false&rootIsCheckable=true&minRoleId=12”

What is interesting in this url is the last parameter: minRoleId=12. What it probably means is “show only the folders to the users who have at least a role 12 – producer role – assigned to it.” That’s it, we have our solution…By removing this parameter altogether, the tree will now show the folders to which the user has minimum access to (“reader” access), hence fixing the user case explained above:

A producer in folder X > Y > Z will now be able to browse down to the Z folder, where he will be able to create a folder.

And don’t worry, it does not impact core security at all. If the user tries to select folder X or Y as a container for his “announcement” portlet, he will simply receive an error message saying ”you do not have enough access to create a folder”…so no problem.

Detailed instructions:

  1. Make a backup of the publisher application files \bea\alui\ptcs\6.4\webapp\ptcs.ear and \bea\alui\ptcs\6.4\webapp\ptcs.war (obvious, no?)
  2. Unpack the publisher archive (ptcs.war)
    1. navigate to \bea\alui\ptcs\6.4\webapp folder
    2. create a new dir: ptcs
    3. navigate to that new dir and execute the following jar command: jar –xvf ../ptcs.war
  3. edit the extracted file: ./portlet_packages/portlet_create.jsi
  4. Find the JavaScript functions “ChooseParentFolder” and “NewParentFolder”
  5. Remove the “minRoleId=12” from the “var url = …” line (1st line in each functions)
  6. Still in \bea\alui\ptcs\6.4\webapp\ptcs folder, repackage the war by executing: jar -cvf ptcs.war *
  7. move the newly created war to the \bea\alui\ptcs\6.4\webapp folder: move ptcs.war ../
  8. navigate to \bea\alui\ptcs\6.4\webapp: cd ../
  9. update the ptcs.ear file with the following command: jar -uvf ptcs.ear ptcs.war

That’s it…

Before restarting publisher, clean the temp files from the publisher container folders just to make sure your modification is loaded properly.

  • \ptcs\6.4\container\tmp\deploy
  • \ptcs\6.4\container\work\jboss.web\localhost

As usual, do this at your own risk…and test it well before deploying to production :)

Monday, February 23, 2009

New Portal Tools On ALUI Toolbox

In my efforts of improving the ALUI/Webcenter portal, and especially enhancing its admin management capabilities, I created over time a set of utilities that I think could be useful to the ALUI/Webcenter community. In one of my previous post, I already talked about the "PT URL Replace" utility (refer to: http://fsanglier.blogspot.com/2008/01/alui-administration-tool-for.html) which was already on the ALUI Toolbox Google code project as a download only. What I did some days ago is updating the ALUI Toolbox Google project (http://code.google.com/p/alui-toolbox/) with the "PT URL Replace" code, as well as a couple of new applications/utilities:

  • Portlet Caching Clearer (web portlet - c#)
  • Object Identifier (web portlet - c#)
  • Web Service Changer (web portlet - c#)
  • Page Lister (web portlet - c#)
  • ALUI Knowledge Directory Security Agent (java app runnable from console and/or scheduled task such as ALUI jobs) – feature download at http://code.google.com/p/alui-toolbox/
  • Improvements for PT URL Replace utility – http://code.google.com/p/alui-toolbox/

All the above apps use the Server API (for Server API introduction, refer to previous posts:) because the tasks performed would not be possible by simply using the IDK. It has been written for ALUI 6.1.x versions and might not work fully on version 6.5 and above without minor changes (because the server API does change between portal releases). When I get the chance, I will update the Google code project with 2 extra branches that follows the more current portal versions.

The code is released under the GPL license (you can find a copy the the GPL license in the root folder of the apps, or go to http://www.gnu.org/licenses/gpl.html), and off course is provided without any kind of warranty...

I'll go quickly over each of these utilities in order for you to understand why they might be useful.

Portlet Caching Clearer:
In order to maximize performance, each portlet in the portal can have output caching enabled to a particular timeframe. The drawback of such caching mechanism is that the updates performed by content managers are not instantaneously viewable to end users. This portlet that can be added on your "My Page" allows you to clear portlet caching in 3 different ways:

  1. Using the portal tree picker, select the portlet(s) you want to clear.
  2. Using the portal tree picker, select the "Community Page(s)" whose portlets you want to clear. The utility will find all the portlets currently added to the selected pages, and clear the cache for each of them.
  3. Using the portal tree picker, select the "Community(ies)" you want to clear. The utility will find all the portlets currently added to the selected pages of the selected communities, and clear the cache for each of them.

That way the content managers can easily clear caching for a set of pages, communities, or portlets...

Object Identifier:

Let's say that you have a portlet application that identify a portal object in its config file by its UUID (or its ID for that matter)...Now let's say you come back to that portlet 6 month later because you need to perform an improvement and/or fix something...Unless you clearly documented what object corresponds to this ID/UUID (and where it is in the portal), it will not be easy to find it (unless you can easily run a DB query...). This portlet basically answer that needs:

  • Provide a UUID and it will tell you the corresponding Classid/ObjectID pair + Object Name + Object Location in the Portal Admin Hierarchy.
  • Provide a Classid/ObjectID pair, and it will tell you the corresponding UUID + Object Name + Object Location in the Portal Admin Hierarchy.

Web Service Changer:

Have you ever noticed that you cannot change the webservice attached to a portlet once you initially picked it and created the portlet? Now let's say that you have a bunch of publisher portlets that are all tied to the same "Publish Content Web Service" object. But all a sudden, you change your mind and decide to have some portlets that should be tied to 2 different "Publish Content Web Service" objects, each one with a different caching timeframe (i.e. a long caching for the content that hardly change, and a shorter caching for the content that changes often). In the portal out of the box, you cannot do that easily, and will probably have to recreate all the portlets and re-attach them to the same publisher content etc...(big pain).

Well this portlet allows you to easily change the "portlet web service" OR the "portlet template" attached to a particular portlet or group of portlets:

  • Using the portal tree picker, pick the portlet you want to change.
  • Using the portal tree picker, pick the web service OR portlet template that you want these portlet to be assigned to.
  • Click Submit...Done.

Page Lister:

This simple utility allows to display as a list of HTML links (simple <a href=""></a> tags) all the Community Pages in the portal that a certain users have access to...

Why doing this? it is a simple way of creating a Sitemap that a web "crawler" (either ALUI web crawler, or Google appliance etc...) could hit in order to be dynamically aware of all the ALUI pages of your site that are accessible to the guest users for example...Or that could also be used as a security monitoring tool in order to verify which pages are accessible to a particular user (i.e. Guest) and ensure it is not a security mistake etc...

ALUI Knowledge Directory Security Agent:

This utility, written in java (we have to think about our linux/unix user base too :) ), was created as a scheduled task agent in order to act as a security cop in the knowledge directory. What it does is go through all the KD folders the agent user has access to, and automatically assign the found cards with their parent folder security.

All you need to provide are:

  1. KD folder ID to start with,
  2. A user ID / password (or session token if you run as a portal "external operation") the agent should impersonate with,
  3. (Optional) CrawlerIDs (if you want to change only the cards that were brought into KD by a specific set of crawlers)

2 main use cases I see for this tool:

  • Use it as a "cop" background schedule job in order to ensure the security on the cards is always right, based on the security of the folder.
  • In the event you use a single crawler with various filters that organize your content in various folders. Without this agent, the crawled cards will get the security defined in the crawler's "crawled content permission" section...and that might not be in phase with the security of the various KD folders the content will be organized into (due to filters). If you run this cop security agent after each crawl, then you are all set based on the destination folder, not on the crawler's "crawled content permission" section...

That's it for now. I hope you'll test them out and let me know if you think these are useful or not.
Don’t hesitate to give ideas and/or wish lists of things which would be good to have in the portal...and don't hesitate to share the cool utilities you've done too

I or others will be updating the project every so often so keep informed or you might miss out on some good resources :)

Sunday, January 18, 2009

WebCenter Native API Development: Advanced Search Query explained (and applied to WebSite Search)

In his article about "Dot Com Portals: Smart Searching", Jordan Rose already explained really well how to easily implement with Webcenter Interaction an efficient and accurate website-like search (enter a search term, and expect in the results either website pages, or documents within these pages).

To summarize the challenge:

The very powerful WebCenter search component will index everything the user has access to (i.e. web content items, documents, crawled third party websites, etc…) independently from their real presence on the web site pages. For example, the search results would present web content items instead of the website pages where the web content item is displayed through a portlet.

To summarize a bit the solution:

Using WebCenter interaction “web crawler” capability (google like spider that follows links on a page and index its content for future searches) coupled with experience definition features (to hide the part of the page that we don’t want the crawler to index, like the top/left navigation, the banner, etc…), it is easy to actually implement a website-like search with accurate portal page results (Refer to Jordan's blog post:Dot Com Portals: Smart Searching)

But one thing that was not there yet in the solution was: "How can the crawler navigate from page to page" if the navigation is not there? What we did at first (we did not have time to do better) is create manually this HTML file that would contain all the pages of the website, and direct the web crawler to that page, instead of to the root of the public portal website url.

This would work ok, but would require a manual update of this file each time you create a new page...not super practical. Anyway, I finally took the time to improve it, and created this "Page Listing" code that basically render a list of pages located within a specific folder...Basically, you simply create a request with "topfolderid", "includesubfolders", and "openerhost" (http:////PageLinkListing?topfolderid=123&includesubfolders=true&openerhost=yourdomainhost) and the dotnet page will render all the portal page links that correspond to these values.

In this article I'd like to use this example in order to focus on the Native Search API (refer to my previous article about the native API) because the dotnet frontend is pretty simple:

  • DotNet front end page
  • Native Portal API
  • Webcenter search API to query the pages

First as always, it all starts with the native session creation…then, that’s when you can start creating the search request object:

IPTSearchRequest req = m_ptSession.GetSearchRequest();

From there, the PTSearchRequest object allows you to set all sorts of setting that will define the search you want to make. Simply call the SetSettings method. This method takes a setting ID and a value (that can be a string, int, or array of objects). The main problem is the non-documentation of this API (native API is non documented)…but luckily, the setting IDs are all available through the PT_SEARCH_SETTING class, and each name is relativelly straightforward (not always though). Check out the example below that sets the fields to return, specify not to execute best bet and spell check, and the maximum number of results to bring back:




   1: int[] arPropIDs = { PT_INTRINSICS.PT_PROPERTY_OBJECTID, PT_INTRINSICS.PT_PROPERTY_OBJECTNAME, PT_INTRINSICS.PT_PROPERTY_OBJECTSUMMARY};

   2: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_RET_PROPS, arPropIDs);

   3: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_INCLUDE_USUAL_FIELDS, false);

   4: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_KWIC, false);

   5: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_BESTBETS, false);

   6: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_SPELLCHECK, false);

   7: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_SKIPRESULTS, 0);

   8: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_MAXRESULTS, 10000);



You can also specify the admin folders (or KD folders) within which the search should be performed:




   1: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_ADMINFOLDERS, new int[] { adminfolderid });



and the object type the search should be dealing with (here we want to search only community pages, but very similarly to the object type checkboxes in the advanced search interface, you could pick several object type to search for):




   1: req.SetSettings(PT_SEARCH_SETTING.PT_SEARCHSETTING_OBJTYPES, new int[] { PT_CLASSIDS.PT_PAGE_ID });



Finally, you can create all sorts of filters statements that you can add to this search request. It works very similarly to the snapshot query interface: A filter can contain several “Filter Clauses” and each clause can contain several “Filter Statements”. Clauses and Statements can be put together using “OR” or “AND” operations.


Here for this exercise, we will look for objects with ID greater than 230 and name containing “Test”… (kind if useless query…but that’s not the point here…)




   1: // Create a filter for the search request which will "AND" together each filter clause.

   2: IPTFilter ptFilter = PortalObjectsFactory.CreateSearchFilter();

   3: ptFilter.SetOperator(PT_BOOLOPS.PT_BOOLOP_AND);

   4:  

   5: //Create the clause that will contains the statements we need for the query

   6: IPTPropertyFilterClauses ptFilterClause = (IPTPropertyFilterClauses) ptFilter.GetNewFilterItem(PT_FILTER_ITEM_TYPES.PT_FILTER_ITEM_CLAUSES);

   7: // The filter clause should "AND" each of the statements.

   8: ptFilterClause.SetOperator(PT_BOOLOPS.PT_BOOLOP_AND);

   9:  

  10: //Statement 1: ObjectID > 230

  11: IPTPropertyFilterStatement statement1 = (IPTPropertyFilterStatement)filter.GetNewFilterItem(PT_FILTER_ITEM_TYPES.PT_FILTER_ITEM_STATEMENT);

  12: statement1.SetOperand(PT_INTRINSICS.PT_PROPERTY_OBJECTID);

  13: statement1.SetOperator(PT_FILTEROPS.PT_FILTEROP_GT);

  14: statement1.SetValue(230);

  15:  

  16: //Statement 2: Object Name contains the text "Test"

  17: IPTPropertyFilterStatement statement2 = (IPTPropertyFilterStatement) ptFilter.GetNewFilterItem(PT_FILTER_ITEM_TYPES.PT_FILTER_ITEM_STATEMENT);

  18: //search on the name property.

  19: statement2.SetOperator(PT_FILTEROPS.PT_FILTEROP_CONTAINS);

  20: statement2.SetValue("Test");

  21:  

  22: //add statements to clause

  23: ptFilterClause.AddItem(statement1, ptFilterClause.GetCount());

  24: ptFilterClause.AddItem(statement2, ptFilterClause.GetCount());

  25:  

  26: //add clause to filter

  27: ptFilter.SetPropertyFilter(ptFilterClause);



As you can see it is very powerful and straightforward, and allows you to perform all sort of searches that fit your needs.


Finally, when you are done with the search parameters and filters, you simply need to execute the query, and get the results back…




   1: IPTSearchQuery query = req.CreateAdvancedQuery(filter);

   2: IPTSearchResponse ptPagesResponse = req.Search(query);

   3: int nResultCount = ptPagesResponse.GetResultsReturned(); 

   4: for (int nIndex = 0; nIndex < nResultCount; nIndex++) { 

   5:     //do something with the data... 

   6:     ptPagesResponse.GetFieldsAsInt(nIndex, PT_INTRINSICS.PT_PROPERTY_OBJECTID));

   7:     ptPagesResponse.GetFieldsAsString(nIndex, PT_INTRINSICS.PT_PROPERTY_OBJECTNAME));

   8:     ptPagesResponse.GetFieldsAsString(nIndex, PT_INTRINSICS.PT_PROPERTY_OBJECTSUMMARY));

   9: }



Here it is, I hope you see the endless possibilities you now have using the native search API in your various Native API Utilities (Portlet, Console application, etc…). I will soon post on the ALUI Toolbox google project the integrality of this code plus many other extras. Stay tune, and Happy new year! :)

Sunday, November 16, 2008

ALUI / WebCenter Interaction: Introduction to Native API development (Part 1)

Most of the time, creating a remote portlet application using the standard IDK is enough. Indeed, commonly, you just need to access user profile information, portlet ID, page ID etc... or perform simple operations exposed by the IDK PRC API.

But if you hit the limits of the IDK, it is not the end of the road. In the ALUI/Webcenter development documentation, they explain how to perform various UI customizations such as view replacement, PEI development, Activity Spaces creation or extensions, etc... All these are created within the portal application itself...Another option is to create what I call a "Native API Portlet Application". As its name indicates, this portlet application accesses directly the portal API and can perform virtually any operation that the portal can do...very powerful (but non supported :) so make sure your application is well written so that you can change the portal API access layer easily if you upgrade the portal version for example)

First, the 2 main requirements in order to create a Native API application are:

  • The application needs to be on the same server as Portal, or Automation, or API service
  • The application needs to reference various portal DLLs (located in /ptportal/6.1/bin/assemblies for dotnet, or /ptportal/6.1/lib/java for java). You don’t need them all. Here is the minimum list:



    ptportal\6.1MP1\bin\assemblies\opencache.dll

    ptportal\6.1MP1\bin\assemblies\openconfig.dll

    ptportal\6.1MP1\bin\assemblies\opencounters.dll

    ptportal\6.1MP1\bin\assemblies\openfoundation.dll

    ptportal\6.1MP1\bin\assemblies\openhttp.dll

    ptportal\6.1MP1\bin\assemblies\openkernel.dll

    ptportal\6.1MP1\bin\assemblies\openkernelsearch.dll

    ptportal\6.1MP1\bin\assemblies\openkernelsearchimpl.dll

    ptportal\6.1MP1\bin\assemblies\openlog-framework.dll

    ptportal\6.1MP1\bin\assemblies\openprocman.dll

    ptportal\6.1MP1\bin\assemblies\opensharedcache.dll

    ptportal\6.1MP1\bin\assemblies\opentempfile.dll

    ptportal\6.1MP1\bin\assemblies\openusage.dll

    ptportal\6.1MP1\bin\assemblies\openusage-api.dll

    ptportal\6.1MP1\bin\assemblies\openusage-impl.dll

    ptportal\6.1MP1\bin\assemblies\plumtreeserver.dll

    ptportal\6.1MP1\bin\assemblies\portal.dll

    ptportal\6.1MP1\bin\assemblies\pthome.dll

    ptportal\6.1MP1\bin\assemblies\ptportalobjects.dll

Important note: It is perfectly allowed to also include the IDK Dlls here too... and I find it particularly recommended if you are writing a native API portlet (for instance getting the current login token for example is fairly easy using the IDK API)

Second, you need to create the native session (everything starts from here, really):

1: String strServerConfigDir = ConfigPathResolver.GetOpenConfigPath(); 
2: IOKContext configContext = OKConfigFactory.createInstance(strServerConfigDir,"portal");
3: PortalObjectsFactory.Init(configContext);
4: IPTSession ptsession = PortalObjectsFactory.CreateSession();

From there you actually need to connect this session with a particular user identity. That way, the session object will be aware of your identity and especially the security and permission associated with your identity. In other words, even if you are using the native API, you cannot do more than you are allowed to.

To connect, 2 main options:

  • Use the login token that you can simply get from the IDK API (that is prefered option if you can)
1: String loginToken = m_portletRequest.GetLoginToken(); //IDK call
2: ptsession.Reconnect(loginToken);
  • Use explicit Username (or user ID) / Password to connect.

1: String username = "myusername";
2: String pwd = "mypassword";
3: ptsession.Connect(username, pwd, null);

Third (and Final), you access the right ObjectManager Class depending on which type of portal object you want to interact with.

When the session is created (that was pretty easy, right?), that is when you can actually start interacting with the portal internals...and a majority of actions goes through the PTObjectManager objects (IPTObjectManager interface). For instance:

1: IPTCommunityManager ptCommManager = ptSession.GetCommunities(); //for community objects
2: IPTPageManager ptPageManager = ptSession.GetPages(); //for community page objects
3: IPTObjectManager ptGadgetManager = ptSession.GetGadgets(); //for portlet objects
4: //etc...

You noticed that for portlets, there is not a IPTGadgetManager insterface....that’s ok as all the "Manager" interfaces are children of the base IPTObjectManager interface.

Although the vast majority of manager objects are accessible through a direct ptsession.Get() call, you can also get the right manager using the generic call below (using the classID of the object type you want)

1: IPTObjectManager ptCrawlerManager = ptSession.GetObjectManagers(PT_CLASSIDS.PT_CRAWLER_ID); 
2: //using     the class id of the object type you want

From there, the freedom is yours... and various operations will be available depending on the manager you called. One call for instance that all managers have is the open object:
1: ptObjectManager.Open(objectid, lockObject);

With this call, you get directly access a particular object in the portal, and interact with it as if you were in the UI

In the next article, I will show you how I package these API call in a standalone library to minimize as much as possible any API call within the presentation layer code...

Monday, October 27, 2008

ALUI Publisher - Part 3: No Redirect Bug Fix of Bug Fix :)

I have been relatively lazy in regards of my blog lately, and I have plenty of articles that are stacking up...But in the meantime, I thought this one is pretty urgent.

In one of my previous article, ALUI Publisher - Part 2: Increase Performance by enabling REAL Caching - No Redirect Bug Fix, I was explaining how to fix the Publisher published_content_noredirect.jsp (see  in that serie the benefits of using this instead of published_content_redirect.jsp).

Well, I found out a small little bug on my part, and it is definitely worth fixing if you have not done so already. In my corrective code, I was trimming the content string from its end spaces (just to optimized the HTML output)...and I did not think of the likely negative effects, such as if the buffered content does finish on a meaningful space character (such as a sentence separator etc...)

So the updated code with bug fix is: (specifically the trim that is removed)

//if there is content, forward to the requesting client
int buffersize = 2000;
int charread = 0;
char[] content;

//read until no byte is found in the input stream because request content length is no reliable
// UTF-8 is necessary
BufferedReader bisr = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));

do {
content = new char[buffersize];
charread = bisr.read(content);
out.write(new String(content).trim());
} while(charread > -1);

bisr.close();
bisr = null;
content = null;


Better to find it late than never! :)

The code should be updated on the famous (yeah right...) ALUI Toolbox Google project (or should be soon)

So Long.

Sunday, September 28, 2008

ALUI Enhancement: Get/Set All Preferences from Javascript

As you probably already know, ALUI Portal personalization features are mostly guarantied by the possibility of setting/getting "preferences" with various scopes. Not to myself extend too much on this concept, you can refer to the ALUI Development Documentation (Link HERE) to have further explanations. While you read this documentation, you'll learn that preferences can be set and retrieved through:

  • the IDK API (most commonly),
  • the server API (much less common since Server API should mostly be used only if the IDK cannot fulfill your needs), or
  • the Javascript API (but only for preferences of scope "session")

What is usually done consists in building a JAVA or Dotnet application that uses the IDK API to get/set preferences within a portlet, allowing for the presentation or behavior of the portlet to change based on the preference chosen.

But as you build ALUI intranet or even Internet websites, you often realize that most of the portlets are either content-specific with some kind of personalization (i.e. a link showing only if this preference has been set for that user...), or have a pretty simple behavior and presentation.

Would not that be cool to be able to use "Publisher" for example to build those simple portlets? In most cases, with the power of the ALUI Portal Adaptive Tags coupled with the Publisher PCS Tags (this could apply to any other Web Content Management Systems, but why not use Publisher as an example since it is still alive :) ), you could build such simple portlets without the need for programming a portlet in JAVA or Dotnet...

But what about preferences? How could you set and get preferences, since they are available only through an API that can be used only using those programming languages? Well, ALUI has already thought it through, and integrated within their Javascript API the possibility to set/get "Session" preferences. That was great thinking indeed. But I kept (and still keep) asking myself: Why having developed this idea only half way through?? Why allowing to only set/get "Session" preferences through Javascript? All I know is that there is no reason why not...and I just wanted to share this essential feature.

Javascript Session preference: How does this work?

So first, let's understand how the Javascript preference access works. Basically an activity space has been developed and its code is found in the following package: com.plumtree.portalpages.browsing.sessionprefs. As the portal follows a strict MVC (Model View Controller) architecture, an activity space is mostly composed of a "Control" class (implementing IControl interface), a "Model" Class (implementing IModel interface) and a "Presentation" class (implenting IDisplayPage interface). You can refer to the "ALUI Portal Customization Package" to see the code of this sessionPrefs activity space. To request the activity space functionnality, simply create a HTTP request with the right parameters, like this:

http://your.host.domain.com/portal/server.pt?space=SessionPrefs&control=SessionPrefs&action=getprefs&_preferencename=

http://your.host.domain.com/portal/server.pt?space=SessionPrefs&control=SessionPrefs&action=setprefs&_preferencename=preferencervalue

From there, all there is to do is to allow that request to be called from Javascript, through an AJAX HTTPWebRequest...The javascript methods getSession() and SetSession() which initiate that AJAX HTTPWebRequest will use the PTPortalContext javascript object defined on every page to get the right ActivitySpace url as follows:

PTPortalContext.GET_SESSION_PREFS_URL = 'http://localhost/portal/server.pt?space=SessionPrefs&control=SessionPrefs&action=getprefs';
PTPortalContext.SET_SESSION_PREFS_URL = 'http://localhost/portal/server.pt?space=SessionPrefs&control=SessionPrefs&action=setprefs';

...and that's it...Plumtree/BEA delivered your ALUI Javascript-Enabled "Session" Preference behavior.

Javascript preference of "All Type": How does this work?

All I did was extending on this principle in order to make all type of preferences available through Javascript, and as you understood now, through the behind-the-scene Preference Activity Space. Since I post the code on "ALUI Toolbox Google Project", I will pass on the very details of the code...

The main outline is:

  • Create a new Activity Space called "PortalPrefs" instead of SessionPrefs
  • Update the PortalPrefControl class to take into account a new parameter "type" which allow user to specify the type of preference you want to get/set (possible values are: portlet, admin, session, user, community, communityportlet).
  • Update the PortalPrefsModel class with the corresponding Preference Getter/Setter methods for each preference type (or scope)

Now, after installing this new code, the urls to access the AS are (differences in bold)

http://your.host.domain.com/portal/server.pt?space=PortalPrefs&control=PortalPrefs&action=getprefs&type=PreferenceType&_preferencename=

http://your.host.domain.com/portal/server.pt?space=PortalPrefs&control=PortalPrefs&action=setprefs&type=PreferenceType&_preferencename=preferencervalue

Then you have the choice as far as Javascript is concerned: either you create your own layer that performs the call to this new activity space, or you plug this new bahavior into the already existing Javascript framework, without any modification to the framework itself.

I personally chose the later, since I always try not to modify the portal existing libraries (just to allow for easier future upgrades of those libraries). All you have to do is to make sure to override on your portal page the PTPortalContext URL properties with the new AS URL end point BEFORE you call the already existing GetSession()/SetSession() javascript methods. To do that, you could simply create the following Facade JS methods to be called from your portlets:

<Script>

GetPortalPreference(prefType, prefname){

PTPortalContext.GET_SESSION_PREFS_URL = 'http://your.host.domain.com/portal/server.pt?space=PortalPrefs&control=PortalPrefs&action=getprefs&type=' + prefType;

GetSession(prefname); (this is the call to the already existing JS framework for session preference)

}

SetPortalPreference(prefType, prefname, prefvalue){

PTPortalContext.SET_SESSION_PREFS_URL = 'http://your.host.domain.com/portal/server.pt?space=PortalPrefs&control=PortalPrefs&action=setprefs&type=' + prefType;

SetSession(prefname); (this is the call to the already existing JS framework for session preference)

}

</script>

That's it! Feel free to browse the code (only JAVA version for now) at the Google Code Project I created (I also created for you the JAR that contains this code...that way, you can easily install and test this on your portal) and please let me know your thoughts! Hopefully, this will be integrated in the future releases of ALUI.

Monday, August 4, 2008

ALUI Best Practice - Don't Hardcode ObjectIDs: Use UUIDs Instead

In ALUI, there are 2 ways to identify uniquely an object:

  • ClassID (the type of object) + ObjectID (The ID of the object within the classID family)
  • Global UUID (unique ID throughout the environment)

The ClassID/ObjectID combination is used throughout the Portal API to query/open/manage the ALUI objects, as well as navigate to communities and pages. The ALUI ADAPTIVE TAGS are no exception: you will notice that they require ObjectID / ClassID to perform their tasks, like for example the opener tag:

<pt:standard.openerlink pt:objectid="219" pt:classid="514" pt:mode="2" target="myWindow">view community page</pt:standard.openerlink>

The main problem is not in the fact the ObjectID / ClassID is a bad way to identify an object, but in the fact that the ObjectID WILL NOT NECESSARILY (very improbable actually) be the same when you migrate objects from one environment to another. And that's where it can hurt...

Indeed good practice is to test out your creation in DEV / TEST / STAGING etc... and then migrate it using the ALUI migration tool. Since objectIDs will be different after migration in the new environment, all the objectIDs used in Adaptive Tags will have to be changed...hassle indeed.

Fortunately, UUID does NOT change with migration (or very improbable) from environments to environments. So that would seem a good option if you need to hardcode IDs, especially in Adaptive Tags, and I'd recommend that option everywhere you can.

In order to make it possible, I built a Adaptive Tags that does just this: Transform a UUID into its corresponding ClassID/ObjectID pair. All you need to do is use that Adaptive Tag before using the opener link tag for example. The object and class IDs are stored in shared memory (adaptive tag framework) with the specified scope and can be reused with any other tags that require objectID / ClassID.

<pt:taglibname.convertuuidtoid pt:uuid="{UUID}" pt:objectid="objectIDKey1" pt:classid="classIDKey1" pt:scope="portlet request" />

<pt:standard.openerlink pt:objectid="$objectIDKey1" pt:classid="$classIDKey1" pt:mode="2" target="myWindow">view community page</pt:standard.openerlink>

What's even better is that you can use this tag even for your remote portlet applications, like any other tags.

The code to change a UUID to the ObjectID / ClassID pair is fairly simple. Just get the Migration manager and call the convert method UUIDToObjectID to get the job done. I created a helper method that package it altogether:

public Object[] getClassObjectID(String uuid){
        Object[] oClassIDObjectID = null;
        if (null != uuid) {
            try {
                IPTMigrationManager oPTMigrationMgr = (IPTMigrationManager) oPTSession
                        .OpenGlobalObject(
                                PT_GLOBALOBJECTS.PT_GLOBAL_MIGRATION_MANAGER,
                                false);
                oClassIDObjectID = oPTMigrationMgr.UUIDToObjectID(uuid);
            } catch (Exception exc) {
                oClassIDObjectID = null;
            }
        }
        return oClassIDObjectID;
    }

The first item in the returned array is the ClassID (oClassIDObjectID[0]), the second item is the object ID (oClassIDObjectID[1]).

You can download the code on my newly created subversion project (http://alui-toolbox.googlecode.com) on Google code (I'll be updating/maintaining this as I see fit - feel free to suggest at will :)):

svn checkout http://alui-toolbox.googlecode.com/svn/trunk/ alui-toolbox-read-only

No more mocking around with IDs during migration :)

Hope that helps!