Skip Headers
Oracle® Fusion Middleware User Interface Customization Guide for Oracle WebCenter Interaction
10g Release 4 (10.3.3.0.0)

Part Number E14110-03
Go to Documentation Home
Home
Go to Table of Contents
Contents
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

12 Using PEIs

The Programmable Event Interface (PEI) framework defines a set of user actions that fire programmatic events that can be used to execute custom code without editing the portal source code. This approach allows you to upgrade the portal as service packs become available without losing your customizations.

A PEI is a Java or C# interface that defines event methods. For example, the ILoginActions PEI defines methods that are called when a user logs into the portal (e.g., On BeforeLogin, OnAfterLogin). The PEI framework provides interfaces only. To customize the portal using a PEI, you must create a class that implements the PEI and make the class available to the portal using Dynamic Discovery. This chapter provides a full ist of available PEIs and detailed instructions on implementation and deployment.

Note:

The events available via PEIs are fired only in response to a user's direct action on the portal. If an action occurs as a result of some automated process or a direct call to the portal server, PEI methods are not called. For a thorough explanation of how PEIs are executed by the portal, see Lifecycle of a PEI at the end of this chapter.

Step 1: Choosing a PEI

There is a wide range of PEIs available, each with a specific purpose. The table below summarizes the available PEIs and associated methods. For detailed information on available methods for each PEI, see the API documentation. (For links to all portal API documentation, see Appendix B, "Portal API Documentation".)

PEI Name and Description Available Methods

ILoginActions and ILoginActions2 (.uiinfrastructure.pei) allow you to create functionality within the scope of the login process. The login process is the most commonly customized functionality in the portal. ILoginActions2 extends ILoginActions and supports returning redirects for failed logins using SSO. For details, see the API documentation.

  • OnBeforeLogin

  • OnAfterLogin

  • OnFailedLogin

  • OnBeforeLogout

IOpenerActions (.uiinfrastructure.pei) allows you to implement custom functionality when the Common Opener is used to open an object or redirect to an Activity Space.

  • OnBeforeOpen

IPageActions (.uiinfrastructure.pei) allows you to add code to every page processed by the portal. This PEI should be used sparingly.

  • OnPageStart

  • OnPageFinish

IDisplayJavaScript (.uiinfrastructure.pei) allows you to add Javascript to every banner and editor page. This PEI should be used sparingly.

  • DisplayJavaScript

INewEditObjectActions (.portaluiinfrastructure.pei) allows you to implement custom functionality to be processed during the most common of all administrative actions: creating or editing a new object within the portal (the items that can be created from the Create Object menu in portal administration). For additional functionality, use IObjectActions (described below)

  • OnCreateObject

  • OnEditObject

  • OnBeforeStoreObject

  • OnAfterStoreObject

IDirectoryActions (.portalpages.pei) allows you to implement functionality in response to directory actions. For example, executing extra code when a user opens a folder or document within the portal Knowledge Directory.

  • OnOpenFolder

  • OnBeforeCreateDirectoryFolder

  • OnAfterCreateDirectoryFolder

  • OnBeforeDeleteDirectoryFolder

  • OAfterDeleteDirectoryFolder

  • OnBeforeDeleteDocument

  • OnBeforeCreateABOJob

  • OnAfterCreateABOJob

  • OnClickThroughToDoc

IUserProfileActions (.portalpages.pei) allows you to execute functionality when a user attempts to modify User Profile information.

  • OnBeforeChangeUserProfile

  • OnBeforeStoreUserProfile

IPasswordActions (.portalpages.pei) allows you to enforce restrictions on the password or verify the text entered by the user.

  • OnBeforeChangePassword

ICreateAccountActions (.portalpages.pei) allows you to execute functionality when a new user attempts to create an account, either through the Create Account button on the login page or in response to an invitation.

  • OnBeforeCreateAccount

  • OnAcceptInvite

IMyPortalPageActions (.portalpages.pei) allows you to perform validation before allowing users to add or remove portal pages.

  • OnBeforeAddMyPortalPage

  • OnAfterAddMyPortalPage

  • OnBeforeRemoveMyPortalPage

  • OnAfterRemoveMyPortalPage

  • OnBeforeEditMyPortalPage

  • OnAfterEditMyPortalPage

ICommunityActions (.portalpages.pei) allows you to add functionality dynamically when a user directly joins a Community or unsubscribes from a Community.

  • OnAfterUserJoinsCommunity

  • OnAfterUserQuitsCommunity

IAdvancedSearchActions and IBannerSearchActions (.portalpages.pei) allow you to make modifications to the query being processed.

  • CustomizeQueryOnBeforeSearch

  • GetCustomActionsOnBeforeSearch

INetworkSearchActions (.portalpages.pei) allows you to make changes to network searches after they have been submitted by the user.

  • OnBeforeNetworkSearchProcess

ISearchSettingActions (.portalpages.pei) allows you to track creation and deletion of saved searches (Snapshot Queries), and control naming and encoding for new saved searches.

  • OnBeforeSaveSearch

  • OnAfterSaveSearch

  • OnBeforeRemoveSavedSearch

  • OnAfterModifyOrRemoveSavedSearch

IObjectActions (.portalpages.pei) allows you to add functionality to almost any event that occurs during portal administration, including Delete, Move, Copy, and Object Migration. Each method on this PEI is executed when the corresponding event is processed within portal Administration and determines if the process should continue or if modifications are required.

Note: Use the *ObjectActions PEI sparingly; these functions are loaded and processed each time the corresponding event is called.

  • OnBeforeDeleteObject

  • OnBeforeMoveObject

  • OnBeforeMigrateObject

  • OnBeforeCopyObject

  • OnBeforeCreateABOJob

  • OnAfterCreateABOJob

  • OnBeforeCreateAdminFolder

  • OnBeforeCopyAdminFolder

  • OnBeforeDeleteAdminFolder


Implementing a PEI in a Custom Class

To customize portal functionality using a PEI, you must create a class that implements the PEI.

Below are some important objects used by multiple PEIs:

For more detailed information on objects and methods, see the API documentation.

To create a custom PEI, follow the steps below.

  1. Create your own custom project and custom PEI class (for example, a CustomLoginPEI project and a CustomLoginPEI class in com.yourcompany.pei.login).

  2. Edit the new class in your custom project.

  3. Compile the new class into a new JAR/DLL file with an intuitive name. Note: You must re-compile PEIs against each new release of the .NET portal even if you have not made any modifications.

This section provides three examples of implementing PEIs: Example 1: Hello World Login PEI, Example 2: Login Usage Agreement, and Example 3: Banner Search Customization.

Example 1: Hello World Login PEI

The sample customization below adds a message ("HELLO WORLD ") to be displayed in Logging Spy after a user logs in, by implementing the ILoginActions PEI.

The name of the file is important. All classes that implement PEIs should use a file name the references the name of the interface that they implement. The ILoginActions interface defines a few simple methods: OnBeforeLogin, OnAfterLogin, OnFailedLogin, and OnBeforeLogout. Use your IDE to navigate to the interface definition or view the API documentation for PEI classes.

  1. The OnAfterLogin() method allows for some functionality to occur once the user has successfully logged in and then possibly do a redirect to someplace other than the MyPage. As noted above, this code tells Logging Spy to to print out a HELLO WORLD string after a user logs in. This code uses the Error tracing type; you must confirm that Logging Spy Error tracing is enabled when you deploy the code to view the message.

    Java:

    public Redirect OnAfterLogin(Object _oUserSession, ApplicationData _appData) 
    { 
       // Print out "HELLO WORLD" to PTSpy
    log.Error("HELLO WORLD");
       return null; 
    } 
    

    C#:

    public virtual Redirect OnAfterLogin(Object _oUserSession, ApplicationData _appData) 
    { 
    // Print out "HELLO WORLD" to PTSpy
    log.Error("HELLO WORLD"); 
    return null; 
    } 
    
  2. The remaining methods in ILoginActions are unused in this example, so they simply return null. They could be used to perform similar actions before logging in or out, or when a login fails. For a more advanced login customization, see the next example.

.NET only: After the code is complete, you must associate the main portal project with the custom project.

  1. In Visual Studio, navigate to the SOURCE_HOME\portal.NET\prod and open portal*.sln.

  2. Expand the project, right-click on References, and select Add Reference.

  3. On the Projects tab, highlight the sampleloginpei project.

  4. Click Select and OK. Click OK to close the References window.

  5. Build the portal* solution to ensure that all projects build successfully.

  6. Close Visual Studio.

Once you have written the code for your new PEI, you must deploy it for use by the portal, described in the next section, Step 3: Deploying a Custom PEI.

Example 2: Login Usage Agreement

This customization redirects guest users to a usage agreement page when they log in. Based on whether they accept or reject the agreement, they are redirected to the appropriate My Page or back to the Login page. The customization includes the following classes:

  • The LoginAgreementActions class implements the ILoginActions PEI and executes custom code when users log in.

  • The GuestLoginAgreementControl class implements both the ILoginControl and IHTTPControl interfaces and logs users in as a custom guest user.

  • The MarkAsGuestControl class sets a variable on the HTTP Session to show that this custom guest user should still be treated as a guest user, basically invalidating their session.

  • The LoginAgreementRepostControl class handles the users choice to accept the agreement or to reject it.

The sections below summarize the functionality of each class. To view all the code for this customization, see the sampleagreementlogin project.

LoginAgreementActions

The LoginAgreementActions class implements ILoginActions and executes custom code when users log in to the portal.

The OnAfterLogin() method gets the PTSession and checks if the login is for an actual user, then checks if the user has already accepted the agreement. If the user has accepted the agreement, the login proceeds to the portal normally; if the user has not accepted the agreement, the login page redirects to GuestLoginAgreementControl.

public Redirect OnAfterLogin(Object _oUserSession, ApplicationData _appData) { 
   IPTSession ptSession = (IPTSession) _oUserSession;
if (ptSession.GetSessionInfo().GetCurrentUserID() != PT_INTRINSICS.PT_USER_GUEST)
{
IPTSessionInfo sessionInfo = ptSession.GetSessionInfo();
Object[][] result = sessionInfo.LookupPreference(AGREED, 0);
if (result[1][0] == null || !(((String) result[1][0]).equals(TRUE))) 
{
  Redirect guestRedirect = new Redirect();
  guestRedirect.SetLinkCreateNewSpace(LoginAgreementAS.STR_MVC_CLASS_NAME, null); 
  guestRedirect.SetControl(GuestLoginAgreementControl.STR_MVC_CLASS_NAME);
  return guestRedirect;
}
else 
{
  return null;
}
}
Return null;
}

GuestLoginAgreementControl

The GuestLoginAgreementControl class is responsible for logging users in and out. When users are first redirected to GuestLoginAgreementControl they are logged out and logged back in as a guest. After they have seen the agreements page, it redirects them back to GuestLoginAgreementControl, which logs users in through their account or redirects them back to the login page.

The GuestLoginAgreementControl class implements both the ILoginControl and IHTTPControl interfaces. The SetHttpItems() method accesses the HTTP request and PageData objects, used later in the AttemptLogin method. It is very important to clear these member variables (set them equal to null) after they have been used to make sure they are not leaked.

public void SetHTTPItems(IXPRequest _request, IWebData _pageData)
{
// In a given request, this method is called first.
m_xpRequest = _request;
m_WebData = _pageData;
}

The CheckActionSecurityAndExecute() method is used to return a Redirect object. This Redirect is followed after the login from the Control is processed. The Redirect object returned depends on the user's current status.

  • When users are directed to this control the first time, they are redirected to MarkAsGuestControl, which marks them as a guest and invalidates the session. This is to prevent users from viewing parts of the portal to which they do not have access. The users PTSession is cached for later use.

  • After users have viewed the agreements page and are redirected back to this control (the user session retrieved from the persistent sub-session is not null), the CheckActionSecurityAndExecute method checks whether they accepted the agreement or not. If they accepted the agreement, a redirect to their My Pages is returned. If they did not accept the agreement, they are redirected back to MarkAsGuestControl, which invalidates the session and redirects to the Login page.

public Redirect CheckActionSecurityAndExecute(XPHashtable _arguments)
{
Redirect rReturn = new Redirect();
rReturn.SetLinkCreateNewSpace(LoginAgreementAS.STR_MVC_CLASS_NAME, m_asOwner); 
rReturn.SetControl(MarkAsGuestControl.STR_MVC_CLASS_NAME);
ISessionManager perSession = m_asOwner.GetPersistentSubSession();
IPTSession userSession = (IPTSession) perSession.GetAttribute(SESSION);
if (userSession != null) 
{
IPTSessionInfo sessionInfo = userSession.GetSessionInfo();
Object[][] result = sessionInfo.LookupPreference(AGREED, 0);
String strResult = (String) result[1][0];
if (strResult != null && strResult.equals(TRUE)) 
{
      m_userSession = userSession;
      m_bAcceptance = true;
      Redirect myRedirect = new Redirect();  
      myRedirect.SetLinkCreateNewSpace(MyPageAS.STR_MVC_CLASS_NAME, m_asOwner);
      myRedirect.SetControl(MyPageAS.STR_MVC_CLASS_NAME); 
      return myRedirect;
}
else 
{
      Redirect markGuest = new Redirect();
      markGuest.SetLinkCreateNewSpace(LoginAgreementAS.STR_MVC_CLASS_NAME, null);    
      markGuest.SetControl(MarkAsGuestControl.STR_MVC_CLASS_NAME);
      perSession.RemoveAttribute(SESSION);
      return markGuest; 
}
}
IPTSession ptsession = (IPTSession) m_asOwner.GetUserSession();
perSession.SetAttribute(SESSION, ptsession);
return (Redirect) rReturn;
}

The DoGetSession() method tells the Interpreter whether or not to log in a new user during this request. This method creates a default guest user PTSession to be used in GetSession() if the user has not accepted the usage agreement. If a user has already accepted the usage agreement, this method does nothing.

public boolean DoGetSession()
{
try
{
String strCustomGuestName = "guest";
String strCustomGuestPassword = "";
if (!m_bAcceptance) 
{
     // Connect as the guest user
     m_userSession = PortalObjectsFactory.CreateSession();
     m_userSession.Connect(strCustomGuestName, strCustomGuestPassword, null);
}
}
catch (Exception e)
{
log.Error(e, "Unable to connect as guest.");
m_userSession = null;
// Unable to connect to custom guest user, do not log in
return false;
}
return true;
}

The GetSession() method returns a PTSession for the current user. The LoginHelper AttemptLogin method attempts to log users out of their account and log in to the default guest user account (using the PTSession created by DoGetSession() above) and calls all the appropriate login PEIs. This code nulls out the IHTTPControl member variables to make sure they are not leaked and returns the PTSession created earlier. It also removes the user's PTSession cached in the persistent sub-session if it is no longer needed.

Only after users have accepted the agreement are they allowed to log in through their account. In this case, the PTSession is the user session they originally logged in through, that was stored when they were first directed to this control in CheckActionSecurityAndExecute. If the agreement was not accepted, the PTSession returned is a new session for the guest user.

public Object GetSession()
{
if (null != m_userSession)
{
LoginResult rReturn = null;
try
{
     // Login the custom guest user.  This calls the login PEIs.
     rReturn = LoginHelper.INSTANCE.AttemptLogin(m_userSession, m_asOwner, m_xpRequest, m_WebData);
}
catch (Exception e)
{
     log.Error(e, "AttemptLogin() failed.");
}
 
          if (!rReturn.m_bSuccess)
{
     log.Error("GuestLoginControl AttemptLogin() as guest failed: " + rReturn.m_strError);
}
 
          if (null != rReturn.m_Redirect)
{
     log.Error("GuestLoginControl AttemptLogin() return redirect ignored.");
}
}
IPTSession userSession = m_userSession;
// Null out the IHTTPControl data so we don't retain the memory after the
// request is done (i.e. leak the memory)
m_xpRequest = null;
m_WebData = null;
m_userSession = null;
 
    // clean up
if (m_bAcceptance) 
{
ISessionManager perSession = m_asOwner.GetPersistentSubSession();
perSession.RemoveAttribute(SESSION);
m_bAcceptance = false;
}
return userSession;
}

MarkAsGuestControl

The MarkAsGuestControl class is another essential component in this customization. In order to prevent users from potentially viewing parts of the portal to which they should not have access, it is necessary to invalidate their session when logged in. This control essentially marks users as a guest, making sure they are unable to access any portion of the portal. This control is used when users are first logged out and logged back in as a guest, and also if users reject the agreement and are logged in as a guest and redirected to the login page.

The CheckActionSecurityAndExecute() method in this control sets a variable on the HTTP Session to show that users should still be treated as a guest user. The method sets an attribute (variable) on the user sub-session (HTTP Session). Setting the USERSESSIONVALID attribute to False tells the TopBar to treat users as a guest or non-authenticated user. The method then checks whether the user rejected the agreement or has not yet been prompted with the agreement by checking for a cached PTSession in the persistent sub-session. If the user has not yet been prompted with the agreement (i.e., there is a PTSession in the sub-session), the login cookie is removed to prevent a session timeout and they are redirected to LoginAgreementRepostControl. If the user has rejected the agreement, they are redirected to the login page.

public Redirect CheckActionSecurityAndExecute(XPHashtable _arguments)
{       
m_asOwner.GetSubSession().SetAttribute(Interpreter.USERSESSIONVALID, Boolean.FALSE);  
ISessionManager perSession = m_asOwner.GetPersistentSubSession();
IPTSession userSession = (IPTSession) perSession.GetAttribute(SESSION);
if (userSession != null) 
{
LoginHandlers.ClearLoginOccurredCookiePresent(m_asOwner.GetCurrentHTTPResponse());
Redirect rReturn = new Redirect();
rReturn.SetIsHTTPRedirect(true); 
rReturn.SetLinkGetSpaceIfCached(LoginAgreementAS.STR_MVC_CLASS_NAME, m_asOwner);       
rReturn.SetControl(LoginAgreementRepostControl.STR_MVC_CLASS_NAME);
return rReturn;
}
else 
{
Redirect toLogin = new Redirect(); toLogin.SetLinkGetSpaceIfCached(LoginAS.STR_MVC_CLASS_NAME, m_asOwner); toLogin.SetControl(DefaultLoginControl.STR_MVC_CLASS_NAME);
toLogin.SetControl(LoginControl.STR_MVC_CLASS_NAME);
return toLogin;
}
}

LoginAgreementRepostControl

The LoginAgreementRepostControl class implements the second half of this customization. GuestLoginAgreementControl handles the necessary details of logging a user in and out of the portal, while LoginAgreementRepostControl handles the corresponding user actions on the agreements page.

The CheckActionSecurityAndExecute() method in this control first checks if the user has already accepted the agreement. If users accept the agreement by clicking OK, a value is stored in the users preferences so they do not see the agreements page on future logins. Whether they accept the agreement or not, all users are redirected back to GuestLoginAgreementControl, which redirects to the proper Activity Space (the login page or the user's My Pages). The cached PTSession in the persistent sub-session is removed by GuestLoginAgreementControl either in GetSession() or CheckActionSecurityAndExecute() depending on whether the user has accepted the agreement or not.

public Redirect CheckActionSecurityAndExecute(XPHashtable arguments)
{
String[] sPostToSelf = (String[]) arguments.GetElement(RepostControl.HTMLINPUT_POSTTOSELF);
if(sPostToSelf == null)
{     
 return null;
} 
else
{
 if(XPConvert.ToInteger(sPostToSelf[0])== POSTTOSELF_ACTION_OK)
 {
      ISessionManager perSession = m_asOwner.GetPersistentSubSession();
      IPTSession newSession = (IPTSession) perSession.GetAttribute(SESSION);
      IPTSessionInfo mySessionInfo = newSession.GetSessionInfo();
      mySessionInfo.AddPreference(AGREED, TRUE, 0);
      Redirect guestRedirect = new Redirect();        
      guestRedirect.SetLinkCreateNewSpace(LoginAgreementAS.STR_MVC_CLASS_NAME, null); 
      return guestRedirect;         
 }
 else if(XPConvert.ToInteger(sPostToSelf[0]) == POSTTOSELF_ACTION_CANCEL)
 {
      Redirect guestRedirect = new Redirect();
      guestRedirect.SetLinkCreateNewSpace(LoginAgreementAS.STR_MVC_CLASS_NAME, null);
      guestRedirect.SetControl(GuestLoginAgreementControl.STR_MVC_CLASS_NAME);
      return guestRedirect;
 } 
 else
 {
      log.Debug("invalid POSTTOSELF option.");
      return null;
 }
}
}

Example 3: Banner Search Customization

The examples in this section customize portal banner search functionality through the IBeforeBannerSearchActions PEI.

Adding Strings to Search Queries

This code adds the string "oracle" to every banner search query.

package com.samplecompany.portalpages.pei;
import com.plumtree.server.*;
import com.plumtree.uiinfrastructure.activityspace.*;
import com.plumtree.portaluiinfrastructure.search.*;
import com.plumtree.portalpages.pei.*; 
public class SampleBannerSearchPEI1 implements IBannerSearchActions
{ 
public void CustomizeQueryOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession, QueryArguments _qaQueryInfo)
{ 
// Require items to contain the string "oracle" in 
// addition to the user's query
 
_qaQueryInfo.userQuery = "(" + _qaQueryInfo.userQuery + ") and oracle";
} 
public SearchSettingCollection GetCustomSettingsOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession)
{ 
return null; 
} 
}

Adding Properties to Search Fields

This example adds an author property to the set of fields to be searched.

package com.samplecompany.portalpages.pei; 
import com.plumtree.server.*;
import com.plumtree.uiinfrastructure.activityspace.*;
import com.plumtree.portaluiinfrastructure.search.*;
import com.plumtree.portalpages.pei.*; 
public class SampleBannerSearchPEI2 implements IBannerSearchActions 
{ 
public void CustomizeQueryOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession, QueryArguments _qaQueryInfo) 
{
// Add the "author" property (property 103) to the set 
// of fields to be searched
_qaQueryInfo.basicFields = "PT1[0.5],PT2[0.2],PT50[0.2],PT103[0.1]"; 
} 
public SearchSettingCollection GetCustomSettingsOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession) 
{ 
return null; 
} 
}

Adding Constraints to Properties

This example adds a constraint that the author property must contain "oracle" (in addition to the user's query, which can match on Name, Description, or Content).

package com.samplecompany.portalpages.pei;
 
import com.plumtree.server.*;
import com.plumtree.uiinfrastructure.activityspace.*;
import com.plumtree.portaluiinfrastructure.search.*;
import com.plumtree.portalpages.pei.*;
 
public class SampleBannerSearchPEI3 implements IBannerSearchActions
 
{
 
public void CustomizeQueryOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession, QueryArguments _qaQueryInfo)
 
{
 
// Add a constraint that the "author" property contain 
// "oracle"in addition to the user's query (on the name, 
// description, and content fields) 
 
// Create an advanced-search filter to replace the simple query
IPTFilter filter = PortalObjectsFactory.CreateSearchFilter();
 
// Set the user's query as the search string
filter.SetSearchString(_qaQueryInfo.userQuery);
 
// Want to AND the user's query with the property constraint
filter.SetOperator(PT_BOOLOPS.PT_BOOLOP_AND);
 
// Create the property part of the filter
IPTPropertyFilterClauses clause = 
(IPTPropertyFilterClauses)   
filter.GetNewFilterItem(PT_FILTER_ITEM_TYPES.PT_FILTER_ITEM_CLAUSES);
clause.SetOperator(PT_BOOLOPS.PT_BOOLOP_AND);
 
// Attach it to the filter
filter.SetPropertyFilter(clause);
 
// Create the single "author contains oracle" statement
IPTPropertyFilterStatement statement =
(IPTPropertyFilterStatement)  filter.GetNewFilterItem(
PT_FILTER_ITEM_TYPES.PT_FILTER_ITEM_STATEMENT);    
statement.SetOperand(103);   // property 103 == author
statement.SetOperator(PT_FILTEROPS.PT_FILTEROP_CONTAINS);
statement.SetValue("oracle");
 
// Attach statement to clause
clause.AddItem(statement, 0);
 
// Use the filter in place of the original user query
_qaQueryInfo.advancedFilter = filter;
_qaQueryInfo.userQuery = null;
 
}
 
public SearchSettingCollection GetCustomSettingsOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession) 
{ 
return null; 
} 
}

Restricting Banner Search

This example restricts banner search to match only documents and folders by turning off banner search of users, portlets, communities, Collaboration, and Publisher. This code also turns off spell correction.

package com.samplecompany.portalpages.pei; 
import com.plumtree.server.*;
import com.plumtree.uiinfrastructure.activityspace.*;
import com.plumtree.portaluiinfrastructure.search.*;
import com.plumtree.portalpages.pei.*;
 
public class SampleBannerSearchPEI4 implements IBannerSearchActions 
{ 
public void CustomizeQueryOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession, QueryArguments _qaQueryInfo) 
{ 
// Nothing here
} 
public SearchSettingCollection GetCustomSettingsOnBeforeSearch(AActivitySpace _asCurrentSpace, IPTSession _ptUserSession)
{ 
SearchSettingCollection c = new SearchSettingCollection(); 
// Restrict to cards and folders only, no other objects
c.add(PT_SEARCH_SETTING.PT_SEARCHSETTING_OBJTYPES,
new int[] { PT_CLASSIDS.PT_CATALOGCARD_ID, PT_CLASSIDS.PT_CATALOGFOLDER_ID });
 
// Restrict to portal items only, no Collab or Content
c.add(PT_SEARCH_SETTING.PT_SEARCHSETTING_APPS, PT_SEARCH_APPS.PT_SEARCH_APPS_PORTAL); 
// Turn off spell correction
c.add(PT_SEARCH_SETTING.PT_SEARCHSETTING_SPELLCHECK, false);
return c; 
} 
}

Once you have written the code for your new PEI, you must deploy it for use by the portal, described in the next section.

Step 3: Deploying a Custom PEI

After you create a custom project as described in the previous section, you must deploy it to the portal using Dynamic Discovery. For detailed information and instructions, see Chapter 18, "Deploying Custom Code Using Dynamic Discovery". To deploy a PEI, use Interface-Based Dynamic Discovery.

The example below deploys the Hello World Login PEI sample code from the previous section. Always confirm that your code was deployed correctly, explained in Viewing Your Customization in the Portal at the bottom of this section.

Example: Deploying the Hello World Login PEI

These instructions use Visual Studio in .NET and Ant scripts in Java to deploy your custom code.

First, add the library containing the new HelloWorldLoginAction class to the LoginActions.xml file so it can be deployed by Dynamic Discovery.

  1. Navigate to PT_HOME\settings\portal\dynamicloads\PEIs and open LoginActions.xml in a text editor.

  2. Add the name of the new PEI to the existing XML as shown below. Make sure that the HelloWorldLoginAction is listed after the PTLoginActions class and the spelling and capitalization is exactly the same as the full class name.

    <root>
    <interface name="com.plumtree.uiinfrastructure.pei.ILoginActions"/> 
    <interfaceassembly name="uiinfrastructure"/> 
    <class name="com.plumtree.portalpages.pei.PTLoginActions"/> 
    <class name="com.plumtree.sampleui.pei.HelloWorldLoginActions"/> 
    </root>
    

You must also run a clean build in order to deploy the custom code.

Java:

  1. Open a command prompt and change the directory to the \ptwebui directory where you installed the portal source code

  2. Run a clean build using the following Ant script: ant build.

  3. Generate a new WAR file for the application server using the following Ant script: ant install.

    Note: This target deletes and rebuilds all jar files associated with all the UI source projects (as well as the custom projects in the ptwebui folder).

C#:

  1. Build the project in Visual Studio.

  2. Visual Studio should copy the sampleview.dll file from SOURCE_HOME\sampleview\dotnet\prod\bin to PORTAL_HOME\webapp\portal\bin for you. If there are problems with Dynamic Discovery on startup, you might need to do this step manually. This is necessary to allow Dynamic Discovery to find the new library.

Viewing Your Customization in the Portal

Once you have deployed your code, view the changes in the portal to confirm that they were loaded correctly.

  1. Open Logging Spy. For details, see the Administrator Guide for Oracle WebCenter Interaction.

  2. Click the Set Filters button to open the Filter Settings dialog. Make sure the Error checkbox is selected. (You will not be able to see the customization run if this logging level is not enabled.)

  3. Start the portal and view Logging Spy. During startup, you should see a message about the loading of LoginActions classes; two ILoginAction classes should be loaded. If no ILoginActions classes were loaded, you might have misspelled or mis-capitalized one of the names.

  4. Open a new browser window and navigate to the portal. Do not log in. You should see the "HELLO WORLD" string in Logging Spy. This is because when you first hit the portal, you are logged in as the guest user automatically to display the login page.

  5. Login as the administrator. You should see the "HELLO WORLD" string again in Logging Spy because you have explicitly logged in as a user.

The next step is to debug your code.

Step 4: Debugging and Troubleshooting

This section provides technical tips for common problems and instructions on how to debug your new PEI.

Technical Tips

If your custom PEI does not function, first make sure the full class name of the HelloWorldLoginAction PEI is listed in LoginActions.xml exactly as it is spelled in the code. It will not load if spelled or capitalized incorrectly. This may not produce any errors during startup. One way to check that the ILoginActions loaded correctly is to make sure that Logging Spy says the correct number was loaded (2). This is explained in Viewing Your Customization in the Portal.

If this does not solve the problem, debug your code using the instructions below.

Debugging

These instructions use the Hello World Login PEI class created in the previous sections as an example.

Java

  1. In Eclipse, stop the Tomcat debugging session and open HelloWorldLoginActions.java.

  2. Add a breakpoint at the log.Error line .

  3. In the Eclipse menu, click Run | Debug… and select the Tomcat application.

  4. Choose the Classpath tab, select Add Projects, and add the sampleloginpei project.

  5. Hit Debug (and Save to retain your changes).

  6. Navigate to your portal and view the login page. You should hit this breakpoint, since you are automatically logged in as the guest user when you first view the portal in a new browser.

C#

  1. Stop the Visual Studio debugger (and close your browser if it is still open) and open HelloWorldLoginActions.cs in Visual Studio.

  2. Add a breakpoint at the log.Error line.

  3. Start the Visual Studio debugger (F5 or Start | Debug).

  4. Navigate to your portal and view the login page. You should hit this breakpoint, since you are automatically logged in as the guest user when you first view the portal in a new browser.

Lifecycle of a PEI

This section traces the lifecycle of a PEI and provide a comprehensive view of what happens to it, from the mechanism that loads the PEI to the actual code invoked when a PEI event occurs.

Step 1: Loading the PEI

All PEIs are loaded at runtime when the portal first starts up. Because PEIs are loaded using Dynamic Discovery, they can be plugged into the portal without modifying existing UI code. This section examines some of the code that makes up the PEI infrastructure. This code is from the UIInfrastructure project, and the source for this project is not distributed with the portal; it is included here solely for the purpose of understanding PEIs.

When the portal first starts up, it is initialized using an Init method in the AppWarmUp class from the com.plumtree.uiinfrastructure package:

public class AppWarmUp 
public static final void Init(String strVarPackXMLFile, String strApplicationName, String _strPlatform)

The Init method initializes most aspects of the portal, including the loading of PEIs. The following code from the Init method shows exactly how it happens. First, it establishes the path from which the portal will get the PEI loading information, which is in the \dynamicloads\PEIs folder inside the portal configuration folder (PT_HOME\settings\portal\dynamicloads\PEIs). This directory contains the files that allow you to add a new PEI into the system. The code then makes a call to the LoadSettings helper method with this path.

String strPortalDynamicLoadFolder = strPortalConfFolder + strFileSeparator + "dynamicloads" + strFileSeparator; 
// CODE TRUNCATED HERE FOR BREVITY 
try 
{ 
   // Load Dynamic Loads 
   if(PTDebug.IsInfoTracingEnabled(Component.UI_Infrastructure)) 
   { 
     PTDebug.Trace(Component.UI_Infrastructure, TraceType.Info, "Loading the dynamic loads."); 
   } 
      LoadSettings(application, strPortalDynamicLoadFolder, strLibHomePath); 
}

The LoadSettings helper method takes the path to the \dynamicloads\PEIs folder, and attempts to discover the corresponding PEI for each XML file in the folder (the looping of files is omitted in the code below). As the information from each XML file is loaded and the PEI instances are created, they are stored in the portal application; the strCacheString reference in the code below is then used to retrieve the instances (explained in the next section).

The GetCachingManager.SetEntry (and analogous GetCachingManager.GetEntry) methods are frequently used to put custom objects on the portal application. These objects can then be retrieved in other custom code. These methods are also used in other dynamically discovered objects, such as custom navigation schemes.

private static final void LoadSettings(IApplication app, String strSettingsFolder, String strLibHomePath) 
strCacheString = strFileName.substring(0, nEndIndex); 
// CODE TRUNCATED HERE FOR BREVITY 
try 
{ 
   settings = XPDynamicDiscovery.GetInstancesFromXML(strSettingsFolder + strFileName, strLibHomePath); 
   if (null != settings) 
   {   
     if (PTDebug.IsInfoTracingEnabled(Component.UI_Infrastructure)) 
     { 
       PTDebug.Trace(Component.UI_Infrastructure, TraceType.Info, 
         " Found: " + settings.length + " instances of interface" + 
         " described in " + strCacheString); 
     } 
   } 
} 
// CODE TRUNCATED HERE FOR BREVITY 
app.GetCachingManager().SetEntry(strCacheString, settings);

Each XML file in the \dynamicloads\PEIs directory is processed at startup. If Logging Spy is running, you can view a report of how many interfaces were discovered for each file. This is a good way to validate that your PEI was added correctly. For example, if you want to add a LoginActions PEI, first start the portal without changing the LoginActions.xml file. Write down the number of instances for LoginActions that Logging Spy reports. Add your PEI to LoginActions.xml, restart the portal, and compare the number of instances to the number you wrote down. It should have incremented by the number of interfaces you added.

Memory Debug Page

The Memory Debug page is another useful tool for gathering information about the portal. This page provides a summary of your memory internals and lists the objects residing in the HTTPSession, the Activity Space Cache, the Portal Application, etc. As explained above, the PEI instances created from each XML file are stored in the Portal Application cache. The Memory Debug page displays an object representation of an array for each of the XML files (UserProfileActions is highlighted in the image below).

To access the Memory Debug page, log in to the portal as a user with Administrative rights, and navigate to the page by appending the following string to the portal URL: ?space=MemoryDebug.

Once the PEIs are loaded, the portal must execute them in response to the appropriate actions, as explained next.

Step 2: Executing the PEI

To understand what happens when a PEI event is invoked, you must look at the supporting PEI framework code in the portal. The code that supports each PEI differs slightly, but the concept is the same.

This example looks at the code that supports the OnAfterLogin PEI, located in the LoginHelper class in the com.plumtree.uiinfrastructure.login package. The DoTasksAfterLogin method is called by the portal after a successful login attempt:

public final class LoginHelper 
 
private Redirect DoTasksAfterLogin(Object userSession, IXPRequest request, IWebData webData, IApplication application, ISessionManager sessionManager)

One of the first actions of DoTasksAfterLogin is to retrieve the PEIs loaded in the portal application cache. As explained in the previous section, PEI instances are created from the XML files in the \dynamicloads\PEIs folder and stored on the portal application cache using strCacheString.

The strCacheString key is the filename of the XML file without the extension. This example deals with a Login PEI, so the DoTasksAfterLogin method uses the key "LoginActions" to refer to the PEI instances loaded from processing the LoginActions.XML file.

oa = (Object[]) application.GetCachingManager().GetEntry("LoginActions");

After the appropriate PEI instances are retrieved from the application, the following loop iterates through them by calling the OnAfterLogin method on each of the LoginAction PEI instances. As you can see from the logic and the comment in the code, each PEI placed in LoginActions.XML is called in order until one of them returns a valid Redirect.

For example:

  • If none of the PEIs return a valid Redirect, all of the PEIs will be called.

  • If the first PEI returns a valid Redirect, then it will be the only PEI called. All others will be ignored.

  • If the method called on the PEI returns void, another PEI will be called.

Note: This is the general process for PEI framework code, but there are exceptions in which the loop is different. For example, the DoTaskOnFailedLogin method that handles OnFailedLogin for a Login PEI returns a String instead of a Redirect. In this case, the method calls all instances of the PEIs regardless of the String returned, concatenates them together and returns the final result.

for(int x = 0; x < oa.length; x ++) 
{ 
   o = oa[x]; 
   // CODE TRUNCATED HERE FOR BREVITY 
   if (o != null) 
   { 
      iActions = (ILoginActions) o; 
      //   OnAfterLogin returns a Redirect. 
      //   If it returns a valid object then this Redirect is stored, 
      //   and possibly returned. If there are multiple implementations 
      //   of this method, and multiple valid redirects are returned, 
      //   then the last one will stick, and the others will be forgotten. 
      rTemp = iActions.OnAfterLogin(userSession, myData); 
      if (rTemp != null) 
      { 
        rReturn = rTemp; 
        return rReturn; 
      } 
   } 
}