A common way to model relationships between LDAP entries is to put the LDAP distinguished name of the referenced LDAP entry to an attribute of the referencing LDAP entry. For example entries with object class groupOfNames use the attribute member which contains distinguished names of the group members.
We just describe 1-N relationship mapping here and distinguish between unidirectional and bidirectional relationships. The metadata for 1-1, N-1 and M-N relationship mapping looks identical, the only difference is whether single-valued or multi-valued attributes are used in LDAP to store the relationships.
We use the following example LDAP tree and Java classes:
dc=example,dc=com public class Department { | String name; |-- ou=Departments Set<Employee> employees; | |-- cn=Sales } | |-- cn=Engineering | |-- ... public class Employee { | String firstName; |-- ou=Employees String lastName; | |-- cn=Bugs Bunny String fullName; | |-- cn=Daffy Duck } | |-- cn=Speedy Gonzales | |-- ...
We have a flat LDAP tree with one container for all the departments and one container for all the employees. We have two Java classes, Department and Employee. The Department class contains a Collection of type Employee. The Employee knows nothing about the Department it belongs to.
There are 2 ways that we can persist this relationship in LDAP because the DN reference could be stored at the one or at the other LDAP entry.
The obious way is to store the reference at the owner object side, in our case at the department entry. This is possible since LDAP allows multi-valued attributes. The example department entry looks like this:
dn: cn=Sales,ou=Departments,dc=example,dc=com objectClass: top objectClass: groupOfNames cn: Sales member: cn=Bugs Bunny,ou=Employees,dc=example,dc=com member: cn=Daffy Duck,ou=Employees,dc=example,dc=com
Our JDO metadata looks like this:
<jdo> <package name="com.example"> <class name="Department" table="ou=Departments,dc=example,dc=com" schema="top,groupOfNames"> <field name="name" primary-key="true" column="cn" /> <field name="employees" column="member"> <extension vendor-name="datanucleus" key="empty-value" value="uid=admin,ou=system"/> </field> </class> <class name="Employee" table="ou=Employees,dc=example,dc=com" schema="top,person,organizationalPerson,inetOrgPerson"> <field name="fullName" primary-key="true column="cn" /> <field name="firstName" column="givenName" /> <field name="lastName" column="sn" /> </class> </package> </jdo>
So we define that the attribute member should be used to persist the relationship of field employees.
Note: We use the extension empty-value here. The groupOfNames object class defines the member attribute as mandatory attribute. In case where you remove all the employees from a department would delete all member attributes which isn't allowed. In that case DataNucleus adds this empty value to the member attribute. This value is also filtered when DataNucleus reads the object from LDAP.
Another possible way is to store the reference at the non-owner object side, in our case at the employee entry. The example employee entry looks like this:
dn: cn=Bugs Bunny,ou=Employees,dc=example,dc=com objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson cn: Bugs Bunny givenName: Bugs sn: Bunny departmentNumber: cn=Sales,ou=Departments,dc=example,dc=com
Our JDO metadata looks like this:
<jdo> <package name="com.example"> <class name="Department" table="ou=Departments,dc=example,dc=com" schema="top,groupOfNames"> <field name="name" primary-key="true" column="cn" /> <field name="employees"> <element column="departmentNumber" /> </field> </class> <class name="Employee" table="ou=Employees,dc=example,dc=com" schema="top,person,organizationalPerson,inetOrgPerson"> <field name="fullName" primary-key="true column="cn" /> <field name="firstName" column="givenName" /> <field name="lastName" column="sn" /> </class> </package> </jdo>
We need to define the relationship at the department metadata because the employee doesn't know about the department it belongs to. With the <element> tag we specify that the relationship should be persisted at the other side, the column attribute defines the LDAP attribute to use. In this case the relationship is persisted in the departmentNumber attribute at the employee entry.
We use the following example LDAP tree and Java classes:
dc=example,dc=com public class Department { | String name; |-- ou=Departments Set<Employee> employees; | |-- cn=Sales } | |-- cn=Engineering | |-- ... public class Employee { | String firstName; |-- ou=Employees String lastName; | |-- cn=Bugs Bunny String fullName; | |-- cn=Daffy Duck Department department; | |-- cn=Speedy Gonzales } | |-- ...
We have a flat LDAP tree with one container for all the departments and one container for all the employees. We have two Java classes, Department and Employee. The Department class contains a Collection of type Employee. Now each Employee has a reference to its Department.
It is possible to persist this relationship on both sides.
dn: cn=Sales,ou=Departments,dc=example,dc=com objectClass: top objectClass: groupOfNames cn: Sales member: cn=Bugs Bunny,ou=Employees,dc=example,dc=com member: cn=Daffy Duck,ou=Employees,dc=example,dc=com
<jdo> <package name="com.example"> <class name="Department" table="ou=Departments,dc=example,dc=com" schema="top,groupOfNames"> <field name="name" primary-key="true" column="cn" /> <field name="employees" column="member"> <extension vendor-name="datanucleus" key="empty-value" value="uid=admin,ou=system"/> </field> </class> <class name="Employee" table="ou=Employees,dc=example,dc=com" schema="top,person,organizationalPerson,inetOrgPerson"> <field name="fullName" primary-key="true column="cn" /> <field name="firstName" column="givenName" /> <field name="lastName" column="sn" /> <field name="department" mapped-by="employees" /> </class> </package> </jdo>
In this case we store the relation at the department entry side in a multi-valued attribute member. Now the employee metadata contains a department field that is mapped-by the employees field of department.
Note: We use the extension empty-value here. The groupOfNames object class defines the member attribute as mandatory attribute. In case where you remove all the employees from a department would delete all member attributes which isn't allowed. In that case DataNucleus adds this empty value to the member attribute. This value is also filtered when DataNucleus reads the object from LDAP.