Class: Tasker::HandlerFactory

Inherits:
Registry::BaseRegistry show all
Includes:
Singleton
Defined in:
lib/tasker/handler_factory.rb

Overview

Factory for creating task handler instances

This class maintains a registry of task handlers by name and dependent system, providing namespaced handler organization while maintaining backward compatibility. It follows the Singleton pattern to ensure a single registry and uses thread-safe storage with modern registry patterns.

Constant Summary

Constants included from Concerns::StructuredLogging

Concerns::StructuredLogging::CORRELATION_ID_KEY

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Registry::BaseRegistry

#base_stats, #health_check, #healthy?, #log_registration, #log_registry_error, #log_registry_operation, #log_unregistration, #log_validation_failure, #thread_safe_operation, #validate_registration_params!

Methods included from Concerns::StructuredLogging

#correlation_id, #correlation_id=, #log_exception, #log_orchestration_event, #log_performance_event, #log_step_event, #log_structured, #log_task_event, #with_correlation_id

Constructor Details

#initializeHandlerFactory

Initialize a new handler factory



28
29
30
31
32
33
# File 'lib/tasker/handler_factory.rb', line 28

def initialize
  super
  @handler_classes = Concurrent::Hash.new
  @namespaces = Set.new([:default])
  log_registry_operation('initialized', namespaces: @namespaces.to_a)
end

Instance Attribute Details

#handler_classesConcurrent::Hash (readonly)

Returns Thread-safe registered handler classes by dependent system and handler name.

Returns:

  • (Concurrent::Hash)

    Thread-safe registered handler classes by dependent system and handler name



20
21
22
# File 'lib/tasker/handler_factory.rb', line 20

def handler_classes
  @handler_classes
end

#namespacesSet (readonly)

Returns Set of registered namespaces for efficient enumeration.

Returns:

  • (Set)

    Set of registered namespaces for efficient enumeration



23
24
25
# File 'lib/tasker/handler_factory.rb', line 23

def namespaces
  @namespaces
end

Instance Method Details

#all_itemsHash

Get all registered handlers (required by BaseRegistry)

Returns:

  • (Hash)

    All registered handlers



161
162
163
# File 'lib/tasker/handler_factory.rb', line 161

def all_items
  @handler_classes.dup
end

#clear!Boolean

Clear all registered handlers (required by BaseRegistry)

Returns:

  • (Boolean)

    True if cleared successfully



168
169
170
# File 'lib/tasker/handler_factory.rb', line 168

def clear!
  clear_all!
end

#clear_all!Boolean

Clear all registered handlers (for testing)

Returns:

  • (Boolean)

    True if cleared successfully



175
176
177
178
179
180
181
182
# File 'lib/tasker/handler_factory.rb', line 175

def clear_all!
  thread_safe_operation do
    @handler_classes.clear
    @namespaces = Set.new([:default])
    log_registry_operation('cleared_all')
    true
  end
end

#get(name, namespace_name: :default, version: '0.1.0') ⇒ Object

Get a task handler instance by name and optional dependent system

Parameters:

  • name (String, Symbol)

    The name of the handler to retrieve

  • namespace_name (String, Symbol) (defaults to: :default)

    The dependent system namespace (defaults to 'default')

  • version (String) (defaults to: '0.1.0')

    The version of the handler (defaults to '0.1.0')

Returns:

  • (Object)

    An instance of the requested task handler

Raises:



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/tasker/handler_factory.rb', line 42

def get(name, namespace_name: :default, version: '0.1.0')
  namespace_name = namespace_name.to_sym
  name_sym = name.to_sym

  # Find the handler class within the mutex lock
  handler_class = thread_safe_operation do
    # Direct namespace lookup - allows same name in different systems
    handler_class = @handler_classes.dig(namespace_name, name_sym, version)
    raise_handler_not_found(name, namespace_name, version) unless handler_class

    log_registry_operation('handler_retrieved',
                           entity_type: 'task_handler',
                           entity_id: "#{namespace_name}/#{name_sym}/#{version}",
                           handler_class: handler_class.is_a?(Class) ? handler_class.name : handler_class)

    handler_class
  end

  # Instantiate the handler OUTSIDE the mutex lock to avoid recursive locking
  # when ConfiguredTask handlers try to register themselves during instantiation
  instantiate_handler(handler_class)
end

#list_handlers(namespace: nil) ⇒ Hash

List handlers, optionally filtered by namespace

Parameters:

  • namespace (String, Symbol, nil) (defaults to: nil)

    Optional namespace filter

Returns:

  • (Hash)

    Handlers hash, either for specific namespace or all namespaces



131
132
133
134
135
136
137
# File 'lib/tasker/handler_factory.rb', line 131

def list_handlers(namespace: nil)
  if namespace
    @handler_classes[namespace.to_sym] || Concurrent::Hash.new
  else
    @handler_classes.dup
  end
end

#register(name, class_or_class_name, namespace_name: :default, version: '0.1.0', **options) ⇒ Boolean

Register a task handler class with a name and optional dependent system

Parameters:

  • name (String, Symbol)

    The name to register the handler under

  • class_or_class_name (Class, String)

    The handler class to register

  • namespace_name (String, Symbol) (defaults to: :default)

    The dependent system namespace (defaults to 'default')

  • version (String) (defaults to: '0.1.0')

    The version of the handler (defaults to '0.1.0')

  • options (Hash)

    Additional registration options

Options Hash (**options):

  • :replace (Boolean) — default: false

    Whether to replace existing handler

Returns:

  • (Boolean)

    True if registration successful

Raises:

  • (StandardError)

    If custom event configuration fails (fail fast)

  • (ArgumentError)

    If handler already exists and replace is false



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tasker/handler_factory.rb', line 76

def register(name, class_or_class_name, namespace_name: :default, version: '0.1.0', **options)
  name_sym = name.to_sym
  namespace_name = namespace_name.to_sym
  replace = options.fetch(:replace, false)

  thread_safe_operation do
    # Validate custom event configuration BEFORE modifying registry state
    # This ensures atomic registration - either fully succeeds or fully fails
    normalized_class = normalize_class_name(class_or_class_name)

    # Validate handler interface using unified validator
    begin
      Registry::InterfaceValidator.validate_handler!(normalized_class.is_a?(Class) ? normalized_class : normalized_class.constantize)
    rescue ArgumentError => e
      log_validation_failure('task_handler', "#{namespace_name}/#{name_sym}/#{version}", e.message)
      raise
    end

    discover_and_register_custom_events(class_or_class_name)

    # Initialize nested hash structure
    @handler_classes[namespace_name] ||= Concurrent::Hash.new
    @handler_classes[namespace_name][name_sym] ||= Concurrent::Hash.new

    # Check for existing handler
    entity_id = "#{namespace_name}/#{name_sym}/#{version}"
    if @handler_classes[namespace_name][name_sym].key?(version) && !replace
      raise ArgumentError,
            "Handler '#{name_sym}' already registered in namespace '#{namespace_name}' version '#{version}'. Use replace: true to override."
    end

    # Log replacement if needed
    if @handler_classes[namespace_name][name_sym].key?(version)
      existing_class = @handler_classes[namespace_name][name_sym][version]
      log_unregistration('task_handler', entity_id,
                         existing_class.is_a?(Class) ? existing_class : existing_class.constantize)
    end

    # Register handler
    @handler_classes[namespace_name][name_sym][version] = normalized_class
    @namespaces.add(namespace_name)

    # Log successful registration
    log_registration('task_handler', entity_id,
                     normalized_class.is_a?(Class) ? normalized_class : normalized_class.constantize,
                     { namespace_name: namespace_name, version: version, **options })

    true
  end
end

#registered_namespacesArray<Symbol>

Get list of all registered namespaces

Returns:

  • (Array<Symbol>)

    Array of namespace symbols



142
143
144
# File 'lib/tasker/handler_factory.rb', line 142

def registered_namespaces
  @namespaces.to_a
end

#statsHash

Get comprehensive registry statistics

Returns:

  • (Hash)

    Detailed statistics about the registry



149
150
151
152
153
154
155
156
# File 'lib/tasker/handler_factory.rb', line 149

def stats
  base_stats.merge(
    total_namespaces: @namespaces.size,
    total_handlers: count_total_handlers,
    handlers_by_namespace: count_handlers_by_namespace,
    versions_by_namespace: count_versions_by_namespace
  )
end