Wednesday, December 3, 2014

Webgate Installation on Solaris 10 Sparc 64 bit machine

Environment Details

OHS/Webgate (11.1.1.5)
Solaris 10 Sparc 64 bit

OHS/Webgate 11.1.1.5) requires the compiler package gcc 3.3.2 version. This has been clearly stated in Oracle Documentation http://docs.oracle.com/cd/E21764_01/install.1111/e12002/webgate.htm#INOIM75766. By default, Solaris 10 comes with the gcc 3.4.3 libraries. I checked with product development folks and they told me that there was a design decision to change over the compilation from gcc compiler to the native solaris compiler from 11.1.1.7 and  hence the OHS / Webgate product support for other newer versions of gcc are not there and I guess they found some issues with gcc 3.4.3+.

For details check the Oracle Support Document 1460975.1 (Where are the Gcc 3.3.2 64bits Libraries Required by the OAM11g WebGate on Solaris 10 Sparc 64-bit) can be found at: https://support.oracle.com/epmos/faces/DocumentDisplay?id=1460975.1.

libgcc 3.3 package includes both 32 and 64 bit versions of the library files. Unzip and install the libgcc 3.3 package, by default this will install to /usr/local/lib.  Use the files in the /usr/local/lib/sparcv9/ directory as these are the 64 bit versions. The versions installed to /usr/local/lib/ are 32 bit versions.
If the correct version is not used, you will see the below error during installation -
ld.so.1: configureWebGate: fatal: 
/<WG install directory>/access/oblix/tools/configureWebGate/../../../oblix/lib/libstdc++.so.5: 
wrong ELF class: ELFCLASS32





Monday, November 10, 2014

X11 forwarding

This blog is about X11 forwarding. This comes in handy when you want to see the graphical interfaces of X11 programs running on a remote Linux server to be displayed on a local client machine. I am not an Linux expert so it took some time for me to set this up, so I thought of writing it in blog for future references.

Need ?

I came across the need to do X11 forwarding for one of my project. I had a remote access to the linux server and my task was to install one of the Oracle FMW product. During installation, graphical wizards comes for the configuration settings and this is the place where you require X11 forwarding.
When you connect to linux server via SSH and try to run the graphical applications you will get the error - Can't open display. Basically when you SSH in and run a graphical application, it has no location in which to draw the window. To put it another way, there is no display associated with that connection.In order to get the instructions on how to draw the window sent back over the network to our computer the SSH client needs to be configured to request them.

How to solve the problem ?

Problem can be fixed by following 2 steps -
1. Requesting the remote linux server to forward X connections - Configure the SSH client to send the instructions to draw the GUI sent over the network to the client machine.

2. Running an X server on the client machine to listen for the connections - The role of the X server is to make sense of the instructions coming from remote Linux server and translate them into commands that can be drawn by Windows.

Prerequisites 

  • Install putty and configure for SSH.
  • Install Xming server on your Windows PC. You can download it from Sourceforge link.

Configuration Steps

1 Set X11 forwarding in your putty session



2. SSH to the remote Linux server and follow the below steps.

i) Before issuing the su or sudo, request the cookie for the current DISPLAY to connect to your Xming server

$xauth list
host/domain:10 MIT-MAGIC-COOKIE-1 HEX-KEY

For Ex:
$ xauth list
atloraman/unix:10  MIT-MAGIC-COOKIE-1  926b224b5afc20f8db4bf06c0ef6a6c8


Note: Above output will be different for every session

ii) Switch to atladmin or oracle account
    $ su - atladmin

   Export your DISPLAY variable
  $ export DISPLAY=localhost:10.0

iii) Add the output string from the xauth list command above:

$ xauth add displayname protocolname hexkey

For Ex:
$ xauth add atloraman/unix:10  MIT-MAGIC-COOKIE-1  926b224b5afc20f8db4bf06c0ef6a6c8

Run xclock to verify your X11 session is working





iv) When you don't need the access, execute the below command -
xauth remove $DISPLAY

Thursday, October 16, 2014

OAM - Webcenter Content Imaging (IPM) Logout Configuration

Environment Details

Oracle Access Manager - Version 11.1.1.5.0 and later
Oracle WebCenter Content: Imaging - Version 11.1.1.6.0 and later

Scenario

Here we are talking about the typical Fusion Application environment consisting of multiple applications like for ex: Oracle Webcenter Content, Imaging and Portal. OAM is used to provide the SSO fuctionality. I faced problem while configuring the SSO logout URL for IPM application.

Solution

To configure SSO logout url for Oracle Webcenter Content and portal, we run the command -

addOAMSSOProvider(loginuri, logouturi, autologinuri)

See Oracle Doc for details.

The same solution doesn't go well with IPM. I fixed it by writing the redirect rules in web server using the OHS URL rewrite engine. I am not sure if it's the oracle recommended approach.

RewriteCond %{QUERY_STRING} end_url=/imaging [NC]
RewriteCond %{QUERY_STRING} logout=true [NC]
RewriteRule ^/imaging/adfAuthentication  /imaging/adfAuthentication?logout=true&end_url=<OAM-Logout-URL>[L,R=301]

<OAM-Logout-URL> - http://OAMServer_host:14200/oam/server/logout

Go through the below links to get better understanding of OHS Rewrite Rules and flags -

http://www.webforgers.net/mod-rewrite/mod-rewrite-syntax.php
http://www.colder.ch/news/01-26-2007/24/truth-about-the-last-mod_.html (Very Good explaination of L Flag)





   

Passed Oracle ADF 12c Essentials Exam (1Z0-419) !!

I recently passed the Oracle ADF 12c exam(1Z0-419) and impressed with the exam pattern and question selection.



Here are my thoughts -


  1. Questions are more structured and conceptual compared to the earlier ADF Essentials Exam (1Z0-554).
  2. Surprisingly, not even one question asked on the added features in ADF 12c. So, the course material is same as earlier ADF exam.
  3. Most of the questions test the practical knowledge and concepts instead of just mugging up the study/developer guide.
  4. 87 questions in 120 minutes is not easy as many of the questions are multiple choice and require reading al l the options. So, don't think time is too much.
  5. To pass the exam, just follow the Student Guides and cover each and every topic because questions are asked from all the topics.I really liked the questions asked on debugging scenarios. I followed Oracle student guides(D68160 and D68161). D68162 covers advance topics and not many questions are asked in exam from that.





Saturday, July 26, 2014

Deployment Script for Webcenter Application


Oracle WebCenter Portal applications differ from traditional Java EE applications in that they support run-time customization. WebCenter Portal application customizations are stored in Oracle Metadata Services (MDS), which is installed in a database/file. So, while deploying the webcenter application on WLS, you need to  specify the metadata repository and the partition in the repository that application will be deployed to.
As far as I know, targeting the application to MDS repository deployment is not possible using the wldeploy ant task. WLST commands can be used  to target the application to MDS repository and deploy the application on WLS. So, first create a python script and then call it from the ant script.

Here is the script deploy.py -

# Below args are passed by the ant script
adminUser=sys.argv[1]
adminPassword=sys.argv[2]
adminUrl=sys.argv[3]
# Connect to WLS
connect(adminUser,adminPassword,adminUrl)
domainRuntime()

# Stop the application
def stop():
 stopApplication(sys.argv[6])

# Undeploy the applciation
def undeployApp():
 print 'Begin undeploy'
 undeploy(sys.argv[6])
 print 'End undeploy'

# Deploy the application
def deployApp():
 print 'Begin deployAll'
 serverConfig()
 # Returns a handle to the MDSArchiveConfig object for the specified archive 
 archive = getMDSArchiveConfig(fromLocation=sys.argv[4])
 # Sets the connection details for the application metadata repository
 archive.setAppMetadataRepository(repository=sys.argv[5],partition=sys.argv[6],
 type=sys.argv[7], jndi=sys.argv[8])
 archive.save()
 deploy(appName=sys.argv[6], path=sys.argv[4], targets=sys.argv[9], upload='true')
 
 print 'End deployAll' 

# Start the application 
def start(): 
 startApplication(sys.argv[3])
 
# Deploy script init- First Stop the application and then undeploy. 
#After undeploying, deploy the application and restart

try:
 try:
  stop()
 except:
  print sys.exc_info()[0]
 print 'Stop Done'
 try:
  undeployApp()
 except:
  print sys.exc_info()[0]
 print 'Undeploy Done'
 deployApp()
 start()
 
except:
 print 'Unexpected error: ', sys.exc_info()[0]
 dumpStack()
 raise


Build.xml
See the target "deploy-to-dev-server" to call the above python script.

<?xml version="1.0" encoding="US-ASCII" ?>
<project name="DigitalFirst" default="all" basedir=".">
<property environment="env"/>
<property file="build.properties"/>

<taskdef name="ojdeploy"
           classname="oracle.jdeveloper.deploy.ant.OJDeployAntTask"
           uri="oraclelib:OJDeployAntTask"
           classpath="${oracle.jdeveloper.ant.library}"/>

 <!-- This target is to build ear for application -->   
  <target name="deploy-to-ear" depends="deploy-all-projects">
    <ora:ojdeploy xmlns:ora="oraclelib:OJDeployAntTask"
                  executable="${oracle.jdeveloper.ojdeploy.path}"
                  ora:buildscript="${oracle.jdeveloper.deploy.dir}/ojdeploy-build.xml"
                  ora:statuslog="${oracle.jdeveloper.deploy.dir}/ojdeploy-statuslog.xml">
      <ora:deploy>
        <ora:parameter name="workspace"
                       value="${oracle.jdeveloper.workspace.path}"/>
        <ora:parameter name="profile" value="*"/>
        <ora:parameter name="outputfile"
                       value="${oracle.jdeveloper.workspace.dir}/deploy/${ear.filename}"/>
      </ora:deploy>
    </ora:ojdeploy>
  </target>

  <!-- This target is to deploy the application on WLS server  -->  
  <target name="deploy-to-dev-server" >
    <exec executable="${oracle.wlst.path}"
          spawn="false" failonerror="true">
      <!-- Python Script Relative Path from build.xml.In this case both
      are in same folder-->
      <arg value="deploy.py"/>
      <!-- Application ear Path -->
      <arg value="${ear.location}"/>
      <!-- MDS Repository Name -->
      <arg value="${dev.wc.mds.repository}"/>
      <!-- Application Name. See Project Properties -> Java EE Application -->
      <arg value="${application.name}"/>
      <!-- Repository type (DB/file) -->
      <arg value="${dev.wc.mds.repository.type}"/>
      <!-- Repository JNDI. See WLS EM console -->
      <arg value="${dev.wc.mds.repository.jndi}"/>
      <!-- WLS credential and URL -->
      <arg value="${dev.wls.username}"/>
      <arg value="${dev.wls.password}"/>
      <arg value="${dev.wls.adminurl}"/>
      <arg value="${dev.wls.cluster.name}"/>
    </exec>
  </target>


Build.properties

Below is the same build.properties file. Set the paramters based on your environment -

# Oracle Jdev Library Path variables
oracle.middleware.home=<Middleware Home> e.g C:/Oracle/Middleware
oracle.jdeveloper.home=${oracle.middleware.home}/jdeveloper
oracle.wls.home=${oracle.middleware.home}/wlserver_10.3
oracle.wlst.path=${oracle.middleware.home}/oracle_common/common/bin/wlst.cmd
oracle.jdeveloper.ant.library=${oracle.jdeveloper.home}/jdev/lib/ant-jdeveloper.jar
oracle.jdeveloper.ojdeploy.path=${oracle.jdeveloper.home}/jdev/bin/ojdeploy.exe

# Worspace related
oracle.jdeveloper.workspace.dir=<Workspace Directory> e.g C:/MyProjects/PortalApplication
oracle.jdeveloper.workspace.path=${oracle.jdeveloper.workspace.dir}/DigitalFirstApp.jws

# Deployment and ear related parameters
oracle.jdeveloper.deploy.dir=${oracle.jdeveloper.workspace.dir}/deploy
application.name=PortalApplication
ear.filename=PortalApplication.ear
ear.location=${oracle.jdeveloper.deploy.dir}/PortalApplication.ear

# Set the WLS environment related parameters
dev.wls.username=<weblogic admin username>
dev.wls.password=<weblogic admin password>
dev.wls.adminurl=<HostName>:<port>
dev.wls.cluster.name=<WLS Cluster name on which application is deployed>

# Set the MDS related parameters
dev.wc.mds.repository=<MDS Repository Name> e.g mds-CustomPortalDS
dev.wc.mds.repository.type=< It can be DB/file>
dev.wc.mds.repository.jndi=<Repository JNDI> e.g jdbc/mds/CustomPortalDS



Monday, July 21, 2014

Authentication against Active Directory with Java and capturing error code

Recently, I had a requirement to authenticate against active directory using Java. 
Summary of steps in the process of authentication are - 

1. Set the AD Host Name URL 

2. Set the read only username/credential to connect to AD. This is required because you can bind to AD only if you know the full distinguished name of the user.So, first you need the read only user with full distinguished name to connect to AD. 

3. Set the username attribute for ex: sAMAccountName. This attribute value is the login name for the user. It can be set to any possible User Naming attributes. Check Specifying User Name attributes 

4. Set the search related parameters e.g. search scope, search base dn and user object class. Check Specifying the Search Scope 

5. Bind the read only user and if binding successful proceed further. 

6. Create a Search Control object passing the filter and constraint. Search for the username and if found get the DN(Distinguished Name) for the user. 

7. Bind the username DN and password and check if authentication is successful 8. Return the error code by capturing it from the exception message. Possible error codes are - 

USERNAME_NOT_FOUND = 0x525; 
INVALID_PASSWORD = 0x52e; 
NOT_PERMITTED = 0x530; 
PASSWORD_EXPIRED = 0x532; 
ACCOUNT_DISABLED = 0x533; 
ACCOUNT_EXPIRED = 0x701; 
PASSWORD_NEEDS_RESET = 0x773; 
ACCOUNT_LOCKED = 0x775;


package com.usfoods.df.auth.ad;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import java.util.regex.Matcher;

import java.util.regex.Pattern;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import oracle.jbo.JboException;

public class ADAuthenticator {
  private static final String INITCTX = "com.sun.jndi.ldap.LdapCtxFactory";
  private static final String AD_HOSTNAME = "AD_HOST";
  private static final String AD_PORT= "AD_PORT"; //Default is 389
  
  //Read Only user to connect to AD
  private static final String AD_SECURITY_PRINCIPAL="CN=readOnlyUser,DC=org,DC=local";
  
  //Read only user password to make a coonection to AD
  private static final String AD_SECURITY_PASSWORD="password";
  
  //Username attribute specified in Active Directory, it can be any user attribute
  private static final String AD_USERNAME_ATTR="sAMAccountName";
  
  //Search scope - Possible Value (base, one-level, subtree)
  private static final String AD_SEARCH_SCOPE="subtree";
  
  //Object class for the User
  private static final String AD_USER_OBJECT_CLASS="user";
  
  //Search base DN 
  private static final String AD_USER_BASE_DN="DC=employee,DC=org,DC=local";
  
  //Search Scopes
  private static final String ONELEVEL_SCOPE = "onelevel";
  private static final String SUBTREE_SCOPE = "subtree";
  
  //Error Codes
  private static final int USERNAME_NOT_FOUND = 0x525;
  private static final int INVALID_PASSWORD = 0x52e;
  private static final int NOT_PERMITTED = 0x530;
  private static final int PASSWORD_EXPIRED = 0x532;
  private static final int ACCOUNT_DISABLED = 0x533;
  private static final int ACCOUNT_EXPIRED = 0x701;
  private static final int PASSWORD_NEEDS_RESET = 0x773;
  private static final int ACCOUNT_LOCKED = 0x775;

  private static final Pattern ERROR_MESSAGE_PATTERN = Pattern.compile(".*\\s([0-9a-f]{3}).*");
  
  public ADAuthenticator() {
    super();
  }

  public int authenticate(String username, String password) {
    int errorCode;
    DirContext ctx = null;
    NamingEnumeration results = null;

    try {
      // Setting environment variables for read only user to connect to AD for search
      Hashtable adminEnv = new Hashtable();
      adminEnv.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX);
      adminEnv.put(Context.PROVIDER_URL,
                   "ldap://" + AD_HOSTNAME + ":" + AD_PORT);
      adminEnv.put(Context.SECURITY_PRINCIPAL, AD_SECURITY_PRINCIPAL);
      adminEnv.put(Context.SECURITY_CREDENTIALS, AD_SECURITY_PASSWORD);
      adminEnv.put(Context.REFERRAL, "follow");

      // Binding to LDAP directory using read only user
      ctx = new InitialDirContext(adminEnv);

      //Search for the logged in user
      SearchControls constraints = new SearchControls();
      // Set search scope
      String searchScope = AD_SEARCH_SCOPE;
      if (ONELEVEL_SCOPE.equals(searchScope)) {
        constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
      } else if (SUBTREE_SCOPE.equals(searchScope)) {
        constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
      }

      String userNameAttr = AD_USERNAME_ATTR;
      String userObjectClass = AD_USER_OBJECT_CLASS;
      //Create search filter based on username attribute and object class
      String searchFilter =
        "(&(objectClass=" + userObjectClass + ")(" + userNameAttr + "=" +
        username + "))";
      
      //Apply search filter and contraints and get the search results
      results = 
          ctx.search(AD_USER_BASE_DN, searchFilter, constraints);


      String dn = "";
      List dnList = new ArrayList();
      // Fetch the DN for the given username
      while (results != null && results.hasMore()) {
        SearchResult sr = (SearchResult)results.next();
        dn = sr.getName() + ", " + AD_USER_BASE_DN;
        dnList.add(dn);

      }

      // If no user exist with the given username then throw User not found error
      if (dnList.size() == 0 || dnList.size() > 1) {
        errorCode = USERNAME_NOT_FOUND;
        return errorCode;
      }
      
      // If username exist then bind using the given password
      else if (dnList.size() == 1) {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX);
        env.put(Context.PROVIDER_URL,
                "ldap://" + AD_HOSTNAME + ":" + AD_PORT);
        env.put(Context.SECURITY_PRINCIPAL, dnList.get(0));
        env.put(Context.SECURITY_CREDENTIALS, password);
        ctx = new InitialDirContext(env);
      }
    } catch (NamingException e) {
      if ((e instanceof AuthenticationException) ||
          (e instanceof OperationNotSupportedException)) {
        //Parse the error message and return the error code
        return handleNamingException(e);
      } else {
        //Handle the other excetions
      }
    }
     finally {
      if (results != null) {
        try {
          results.close();
        } catch (Exception e) {
          // Never mind this.
        }
      }
      if (ctx != null) {
        try {
          ctx.close();
        } catch (Exception e) {
          // Never mind this.
        }
      }
    }

    return errorCode;
  }
  
  private int handleNamingException(NamingException exception) {
    int errorCode = parseSubErrorCode(exception.getMessage());
    //Log the error message
    System.out.println(subCodeToLogMessage(errorCode));
    return errorCode;
  }
  
  private String subCodeToLogMessage(int code) {
      switch (code) {
          case USERNAME_NOT_FOUND:
              return "User was not found in directory";
          case INVALID_PASSWORD:
              return "Supplied password was invalid";
          case NOT_PERMITTED:
              return "User not permitted to logon at this time";
          case PASSWORD_EXPIRED:
              return "Password has expired";
          case ACCOUNT_DISABLED:
              return "Account is disabled";
          case ACCOUNT_EXPIRED:
              return "Account expired";
          case PASSWORD_NEEDS_RESET:
              return "User must reset password";
          case ACCOUNT_LOCKED:
              return "Account locked";
      }

      return "Unknown (error code " + Integer.toHexString(code) +")";
  }
  
  /**
   * This method parse the error code from the exception message and return the error code
   * For Ex: Authentication Exception is of the format - 
   * "[LDAP: error code 49 - 80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error, data 773, vece"
   * 773 is the Hex Error Code that need be captured
   * Refer the java Matcher and Pattern classes for capturing the groups in the matched string
   * @param message
   * @return
   */
  private int parseSubErrorCode(String message) {
      Matcher m = ERROR_MESSAGE_PATTERN.matcher(message);

      if (m.matches()) {
          //Error code is in hex, so parse it for base 16
          return Integer.parseInt(m.group(1), 16);
      }

      return -1;
  }
}

Thursday, July 17, 2014

Allow unauthenticated/public access to web resources in ADF/Webcenter application

Overview

This post is about configuring public access to web resources like images, javascript, css, fonts etc in ADF/Webcenter application.

Assumption

Adf security already configured for the application.

Implementation

There are two ways to implement security in any ADF/Webcenter application, one is the container-managed security and the other is the ADF security. Container managed security is common for any J2EE web application.

Using ADF secuirty you can secure taskflows and databound web pages (having page definition) by configuring grants in jazn-data.xml.Now, lets say you want to allow public access to images in the folder /Portal/public_html/images/*. I am not sure if it is possible to do with ADF security. I  define the new security constraint in  application web.xml(Portal/public_html/WEB_INF/web.xml).

Steps to create security constraint -

1. Open web.xml and click on Security Tab.




2. Click on create icon and create a new Security Constraint for public resources.


3. Under Web Resource Collection add the web resource name as "Public Images". Add the URL Pattern relative to public_html folder e.g /images/*. Select all HTTP Methods.


You can define multiple URL patterns under the same web resource name or group URL patterns under different  web resource names.


4. Under Authorization don't select any weblogic mapped role.
Note: If ADF security is enabled, by default valid-users role created in public_html/WEB-INF/weblogic.xml and mapped to weblogic default group users. All the authenticated users get the valid-users role.



Done.
Now, you can see without authentication you can access all the images under public_html/WEB-INF/images folder.



Tuesday, July 1, 2014

Performance: Filtering Navigation Links in Webcenter Navigation Model

There are two ways to filter navigation links in Webcenter Navigation Model -

1. Resource-Level filtering - This is done by specifying the visible property for a navigation resource which determines whether the resource will be displayed or not at run time. You can also specify EL expression for visible property.



2. Catalog-Level Filtering - This is done by defining the class that implements CatalogDefinitionFilter(oracle.adf.rc.spi.plugin.catalog.CatalogDefinitionFilter).
Implement the api includeInCatalog that takes CatalogElement(Navigation resource) as a parameter. This api called for every resource in NavigationModel. If the api returns true then only resource will be visible in the NavigationModel. So, check the resource id(CatalogElement.getId()) and based on conditions return true or false.

 

Check Oracle Document for more details.

I debugged the code to find out how and when filter conditions are evaluated based on the filtering level.Here are some of the scenarios where we can make it more performant.

Resource Level Filtering


Scenario 1 - 
Visible condition result not changing for a user session

Buseiness Usecase - Based on user type show/hide navigation links. So, on login check the business logic for user type and after that no need to evaluate it again until user logout and new user login.



In Resource Level Filtering visible property is evaluated for each request. So, it's better to call the business logic only once to evaluate the visible condition assuming the visible condition evaluate to same value for a user session.
In this case you can save the result of the visible condition  in session scope variable.Now business logic will be called only once on login and after that it will check the session scope variable.

Implementation :

1. Set the visible property in the navigation model





 2. Create a Managed Bean SessionInfo and register it in adfc-config.xml with session scope.



3. In SessionInfo Bean create a Boolean property financeVisible






Idea here is not to call the logic in getter method of visible property every time. Other way to implement is to set the visible property in the login initialization logic.

Scenario 2 - 
Visible condition result may change based on business logic in a user session.

Business Usecase - For a shopping portal , user can select the departments and on the basis of selected department show/hide some navigation links.

In this case reset the session variable when user select the new department. I found Navigation Model don't refresh even if visible conditions changed. So, refresh the Navigation Model explicitly by invalidating the cache.

Code Snippet to invalidate cache -

SiteStructureContext ctx = SiteStructureContext.getInstance(); 
SiteStructure model = ctx.getDefaultSiteStructure(); 
model.invalidateCache();  
 

Catalog Level Filtering

How and When it is called ? For each navigation resource, CatalogDefinitionFilter.includeInCatalog method is called for every request. 

Scenarios where it can be used

#1 Same visible condition applied to most of the navigation links.

As the includeInCatalog method is called for each resource, it's better to use resource level filtering. But if the same visible condition is used for most of the navigation link then it's better to use Catalog level because in this way code is more readable and mangeable.
For Ex: For a Payments Only User disable all the navigation links except the Payments Page.

Implementation

  public boolean includeInCatalog(CatalogElement catalogElement,
                                  Hashtable hashtable) {
    //Get the user type
    String userType = Util.getUserInfo().getUserType();
    //If user is payment only and the navigation link id is payments page show it
    //otherwise hide it
    if("PaymentsOnly".equals(userType) && catalogElement.getId().equalsIgnoreCase("paymentsPage")){
        return true;

      }
    return false;
  }