Java Bean to Java Bean mapper that recursively copies data from one object to another
—
Lifecycle hooks for intercepting and customizing mapping operations at various stages, enabling audit logging, validation, performance monitoring, and custom mapping behavior.
Base interface for handling mapping lifecycle events.
/**
* Callback handler for mapping lifecycle events
*/
public interface EventListener {
/**
* Called when mapping operation starts
* @param event event containing mapping context information
*/
void onMappingStarted(Event event);
/**
* Called before writing value to destination field
* @param event event containing mapping context and field information
*/
void onPreWritingDestinationValue(Event event);
/**
* Called after writing value to destination field
* @param event event containing mapping context and field information
*/
void onPostWritingDestinationValue(Event event);
/**
* Called when mapping operation completes
* @param event event containing mapping context information
*/
void onMappingFinished(Event event);
}Contains information about the current mapping operation and context.
/**
* Represents an event triggered during mapping process
*/
public interface Event {
/**
* Gets the type of event that occurred
* @return event type
*/
EventTypes getType();
/**
* Gets the class mapping definition being used
* @return class mapping metadata
*/
ClassMap getClassMap();
/**
* Gets the field mapping definition being used (for field-level events)
* @return field mapping metadata or null for class-level events
*/
FieldMap getFieldMap();
/**
* Gets the source object being mapped from
* @return source object instance
*/
Object getSourceObject();
/**
* Gets the destination object being mapped to
* @return destination object instance
*/
Object getDestinationObject();
/**
* Gets the value being written to destination (for write events)
* @return destination value or null for non-write events
*/
Object getDestinationValue();
}Defines the types of events that can be triggered during mapping.
/**
* Defines types of events that can be triggered during mapping
*/
public enum EventTypes {
/** Fired when mapping operation begins */
MAPPING_STARTED,
/** Fired before writing value to destination field */
MAPPING_PRE_WRITING_DEST_VALUE,
/** Fired after writing value to destination field */
MAPPING_POST_WRITING_DEST_VALUE,
/** Fired when mapping operation completes */
MAPPING_FINISHED
}import com.github.dozermapper.core.events.Event;
import com.github.dozermapper.core.events.EventListener;
import com.github.dozermapper.core.events.EventTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AuditLoggingListener implements EventListener {
private static final Logger logger = LoggerFactory.getLogger(AuditLoggingListener.class);
@Override
public void onMappingStarted(Event event) {
logger.info("Mapping started: {} -> {}",
event.getSourceObject().getClass().getSimpleName(),
event.getDestinationObject().getClass().getSimpleName());
}
@Override
public void onPreWritingDestinationValue(Event event) {
if (event.getFieldMap() != null) {
logger.debug("Writing field: {} = {}",
event.getFieldMap().getDestFieldName(),
event.getDestinationValue());
}
}
@Override
public void onPostWritingDestinationValue(Event event) {
// Log successful field writes
if (event.getFieldMap() != null) {
logger.debug("Field written successfully: {}",
event.getFieldMap().getDestFieldName());
}
}
@Override
public void onMappingFinished(Event event) {
logger.info("Mapping completed: {} -> {}",
event.getSourceObject().getClass().getSimpleName(),
event.getDestinationObject().getClass().getSimpleName());
}
}public class PerformanceMonitorListener implements EventListener {
private final Map<Object, Long> mappingStartTimes = new ConcurrentHashMap<>();
private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorListener.class);
@Override
public void onMappingStarted(Event event) {
mappingStartTimes.put(event.getDestinationObject(), System.currentTimeMillis());
}
@Override
public void onPreWritingDestinationValue(Event event) {
// Can be used to monitor field-level performance
}
@Override
public void onPostWritingDestinationValue(Event event) {
// Can be used to monitor field-level performance
}
@Override
public void onMappingFinished(Event event) {
Long startTime = mappingStartTimes.remove(event.getDestinationObject());
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
logger.info("Mapping took {}ms: {} -> {}", duration,
event.getSourceObject().getClass().getSimpleName(),
event.getDestinationObject().getClass().getSimpleName());
}
}
}public class ValidationListener implements EventListener {
@Override
public void onMappingStarted(Event event) {
validateSourceObject(event.getSourceObject());
}
@Override
public void onPreWritingDestinationValue(Event event) {
// Validate before writing
if (event.getDestinationValue() != null) {
validateFieldValue(event.getFieldMap().getDestFieldName(),
event.getDestinationValue());
}
}
@Override
public void onPostWritingDestinationValue(Event event) {
// Additional validation after field write if needed
}
@Override
public void onMappingFinished(Event event) {
validateDestinationObject(event.getDestinationObject());
}
private void validateSourceObject(Object source) {
if (source == null) {
throw new MappingException("Source object cannot be null");
}
// Additional source validation logic
}
private void validateFieldValue(String fieldName, Object value) {
// Field-specific validation logic
if ("email".equals(fieldName) && value instanceof String) {
if (!isValidEmail((String) value)) {
throw new MappingException("Invalid email format: " + value);
}
}
}
private void validateDestinationObject(Object destination) {
// Final destination object validation
}
private boolean isValidEmail(String email) {
return email.contains("@") && email.contains(".");
}
}public class CustomTransformationListener implements EventListener {
@Override
public void onMappingStarted(Event event) {
// No action needed for mapping start
}
@Override
public void onPreWritingDestinationValue(Event event) {
// Transform values before they are written
if (event.getFieldMap() != null) {
String fieldName = event.getFieldMap().getDestFieldName();
Object value = event.getDestinationValue();
// Custom transformation logic
if ("fullName".equals(fieldName) && value instanceof String) {
// Modify the event's destination value (if mutable)
transformFullName(event, (String) value);
}
}
}
@Override
public void onPostWritingDestinationValue(Event event) {
// Post-processing after field write
}
@Override
public void onMappingFinished(Event event) {
// Final processing after mapping completes
performFinalTransformations(event.getDestinationObject());
}
private void transformFullName(Event event, String fullName) {
// Example: Convert to title case
String titleCase = Arrays.stream(fullName.split(" "))
.map(word -> word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase())
.collect(Collectors.joining(" "));
// Note: Event interface may not allow direct modification
// This is conceptual - actual implementation depends on Event mutability
}
private void performFinalTransformations(Object destination) {
// Apply any final transformations to the complete object
}
}Mapper mapper = DozerBeanMapperBuilder.create()
.withEventListener(new AuditLoggingListener())
.build();Mapper mapper = DozerBeanMapperBuilder.create()
.withEventListener(new AuditLoggingListener())
.withEventListener(new PerformanceMonitorListener())
.withEventListener(new ValidationListener())
.build();List<EventListener> listeners = Arrays.asList(
new AuditLoggingListener(),
new PerformanceMonitorListener(),
new ValidationListener()
);
Mapper mapper = DozerBeanMapperBuilder.create()
.withEventListeners(listeners)
.build();public class ConditionalListener implements EventListener {
@Override
public void onMappingStarted(Event event) {
// Only process certain types
if (shouldProcessMapping(event)) {
processMapping(event);
}
}
@Override
public void onPreWritingDestinationValue(Event event) {
// Only process sensitive fields
if (isSensitiveField(event.getFieldMap())) {
handleSensitiveData(event);
}
}
@Override
public void onPostWritingDestinationValue(Event event) {
// Post-processing for specific conditions
}
@Override
public void onMappingFinished(Event event) {
// Cleanup or finalization
}
private boolean shouldProcessMapping(Event event) {
return event.getSourceObject() instanceof UserEntity;
}
private boolean isSensitiveField(FieldMap fieldMap) {
if (fieldMap == null) return false;
String fieldName = fieldMap.getDestFieldName();
return fieldName.contains("password") || fieldName.contains("ssn");
}
private void handleSensitiveData(Event event) {
// Log or mask sensitive data
logger.warn("Sensitive field accessed: {}",
event.getFieldMap().getDestFieldName());
}
}public abstract class ChainedEventListener implements EventListener {
private EventListener next;
public void setNext(EventListener next) {
this.next = next;
}
protected void callNext(Event event, EventTypes eventType) {
if (next != null) {
switch (eventType) {
case MAPPING_STARTED:
next.onMappingStarted(event);
break;
case MAPPING_PRE_WRITING_DEST_VALUE:
next.onPreWritingDestinationValue(event);
break;
case MAPPING_POST_WRITING_DEST_VALUE:
next.onPostWritingDestinationValue(event);
break;
case MAPPING_FINISHED:
next.onMappingFinished(event);
break;
}
}
}
}public class MetadataAccessListener implements EventListener {
@Override
public void onMappingStarted(Event event) {
ClassMap classMap = event.getClassMap();
if (classMap != null) {
logger.info("Mapping class: {} -> {}",
classMap.getSrcClassName(),
classMap.getDestClassName());
}
}
@Override
public void onPreWritingDestinationValue(Event event) {
FieldMap fieldMap = event.getFieldMap();
if (fieldMap != null) {
logger.debug("Field mapping: {} -> {}",
fieldMap.getSrcFieldName(),
fieldMap.getDestFieldName());
}
}
@Override
public void onPostWritingDestinationValue(Event event) {
// Access both source and destination values
Object sourceValue = getSourceValue(event);
Object destValue = event.getDestinationValue();
logger.debug("Value transformation: {} -> {}", sourceValue, destValue);
}
@Override
public void onMappingFinished(Event event) {
// Final validation or logging
}
private Object getSourceValue(Event event) {
// Extract source value using reflection or field map
// Implementation depends on available Event API methods
return null; // Placeholder
}
}Internal interface providing access to class mapping metadata in events.
/**
* Internal interface representing class mapping configuration (read-only access in events)
*/
public interface ClassMap {
/**
* Gets the source class name for this mapping
* @return source class name
*/
String getSrcClassName();
/**
* Gets the destination class name for this mapping
* @return destination class name
*/
String getDestClassName();
/**
* Gets the map ID if specified for this mapping
* @return map ID or null if not specified
*/
String getMapId();
/**
* Gets the source class type
* @return source class
*/
Class<?> getSrcClassToMap();
/**
* Gets the destination class type
* @return destination class
*/
Class<?> getDestClassToMap();
}Internal interface providing access to field mapping metadata in events.
/**
* Internal interface representing field mapping configuration (read-only access in events)
*/
public interface FieldMap {
/**
* Gets the source field name
* @return source field name
*/
String getSrcFieldName();
/**
* Gets the destination field name
* @return destination field name
*/
String getDestFieldName();
/**
* Gets the mapping type for this field
* @return field mapping type
*/
FieldMappingType getType();
/**
* Checks if this field should be copied by reference
* @return true if copy by reference, false otherwise
*/
boolean isCopyByReference();
/**
* Gets the custom converter class if specified
* @return custom converter class or null
*/
Class<? extends CustomConverter> getCustomConverter();
}Internal configuration container providing access to runtime configuration.
/**
* Internal bean container for runtime configuration access
*/
public interface BeanContainer {
/**
* Gets the configured class loader
* @return DozerClassLoader instance
*/
DozerClassLoader getClassLoader();
/**
* Gets the proxy resolver
* @return proxy resolver instance
*/
DozerProxyResolver getProxyResolver();
/**
* Gets the expression language engine
* @return EL engine instance
*/
ELEngine getElEngine();
}Service provider interface for class and resource loading.
/**
* Service Provider Interface for control of Dozer Class and Resource loading behavior.
* Could be used in order to provide specific implementations for web-container,
* j2ee container and osgi environments.
*/
public interface DozerClassLoader {
/**
* Loads class by provided name
* @param className fully qualified class name
* @return the class if found, null otherwise
*/
Class<?> loadClass(String className);
/**
* Loads the resource by URI as an URL
* @param uri uri of the resource
* @return resource URL
*/
URL loadResource(String uri);
}Install with Tessl CLI
npx tessl i tessl/maven-com-github-dozermapper--dozer-core