Portal UI Customization: Advanced: PEIs

Step 2: Implementing the PEI in a Custom Class

To customize portal functionality using a PEI, you must create a class that implements the PEI. For a list of PEIs, see Step 1: Choosing a PEI. For detailed information on available methods for each PEI, see the API documentation. This page provides three examples of implementing PEIs: Hello World Login PEI, Login Usage Agreement, and Banner Search Customization.

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 (e.g., a CustomLoginPEI project and a CustomLoginPEI class in com.yourcompany.pei.login). For instructions on creating a custom project, see ALI Portal Development Environment: Creating a Custom Project (Java | .NET).

  2. Edit the new class in your custom project.

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

This page provides three examples of implementing PEIs: Hello World Login PEI, Login Usage Agreement, and Banner Search Customization.

Example 1: Hello World Login PEI

The sample customization below adds a message ("HELLO WORLD ") to be displayed in PTSpy after a user logs in.

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 (e.g., ILoginActions).

Note: These instructions apply to AquaLogic Interaction (formerly called the Plumtree Portal) version 6.0.

  1. This example uses the sample code from the Developer Center: SampleProjects-60.zip. For details, see Installing UI Customization Sample Projects (Java | .NET).  Install the files and add the sampleloginpei project to your IDE. (You must also set up your IDE for portal development as explained in Setting Up the Development Portal.)

  2. Open the HelloWorldLoginActions file (.java or .cs). This file implements the ILoginActions interface at com.plumtree.uiinfrastructure.pei.

  3. 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 PTSpy to to print out a HELLO WORLD string after a user logs in. This code uses the Error tracing type; you must confirm that PTSpy 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;

    }

  4. The remaining methods 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.

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

    1. In Visual Studio, navigate to the SOURCE_HOME\portal.NET\prod and open portal60.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 portal60 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 step.

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 sections below summarize the functionality of each class. To view all the code for this customization, see the sampleagreementlogin project in SampleProjects-60.zip (available on the ALUI Developer Center on dev2dev).

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.  

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 plumtree 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 "plumtree" in
// addition to the user's query

_qaQueryInfo.userQuery = "(" + _qaQueryInfo.userQuery + ") and plumtree";

}

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 plumtree (in addition to the users 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
// "plumtree"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 plumtree" 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("plumtree");

// 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 provides a 4.5WS-like banner search experience.  It restricts banner search to match only documents and folders by turning off banner search of users, portlets, communities, Collaboration Server, and Content Server.  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 step.

Next: Step 3: Deploying a Custom Project