Class: Lutaml::Model::TransformationRegistry
- Inherits:
-
Object
- Object
- Lutaml::Model::TransformationRegistry
- Defined in:
- lib/lutaml/model/transformation_registry.rb
Overview
Registry for managing transformation lifecycle and caching.
This registry is the single source of truth for all transformation instances and resolved mappings. It provides:
-
Centralized caching - both transformations and mappings
-
Thread safety - Mutex-protected access
-
Cycle detection - handles self-referential models (e.g., Address.address: Address)
-
Single responsibility - manages transformation lifecycle only
-
Open/Closed - extensible via builder registration
Architecture layers:
-
Configuration (Model classes): Define mappings (stateless DSL)
-
Compilation (TransformationBuilder): Build transformation (stateless) <- NEW
-
Registry (TransformationRegistry): Cache and lifecycle (stateful service) <- THIS
-
Execution (Transformation): Serialize data (stateful instances)
Class Method Summary collapse
-
.builder_for(format) ⇒ Class?
Get the registered builder for a format.
-
.instance ⇒ Object
Get singleton instance.
-
.register_builder(format, builder) ⇒ void
Register a builder for a format.
-
.reset_builders! ⇒ void
Reset builders to load-time defaults (useful for testing).
Instance Method Summary collapse
-
#clear ⇒ Object
Clear all cached data (transformations and mappings).
-
#get_or_build_mapping(model_class, format, register) ⇒ Mapping
Get or build resolved mapping for a model class and format.
-
#get_or_build_transformation(model_class, format, register) ⇒ Transformation?
Get or build transformation for a model class and format.
-
#initialize ⇒ TransformationRegistry
constructor
A new instance of TransformationRegistry.
Constructor Details
#initialize ⇒ TransformationRegistry
Returns a new instance of TransformationRegistry.
91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/lutaml/model/transformation_registry.rb', line 91 def initialize @transformations = {} # Cache for transformation instances (mutex-protected for cycle detection) @mappings = if RuntimeCompatibility.opal? {} else Concurrent::Map.new end @mutex = Mutex.new @building_threads = {} # Track which thread is building each transformation @condition = ConditionVariable.new end |
Class Method Details
.builder_for(format) ⇒ Class?
Get the registered builder for a format.
75 76 77 78 |
# File 'lib/lutaml/model/transformation_registry.rb', line 75 def builder_for(format) @builders ||= {} @builders[format] end |
.instance ⇒ Object
Get singleton instance
37 38 39 |
# File 'lib/lutaml/model/transformation_registry.rb', line 37 def instance @instance ||= new end |
.register_builder(format, builder) ⇒ void
This method returns an undefined value.
Register a builder for a format.
This allows extending TransformationRegistry with new formats without modifying its code (Open/Closed Principle).
Builders are registered at load time by each format module:
-
XML: registered by lib/lutaml/xml.rb
-
KeyValue formats: registered by lib/lutaml/key_value.rb
56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/lutaml/model/transformation_registry.rb', line 56 def register_builder(format, builder) unless builder < TransformationBuilder raise ArgumentError, "Builder must inherit from TransformationBuilder" end @builders ||= {} @builders[format] = builder # Track load-time registrations so reset can restore them # Only record the first registration per format as the default @default_builders ||= {} @default_builders[format] ||= builder end |
.reset_builders! ⇒ void
This method returns an undefined value.
Reset builders to load-time defaults (useful for testing)
Restores the builders to the set registered at load time, removing any test-specific overrides.
86 87 88 |
# File 'lib/lutaml/model/transformation_registry.rb', line 86 def reset_builders! @builders = (@default_builders || {}).dup end |
Instance Method Details
#clear ⇒ Object
Clear all cached data (transformations and mappings).
This is useful for testing or when configuration changes dynamically.
199 200 201 202 203 204 |
# File 'lib/lutaml/model/transformation_registry.rb', line 199 def clear @mutex.synchronize do @transformations.clear end @mappings.clear end |
#get_or_build_mapping(model_class, format, register) ⇒ Mapping
Get or build resolved mapping for a model class and format.
This method caches the resolved mapping (either from mappings or from default_mappings(format)).
CRITICAL: Ensures mappings are imported before returning, which handles deferred symbol-based imports (e.g., import_model :SomeModel).
NOTE: Building is done OUTSIDE the mutex to avoid deadlock when ensure_mappings_imported! recursively calls mappings_for.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/lutaml/model/transformation_registry.rb', line 176 def get_or_build_mapping(model_class, format, register) # Performance: Use fast array key instead of symbol construction register_id = extract_register_id(register) key = [model_class.object_id, format, register_id] # Fast path: native Ruby uses Concurrent::Map, Opal uses Hash. cached = @mappings[key] return cached if cached # Build mapping OUTSIDE any lock to avoid deadlock # (ensure_mappings_imported! may recursively call get_or_build_mapping) mapping = model_class.mappings[format] mapping = mapping || model_class.send(:default_mappings, format) mapping.ensure_mappings_imported!(register_id) @mappings[key] = mapping mapping end |
#get_or_build_transformation(model_class, format, register) ⇒ Transformation?
Get or build transformation for a model class and format.
This method provides cycle detection for self-referential models. When a transformation is being built, it’s marked with :building to prevent infinite recursion.
THREAD SAFETY: Uses ConditionVariable to wait for in-progress builds from other threads, while still detecting cycles within the same thread.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/lutaml/model/transformation_registry.rb', line 116 def get_or_build_transformation(model_class, format, register) key = transformation_key(model_class, format, register) current_thread = Thread.current @mutex.synchronize do loop do cached = @transformations[key] # Return if already built return cached if cached && cached != :building # Check for cycles (same thread trying to build same transformation) if cached == :building && @building_threads[key] == current_thread return nil end # If another thread is building, wait for it if cached == :building @condition.wait(@mutex) next # Re-check after waking end # Mark as building and record which thread @transformations[key] = :building @building_threads[key] = current_thread break end end # Build transformation OUTSIDE the lock to avoid deadlock # (building may trigger recursive calls to get_or_build_transformation) mapping = get_or_build_mapping(model_class, format, register) transformation = build_transformation(model_class, mapping, format, register) # Cache and return the result @mutex.synchronize do @transformations[key] = transformation @building_threads.delete(key) @condition.broadcast end transformation end |