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>parent.cn</variable> </field> <field name="username"> <variable>uid</variable> </field> </source> <source name="u"> <source-name>users</source-name> <field name="username"> <variable>uid</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>
Finally in the same mapping we define the relationships among the sources.
<entry dn="uid=...,cn=...,ou=Groups,dc=Example,dc=com">
<relationship>
<expression>g.groupname = ug.groupname</expression>
</relationship>
<relationship>
<expression>ug.username = u.username</expression>
</relationship>
</entry>
The first relationship links the "groups" source that we defined in the parent mapping to the "usergroups" source to determine group membership. The second relationship links the "usergroups" source to the "users" source to obtain the first and last name of the members.