Skip to content

Commit

Permalink
Backport PR #15679 to 8.x: [Spacetime] Reimplement config Setting cla…
Browse files Browse the repository at this point in the history
…sse in java (#16490)

* [Spacetime] Reimplement config Setting classe in java (#15679)

Reimplement the root Ruby Setting class in Java and use it from the Ruby one moving the original Ruby class to a shell wrapping the Java instance.
In particular create a new symmetric hierarchy (at the time just for `Setting`, `Coercible` and `Boolean` classes) to the Ruby one, moving also the feature for setting deprecation. In this way the new `org.logstash.settings.Boolean` is syntactically and semantically equivalent to the old Ruby Boolean class, which replaces.

(cherry picked from commit 61de60f)

* Adds supress warnings related to this-escape for Java Settings classes

---------

Co-authored-by: Andrea Selva <[email protected]>
  • Loading branch information
github-actions[bot] and andsel authored Oct 11, 2024
1 parent 26c2f61 commit 0594c88
Show file tree
Hide file tree
Showing 12 changed files with 802 additions and 76 deletions.
2 changes: 1 addition & 1 deletion config/log4j2.properties
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ appender.deprecation_rolling.policies.size.size = 100MB
appender.deprecation_rolling.strategy.type = DefaultRolloverStrategy
appender.deprecation_rolling.strategy.max = 30

logger.deprecation.name = org.logstash.deprecation, deprecation
logger.deprecation.name = org.logstash.deprecation
logger.deprecation.level = WARN
logger.deprecation.appenderRef.deprecation_rolling.ref = deprecation_plain_rolling
logger.deprecation.additivity = false
Expand Down
155 changes: 84 additions & 71 deletions logstash-core/lib/logstash/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ def initialize
end

def register(setting)
return setting.map { |s| register(s) } if setting.kind_of?(Array)
# Method #with_deprecated_alias returns collection containing couple of other settings.
# In case the method is implemented in Ruby returns an Array while for the Java implementation
# return a List, so the following type checking before going deep by one layer.
return setting.map { |s| register(s) } if setting.kind_of?(Array) || setting.kind_of?(java.util.List)

if @settings.key?(setting.name)
raise ArgumentError.new("Setting \"#{setting.name}\" has already been registered as #{setting.inspect}")
Expand Down Expand Up @@ -244,54 +247,73 @@ def flatten_hash(h, f = "", g = {})
class Setting
include LogStash::Settings::LOGGABLE_PROXY

attr_reader :name, :default
attr_reader :wrapped_setting

def initialize(name, klass, default = nil, strict = true, &validator_proc)
@name = name
unless klass.is_a?(Class)
raise ArgumentError.new("Setting \"#{@name}\" must be initialized with a class (received #{klass})")
raise ArgumentError.new("Setting \"#{name}\" must be initialized with a class (received #{klass})")
end
setting_builder = Java::org.logstash.settings.BaseSetting.create(name)
.defaultValue(default)
.strict(strict)
if validator_proc
setting_builder = setting_builder.validator(validator_proc)
end

@wrapped_setting = setting_builder.build()

@klass = klass
@validator_proc = validator_proc
@value = nil
@value_is_set = false
@strict = strict

validate(default) if @strict
@default = default
validate(default) if strict?
end

def default
@wrapped_setting.default
end

def name
@wrapped_setting.name
end

def initialize_copy(original)
@wrapped_setting = original.wrapped_setting.clone
end

# To be used only internally
def update_wrapper(wrapped_setting)
@wrapped_setting = wrapped_setting
end

def value
@value_is_set ? @value : default
@wrapped_setting.value()
end

def set?
@value_is_set
@wrapped_setting.set?
end

def strict?
@strict
@wrapped_setting.strict?
end

def set(value)
validate(value) if @strict
@value = value
@value_is_set = true
@value
validate(value) if strict?
@wrapped_setting.set(value)
@wrapped_setting.value
end

def reset
@value = nil
@value_is_set = false
@wrapped_setting.reset
end

def to_hash
{
"name" => @name,
"name" => @wrapped_setting.name,
"klass" => @klass,
"value" => @value,
"value_is_set" => @value_is_set,
"default" => @default,
"value" => @wrapped_setting.value,
"value_is_set" => @wrapped_setting.set?,
"default" => @wrapped_setting.default,
# Proc#== will only return true if it's the same obj
# so no there's no point in comparing it
# also thereś no use case atm to return the proc
Expand All @@ -301,7 +323,7 @@ def to_hash
end

def inspect
"<#{self.class.name}(#{name}): #{value.inspect}" + (@value_is_set ? '' : ' (DEFAULT)') + ">"
"<#{self.class.name}(#{name}): #{value.inspect}" + (@wrapped_setting.set? ? '' : ' (DEFAULT)') + ">"
end

def ==(other)
Expand All @@ -323,58 +345,65 @@ def nullable
end

def format(output)
effective_value = self.value
default_value = self.default
setting_name = self.name
@wrapped_setting.format(output)
end

if default_value == value # print setting and its default value
output << "#{setting_name}: #{effective_value.inspect}" unless effective_value.nil?
elsif default_value.nil? # print setting and warn it has been set
output << "*#{setting_name}: #{effective_value.inspect}"
elsif effective_value.nil? # default setting not set by user
output << "#{setting_name}: #{default_value.inspect}"
else # print setting, warn it has been set, and show default value
output << "*#{setting_name}: #{effective_value.inspect} (default: #{default_value.inspect})"
end
def clone(*args)
copy = self.dup
copy.update_wrapper(@wrapped_setting.clone())
copy
end

protected
def validate(input)
if !input.is_a?(@klass)
raise ArgumentError.new("Setting \"#{@name}\" must be a #{@klass}. Received: #{input} (#{input.class})")
raise ArgumentError.new("Setting \"#{@wrapped_setting.name}\" must be a #{@klass}. Received: #{input} (#{input.class})")
end

if @validator_proc && !@validator_proc.call(input)
raise ArgumentError.new("Failed to validate setting \"#{@name}\" with value: #{input}")
raise ArgumentError.new("Failed to validate setting \"#{@wrapped_setting.name}\" with value: #{input}")
end
end

class Coercible < Setting
def initialize(name, klass, default = nil, strict = true, &validator_proc)
@name = name
unless klass.is_a?(Class)
raise ArgumentError.new("Setting \"#{@name}\" must be initialized with a class (received #{klass})")
raise ArgumentError.new("Setting \"#{name}\" must be initialized with a class (received #{klass})")
end

@klass = klass
@validator_proc = validator_proc
@value = nil
@value_is_set = false

# needed to have the name method accessible when invoking validate
@wrapped_setting = Java::org.logstash.settings.BaseSetting.create(name)
.defaultValue(default)
.strict(strict)
.build()

if strict
coerced_default = coerce(default)
validate(coerced_default)
@default = coerced_default
updated_default = coerced_default
else
@default = default
updated_default = default
end

# default value must be coerced to the right type before being set
setting_builder = Java::org.logstash.settings.BaseSetting.create(name)
.defaultValue(updated_default)
.strict(strict)
if validator_proc
setting_builder = setting_builder.validator(validator_proc)
end

@wrapped_setting = setting_builder.build()
end

def set(value)
coerced_value = coerce(value)
validate(coerced_value)
@value = coerce(coerced_value)
@value_is_set = true
@value
@wrapped_setting.set(coerced_value)
coerced_value
end

def coerce(value)
Expand All @@ -383,22 +412,7 @@ def coerce(value)
end
### Specific settings #####

class Boolean < Coercible
def initialize(name, default, strict = true, &validator_proc)
super(name, Object, default, strict, &validator_proc)
end

def coerce(value)
case value
when TrueClass, "true"
true
when FalseClass, "false"
false
else
raise ArgumentError.new("could not coerce #{value} into a boolean")
end
end
end
java_import org.logstash.settings.Boolean

class Numeric < Coercible
def initialize(name, default = nil, strict = true)
Expand Down Expand Up @@ -733,15 +747,15 @@ def coerce(value)
protected
def validate(input)
if !input.is_a?(@klass)
raise ArgumentError.new("Setting \"#{@name}\" must be a #{@klass}. Received: #{input} (#{input.class})")
raise ArgumentError.new("Setting \"#{@wrapped_setting.name}\" must be a #{@klass}. Received: #{input} (#{input.class})")
end

unless input.all? {|el| el.kind_of?(@element_class) }
raise ArgumentError.new("Values of setting \"#{@name}\" must be #{@element_class}. Received: #{input.map(&:class)}")
raise ArgumentError.new("Values of setting \"#{@wrapped_setting.name}\" must be #{@element_class}. Received: #{input.map(&:class)}")
end

if @validator_proc && !@validator_proc.call(input)
raise ArgumentError.new("Failed to validate setting \"#{@name}\" with value: #{input}")
raise ArgumentError.new("Failed to validate setting \"#{@wrapped_setting.name}\" with value: #{input}")
end
end
end
Expand Down Expand Up @@ -782,7 +796,7 @@ def validate(value)
return unless invalid_value.any?

raise ArgumentError,
"Failed to validate the setting \"#{@name}\" value(s): #{invalid_value.inspect}. Valid options are: #{@possible_strings.inspect}"
"Failed to validate the setting \"#{@wrapped_setting.name}\" value(s): #{invalid_value.inspect}. Valid options are: #{@possible_strings.inspect}"
end
end

Expand All @@ -792,9 +806,9 @@ def initialize(name, klass, default = nil)
end

def set(value)
@value = coerce(value)
@value_is_set = true
@value
coerced_value = coerce(value)
@wrapped_setting.set(coerced_value)
coerced_value
end

def coerce(value)
Expand Down Expand Up @@ -839,8 +853,7 @@ def initialize(canonical_proxy, alias_name)
@canonical_proxy = canonical_proxy

clone = @canonical_proxy.canonical_setting.clone
clone.instance_variable_set(:@name, alias_name)
clone.instance_variable_set(:@default, nil)
clone.update_wrapper(clone.wrapped_setting.deprecate(alias_name))

super(clone)
end
Expand Down
Loading

0 comments on commit 0594c88

Please sign in to comment.