PersonDirectory

Versions of PersonDirectory

PersonDirectory was refactored for uPortal 2.5 to be backed by Spring to wire together its implementation. However, documentation exists for PersonDirectory in uPortal 2.4 and earlier.

PersonDirectory as API

In keeping with the uPortal 2 static service class model (often these static services are factories, e.g. ChannelManagerFactory, but sometimes they are merely static services such as RDBMServices, LdapServices, and PersonDirectory), PersonDirectory now provides a public static method whereby you can get an instance of IPersonAttributeDao.

IPersonAttributeDao dao = PersonDirectory.getPersonAttributeDaoInstance();

The public IPersonAttributeDao interface methods, in turn, provide an API whereby uPortal components access user attributes.

/**
 * Obtains a mutable {@link Map} from attribute names to values for
 * the given query seed. The values may be mutable objects but it is
 * recommended they are immutable.<br>
 * 
 * For the returned {@link Map}; Names must be {@link String}, Values
 * can be any {@link Object}, they are typically {@link String}<br>
 * 
 * Values may also be multi-valued, in this case they are of type
 * {@link java.util.List} and the lists contents are the values of the
 * attribute.<br>
 * 
 * The return of this method uses the following rules:<br>
 * <ul>
 *  <li>If the user exists and has attributes a populated {@link Map} is returned.</li>
 *  <li>If the user exists and has no attributes an empty {@link Map} is returned.</li>
 *  <li>If the user doesn't exist <code>null</code> is returned.</li>
 *  <li>If an error occurs while getting the attributes the appropriate exception will be propagated.</li>
 * </ul>
 * 
 * @param seed Map of attributes and values to use for the query
 * @return Map from attribute names to values
 * @throws IllegalArgumentException If <code>seed</code> is <code>null</code>
 */
public Map getUserAttributes(final Map seed);


/**
 * This method uses the default attribute to construct a seed
 * {@link Map} and call the {@link #getUserAttributes(Map)} method
 * with.
 * 
 * @param uid The string to use as the value in the seed
 * @return The same value as returned by {@link #getUserAttributes(Map)}
 * @see #getUserAttributes(Map)
 */
public Map getUserAttributes(final String uid);


/**
 * Gets a {@link Set} of attribute names that may be returned by the
 * {@link #getUserAttributes(Map)}. The names returned represent all
 * possible names {@link #getUserAttributes(Map)} could return. If the
 * dao doesn't have a way to know all possible attribute names this
 * method should return <code>null</code>
 * <br>
 * An immutable {@link Set} is returned.
 * 
 * @return A {link Set} of possible attribute names for user queries.
 */
public Set getPossibleUserAttributeNames();

To get the attributes into an IPerson object use the new (as of uP 2.5) setAttributes method:

/**
 * Associates attributes with the user
 * @param attrs
 */
public void setAttributes (Map attrs);

PersonFactory now offers the service of RestrictedPerson creation.

/**
 * Creates a <i>restricted</i> user.
 * @return <i>restricted</i> user
 */
public static RestrictedPerson createRestrictedPerson() {
    ...
}

An example of how to get a standard IPerson and attributes for it:

IPersonAttributeDao dao = PersonDirectory.getPersonAttributeDaoInstance();
Map userAttributes = dao.getUserAttributes(UserID);
IPerson person = PersonFactory.createPerson();
person.setAttributes(userAttributes);

PersonDirectory as configurable facility

PersonDirectory is a uPortal customization point.

You have three basic options for configuring PersonDirectory:

PersonDirs.xml configuration

You can configure purely by editing PersonDirs.xml, perhaps leveraging your existing skills with that file format. And you have a good deal of flexibility there, using LDAP and JDBC to get your attributes.

The uPortal 2.5 PersonDirectory continues to support PersonDirs.xml to configure your person attribute sources. The PersonDirs.xml language allows you to specify in the PersonDirs.xml the configuration parameters for LDAP or JDBC queries as sources of attributes, with the server configuration (database URL, username, password, driver, etc.) being configured right there. It also lets you configure queries against databases or LDAP servers that are managed by RDBMServices or LdapServices. So there's quite a bit of flexibility right there.

This means of configuration is supported in 2.5 – the new implementation even provides additional validation of PersonDirs.xml configuration data to catch problems and provide more helpful logging and exception messages.

This is a recommended approach if the PersonDirs.xml language supports everything you need.

uPortal 2.5 implements uPortal 2.4-style PersonDirs.xml-driven configuration by means of LegacyPersonAttributeDao.

Custom Java implementation

If you're most comfortable editing Java and don't want to be working with Spring XML, you can change the class name in the personDirectory.xml beans declaration to be a custom class of your choice, so long as it implements IPersonAttributeDao. You can then write the custom implementation that does exactly the queries, caching, and any other behavior you need.

<beans>
        <!-- notice that the bean's class has changed -->
    <bean id="personAttributeDao" class="edu.youruniv.portal.persondir.CustomPersonAttributeDaoImpl">
        <!-- if you're implementing all your configuration directly in the Java, you need not 
                       add any properties here -->
    </bean>
</beans>

If you're customizing PersonDirectory behavior, Spring likely has something to offer you in terms of configuring your custom IPersonAttributeDao implementation. However, if you really want to, you can code all of your configuration directly in Java, compile it, and simply reference your implementation.

Using Spring to configure an IPersonAttributeDao implementation

You can use Spring to wire together a Java object that implements IPersonAttributeDao. This facility was added in uPortal 2.5. The remainder of this Wiki page provides further information on how this works. We expect that the provided implementations of IPersonAttributeDao will be sufficient for most uPortal deployments.

Spring and PersonDirectory

PersonDirectory is implemented using Spring. Currently a Spring beans.dtd-compliant XML file named personDirectory.xml declares the configuration of an instance of IPersonAttributeDao. The class PersonDirectory delegates to this Spring-configured IPersonAttributeDao instance to actually implement the PersonDirectory behavior. PersonDirectory uses PortalApplicationContextFacade to access the the uPortal Spring application context.

The default personDirectory.xml that ships with uPortal declares that the IPersonAttributeDao we want to use is the LegacyPersonAttributeDao, which parses and instantiates the "recipe" given in PersonDirs.xml.

<beans>
    <bean id="personAttributeDao" class="org.jasig.portal.services.persondir.support.legacy.LegacyPersonAttributeDao">
        <!-- the legacy implementation takes no configuration -->
    </bean>
</beans>

Example disclaimer

While these examples conform to beans.dtd, they haven't necessarily been actually run in uPortal instances, so these XMLs might not actually work as is. If you find anything wrong with these example XMLs, please either fix them if you feel comfortable or else comment on this page so that they can be fixed. Thanks!

JDBC Example

Here's an example of using Spring to wire together a JdbcPersonAttributeDaoImpl that implements IPersonAttributeDao by querying a JDBC attribute store. The database used is that managed by RDBMServices under the name "personDb". We configure also the SQL query we'll use to get attributes for the user and the mapping from column names to uPortal attribute names. Configuration supports mapping a column to multiple uPortal attribute names and using DataSources other than those configured in RDBMServices.

<beans>
    <!-- this is the key bean, with the special id "personAttributeDao" that PersonDirectory is looking for. -->
    <bean id="personAttributeDao"
        class="org.jasig.portal.services.persondir.support.JdbcPersonAttributeDaoImpl">
                        <!-- the JDBC implementation takes two arguments in its constructor.
                              the first is a DataSource -->
        <constructor-arg>
             <!-- here we're using the default DataSource from RDBMServices. -->
            <bean 
                class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
                <property name="staticMethod" value="org.jasig.portal.RDBMServices.getDataSource"/>
            </bean>
        </constructor-arg>
            <!-- the second constructor argument to the JDBC implementation is the SQL
              query that will yield a single row.  The ? will be replaced with the
              userid about whom we are querying -->
        <constructor-arg>
            <value>SELECT name, shirt_color FROM someschema.sometable 
                WHERE uid = ?</value>
        </constructor-arg>
            <!-- the JDBC implementation has one other requirement: a Map
               from column names in the ResultSet to uPortal IPerson attribute names. -->
        <property name="columnsToAttributes">
            <map>
                <entry key="name">
                    <value>name</value>
                </entry>
                <entry key="shirt_color">
                    <value>shirtColor</value>
                </entry>
            </map>
        </property>
    </bean>
</beans>

For more examples, visit the page dedicated to JdbcPersonAttributeDaoImpl.

LDAP example

In this example we implement IPersonAttributeDao using an LdapPersonAttributeDaoImpl.

Here we use the ILdapServer named "personLdap" managed by LdapServices. We configure the map from LDAP attribute names to uPortal attribute names and the query we'll use to look up the user. Advanced configuration allows mapping LDAP attribute names to multiple uPortal attribute names.

<beans>
    <bean id="personDirectory"
        class="org.jasig.portal.services.persondir.support.LdapPersonAttributeDaoImpl">
        <property name="ldapAttributesToPortalAttributes">
            <map>
                <entry key="name">
                    <value>name</value>
                </entry>
                <entry key="shirt_color">
                    <value>shirtColor</value>
                </entry>
            </map>
        </property>
        <property name="ldapServer">
            <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
                <property name="staticMethod">
                   <value>org.jasig.portal.ldap.LdapServices.getLdapServer</value>
                </property>
                <property name="arguments">
                    <list>
                        <value>personLdap</value>
                    </list>
                </property>
            </bean>
        </property>
        <property name="uidQuery">
            <value>(cn={0})</value>
        </property>
    </bean>
</beans>

Merging example

This would get pretty boring if we could only ever have one source of user attributes at a time. MergingPersonAttributeDaoImpl merges attributes from multiple sources.

Here we're merging together attributes from a JDBCPersonAttributeDaoImpl and from an LdapPersonAttributeDaoImpl. Here we've accepted the default merge strategy, but MergingPersonAttributeDaoImpl is configurable to accept alternative merge strategies. MergingPersonAttributeDaoImpl can also be configured as to how it should handle exceptions thrown by the DAOs it is merging.

<beans>
    <bean id="personAttributeDao" 
        class="org.jasig.portal.services.persondir.support.MergingPersonAttributeDaoImpl">
        <property name="personAttributeDaos">
            <list>
                <bean 
                    class="org.jasig.portal.services.persondir.support.JdbcPersonAttributeDaoImpl">
                    <constructor-arg>
                       <bean
                         class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		         <property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
		         <property name="url"><value>${jdbc.url}</value></property>
		         <property name="username"><value>${jdbc.username}</value></property>
		         <property name="password"><value>${jdbc.password}</value></property>
	               </bean>
                    </constructor-arg>
                    <constructor-arg>
                        <value>SELECT name, shirt_color FROM 
                            someschema.sometable WHERE uid = ?</value>
                    </constructor-arg>
                    <property name="columnsToAttributes">
                        <map>
                            <entry key="name">
                                <value>name</value>
                            </entry>
                            <entry key="shirt_color">
                                <value>shirtColor</value>
                            </entry>
                        </map>
                    </property>
                </bean>
                <bean 
                    class="org.jasig.portal.services.persondir.support.LdapPersonAttributeDaoImpl">
                    <property name="ldapAttributesToPortalAttributes">
                        <map>
                            <entry key="name">
                                <value>name</value>
                            </entry>
                            <entry key="shirt_color">
                                <value>shirtColor</value>
                            </entry>
                        </map>
                    </property>
                    <property name="ldapServer">
                        <bean 
                            class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
                            <property name="staticMethod">
                                <value>
                                    org.jasig.portal.ldap.LdapServices.getLdapServer</value>
                            </property>
                            <property name="arguments">
                                <list>
                                    <value>personLdap</value>
                                </list>
                            </property>
                        </bean>
                    </property>
                    <property name="uidQuery">
                        <value>(cn={0})</value>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
</beans>

Spring recap

Now, you might be thinking, "That makes perfect sense to me, I've seen this before because I use Spring for my other applications." If so, this way of configuration might be right for you.

LegacyPersonDirectoryToPersonAttributeDaoAdapter and PersonDirs.xml revisited

But you might be thinking, "That's really complicated. I don't like this. I'd be much happier using the old PersonDirs.xml approach to life." And if that's what you're thinking, LegacyPersonAttributeDao and the default personDirectory.xml configuration will probably be right for you.

Rolling your own

Or you might even be thinking, "I'd really rather just implement IPersonAttributeDao myself."
If you're thinking that, note that you can still use the provided IPersonAttributeDao support classes where they fit in with the particular attribute retrieval strategy you're trying to implement.

<beans>
    <bean id="personAttributeDao"
        class="edu.youruniv.portal.persondir.CustomPersonAttributeDaoImpl">
    </bean>
</beans>

Caching

As of uPortal 2.4.2, caching internal to PersonDirectory was removed in uPortal 2.4 and 2.HEAD. This was because the caching implementation leaked memory.

If you want to re-introduce caching, one way you could accomplish that would be with a caching wrapper. Such a wrapper would implement IPersonAttributeDao and be dependent upon an implementation of the same interface it implements. It would delegate to the underlying implementation, caching the results from its delegate and responding from its cache when possible. This can be a way to trade memory for performance in the case where you know exactly when your underlying attribute store (infrequently) updates.