operaton-bpm-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +134 -0
  3. data/lib/operaton/bpm/client/backoff/backoff_strategy.rb +20 -0
  4. data/lib/operaton/bpm/client/backoff/error_aware_backoff_strategy.rb +22 -0
  5. data/lib/operaton/bpm/client/backoff/exponential_backoff_strategy.rb +38 -0
  6. data/lib/operaton/bpm/client/backoff/exponential_error_backoff_strategy.rb +38 -0
  7. data/lib/operaton/bpm/client/exceptions.rb +74 -0
  8. data/lib/operaton/bpm/client/external_task_client.rb +20 -0
  9. data/lib/operaton/bpm/client/external_task_client_builder.rb +18 -0
  10. data/lib/operaton/bpm/client/impl/engine_client.rb +132 -0
  11. data/lib/operaton/bpm/client/impl/engine_client_exception.rb +25 -0
  12. data/lib/operaton/bpm/client/impl/engine_client_logger.rb +54 -0
  13. data/lib/operaton/bpm/client/impl/engine_rest_exception_dto.rb +28 -0
  14. data/lib/operaton/bpm/client/impl/external_task_client_builder_impl.rb +349 -0
  15. data/lib/operaton/bpm/client/impl/external_task_client_impl.rb +41 -0
  16. data/lib/operaton/bpm/client/impl/external_task_client_logger.rb +236 -0
  17. data/lib/operaton/bpm/client/impl/object_mapper.rb +44 -0
  18. data/lib/operaton/bpm/client/impl/permanent_url_resolver.rb +22 -0
  19. data/lib/operaton/bpm/client/impl/request_dto.rb +24 -0
  20. data/lib/operaton/bpm/client/impl/request_executor.rb +143 -0
  21. data/lib/operaton/bpm/client/interceptor/auth/basic_auth_provider.rb +42 -0
  22. data/lib/operaton/bpm/client/interceptor/client_request_context.rb +16 -0
  23. data/lib/operaton/bpm/client/interceptor/client_request_interceptor.rb +18 -0
  24. data/lib/operaton/bpm/client/interceptor/impl/client_request_context_impl.rb +28 -0
  25. data/lib/operaton/bpm/client/interceptor/impl/request_interceptor_handler.rb +43 -0
  26. data/lib/operaton/bpm/client/spi/data_format.rb +32 -0
  27. data/lib/operaton/bpm/client/spi/data_format_configurator.rb +31 -0
  28. data/lib/operaton/bpm/client/spi/data_format_provider.rb +31 -0
  29. data/lib/operaton/bpm/client/task/external_task.rb +22 -0
  30. data/lib/operaton/bpm/client/task/external_task_handler.rb +28 -0
  31. data/lib/operaton/bpm/client/task/external_task_service.rb +18 -0
  32. data/lib/operaton/bpm/client/task/impl/dto/bpmn_error_request_dto.rb +35 -0
  33. data/lib/operaton/bpm/client/task/impl/dto/complete_request_dto.rb +33 -0
  34. data/lib/operaton/bpm/client/task/impl/dto/extend_lock_request_dto.rb +29 -0
  35. data/lib/operaton/bpm/client/task/impl/dto/failure_request_dto.rb +43 -0
  36. data/lib/operaton/bpm/client/task/impl/dto/lock_request_dto.rb +29 -0
  37. data/lib/operaton/bpm/client/task/impl/dto/set_variables_request_dto.rb +29 -0
  38. data/lib/operaton/bpm/client/task/impl/external_task_impl.rb +128 -0
  39. data/lib/operaton/bpm/client/task/impl/external_task_service_impl.rb +90 -0
  40. data/lib/operaton/bpm/client/task/ordering_config.rb +78 -0
  41. data/lib/operaton/bpm/client/task/sorting_dto.rb +29 -0
  42. data/lib/operaton/bpm/client/topic/impl/dto/fetch_and_lock_request_dto.rb +47 -0
  43. data/lib/operaton/bpm/client/topic/impl/dto/fetch_and_lock_response_dto.rb +32 -0
  44. data/lib/operaton/bpm/client/topic/impl/dto/topic_request_dto.rb +70 -0
  45. data/lib/operaton/bpm/client/topic/impl/topic_subscription_builder_impl.rb +164 -0
  46. data/lib/operaton/bpm/client/topic/impl/topic_subscription_impl.rb +76 -0
  47. data/lib/operaton/bpm/client/topic/impl/topic_subscription_manager.rb +222 -0
  48. data/lib/operaton/bpm/client/topic/impl/topic_subscription_manager_logger.rb +52 -0
  49. data/lib/operaton/bpm/client/topic/topic_subscription.rb +20 -0
  50. data/lib/operaton/bpm/client/topic/topic_subscription_builder.rb +22 -0
  51. data/lib/operaton/bpm/client/url_resolver.rb +15 -0
  52. data/lib/operaton/bpm/client/variable/client_values.rb +33 -0
  53. data/lib/operaton/bpm/client/variable/impl/abstract_typed_value_mapper.rb +59 -0
  54. data/lib/operaton/bpm/client/variable/impl/default_value_mappers.rb +71 -0
  55. data/lib/operaton/bpm/client/variable/impl/format/json/json_data_format.rb +82 -0
  56. data/lib/operaton/bpm/client/variable/impl/format/json/json_data_format_provider.rb +34 -0
  57. data/lib/operaton/bpm/client/variable/impl/mapper/boolean_value_mapper.rb +35 -0
  58. data/lib/operaton/bpm/client/variable/impl/mapper/byte_array_value_mapper.rb +53 -0
  59. data/lib/operaton/bpm/client/variable/impl/mapper/date_value_mapper.rb +61 -0
  60. data/lib/operaton/bpm/client/variable/impl/mapper/double_value_mapper.rb +36 -0
  61. data/lib/operaton/bpm/client/variable/impl/mapper/file_value_mapper.rb +73 -0
  62. data/lib/operaton/bpm/client/variable/impl/mapper/integer_value_mapper.rb +36 -0
  63. data/lib/operaton/bpm/client/variable/impl/mapper/json_value_mapper.rb +35 -0
  64. data/lib/operaton/bpm/client/variable/impl/mapper/long_value_mapper.rb +36 -0
  65. data/lib/operaton/bpm/client/variable/impl/mapper/null_value_mapper.rb +45 -0
  66. data/lib/operaton/bpm/client/variable/impl/mapper/number_value_mapper.rb +25 -0
  67. data/lib/operaton/bpm/client/variable/impl/mapper/object_value_mapper.rb +151 -0
  68. data/lib/operaton/bpm/client/variable/impl/mapper/primitive_value_mapper.rb +44 -0
  69. data/lib/operaton/bpm/client/variable/impl/mapper/short_value_mapper.rb +36 -0
  70. data/lib/operaton/bpm/client/variable/impl/mapper/string_value_mapper.rb +35 -0
  71. data/lib/operaton/bpm/client/variable/impl/mapper/xml_value_mapper.rb +35 -0
  72. data/lib/operaton/bpm/client/variable/impl/type/json_type_impl.rb +24 -0
  73. data/lib/operaton/bpm/client/variable/impl/type/xml_type_impl.rb +24 -0
  74. data/lib/operaton/bpm/client/variable/impl/typed_value_field.rb +36 -0
  75. data/lib/operaton/bpm/client/variable/impl/typed_values.rb +100 -0
  76. data/lib/operaton/bpm/client/variable/impl/value/deferred_file_value_impl.rb +57 -0
  77. data/lib/operaton/bpm/client/variable/impl/value/json_value_impl.rb +26 -0
  78. data/lib/operaton/bpm/client/variable/impl/value/xml_value_impl.rb +26 -0
  79. data/lib/operaton/bpm/client/variable/impl/value_mapper.rb +42 -0
  80. data/lib/operaton/bpm/client/variable/impl/value_mappers.rb +26 -0
  81. data/lib/operaton/bpm/client/variable/impl/variable_value.rb +55 -0
  82. data/lib/operaton/bpm/client/variable/value/deferred_file_value.rb +19 -0
  83. data/lib/operaton/bpm/client/variable/value/json_value.rb +16 -0
  84. data/lib/operaton/bpm/client/variable/value/xml_value.rb +16 -0
  85. data/lib/operaton/bpm/client/version.rb +9 -0
  86. data/lib/operaton/bpm/client.rb +121 -0
  87. data/lib/operaton/bpm/engine/variable/typed_value.rb +153 -0
  88. data/lib/operaton/bpm/engine/variable/value_type.rb +126 -0
  89. data/lib/operaton/bpm/engine/variable/variable_map.rb +72 -0
  90. data/lib/operaton/bpm/engine/variable/variables.rb +197 -0
  91. data/lib/operaton-bpm-client.rb +3 -0
  92. metadata +188 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../topic_subscription"
4
+
5
+ module Operaton
6
+ module Bpm
7
+ module Client
8
+ module Topic
9
+ module Impl
10
+ # Mirrors org.operaton.bpm.client.topic.impl.TopicSubscriptionImpl
11
+ class TopicSubscriptionImpl
12
+ include TopicSubscription
13
+
14
+ attr_reader :topic_name, :lock_duration, :external_task_handler, :variable_names, :business_key
15
+ attr_accessor :process_definition_id, :process_definition_id_in,
16
+ :process_definition_key, :process_definition_key_in,
17
+ :process_definition_version_tag, :tenant_id_in
18
+
19
+ def initialize(topic_name, lock_duration, external_task_handler,
20
+ topic_subscription_manager, variable_names, business_key)
21
+ @topic_name = topic_name
22
+ @lock_duration = lock_duration
23
+ @external_task_handler = external_task_handler
24
+ @topic_subscription_manager = topic_subscription_manager
25
+ @variable_names = variable_names
26
+ @business_key = business_key
27
+ @local_variables = false
28
+ @without_tenant_id = false
29
+ @include_extension_properties = false
30
+ @process_variables = nil
31
+ end
32
+
33
+ def close
34
+ @topic_subscription_manager.unsubscribe(self)
35
+ end
36
+
37
+ def local_variables?
38
+ @local_variables
39
+ end
40
+
41
+ attr_writer :local_variables
42
+
43
+ attr_reader :process_variables
44
+
45
+ def process_variables=(process_variables)
46
+ @process_variables ||= {}
47
+ @process_variables.merge!(process_variables)
48
+ end
49
+
50
+ def without_tenant_id?
51
+ @without_tenant_id
52
+ end
53
+
54
+ attr_writer :without_tenant_id
55
+
56
+ def include_extension_properties?
57
+ @include_extension_properties
58
+ end
59
+
60
+ attr_writer :include_extension_properties
61
+
62
+ # Java equality is based on class and topic name only.
63
+ def ==(other)
64
+ other.instance_of?(self.class) && topic_name == other.topic_name
65
+ end
66
+ alias eql? ==
67
+
68
+ def hash
69
+ [self.class, topic_name].hash
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "topic_subscription_manager_logger"
4
+ require_relative "dto/topic_request_dto"
5
+ require_relative "dto/fetch_and_lock_response_dto"
6
+ require_relative "../../task/impl/external_task_service_impl"
7
+ require_relative "../../task/external_task_handler"
8
+ require_relative "../../backoff/error_aware_backoff_strategy"
9
+ require_relative "../../impl/engine_client_exception"
10
+ require_relative "../../impl/external_task_client_logger"
11
+ require_relative "../../exceptions"
12
+
13
+ module Operaton
14
+ module Bpm
15
+ module Client
16
+ module Topic
17
+ module Impl
18
+ # Mirrors org.operaton.bpm.client.topic.impl.TopicSubscriptionManager.
19
+ # Runs the fetch-and-lock loop on a dedicated thread.
20
+ class TopicSubscriptionManager
21
+ attr_reader :engine_client
22
+
23
+ def initialize(engine_client, typed_values, client_lock_duration)
24
+ @engine_client = engine_client
25
+ @subscriptions = []
26
+ @subscriptions_mutex = Mutex.new
27
+ @task_topic_requests = []
28
+ @external_task_handlers = {}
29
+ @client_lock_duration = client_lock_duration
30
+ @typed_values = typed_values
31
+ @external_task_service = Task::Impl::ExternalTaskServiceImpl.new(engine_client)
32
+ @is_backoff_strategy_disabled = false
33
+ @backoff_strategy = nil
34
+
35
+ @is_running = false
36
+ @state_mutex = Mutex.new
37
+ @acquisition_monitor = Mutex.new
38
+ @is_waiting = ConditionVariable.new
39
+ @resume_signaled = false
40
+ @thread = nil
41
+ end
42
+
43
+ def run
44
+ while running?
45
+ begin
46
+ acquire
47
+ rescue StandardError => e
48
+ logger.exception_while_acquiring_tasks(e)
49
+ end
50
+ end
51
+ end
52
+
53
+ def stop
54
+ @state_mutex.synchronize do
55
+ return unless @is_running
56
+
57
+ @is_running = false
58
+ end
59
+ resume
60
+ begin
61
+ @thread&.join
62
+ rescue StandardError => e
63
+ logger.exception_while_shutting_down(e)
64
+ end
65
+ end
66
+
67
+ def start
68
+ @state_mutex.synchronize do
69
+ return if @is_running
70
+
71
+ @is_running = true
72
+ @thread = Thread.new { run }
73
+ @thread.name = "TopicSubscriptionManager"
74
+ end
75
+ end
76
+
77
+ def subscribe(subscription)
78
+ @subscriptions_mutex.synchronize do
79
+ if @subscriptions.include?(subscription)
80
+ raise logger.topic_name_already_subscribed_exception(subscription.topic_name)
81
+ end
82
+
83
+ @subscriptions << subscription
84
+ end
85
+ resume
86
+ end
87
+
88
+ def unsubscribe(subscription)
89
+ @subscriptions_mutex.synchronize { @subscriptions.delete(subscription) }
90
+ end
91
+
92
+ def subscriptions
93
+ @subscriptions_mutex.synchronize { @subscriptions.dup }
94
+ end
95
+
96
+ def running?
97
+ @state_mutex.synchronize { @is_running }
98
+ end
99
+ alias is_running? running?
100
+
101
+ def backoff_strategy=(backoff_strategy)
102
+ @backoff_strategy = backoff_strategy
103
+ end
104
+
105
+ def disable_backoff_strategy
106
+ @is_backoff_strategy_disabled = true
107
+ end
108
+
109
+ protected
110
+
111
+ def acquire
112
+ @task_topic_requests.clear
113
+ @external_task_handlers.clear
114
+ subscriptions.each { |subscription| prepare_acquisition(subscription) }
115
+
116
+ return if @task_topic_requests.empty?
117
+
118
+ fetch_and_lock_response = fetch_and_lock(@task_topic_requests)
119
+
120
+ fetch_and_lock_response.external_tasks.each do |external_task|
121
+ topic_name = external_task.topic_name
122
+ task_handler = @external_task_handlers[topic_name]
123
+
124
+ if task_handler
125
+ handle_external_task(external_task, task_handler)
126
+ else
127
+ logger.task_handler_is_null(topic_name)
128
+ end
129
+ end
130
+
131
+ run_backoff_strategy(fetch_and_lock_response) unless @is_backoff_strategy_disabled
132
+ end
133
+
134
+ def prepare_acquisition(subscription)
135
+ @task_topic_requests << Dto::TopicRequestDto.from_topic_subscription(subscription, @client_lock_duration)
136
+ @external_task_handlers[subscription.topic_name] = subscription.external_task_handler
137
+ end
138
+
139
+ def fetch_and_lock(task_topic_requests)
140
+ logger.fetch_and_lock(task_topic_requests)
141
+ external_tasks = @engine_client.fetch_and_lock(task_topic_requests)
142
+ Dto::FetchAndLockResponseDto.new(external_tasks)
143
+ rescue Client::Impl::EngineClientException => e
144
+ logger.exception_while_performing_fetch_and_lock(e)
145
+ Dto::FetchAndLockResponseDto.new(
146
+ client_logger.handled_engine_client_exception("fetching and locking task", e)
147
+ )
148
+ end
149
+
150
+ def handle_external_task(external_task, task_handler)
151
+ variables = external_task.variables
152
+ external_task.received_variable_map = @typed_values.wrap_variables(external_task, variables)
153
+
154
+ begin
155
+ Task::ExternalTaskHandler.invoke(task_handler, external_task, @external_task_service)
156
+ rescue ExternalTaskClientException => e
157
+ logger.exception_on_external_task_service_method_invocation(external_task.topic_name, e)
158
+ rescue StandardError => e
159
+ logger.exception_while_executing_external_task_handler(external_task.topic_name, e)
160
+ end
161
+ end
162
+
163
+ def run_backoff_strategy(fetch_and_lock_response)
164
+ external_tasks = fetch_and_lock_response.external_tasks
165
+
166
+ if @backoff_strategy.is_a?(Backoff::ErrorAwareBackoffStrategy) ||
167
+ @backoff_strategy.method(:reconfigure).arity != 1
168
+ @backoff_strategy.reconfigure(external_tasks, fetch_and_lock_response.error)
169
+ else
170
+ @backoff_strategy.reconfigure(external_tasks)
171
+ end
172
+
173
+ suspend(@backoff_strategy.calculate_backoff_time)
174
+ rescue StandardError => e
175
+ logger.exception_while_executing_backoff_strategy_method(e)
176
+ end
177
+
178
+ def suspend(wait_time_ms)
179
+ return unless wait_time_ms.positive? && running?
180
+
181
+ @acquisition_monitor.synchronize do
182
+ end_time = monotonic_ms + wait_time_ms
183
+ remaining = wait_time_ms
184
+
185
+ while remaining.positive? && running?
186
+ break if @resume_signaled
187
+
188
+ @is_waiting.wait(@acquisition_monitor, remaining / 1000.0)
189
+ break if @resume_signaled
190
+
191
+ remaining = end_time - monotonic_ms
192
+ end
193
+
194
+ logger.timeout(wait_time_ms) if remaining <= 0 && running? && !@resume_signaled
195
+ @resume_signaled = false
196
+ end
197
+ end
198
+
199
+ def resume
200
+ @acquisition_monitor.synchronize do
201
+ @resume_signaled = true
202
+ @is_waiting.signal
203
+ end
204
+ end
205
+
206
+ def monotonic_ms
207
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
208
+ end
209
+
210
+ def logger
211
+ Client::Impl::ExternalTaskClientLogger.topic_subscription_manager_logger
212
+ end
213
+
214
+ def client_logger
215
+ Client::Impl::ExternalTaskClientLogger.client_logger
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../impl/external_task_client_logger"
4
+
5
+ module Operaton
6
+ module Bpm
7
+ module Client
8
+ module Topic
9
+ module Impl
10
+ # Mirrors org.operaton.bpm.client.topic.impl.TopicSubscriptionManagerLogger
11
+ class TopicSubscriptionManagerLogger < Client::Impl::ExternalTaskClientLogger
12
+ def exception_while_performing_fetch_and_lock(error)
13
+ log_error("001", "Exception while fetching and locking task.", error)
14
+ end
15
+
16
+ def exception_while_executing_external_task_handler(topic_name, error)
17
+ log_error("002", "Exception while executing external task handler '#{topic_name}'.", error)
18
+ end
19
+
20
+ def exception_while_shutting_down(error)
21
+ log_error("003", "Exception while shutting down:", error)
22
+ end
23
+
24
+ def exception_on_external_task_service_method_invocation(topic_name, error)
25
+ log_error("004", "Exception on external task service method invocation for topic '#{topic_name}':", error)
26
+ end
27
+
28
+ def exception_while_executing_backoff_strategy_method(error)
29
+ log_error("005", "Exception while executing back off strategy method.", error)
30
+ end
31
+
32
+ def exception_while_acquiring_tasks(error)
33
+ log_error("006", "Exception while acquiring tasks.", error)
34
+ end
35
+
36
+ def task_handler_is_null(topic_name)
37
+ log_error("007", "Task handler is null for topic '#{topic_name}'.")
38
+ end
39
+
40
+ def fetch_and_lock(subscriptions)
41
+ log_debug("008", "Fetch and lock new external tasks for #{subscriptions.size} topics")
42
+ end
43
+
44
+ def timeout(wait_time)
45
+ log_debug("009", "Timed out after #{wait_time} ms without a signal.")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Operaton
4
+ module Bpm
5
+ module Client
6
+ module Topic
7
+ # Mirrors org.operaton.bpm.client.topic.TopicSubscription. Documents
8
+ # the contract implemented by Topic::Impl::TopicSubscriptionImpl.
9
+ module TopicSubscription
10
+ INTERFACE_METHODS = %i[
11
+ close topic_name lock_duration external_task_handler variable_names
12
+ local_variables? business_key process_definition_id process_definition_id_in
13
+ process_definition_key process_definition_key_in process_definition_version_tag
14
+ process_variables without_tenant_id? tenant_id_in include_extension_properties?
15
+ ].freeze
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Operaton
4
+ module Bpm
5
+ module Client
6
+ module Topic
7
+ # Mirrors org.operaton.bpm.client.topic.TopicSubscriptionBuilder.
8
+ # Documents the fluent contract implemented by
9
+ # Topic::Impl::TopicSubscriptionBuilderImpl.
10
+ module TopicSubscriptionBuilder
11
+ INTERFACE_METHODS = %i[
12
+ lock_duration handler variables local_variables business_key
13
+ process_definition_id process_definition_id_in process_definition_key
14
+ process_definition_key_in process_definition_version_tag
15
+ process_variables_equals_in process_variable_equals without_tenant_id
16
+ tenant_id_in include_extension_properties open
17
+ ].freeze
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Operaton
4
+ module Bpm
5
+ module Client
6
+ # Mirrors org.operaton.bpm.client.UrlResolver. Any object responding to
7
+ # #base_url can act as a URL resolver.
8
+ module UrlResolver
9
+ def base_url
10
+ raise NotImplementedError, "#{self.class} must implement #base_url"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../engine/variable/variables"
4
+ require_relative "impl/type/json_type_impl"
5
+ require_relative "impl/type/xml_type_impl"
6
+
7
+ module Operaton
8
+ module Bpm
9
+ module Client
10
+ module Variable
11
+ # Mirrors org.operaton.bpm.client.variable.ClientValues, which extends
12
+ # the engine Variables factory with json and xml typed values.
13
+ module ClientValues
14
+ extend Engine::Variable::VariablesFactory
15
+
16
+ JSON = Impl::Type::JsonTypeImpl.new
17
+ XML = Impl::Type::XmlTypeImpl.new
18
+
19
+ def self.json_value(json_value, is_transient = false)
20
+ Impl::Value::JsonValueImpl.new(json_value, is_transient)
21
+ end
22
+
23
+ def self.xml_value(xml_value, is_transient = false)
24
+ Impl::Value::XmlValueImpl.new(xml_value, is_transient)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ require_relative "impl/value/json_value_impl"
33
+ require_relative "impl/value/xml_value_impl"
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "value_mapper"
4
+
5
+ module Operaton
6
+ module Bpm
7
+ module Client
8
+ module Variable
9
+ module Impl
10
+ # Mirrors org.operaton.bpm.client.variable.impl.AbstractTypedValueMapper
11
+ class AbstractTypedValueMapper
12
+ include ValueMapper
13
+
14
+ attr_reader :value_type
15
+
16
+ def initialize(value_type)
17
+ @value_type = value_type
18
+ end
19
+
20
+ def type
21
+ value_type
22
+ end
23
+
24
+ def serialization_dataformat
25
+ nil
26
+ end
27
+
28
+ def can_handle_typed_value(typed_value)
29
+ type = typed_value.type
30
+ (type.nil? || type_matches?(type)) && can_write_value(typed_value)
31
+ end
32
+
33
+ def can_handle_typed_value_field(typed_value_field)
34
+ type = typed_value_field.type
35
+ !type.nil? && type == value_type.name && can_read_value(typed_value_field)
36
+ end
37
+
38
+ protected
39
+
40
+ # In Java this is valueType.getClass().isAssignableFrom(type.getClass()),
41
+ # where every value type has its own class. Ruby value types are
42
+ # singleton instances, so identity comparison is the equivalent.
43
+ def type_matches?(type)
44
+ type.equal?(value_type)
45
+ end
46
+
47
+ def can_write_value(typed_value)
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def can_read_value(typed_value_field)
52
+ raise NotImplementedError
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "value_mappers"
4
+ require_relative "../../impl/external_task_client_logger"
5
+
6
+ module Operaton
7
+ module Bpm
8
+ module Client
9
+ module Variable
10
+ module Impl
11
+ # Mirrors org.operaton.bpm.client.variable.impl.DefaultValueMappers
12
+ class DefaultValueMappers
13
+ include ValueMappers
14
+
15
+ def initialize(default_serialization_format)
16
+ @serializer_list = []
17
+ @default_serialization_format = default_serialization_format
18
+ end
19
+
20
+ def find_mapper_for_typed_value(typed_value)
21
+ type = typed_value.type
22
+ raise logger.value_mapper_exception_while_serializing_abstract_value(type.name) if type&.abstract?
23
+
24
+ matched_serializers = []
25
+ @serializer_list.each do |serializer|
26
+ next unless serializer.can_handle_typed_value(typed_value)
27
+
28
+ matched_serializers << serializer
29
+ break if serializer.type.primitive_value_type?
30
+ end
31
+
32
+ case matched_serializers.size
33
+ when 1
34
+ matched_serializers.first
35
+ when 0
36
+ raise logger.value_mapper_exception_due_to_serializer_not_found_for_typed_value(typed_value)
37
+ else
38
+ # ambiguous match, use default serializer
39
+ matched_serializers.find { |s| s.serialization_dataformat == @default_serialization_format } ||
40
+ matched_serializers.first
41
+ end
42
+ end
43
+
44
+ def find_mapper_for_typed_value_field(typed_value_field)
45
+ matched_serializer = @serializer_list.find { |s| s.can_handle_typed_value_field(typed_value_field) }
46
+
47
+ if matched_serializer.nil?
48
+ raise logger.value_mapper_exception_due_to_serializer_not_found_for_typed_value_field(
49
+ typed_value_field.value
50
+ )
51
+ end
52
+
53
+ matched_serializer
54
+ end
55
+
56
+ def add_mapper(serializer)
57
+ @serializer_list << serializer
58
+ self
59
+ end
60
+
61
+ protected
62
+
63
+ def logger
64
+ Client::Impl::ExternalTaskClientLogger.client_logger
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../../../../spi/data_format"
5
+ require_relative "../../../../exceptions"
6
+
7
+ module Operaton
8
+ module Bpm
9
+ module Client
10
+ module Variable
11
+ module Impl
12
+ module Format
13
+ module Json
14
+ # Stands in for org.operaton.bpm.client.variable.impl.format.json
15
+ # .JacksonJsonDataFormat, using Ruby's JSON library.
16
+ class JsonDataFormat
17
+ include Spi::DataFormat
18
+
19
+ NAME = "application/json"
20
+
21
+ def name
22
+ NAME
23
+ end
24
+
25
+ def can_map(value)
26
+ json_native?(value) || value.respond_to?(:as_json) || value.respond_to?(:to_h)
27
+ end
28
+
29
+ def write_value(value)
30
+ JSON.generate(jsonify(value))
31
+ end
32
+
33
+ # type_identifier is the stored objectTypeName; JSON deserialization
34
+ # in Ruby is generic (hashes/arrays), so it is not used for
35
+ # reconstruction the way Jackson uses Java type names.
36
+ def read_value(value, _type_identifier = nil)
37
+ JSON.parse(value)
38
+ rescue JSON::ParserError => e
39
+ raise DataFormatException.new("Unable to parse JSON value", e)
40
+ end
41
+
42
+ def canonical_type_name(value)
43
+ value.class.name
44
+ end
45
+
46
+ protected
47
+
48
+ def jsonify(value)
49
+ case value
50
+ when Hash
51
+ value.transform_values { |v| jsonify(v) }
52
+ when Array
53
+ value.map { |v| jsonify(v) }
54
+ when String, Numeric, TrueClass, FalseClass, NilClass
55
+ value
56
+ else
57
+ if value.respond_to?(:as_json)
58
+ value.as_json
59
+ elsif value.respond_to?(:to_h)
60
+ jsonify(value.to_h)
61
+ else
62
+ value
63
+ end
64
+ end
65
+ end
66
+
67
+ def json_native?(value)
68
+ case value
69
+ when Hash, Array, String, Numeric, TrueClass, FalseClass, NilClass
70
+ true
71
+ else
72
+ false
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../spi/data_format_provider"
4
+ require_relative "json_data_format"
5
+
6
+ module Operaton
7
+ module Bpm
8
+ module Client
9
+ module Variable
10
+ module Impl
11
+ module Format
12
+ module Json
13
+ # Stands in for JacksonJsonDataFormatProvider; registered with the
14
+ # provider registry when the gem is loaded.
15
+ class JsonDataFormatProvider
16
+ include Spi::DataFormatProvider
17
+
18
+ def data_format_name
19
+ JsonDataFormat::NAME
20
+ end
21
+
22
+ def create_instance
23
+ JsonDataFormat.new
24
+ end
25
+ end
26
+
27
+ Spi::DataFormatProvider.register(JsonDataFormatProvider.new)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end