Nested Mapping
Introduction
Penrose supports nested mapping, meaning that you can define a mapping under another mapping and it will inherit the values from the parent.
Example
Suppose we have a table "groups" which contains the information about a group including name and description. Then we have another table "usergroups" which contains the list of members of each groups in the form of <groupname, username> tuples. A group may contain 0 members. Then we also have a table "users" which contains the full user information. We want to create a mapping that produces the following output:
dn: cn=<group name>,ou=Groups,dc=Example,dc=com objectClass: groupOfUniqueNames cn: <group name> description: <group description> dn: uid=<member's username>,cn=<group name>,ou=Groups,dc=Example,dc=com objectClass: person uid: <member's username> cn: <member's full name> sn: <member's last name>
First we create the mapping for the groups, which is a direct mapping of the source fields into the attributes:
<entry dn="cn=...,ou=Groups,dc=Example,dc=com"> <oc>groupOfUniqueNames</oc> <at name="cn" rdn="true"> <variable>g.groupname</variable> </at> <at name="description"> <variable>g.description</variable> </at> <source name="g"> <source-name>groups</source-name> <field name="groupname"> <expression>cn</expression> </field> <field name="description"> <expression>description</expression> </field> </source> </entry>
Then we define the mapping for the members. Note that the entries that appear under a particular group should be only the members of that group.
<entry dn="uid=...,cn=...,ou=Groups,dc=Example,dc=com"> <oc>person</oc> <oc>inetOrgPerson</oc> <at name="uid" rdn="true"> <variable>ug.username</variable> </at> <at name="cn"> <expression> if (u == void || u == null) return null; return u.firstName+" "+u.lastName; </expression> </at> <at name="sn"> <variable>u.lastName</variable> </at> </entry>
The "uid" is mapped from usergroups' username field. The "cn" and "sn" is mapped from the users' first and last names.
Then in the same mapping we define the sources:
<entry dn="uid=...,cn=...,ou=Groups,dc=Example,dc=com"> <source name="ug"> <source-name>usergroups</source-name> <field name="groupname"> <variable>g.groupname</variable> </field> <field name="username"> <variable>uid</variable> </field> </source> <source name="u"> <source-name>users</source-name> <field name="username"> <variable>ug.username</variable> </field> <field name="firstName"> <expression> if (cn == void || cn == null) return null; int i = cn.lastIndexOf(" "); return cn.substring(0, i); </expression> </field> <field name="lastName"> <expression> if (cn == void || cn == null) return null; int i = cn.lastIndexOf(" "); return cn.substring(i+1); </expression> </field> </source> </entry>
The "usergroups" source is linked to the "groups" source in the parent mapping by ug.groupname = g.groupname. This is to determine group membership. The "users" source is linked to the "usergroups" source by u.username = ug.username. This is to obtain the first and last name of the members.