A faithful Ruby recreation of the Operaton
Java external task client (org.operaton.bpm.client). The class structure,
method contract, defaults, validation rules, REST payloads, and error handling
mirror the Java implementation one-to-one; names are translated from Java
camelCase to Ruby snake_case.
org.operaton.bpm.client -> Operaton::Bpm::Client
org.operaton.bpm.client.impl -> Operaton::Bpm::Client::Impl
org.operaton.bpm.client.task -> Operaton::Bpm::Client::Task
org.operaton.bpm.client.topic -> Operaton::Bpm::Client::Topic
org.operaton.bpm.client.backoff -> Operaton::Bpm::Client::Backoff
org.operaton.bpm.client.interceptor -> Operaton::Bpm::Client::Interceptor
org.operaton.bpm.client.variable -> Operaton::Bpm::Client::Variable
org.operaton.bpm.engine.variable -> Operaton::Bpm::Engine::Variable (shim)
Exception classes from org.operaton.bpm.client.exception live directly under
Operaton::Bpm::Client (BadRequestException, NotFoundException,
EngineException, ConnectionLostException, UnknownHttpErrorException,
ValueMapperException, ...).
require "operaton-bpm-client"
client = Operaton::Bpm::Client::ExternalTaskClient.create
.base_url("http://localhost:8080/engine-rest")
.worker_id("my-worker") # optional; defaults to hostname + UUID
.max_tasks(10) # default 10
.lock_duration(20_000) # default 20s (milliseconds)
.async_response_timeout(30_000) # optional long polling
.add_interceptor(
Operaton::Bpm::Client::Interceptor::Auth::BasicAuthProvider.new("demo", "demo")
)
.build # starts fetching immediately
subscription = client.subscribe("invoice-topic")
.lock_duration(10_000)
.process_definition_key("invoice")
.handler do |task, service|
amount = task.variable("amount")
if amount.nil?
service.handle_bpmn_error(task, "MISSING_AMOUNT")
elsif amount.negative?
service.handle_failure(task, "negative amount", nil, (task.retries || 3) - 1, 5_000)
else
service.complete(task, { "approved" => true })
end
end
.open
sleep
# later:
subscription.close
client.stopLike the Java client, create returns a fluent ExternalTaskClientBuilder,
build validates the configuration and (unless disable_auto_fetching was
called) starts the TopicSubscriptionManager polling thread, and subscribe
returns a fluent TopicSubscriptionBuilder whose open activates the
subscription. Handlers may be blocks, lambdas, or objects responding to
execute(task, service).
The engine's typed value API is recreated under Operaton::Bpm::Engine::Variable
and extended by ClientValues:
Variables = Operaton::Bpm::Engine::Variable::Variables
ClientValues = Operaton::Bpm::Client::Variable::ClientValues
service.complete(task, {
"count" => 3, # -> Integer
"big" => 2**40, # -> Long
"rate" => 0.19, # -> Double
"ok" => true, # -> Boolean
"when" => Time.now, # -> Date
"payload" => { "items" => [1, 2, 3] }, # -> Object (application/json)
"explicit" => Variables.long_value(5), # force Long
"raw" => Variables.byte_array_value("\x00\x01"),# -> Bytes (base64)
"doc" => ClientValues.json_value('{"a":1}'), # -> Json
"xml" => ClientValues.xml_value("<a/>"), # -> Xml
})
task.variable("payload") # deserialized value
task.variable_typed("payload") # ObjectValue (lazily deserialized)
task.all_variables # Hash of plain values
task.all_variables_typed # VariableMap of typed valuesFile variables are received as deferred values whose content is fetched from
the engine on first access, mirroring DeferredFileValue.
Necessary adaptations to the Ruby ecosystem; everything else follows the Java source:
- HTTP:
Net::HTTPreplaces Apache HttpClient.customize_http_clientaccepts a block invoked with theNet::HTTPinstance of each request. - JSON: Ruby's
jsonreplaces Jackson. Object values serialize any JSON-mappable Ruby value;objectTypeNamerecords the Ruby class name. - Date format: configured as a Ruby
strftimepattern; the default"%Y-%m-%dT%H:%M:%S.%L%z"matches the Java default"yyyy-MM-dd'T'HH:mm:ss.SSSZ". - SPI:
java.util.ServiceLoaderis replaced by explicit registries (Spi::DataFormatProvider.register,Spi::DataFormatConfigurator.register). The JSON data format is registered by default; the Java-serialization and DOM/XML object data formats have no Ruby equivalent (string-typedxmlvalues are fully supported). - Overloads: Java method overloads collapse into single Ruby methods that
accept either an
ExternalTaskor an id, with optional trailing arguments. useCreateTime(true): the Java implementation appends the same ordering property twice (producing a duplicated sorting entry in the request); the Ruby port configures it once.
bundle install
bundle exec rspecThe spec suite covers builder validation, variable serialization round-trips, every REST endpoint payload, HTTP error translation, interceptors, backoff strategies, and the polling/dispatch loop (84 examples).