Oracle® Fusion Middleware User Interface Customization Guide for Oracle WebCenter Interaction 10g Release 4 (10.3.3.0.0) Part Number E14110-03 |
|
|
View PDF |
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.
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. |
|
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. |
|
IPageActions (.uiinfrastructure.pei) allows you to add code to every page processed by the portal. This PEI should be used sparingly. |
|
IDisplayJavaScript (.uiinfrastructure.pei) allows you to add Javascript to every banner and editor page. This PEI should be used sparingly. |
|
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) |
|
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. |
|
IUserProfileActions (.portalpages.pei) allows you to execute functionality when a user attempts to modify User Profile information. |
|
IPasswordActions (.portalpages.pei) allows you to enforce restrictions on the password or verify the text entered by the user. |
|
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. |
|
IMyPortalPageActions (.portalpages.pei) allows you to perform validation before allowing users to add or remove portal pages. |
|
ICommunityActions (.portalpages.pei) allows you to add functionality dynamically when a user directly joins a Community or unsubscribes from a Community. |
|
IAdvancedSearchActions and IBannerSearchActions (.portalpages.pei) allow you to make modifications to the query being processed. |
|
INetworkSearchActions (.portalpages.pei) allows you to make changes to network searches after they have been submitted by the user. |
|
ISearchSettingActions (.portalpages.pei) allows you to track creation and deletion of saved searches (Snapshot Queries), and control naming and encoding for new saved searches. |
|
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. |
|
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:
The IPTSession
object is always available to PEIs. If the object is not passed to the PEI in the argument, it can be retrieved from the ActivitySpace
object ((IPTSession)myActivitySpace.GetUserSession()
). The IPTSession
object is the portal session for the current user. This object lets you perform any action that the current user has the rights to execute on the portal server. For example, if the current user has the necessary rights, you can create new portal objects, query existing objects, browse the Directory or query MyPages and communities.
The ActivitySpace
object is passed to many PEI functions. This object provides access to the user session and any user specific information, as well as other Activity Space-specific information including page name, Control name and server name. The ActivitySpace
object is the container for all the Model, View, and Control classes that comprise a particular piece of functionality. Any objects required by a piece of functionality can usually be retrieved from the parent ActivitySpace object.
The ApplicationData
object is is passed to PEIs that are not sent the ActivitySpace object. The object provides access to application-specific data from the Activity Space, including the web application and the web session. The ApplicationData
object also lets you perform some actions on the Request object. For example, you can get and set cookies, get the requested URL or set values on the HTTP header.
For more detailed information on objects and methods, see the API documentation.
To create a custom PEI, follow the steps below.
Create your own custom project and custom PEI class (for example, a CustomLoginPEI project and a CustomLoginPEI class in com.yourcompany.pei.login).
Edit the new class in your custom project.
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.
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.
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; }
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.
In Visual Studio, navigate to the SOURCE_HOME\portal.NET\prod and open portal*.sln.
Expand the project, right-click on References, and select Add Reference.
On the Projects tab, highlight the sampleloginpei project.
Click Select and OK. Click OK to close the References window.
Build the portal* solution to ensure that all projects build successfully.
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.
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.
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; }
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; }
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; } }
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; } } }
The examples in this section customize portal banner search functionality through the IBeforeBannerSearchActions PEI.
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; } }
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; } }
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; } }
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.
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.
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.
Navigate to PT_HOME\settings\portal\dynamicloads\PEIs and open LoginActions.xml in a text editor.
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:
Open a command prompt and change the directory to the \ptwebui directory where you installed the portal source code
Run a clean build using the following Ant script: ant build
.
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#:
Build the project in Visual Studio.
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.
Once you have deployed your code, view the changes in the portal to confirm that they were loaded correctly.
Open Logging Spy. For details, see the Administrator Guide for Oracle WebCenter Interaction.
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.)
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.
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.
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.
This section provides technical tips for common problems and instructions on how to debug your new PEI.
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.
These instructions use the Hello World Login PEI class created in the previous sections as an example.
Java
In Eclipse, stop the Tomcat debugging session and open HelloWorldLoginActions.java.
Add a breakpoint at the log.Error
line .
In the Eclipse menu, click Run | Debug… and select the Tomcat application.
Choose the Classpath tab, select Add Projects, and add the sampleloginpei project.
Hit Debug (and Save to retain your changes).
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#
Stop the Visual Studio debugger (and close your browser if it is still open) and open HelloWorldLoginActions.cs in Visual Studio.
Add a breakpoint at the log.Error
line.
Start the Visual Studio debugger (F5 or Start | Debug).
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.
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.
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.
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.
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; } } }