0
# Metadata Access
1
2
Runtime access to mapping metadata and configuration for debugging, introspection, and dynamic behavior based on mapping definitions.
3
4
## Capabilities
5
6
### Mapping Metadata Interface
7
8
Primary interface for accessing mapping metadata and configuration information.
9
10
```java { .api }
11
/**
12
* Provides read-only access to mapping metadata and configuration
13
*/
14
public interface MappingMetadata {
15
/**
16
* Gets all class mappings defined in the mapper
17
* @return list of all class mapping metadata
18
*/
19
List<ClassMappingMetadata> getClassMappings();
20
21
/**
22
* Gets class mappings by source class name
23
* @param sourceClassName fully qualified source class name
24
* @return list of class mappings for the specified source class
25
*/
26
List<ClassMappingMetadata> getClassMappingsBySourceName(String sourceClassName);
27
28
/**
29
* Gets class mappings by destination class name
30
* @param destinationClassName fully qualified destination class name
31
* @return list of class mappings for the specified destination class
32
*/
33
List<ClassMappingMetadata> getClassMappingsByDestinationName(String destinationClassName);
34
35
/**
36
* Gets specific class mapping by source and destination class names
37
* @param sourceClassName fully qualified source class name
38
* @param destinationClassName fully qualified destination class name
39
* @return class mapping metadata or null if not found
40
*/
41
ClassMappingMetadata getClassMappingByName(String sourceClassName, String destinationClassName);
42
43
/**
44
* Gets class mappings by source class
45
* @param sourceClass source class type
46
* @return list of class mappings for the specified source class
47
*/
48
List<ClassMappingMetadata> getClassMappingsBySource(Class<?> sourceClass);
49
50
/**
51
* Gets class mappings by destination class
52
* @param destinationClass destination class type
53
* @return list of class mappings for the specified destination class
54
*/
55
List<ClassMappingMetadata> getClassMappingsByDestination(Class<?> destinationClass);
56
57
/**
58
* Gets specific class mapping by source and destination classes
59
* @param sourceClass source class type
60
* @param destinationClass destination class type
61
* @return class mapping metadata or null if not found
62
*/
63
ClassMappingMetadata getClassMapping(Class<?> sourceClass, Class<?> destinationClass);
64
}
65
```
66
67
### Class Mapping Metadata Interface
68
69
Provides metadata about individual class-to-class mappings.
70
71
```java { .api }
72
/**
73
* Provides metadata about class-level mappings
74
*/
75
public interface ClassMappingMetadata {
76
/**
77
* Gets the source class name
78
* @return fully qualified source class name
79
*/
80
String getSourceClassName();
81
82
/**
83
* Gets the destination class name
84
* @return fully qualified destination class name
85
*/
86
String getDestinationClassName();
87
88
/**
89
* Gets the source class type
90
* @return source class
91
*/
92
Class<?> getSourceClass();
93
94
/**
95
* Gets the destination class type
96
* @return destination class
97
*/
98
Class<?> getDestinationClass();
99
100
/**
101
* Gets all field mappings for this class mapping
102
* @return list of field mapping metadata
103
*/
104
List<FieldMappingMetadata> getFieldMappings();
105
106
/**
107
* Gets field mapping by source field name
108
* @param sourceFieldName source field name
109
* @return field mapping metadata or null if not found
110
*/
111
FieldMappingMetadata getFieldMappingBySource(String sourceFieldName);
112
113
/**
114
* Gets field mapping by destination field name
115
* @param destinationFieldName destination field name
116
* @return field mapping metadata or null if not found
117
*/
118
FieldMappingMetadata getFieldMappingByDestination(String destinationFieldName);
119
120
/**
121
* Gets the mapping ID if specified
122
* @return mapping ID or null if not specified
123
*/
124
String getMappingId();
125
126
/**
127
* Indicates if this is a bidirectional mapping
128
* @return true if bidirectional, false otherwise
129
*/
130
boolean isBidirectional();
131
132
/**
133
* Gets custom converter class if specified for this mapping
134
* @return custom converter class or null if not specified
135
*/
136
Class<? extends CustomConverter> getCustomConverter();
137
}
138
```
139
140
### Field Mapping Metadata Interface
141
142
Provides metadata about individual field-to-field mappings.
143
144
```java { .api }
145
/**
146
* Provides metadata about field-level mappings
147
*/
148
public interface FieldMappingMetadata {
149
/**
150
* Gets the source field name
151
* @return source field name
152
*/
153
String getSourceFieldName();
154
155
/**
156
* Gets the destination field name
157
* @return destination field name
158
*/
159
String getDestinationFieldName();
160
161
/**
162
* Gets the source field type
163
* @return source field class type
164
*/
165
Class<?> getSourceFieldType();
166
167
/**
168
* Gets the destination field type
169
* @return destination field class type
170
*/
171
Class<?> getDestinationFieldType();
172
173
/**
174
* Gets custom converter class if specified for this field
175
* @return custom converter class or null if not specified
176
*/
177
Class<? extends CustomConverter> getCustomConverter();
178
179
/**
180
* Gets custom converter ID if specified for this field
181
* @return custom converter ID or null if not specified
182
*/
183
String getCustomConverterId();
184
185
/**
186
* Indicates if this field is mapped by reference
187
* @return true if copy by reference, false if deep copy
188
*/
189
boolean isCopyByReference();
190
191
/**
192
* Gets date format if specified for date field conversions
193
* @return date format string or null if not specified
194
*/
195
String getDateFormat();
196
197
/**
198
* Indicates if this field mapping is conditional
199
* @return true if conditional, false otherwise
200
*/
201
boolean isConditional();
202
203
/**
204
* Gets the condition expression if this is a conditional mapping
205
* @return condition expression or null if not conditional
206
*/
207
String getCondition();
208
209
/**
210
* Indicates if this field is excluded from mapping
211
* @return true if excluded, false otherwise
212
*/
213
boolean isExcluded();
214
}
215
```
216
217
## Accessing Metadata
218
219
### From Mapper Instance
220
221
```java
222
import com.github.dozermapper.core.Mapper;
223
import com.github.dozermapper.core.metadata.MappingMetadata;
224
import com.github.dozermapper.core.metadata.ClassMappingMetadata;
225
226
Mapper mapper = DozerBeanMapperBuilder.buildDefault();
227
228
// Get metadata - this initializes mappings if not already done
229
MappingMetadata metadata = mapper.getMappingMetadata();
230
231
// List all class mappings
232
List<ClassMappingMetadata> allMappings = metadata.getClassMappings();
233
for (ClassMappingMetadata classMapping : allMappings) {
234
System.out.println("Mapping: " + classMapping.getSourceClassName() +
235
" -> " + classMapping.getDestinationClassName());
236
}
237
```
238
239
### Query Specific Mappings
240
241
```java
242
// Find mappings for a specific source class
243
List<ClassMappingMetadata> userMappings =
244
metadata.getClassMappingsBySource(User.class);
245
246
// Find mappings for a specific destination class
247
List<ClassMappingMetadata> dtoMappings =
248
metadata.getClassMappingsByDestination(UserDto.class);
249
250
// Find specific mapping between two classes
251
ClassMappingMetadata specificMapping =
252
metadata.getClassMapping(User.class, UserDto.class);
253
```
254
255
### Inspect Field Mappings
256
257
```java
258
ClassMappingMetadata classMapping = metadata.getClassMapping(User.class, UserDto.class);
259
if (classMapping != null) {
260
List<FieldMappingMetadata> fieldMappings = classMapping.getFieldMappings();
261
262
for (FieldMappingMetadata fieldMapping : fieldMappings) {
263
System.out.println("Field: " + fieldMapping.getSourceFieldName() +
264
" -> " + fieldMapping.getDestinationFieldName());
265
266
if (fieldMapping.getCustomConverter() != null) {
267
System.out.println(" Custom converter: " +
268
fieldMapping.getCustomConverter().getSimpleName());
269
}
270
271
if (fieldMapping.isCopyByReference()) {
272
System.out.println(" Copy by reference");
273
}
274
275
if (fieldMapping.isConditional()) {
276
System.out.println(" Condition: " + fieldMapping.getCondition());
277
}
278
}
279
}
280
```
281
282
## Practical Use Cases
283
284
### Dynamic Mapping Validation
285
286
```java
287
public class MappingValidator {
288
289
public void validateMappings(Mapper mapper) {
290
MappingMetadata metadata = mapper.getMappingMetadata();
291
List<ClassMappingMetadata> mappings = metadata.getClassMappings();
292
293
for (ClassMappingMetadata mapping : mappings) {
294
validateClassMapping(mapping);
295
}
296
}
297
298
private void validateClassMapping(ClassMappingMetadata mapping) {
299
// Validate source and destination classes exist
300
try {
301
Class<?> sourceClass = mapping.getSourceClass();
302
Class<?> destClass = mapping.getDestinationClass();
303
304
// Check if classes are compatible
305
if (!areClassesCompatible(sourceClass, destClass)) {
306
logger.warn("Potentially incompatible mapping: {} -> {}",
307
sourceClass.getSimpleName(), destClass.getSimpleName());
308
}
309
310
// Validate field mappings
311
validateFieldMappings(mapping.getFieldMappings());
312
313
} catch (Exception e) {
314
logger.error("Invalid mapping configuration: {} -> {}",
315
mapping.getSourceClassName(), mapping.getDestinationClassName(), e);
316
}
317
}
318
319
private void validateFieldMappings(List<FieldMappingMetadata> fieldMappings) {
320
for (FieldMappingMetadata fieldMapping : fieldMappings) {
321
// Check if field types are compatible
322
if (!areTypesCompatible(fieldMapping.getSourceFieldType(),
323
fieldMapping.getDestinationFieldType())) {
324
325
// Ensure custom converter exists for incompatible types
326
if (fieldMapping.getCustomConverter() == null &&
327
fieldMapping.getCustomConverterId() == null) {
328
logger.warn("No converter for incompatible types: {} -> {}",
329
fieldMapping.getSourceFieldType().getSimpleName(),
330
fieldMapping.getDestinationFieldType().getSimpleName());
331
}
332
}
333
}
334
}
335
336
private boolean areClassesCompatible(Class<?> source, Class<?> dest) {
337
// Implementation for class compatibility check
338
return true; // Simplified
339
}
340
341
private boolean areTypesCompatible(Class<?> sourceType, Class<?> destType) {
342
// Implementation for type compatibility check
343
return sourceType.equals(destType) ||
344
destType.isAssignableFrom(sourceType) ||
345
isAutoConvertible(sourceType, destType);
346
}
347
348
private boolean isAutoConvertible(Class<?> source, Class<?> dest) {
349
// Check for built-in conversions (String <-> primitives, etc.)
350
return false; // Simplified
351
}
352
}
353
```
354
355
### Mapping Documentation Generator
356
357
```java
358
public class MappingDocumentationGenerator {
359
360
public void generateDocumentation(Mapper mapper, PrintWriter writer) {
361
MappingMetadata metadata = mapper.getMappingMetadata();
362
List<ClassMappingMetadata> mappings = metadata.getClassMappings();
363
364
writer.println("# Dozer Mapping Documentation");
365
writer.println();
366
367
for (ClassMappingMetadata mapping : mappings) {
368
generateClassMappingDoc(mapping, writer);
369
}
370
}
371
372
private void generateClassMappingDoc(ClassMappingMetadata mapping, PrintWriter writer) {
373
writer.println("## " + mapping.getSourceClass().getSimpleName() +
374
" → " + mapping.getDestinationClass().getSimpleName());
375
writer.println();
376
377
if (mapping.getMappingId() != null) {
378
writer.println("**Mapping ID:** " + mapping.getMappingId());
379
writer.println();
380
}
381
382
writer.println("**Source:** " + mapping.getSourceClassName());
383
writer.println("**Destination:** " + mapping.getDestinationClassName());
384
writer.println();
385
386
List<FieldMappingMetadata> fieldMappings = mapping.getFieldMappings();
387
if (!fieldMappings.isEmpty()) {
388
writer.println("### Field Mappings");
389
writer.println();
390
writer.println("| Source Field | Destination Field | Type | Notes |");
391
writer.println("|--------------|-------------------|------|-------|");
392
393
for (FieldMappingMetadata fieldMapping : fieldMappings) {
394
generateFieldMappingDoc(fieldMapping, writer);
395
}
396
writer.println();
397
}
398
}
399
400
private void generateFieldMappingDoc(FieldMappingMetadata fieldMapping, PrintWriter writer) {
401
String sourceField = fieldMapping.getSourceFieldName();
402
String destField = fieldMapping.getDestinationFieldName();
403
String type = getFieldMappingType(fieldMapping);
404
String notes = getFieldMappingNotes(fieldMapping);
405
406
writer.printf("| %s | %s | %s | %s |%n",
407
sourceField, destField, type, notes);
408
}
409
410
private String getFieldMappingType(FieldMappingMetadata fieldMapping) {
411
if (fieldMapping.getCustomConverter() != null) {
412
return "Custom";
413
} else if (fieldMapping.isCopyByReference()) {
414
return "Reference";
415
} else if (fieldMapping.isConditional()) {
416
return "Conditional";
417
} else {
418
return "Auto";
419
}
420
}
421
422
private String getFieldMappingNotes(FieldMappingMetadata fieldMapping) {
423
StringBuilder notes = new StringBuilder();
424
425
if (fieldMapping.getCustomConverter() != null) {
426
notes.append("Converter: ").append(fieldMapping.getCustomConverter().getSimpleName());
427
}
428
429
if (fieldMapping.getCustomConverterId() != null) {
430
if (notes.length() > 0) notes.append(", ");
431
notes.append("Converter ID: ").append(fieldMapping.getCustomConverterId());
432
}
433
434
if (fieldMapping.getDateFormat() != null) {
435
if (notes.length() > 0) notes.append(", ");
436
notes.append("Date format: ").append(fieldMapping.getDateFormat());
437
}
438
439
if (fieldMapping.isConditional()) {
440
if (notes.length() > 0) notes.append(", ");
441
notes.append("Condition: ").append(fieldMapping.getCondition());
442
}
443
444
return notes.toString();
445
}
446
}
447
```
448
449
### Runtime Mapping Discovery
450
451
```java
452
public class MappingDiscoveryService {
453
454
public boolean canMap(Mapper mapper, Class<?> sourceClass, Class<?> destClass) {
455
MappingMetadata metadata = mapper.getMappingMetadata();
456
ClassMappingMetadata mapping = metadata.getClassMapping(sourceClass, destClass);
457
return mapping != null;
458
}
459
460
public List<Class<?>> getAvailableDestinations(Mapper mapper, Class<?> sourceClass) {
461
MappingMetadata metadata = mapper.getMappingMetadata();
462
List<ClassMappingMetadata> mappings = metadata.getClassMappingsBySource(sourceClass);
463
464
return mappings.stream()
465
.map(ClassMappingMetadata::getDestinationClass)
466
.collect(Collectors.toList());
467
}
468
469
public List<String> getCustomFields(Mapper mapper, Class<?> sourceClass, Class<?> destClass) {
470
MappingMetadata metadata = mapper.getMappingMetadata();
471
ClassMappingMetadata mapping = metadata.getClassMapping(sourceClass, destClass);
472
473
if (mapping == null) {
474
return Collections.emptyList();
475
}
476
477
return mapping.getFieldMappings().stream()
478
.filter(field -> field.getCustomConverter() != null ||
479
field.getCustomConverterId() != null)
480
.map(FieldMappingMetadata::getDestinationFieldName)
481
.collect(Collectors.toList());
482
}
483
}
484
```
485
486
### Mapping Performance Analysis
487
488
```java
489
public class MappingAnalyzer {
490
491
public void analyzeMappings(Mapper mapper) {
492
MappingMetadata metadata = mapper.getMappingMetadata();
493
List<ClassMappingMetadata> mappings = metadata.getClassMappings();
494
495
int totalMappings = mappings.size();
496
int customConverterMappings = 0;
497
int conditionalMappings = 0;
498
int referenceMappings = 0;
499
500
Map<Class<? extends CustomConverter>, Integer> converterUsage = new HashMap<>();
501
502
for (ClassMappingMetadata mapping : mappings) {
503
for (FieldMappingMetadata field : mapping.getFieldMappings()) {
504
if (field.getCustomConverter() != null) {
505
customConverterMappings++;
506
converterUsage.merge(field.getCustomConverter(), 1, Integer::sum);
507
}
508
509
if (field.isConditional()) {
510
conditionalMappings++;
511
}
512
513
if (field.isCopyByReference()) {
514
referenceMappings++;
515
}
516
}
517
}
518
519
System.out.println("Mapping Analysis:");
520
System.out.println("Total class mappings: " + totalMappings);
521
System.out.println("Fields with custom converters: " + customConverterMappings);
522
System.out.println("Conditional field mappings: " + conditionalMappings);
523
System.out.println("Reference copy mappings: " + referenceMappings);
524
525
System.out.println("\nConverter Usage:");
526
converterUsage.entrySet().stream()
527
.sorted(Map.Entry.<Class<? extends CustomConverter>, Integer>comparingByValue().reversed())
528
.forEach(entry -> System.out.println(entry.getKey().getSimpleName() + ": " + entry.getValue()));
529
}
530
}
531
```
532
533
## Error Handling
534
535
```java { .api }
536
/**
537
* Exception thrown when metadata lookup fails
538
*/
539
public class MetadataLookupException extends MappingException {
540
public MetadataLookupException(String message);
541
public MetadataLookupException(String message, Throwable cause);
542
public MetadataLookupException(Throwable cause);
543
}
544
```
545
546
### Safe Metadata Access
547
548
```java
549
public class SafeMetadataAccess {
550
551
public Optional<ClassMappingMetadata> findMapping(Mapper mapper,
552
Class<?> source, Class<?> dest) {
553
try {
554
MappingMetadata metadata = mapper.getMappingMetadata();
555
ClassMappingMetadata mapping = metadata.getClassMapping(source, dest);
556
return Optional.ofNullable(mapping);
557
} catch (MetadataLookupException e) {
558
logger.warn("Failed to lookup mapping metadata", e);
559
return Optional.empty();
560
}
561
}
562
}
563
```
564
565
## Best Practices
566
567
### Performance Considerations
568
- Metadata access initializes mappings if not already done - cache results when possible
569
- Avoid repeated metadata queries in performance-critical code
570
- Use metadata for development/debugging tools rather than runtime logic
571
572
### Threading
573
- Metadata interfaces are thread-safe for read operations
574
- Cache metadata results in thread-safe collections for concurrent access
575
576
### Usage Patterns
577
- Use metadata for validation during application startup
578
- Generate documentation from metadata in build processes
579
- Implement mapping discovery services for dynamic applications
580
- Create debugging tools that inspect mapping configurations