CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-dozermapper--dozer-core

Java Bean to Java Bean mapper that recursively copies data from one object to another

Pending
Overview
Eval results
Files

event-system.mddocs/

Event System

Lifecycle hooks for intercepting and customizing mapping operations at various stages, enabling audit logging, validation, performance monitoring, and custom mapping behavior.

Capabilities

Event Listener Interface

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);
}

Event Interface

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();
}

Event Types Enumeration

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
}

Event Listener Implementation Examples

Audit Logging Listener

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());
    }
}

Performance Monitoring Listener

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());
        }
    }
}

Data Validation Listener

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(".");
    }
}

Custom Field Transformation Listener

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
    }
}

Event Listener Registration

Single Listener Registration

Mapper mapper = DozerBeanMapperBuilder.create()
    .withEventListener(new AuditLoggingListener())
    .build();

Multiple Listeners Registration

Mapper mapper = DozerBeanMapperBuilder.create()
    .withEventListener(new AuditLoggingListener())
    .withEventListener(new PerformanceMonitorListener())
    .withEventListener(new ValidationListener())
    .build();

Bulk Registration

List<EventListener> listeners = Arrays.asList(
    new AuditLoggingListener(),
    new PerformanceMonitorListener(),
    new ValidationListener()
);

Mapper mapper = DozerBeanMapperBuilder.create()
    .withEventListeners(listeners)
    .build();

Advanced Event Handling Patterns

Conditional Event Processing

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());
    }
}

Event Chain Processing

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;
            }
        }
    }
}

Event Context Information Access

Accessing Mapping Metadata

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
    }
}

Best Practices

Performance Considerations

  • Keep event listener logic lightweight to avoid performance impact
  • Use conditional processing to avoid unnecessary operations
  • Consider async processing for heavyweight operations like logging

Error Handling

  • Catch and handle exceptions in listeners to prevent mapping failures
  • Log listener errors without propagating them to mapping operations
  • Provide fallback behavior for critical listener operations

Thread Safety

  • Make listeners thread-safe if mapper is used concurrently
  • Use concurrent collections for shared state
  • Avoid modifying shared mutable state without synchronization

Modularity

  • Create focused listeners with single responsibilities
  • Use composition to combine multiple behaviors
  • Make listeners configurable through constructor parameters

Supporting Types

ClassMap Interface

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();
}

FieldMap Interface

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();
}

BeanContainer Interface

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();
}

DozerClassLoader Interface

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

docs

bean-factory.md

builder-configuration.md

core-mapping.md

custom-conversion.md

event-system.md

index.md

metadata-access.md

programmatic-mapping.md

tile.json