0
# Persistence Framework
1
2
Object-relational mapping system for treating LDAP entries as Java objects using annotations to define the mapping between Java classes and LDAP directory entries.
3
4
## Capabilities
5
6
### Core Persistence Classes
7
8
#### LDAPPersister
9
10
Main persister class providing CRUD operations for persistent objects.
11
12
```java { .api }
13
/**
14
* Main persister for object-relational mapping between Java objects and LDAP entries
15
*/
16
public class LDAPPersister<T> {
17
// Constructors
18
public LDAPPersister(Class<T> type, LDAPInterface ldapInterface) throws LDAPPersistException;
19
public LDAPPersister(Class<T> type, LDAPInterface ldapInterface, String baseDN) throws LDAPPersistException;
20
21
// CRUD operations
22
public T get(String dn) throws LDAPPersistException;
23
public T getByRDN(String rdn) throws LDAPPersistException;
24
public T getByRDN(String parentDN, String rdn) throws LDAPPersistException;
25
26
public PersistedObjects<T> search(Filter filter) throws LDAPPersistException;
27
public PersistedObjects<T> search(String baseDN, SearchScope scope, Filter filter) throws LDAPPersistException;
28
public PersistedObjects<T> search(String baseDN, SearchScope scope, Filter filter, String... attributes) throws LDAPPersistException;
29
public PersistedObjects<T> searchForEntries(SearchRequest searchRequest) throws LDAPPersistException;
30
31
public void add(T object) throws LDAPPersistException;
32
public void add(T object, String parentDN) throws LDAPPersistException;
33
34
public void modify(T object) throws LDAPPersistException;
35
public void modify(T object, boolean deleteNullValues) throws LDAPPersistException;
36
public void modify(T object, boolean deleteNullValues, String... attributesToModify) throws LDAPPersistException;
37
38
public void delete(T object) throws LDAPPersistException;
39
public void delete(String dn) throws LDAPPersistException;
40
41
// Configuration
42
public Class<T> getType();
43
public LDAPInterface getLDAPInterface();
44
public String getDefaultParentDN();
45
public void setDefaultParentDN(String parentDN);
46
47
// Utility methods
48
public Entry encodeEntry(T object, String parentDN) throws LDAPPersistException;
49
public T decodeEntry(Entry entry) throws LDAPPersistException;
50
public String constructDN(T object, String parentDN) throws LDAPPersistException;
51
public Filter createFilter(T object) throws LDAPPersistException;
52
}
53
```
54
55
#### PersistedObjects
56
57
Collection wrapper for search results containing persistent objects.
58
59
```java { .api }
60
/**
61
* Collection of persisted objects returned from search operations
62
*/
63
public class PersistedObjects<T> implements Iterable<T>, Serializable {
64
// Iterator access
65
public Iterator<T> iterator();
66
public boolean hasMore();
67
68
// Size information
69
public int size();
70
public boolean isEmpty();
71
72
// List conversion
73
public List<T> toList();
74
public List<T> toList(int maxObjects);
75
76
// Exception handling
77
public List<LDAPPersistException> getExceptions();
78
public boolean hasExceptions();
79
}
80
```
81
82
### Object Mapping Annotations
83
84
#### @LDAPObject
85
86
Class-level annotation defining LDAP object class mapping.
87
88
```java { .api }
89
/**
90
* Class-level annotation for LDAP persistent objects
91
*/
92
@Target({ElementType.TYPE})
93
@Retention(RetentionPolicy.RUNTIME)
94
public @interface LDAPObject {
95
/**
96
* Object classes for this type (required)
97
*/
98
String[] objectClass();
99
100
/**
101
* Structural object class (optional, defaults to first in objectClass array)
102
*/
103
String structuralClass() default "";
104
105
/**
106
* Superior object classes (optional)
107
*/
108
String[] superiorClass() default {};
109
110
/**
111
* Default parent DN for new instances (optional)
112
*/
113
String defaultParentDN() default "";
114
115
/**
116
* Post-decode method name (optional)
117
*/
118
String postDecodeMethod() default "";
119
120
/**
121
* Post-encode method name (optional)
122
*/
123
String postEncodeMethod() default "";
124
}
125
```
126
127
#### @LDAPField
128
129
Field-level annotation for attribute mapping.
130
131
```java { .api }
132
/**
133
* Field-level annotation for LDAP attribute mapping
134
*/
135
@Target({ElementType.FIELD})
136
@Retention(RetentionPolicy.RUNTIME)
137
public @interface LDAPField {
138
/**
139
* LDAP attribute name (optional, defaults to field name)
140
*/
141
String attribute() default "";
142
143
/**
144
* Whether this attribute is part of the RDN (optional)
145
*/
146
boolean inRDN() default false;
147
148
/**
149
* Whether this is a required attribute (optional)
150
*/
151
boolean requiredForDecode() default false;
152
153
/**
154
* Whether this is a required attribute for encoding (optional)
155
*/
156
boolean requiredForEncode() default false;
157
158
/**
159
* Default values for encoding (optional)
160
*/
161
String[] defaultEncodeValue() default {};
162
163
/**
164
* Default values for decoding (optional)
165
*/
166
String[] defaultDecodeValue() default {};
167
168
/**
169
* Filter usage for this field (optional)
170
*/
171
FilterUsage filterUsage() default FilterUsage.CONDITIONALLY_ALLOWED;
172
173
/**
174
* Whether to always include in modification (optional)
175
*/
176
boolean alwaysModify() default false;
177
178
/**
179
* Whether to never include in modification (optional)
180
*/
181
boolean neverModify() default false;
182
183
/**
184
* Attribute syntax OID (optional)
185
*/
186
String syntaxOID() default "";
187
188
/**
189
* Matching rule OID (optional)
190
*/
191
String matchingRuleOID() default "";
192
}
193
194
/**
195
* Filter usage enumeration for field filtering
196
*/
197
public enum FilterUsage {
198
ALWAYS_ALLOWED,
199
CONDITIONALLY_ALLOWED,
200
EXCLUDED;
201
}
202
```
203
204
#### @LDAPDNField
205
206
Special annotation for DN field mapping.
207
208
```java { .api }
209
/**
210
* Field annotation for DN mapping
211
*/
212
@Target({ElementType.FIELD})
213
@Retention(RetentionPolicy.RUNTIME)
214
public @interface LDAPDNField {
215
// No parameters - indicates this field contains the entry DN
216
}
217
```
218
219
#### @LDAPEntryField
220
221
Annotation for mapping entire entry object.
222
223
```java { .api }
224
/**
225
* Field annotation for whole entry mapping
226
*/
227
@Target({ElementType.FIELD})
228
@Retention(RetentionPolicy.RUNTIME)
229
public @interface LDAPEntryField {
230
// No parameters - indicates this field contains the Entry object
231
}
232
```
233
234
#### @LDAPGetter and @LDAPSetter
235
236
Method-level annotations for custom attribute access.
237
238
```java { .api }
239
/**
240
* Method annotation for getter methods
241
*/
242
@Target({ElementType.METHOD})
243
@Retention(RetentionPolicy.RUNTIME)
244
public @interface LDAPGetter {
245
/**
246
* LDAP attribute name (required)
247
*/
248
String attribute();
249
250
/**
251
* Whether this attribute is part of the RDN (optional)
252
*/
253
boolean inRDN() default false;
254
255
/**
256
* Filter usage for this attribute (optional)
257
*/
258
FilterUsage filterUsage() default FilterUsage.CONDITIONALLY_ALLOWED;
259
260
/**
261
* Attribute syntax OID (optional)
262
*/
263
String syntaxOID() default "";
264
265
/**
266
* Matching rule OID (optional)
267
*/
268
String matchingRuleOID() default "";
269
}
270
271
/**
272
* Method annotation for setter methods
273
*/
274
@Target({ElementType.METHOD})
275
@Retention(RetentionPolicy.RUNTIME)
276
public @interface LDAPSetter {
277
/**
278
* LDAP attribute name (required)
279
*/
280
String attribute();
281
282
/**
283
* Whether this is a required attribute (optional)
284
*/
285
boolean requiredForDecode() default false;
286
287
/**
288
* Whether this is a required attribute for encoding (optional)
289
*/
290
boolean requiredForEncode() default false;
291
292
/**
293
* Default values for encoding (optional)
294
*/
295
String[] defaultEncodeValue() default {};
296
297
/**
298
* Default values for decoding (optional)
299
*/
300
String[] defaultDecodeValue() default {};
301
302
/**
303
* Whether to always include in modification (optional)
304
*/
305
boolean alwaysModify() default false;
306
307
/**
308
* Whether to never include in modification (optional)
309
*/
310
boolean neverModify() default false;
311
}
312
```
313
314
### Exception Handling
315
316
#### LDAPPersistException
317
318
Exception class for persistence operations.
319
320
```java { .api }
321
/**
322
* Exception for LDAP persistence operations
323
*/
324
public class LDAPPersistException extends Exception {
325
// Constructors
326
public LDAPPersistException(String message);
327
public LDAPPersistException(String message, Throwable cause);
328
public LDAPPersistException(String message, String matchedDN, ResultCode resultCode, String diagnosticMessage, String[] referralURLs, Control[] controls);
329
330
// LDAP result information
331
public String getMatchedDN();
332
public ResultCode getResultCode();
333
public String getDiagnosticMessage();
334
public String[] getReferralURLs();
335
public Control[] getControls();
336
337
// Object information
338
public Object getPartiallyDecodedObject();
339
public void setPartiallyDecodedObject(Object obj);
340
}
341
```
342
343
### Field Value Converters
344
345
#### Standard Converters
346
347
Built-in converters for common Java types.
348
349
```java { .api }
350
/**
351
* Interface for custom field value converters
352
*/
353
public interface ObjectEncoder {
354
boolean supportsType(Class<?> type);
355
String[] encodeFieldValue(Field field, Object value, String objectDN) throws LDAPPersistException;
356
void decodeField(Field field, Object object, String attribute, String[] values) throws LDAPPersistException;
357
}
358
359
/**
360
* Default object encoder supporting common Java types
361
*/
362
public class DefaultObjectEncoder implements ObjectEncoder {
363
// Supports: String, primitives, primitive wrappers, arrays, Collections, Date, UUID, etc.
364
public boolean supportsType(Class<?> type);
365
public String[] encodeFieldValue(Field field, Object value, String objectDN) throws LDAPPersistException;
366
public void decodeField(Field field, Object object, String attribute, String[] values) throws LDAPPersistException;
367
}
368
```
369
370
## Usage Examples
371
372
### Basic Object Mapping
373
374
```java
375
import com.unboundid.ldap.sdk.*;
376
import com.unboundid.ldap.sdk.persist.*;
377
378
/**
379
* Simple person object with LDAP mapping
380
*/
381
@LDAPObject(objectClass = {"inetOrgPerson"}, defaultParentDN = "ou=people,dc=example,dc=com")
382
public class Person {
383
@LDAPDNField
384
private String dn;
385
386
@LDAPField(attribute = "cn", inRDN = true, requiredForEncode = true)
387
private String commonName;
388
389
@LDAPField(attribute = "sn", requiredForEncode = true)
390
private String surname;
391
392
@LDAPField(attribute = "givenName")
393
private String givenName;
394
395
@LDAPField(attribute = "mail")
396
private String email;
397
398
@LDAPField(attribute = "telephoneNumber")
399
private String[] phoneNumbers;
400
401
@LDAPField(attribute = "employeeNumber")
402
private Integer employeeNumber;
403
404
@LDAPField(attribute = "description")
405
private String description;
406
407
// Constructors
408
public Person() {}
409
410
public Person(String commonName, String surname, String email) {
411
this.commonName = commonName;
412
this.surname = surname;
413
this.email = email;
414
}
415
416
// Getters and setters
417
public String getDn() { return dn; }
418
public void setDn(String dn) { this.dn = dn; }
419
420
public String getCommonName() { return commonName; }
421
public void setCommonName(String commonName) { this.commonName = commonName; }
422
423
public String getSurname() { return surname; }
424
public void setSurname(String surname) { this.surname = surname; }
425
426
public String getGivenName() { return givenName; }
427
public void setGivenName(String givenName) { this.givenName = givenName; }
428
429
public String getEmail() { return email; }
430
public void setEmail(String email) { this.email = email; }
431
432
public String[] getPhoneNumbers() { return phoneNumbers; }
433
public void setPhoneNumbers(String[] phoneNumbers) { this.phoneNumbers = phoneNumbers; }
434
435
public Integer getEmployeeNumber() { return employeeNumber; }
436
public void setEmployeeNumber(Integer employeeNumber) { this.employeeNumber = employeeNumber; }
437
438
public String getDescription() { return description; }
439
public void setDescription(String description) { this.description = description; }
440
441
@Override
442
public String toString() {
443
return "Person{dn='" + dn + "', cn='" + commonName + "', sn='" + surname + "'}";
444
}
445
}
446
447
// Usage
448
public class PersonManager {
449
public static void main(String[] args) throws Exception {
450
LDAPConnection connection = new LDAPConnection("ldap.example.com", 389);
451
452
try {
453
connection.bind("cn=admin,dc=example,dc=com", "password");
454
455
// Create persister
456
LDAPPersister<Person> persister = new LDAPPersister<>(Person.class, connection);
457
458
// Create new person
459
Person john = new Person("John Doe", "Doe", "john.doe@example.com");
460
john.setGivenName("John");
461
john.setEmployeeNumber(12345);
462
john.setPhoneNumbers(new String[]{"+1-555-0123", "+1-555-0124"});
463
john.setDescription("Software Engineer");
464
465
// Add to LDAP
466
persister.add(john);
467
System.out.println("Added person: " + john.getDn());
468
469
// Retrieve from LDAP
470
Person retrieved = persister.get(john.getDn());
471
System.out.println("Retrieved: " + retrieved);
472
System.out.println("Email: " + retrieved.getEmail());
473
System.out.println("Employee Number: " + retrieved.getEmployeeNumber());
474
475
// Modify person
476
retrieved.setDescription("Senior Software Engineer");
477
retrieved.setPhoneNumbers(new String[]{"+1-555-0123", "+1-555-9999"});
478
persister.modify(retrieved);
479
System.out.println("Modified person");
480
481
// Search for people
482
Filter searchFilter = Filter.createEqualityFilter("department", "Engineering");
483
PersistedObjects<Person> results = persister.search(searchFilter);
484
485
System.out.println("Search results:");
486
for (Person person : results) {
487
System.out.println(" " + person);
488
}
489
490
// Delete person
491
persister.delete(retrieved);
492
System.out.println("Deleted person");
493
494
} finally {
495
connection.close();
496
}
497
}
498
}
499
```
500
501
### Advanced Object Mapping with Getters/Setters
502
503
```java
504
import com.unboundid.ldap.sdk.*;
505
import com.unboundid.ldap.sdk.persist.*;
506
import java.util.*;
507
508
/**
509
* Advanced user object with custom attribute handling
510
*/
511
@LDAPObject(
512
objectClass = {"inetOrgPerson", "customUser"},
513
structuralClass = "inetOrgPerson",
514
defaultParentDN = "ou=users,dc=example,dc=com"
515
)
516
public class User {
517
@LDAPDNField
518
private String dn;
519
520
@LDAPField(attribute = "cn", inRDN = true)
521
private String fullName;
522
523
@LDAPField(attribute = "sn", requiredForEncode = true)
524
private String lastName;
525
526
@LDAPField(attribute = "givenName")
527
private String firstName;
528
529
@LDAPField(attribute = "mail")
530
private String primaryEmail;
531
532
private Set<String> emailAddresses = new HashSet<>();
533
private List<String> roles = new ArrayList<>();
534
private Date lastLoginTime;
535
private boolean isActive = true;
536
537
// Standard getters/setters
538
public String getDn() { return dn; }
539
public void setDn(String dn) { this.dn = dn; }
540
541
public String getFullName() { return fullName; }
542
public void setFullName(String fullName) { this.fullName = fullName; }
543
544
public String getLastName() { return lastName; }
545
public void setLastName(String lastName) { this.lastName = lastName; }
546
547
public String getFirstName() { return firstName; }
548
public void setFirstName(String firstName) { this.firstName = firstName; }
549
550
public String getPrimaryEmail() { return primaryEmail; }
551
public void setPrimaryEmail(String primaryEmail) { this.primaryEmail = primaryEmail; }
552
553
// Custom getter/setter methods with LDAP annotations
554
@LDAPGetter(attribute = "mailAlternateAddress")
555
public String[] getEmailAddresses() {
556
return emailAddresses.toArray(new String[0]);
557
}
558
559
@LDAPSetter(attribute = "mailAlternateAddress")
560
public void setEmailAddresses(String[] addresses) {
561
emailAddresses.clear();
562
if (addresses != null) {
563
Collections.addAll(emailAddresses, addresses);
564
}
565
}
566
567
public void addEmailAddress(String email) {
568
emailAddresses.add(email);
569
}
570
571
public void removeEmailAddress(String email) {
572
emailAddresses.remove(email);
573
}
574
575
@LDAPGetter(attribute = "customRole")
576
public String[] getRoles() {
577
return roles.toArray(new String[0]);
578
}
579
580
@LDAPSetter(attribute = "customRole")
581
public void setRoles(String[] userRoles) {
582
roles.clear();
583
if (userRoles != null) {
584
Collections.addAll(roles, userRoles);
585
}
586
}
587
588
public void addRole(String role) {
589
if (!roles.contains(role)) {
590
roles.add(role);
591
}
592
}
593
594
public void removeRole(String role) {
595
roles.remove(role);
596
}
597
598
@LDAPGetter(attribute = "lastLoginTime", syntaxOID = "1.3.6.1.4.1.1466.115.121.1.24")
599
public String getLastLoginTimeString() {
600
if (lastLoginTime == null) return null;
601
return String.valueOf(lastLoginTime.getTime());
602
}
603
604
@LDAPSetter(attribute = "lastLoginTime")
605
public void setLastLoginTimeString(String timestamp) {
606
if (timestamp == null || timestamp.isEmpty()) {
607
lastLoginTime = null;
608
} else {
609
try {
610
lastLoginTime = new Date(Long.parseLong(timestamp));
611
} catch (NumberFormatException e) {
612
lastLoginTime = null;
613
}
614
}
615
}
616
617
public Date getLastLoginTime() { return lastLoginTime; }
618
public void setLastLoginTime(Date lastLoginTime) { this.lastLoginTime = lastLoginTime; }
619
620
@LDAPGetter(attribute = "customActive")
621
public String getActiveString() {
622
return isActive ? "TRUE" : "FALSE";
623
}
624
625
@LDAPSetter(attribute = "customActive", defaultDecodeValue = {"TRUE"})
626
public void setActiveString(String active) {
627
isActive = "TRUE".equalsIgnoreCase(active);
628
}
629
630
public boolean isActive() { return isActive; }
631
public void setActive(boolean active) { isActive = active; }
632
633
@Override
634
public String toString() {
635
return String.format("User{dn='%s', fullName='%s', primaryEmail='%s', roles=%s, active=%s}",
636
dn, fullName, primaryEmail, roles, isActive);
637
}
638
}
639
```
640
641
### Complex Search Operations
642
643
```java
644
import com.unboundid.ldap.sdk.*;
645
import com.unboundid.ldap.sdk.persist.*;
646
647
public class UserSearchExamples {
648
public static void searchExamples() throws Exception {
649
LDAPConnection connection = new LDAPConnection("ldap.example.com", 389);
650
651
try {
652
connection.bind("cn=admin,dc=example,dc=com", "password");
653
LDAPPersister<User> persister = new LDAPPersister<>(User.class, connection);
654
655
// Search by email domain
656
Filter emailDomainFilter = Filter.createSubstringFilter("mail", null, new String[]{"@example.com"}, null);
657
PersistedObjects<User> domainUsers = persister.search(emailDomainFilter);
658
659
System.out.println("Users with @example.com email:");
660
for (User user : domainUsers) {
661
System.out.println(" " + user.getFullName() + " - " + user.getPrimaryEmail());
662
}
663
664
// Search by role
665
Filter roleFilter = Filter.createEqualityFilter("customRole", "admin");
666
PersistedObjects<User> adminUsers = persister.search(roleFilter);
667
668
System.out.println("\nAdmin users:");
669
for (User user : adminUsers) {
670
System.out.println(" " + user.getFullName() + " - Roles: " + Arrays.toString(user.getRoles()));
671
}
672
673
// Complex search - active engineering users
674
Filter complexFilter = Filter.createANDFilter(
675
Filter.createEqualityFilter("customActive", "TRUE"),
676
Filter.createORFilter(
677
Filter.createEqualityFilter("customRole", "engineer"),
678
Filter.createEqualityFilter("customRole", "senior-engineer"),
679
Filter.createEqualityFilter("customRole", "architect")
680
)
681
);
682
683
PersistedObjects<User> engineeringUsers = persister.search(
684
"ou=users,dc=example,dc=com",
685
SearchScope.SUB,
686
complexFilter
687
);
688
689
System.out.println("\nActive engineering users:");
690
for (User user : engineeringUsers) {
691
System.out.println(" " + user.getFullName() + " - " + Arrays.toString(user.getRoles()));
692
}
693
694
// Search with limited attributes
695
PersistedObjects<User> limitedResults = persister.search(
696
"ou=users,dc=example,dc=com",
697
SearchScope.ONE,
698
Filter.createPresenceFilter("mail"),
699
"cn", "mail", "customRole" // Only retrieve these attributes
700
);
701
702
System.out.println("\nLimited attribute search:");
703
for (User user : limitedResults) {
704
System.out.println(" " + user.getFullName() + " - " + user.getPrimaryEmail());
705
// Note: other fields may be null since they weren't retrieved
706
}
707
708
} finally {
709
connection.close();
710
}
711
}
712
}
713
```
714
715
### Bulk Operations
716
717
```java
718
import com.unboundid.ldap.sdk.*;
719
import com.unboundid.ldap.sdk.persist.*;
720
import java.util.*;
721
722
public class BulkOperations {
723
public static void bulkOperationsExample() throws Exception {
724
LDAPConnection connection = new LDAPConnection("ldap.example.com", 389);
725
726
try {
727
connection.bind("cn=admin,dc=example,dc=com", "password");
728
LDAPPersister<User> persister = new LDAPPersister<>(User.class, connection);
729
730
// Create multiple users
731
List<User> newUsers = Arrays.asList(
732
createUser("Alice Johnson", "Johnson", "Alice", "alice.johnson@example.com", "developer"),
733
createUser("Bob Smith", "Smith", "Bob", "bob.smith@example.com", "tester"),
734
createUser("Carol Davis", "Davis", "Carol", "carol.davis@example.com", "analyst"),
735
createUser("David Wilson", "Wilson", "David", "david.wilson@example.com", "manager")
736
);
737
738
// Add users in batch
739
List<String> addedDNs = new ArrayList<>();
740
for (User user : newUsers) {
741
try {
742
persister.add(user);
743
addedDNs.add(user.getDn());
744
System.out.println("Added: " + user.getFullName());
745
} catch (LDAPPersistException e) {
746
System.err.println("Failed to add " + user.getFullName() + ": " + e.getMessage());
747
}
748
}
749
750
// Bulk modification - add a role to all new users
751
for (String dn : addedDNs) {
752
try {
753
User user = persister.get(dn);
754
user.addRole("employee");
755
user.setLastLoginTime(new Date());
756
757
// Modify only specific attributes
758
persister.modify(user, false, "customRole", "lastLoginTime");
759
System.out.println("Updated roles for: " + user.getFullName());
760
} catch (LDAPPersistException e) {
761
System.err.println("Failed to update " + dn + ": " + e.getMessage());
762
}
763
}
764
765
// Bulk search and update - deactivate users with no recent login
766
Calendar cutoff = Calendar.getInstance();
767
cutoff.add(Calendar.MONTH, -6); // 6 months ago
768
769
PersistedObjects<User> allUsers = persister.search(Filter.createPresenceFilter("cn"));
770
for (User user : allUsers) {
771
if (user.getLastLoginTime() != null && user.getLastLoginTime().before(cutoff.getTime())) {
772
user.setActive(false);
773
persister.modify(user, false, "customActive");
774
System.out.println("Deactivated inactive user: " + user.getFullName());
775
}
776
}
777
778
// Bulk delete - remove test users
779
Filter testUserFilter = Filter.createSubstringFilter("cn", "Test", null, null);
780
PersistedObjects<User> testUsers = persister.search(testUserFilter);
781
782
for (User testUser : testUsers) {
783
try {
784
persister.delete(testUser);
785
System.out.println("Deleted test user: " + testUser.getFullName());
786
} catch (LDAPPersistException e) {
787
System.err.println("Failed to delete " + testUser.getFullName() + ": " + e.getMessage());
788
}
789
}
790
791
} finally {
792
connection.close();
793
}
794
}
795
796
private static User createUser(String fullName, String lastName, String firstName, String email, String role) {
797
User user = new User();
798
user.setFullName(fullName);
799
user.setLastName(lastName);
800
user.setFirstName(firstName);
801
user.setPrimaryEmail(email);
802
user.addRole(role);
803
user.setActive(true);
804
return user;
805
}
806
}
807
```
808
809
### Custom Field Encoding
810
811
```java
812
import com.unboundid.ldap.sdk.*;
813
import com.unboundid.ldap.sdk.persist.*;
814
import java.lang.reflect.Field;
815
import java.util.*;
816
817
/**
818
* Custom encoder for handling special field types
819
*/
820
public class CustomObjectEncoder implements ObjectEncoder {
821
822
public boolean supportsType(Class<?> type) {
823
return Map.class.isAssignableFrom(type) ||
824
Set.class.isAssignableFrom(type) ||
825
Date.class.isAssignableFrom(type);
826
}
827
828
public String[] encodeFieldValue(Field field, Object value, String objectDN) throws LDAPPersistException {
829
if (value == null) {
830
return new String[0];
831
}
832
833
if (value instanceof Map) {
834
Map<?, ?> map = (Map<?, ?>) value;
835
List<String> values = new ArrayList<>();
836
for (Map.Entry<?, ?> entry : map.entrySet()) {
837
values.add(entry.getKey() + "=" + entry.getValue());
838
}
839
return values.toArray(new String[0]);
840
}
841
842
if (value instanceof Set) {
843
Set<?> set = (Set<?>) value;
844
List<String> values = new ArrayList<>();
845
for (Object item : set) {
846
values.add(item.toString());
847
}
848
return values.toArray(new String[0]);
849
}
850
851
if (value instanceof Date) {
852
Date date = (Date) value;
853
return new String[]{String.valueOf(date.getTime())};
854
}
855
856
throw new LDAPPersistException("Unsupported type: " + value.getClass().getSimpleName());
857
}
858
859
public void decodeField(Field field, Object object, String attribute, String[] values) throws LDAPPersistException {
860
if (values == null || values.length == 0) {
861
return;
862
}
863
864
try {
865
field.setAccessible(true);
866
Class<?> fieldType = field.getType();
867
868
if (Map.class.isAssignableFrom(fieldType)) {
869
Map<String, String> map = new HashMap<>();
870
for (String value : values) {
871
String[] parts = value.split("=", 2);
872
if (parts.length == 2) {
873
map.put(parts[0], parts[1]);
874
}
875
}
876
field.set(object, map);
877
}
878
else if (Set.class.isAssignableFrom(fieldType)) {
879
Set<String> set = new HashSet<>(Arrays.asList(values));
880
field.set(object, set);
881
}
882
else if (Date.class.isAssignableFrom(fieldType)) {
883
long timestamp = Long.parseLong(values[0]);
884
field.set(object, new Date(timestamp));
885
}
886
887
} catch (Exception e) {
888
throw new LDAPPersistException("Failed to decode field " + field.getName() + ": " + e.getMessage(), e);
889
}
890
}
891
}
892
893
/**
894
* Example object using custom encoding
895
*/
896
@LDAPObject(objectClass = {"customObject"})
897
public class CustomEncodedObject {
898
@LDAPDNField
899
private String dn;
900
901
@LDAPField(attribute = "cn", inRDN = true)
902
private String name;
903
904
@LDAPField(attribute = "customProperties")
905
private Map<String, String> properties = new HashMap<>();
906
907
@LDAPField(attribute = "customTags")
908
private Set<String> tags = new HashSet<>();
909
910
@LDAPField(attribute = "customTimestamp")
911
private Date timestamp;
912
913
// Getters and setters...
914
public String getDn() { return dn; }
915
public void setDn(String dn) { this.dn = dn; }
916
917
public String getName() { return name; }
918
public void setName(String name) { this.name = name; }
919
920
public Map<String, String> getProperties() { return properties; }
921
public void setProperties(Map<String, String> properties) { this.properties = properties; }
922
923
public Set<String> getTags() { return tags; }
924
public void setTags(Set<String> tags) { this.tags = tags; }
925
926
public Date getTimestamp() { return timestamp; }
927
public void setTimestamp(Date timestamp) { this.timestamp = timestamp; }
928
}
929
```