or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdcontrols-extensions.mdcore-operations.mddata-types.mdindex.mdldif.mdpersistence.mdschema.mdsearch.md

persistence.mddocs/

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

```