mirror of
https://github.com/elastic/logstash.git
synced 2025-06-28 09:46:03 -04:00
[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.
This commit is contained in:
parent
8368c00367
commit
61de60fe26
12 changed files with 797 additions and 76 deletions
|
@ -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
|
||||
|
|
|
@ -86,7 +86,10 @@ module LogStash
|
|||
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}")
|
||||
|
@ -244,54 +247,73 @@ module LogStash
|
|||
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
|
||||
|
@ -301,7 +323,7 @@ module LogStash
|
|||
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)
|
||||
|
@ -323,58 +345,65 @@ module LogStash
|
|||
end
|
||||
|
||||
def format(output)
|
||||
effective_value = self.value
|
||||
default_value = self.default
|
||||
setting_name = self.name
|
||||
|
||||
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})"
|
||||
@wrapped_setting.format(output)
|
||||
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)
|
||||
|
@ -383,22 +412,7 @@ module LogStash
|
|||
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)
|
||||
|
@ -733,15 +747,15 @@ module LogStash
|
|||
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
|
||||
|
@ -782,7 +796,7 @@ module LogStash
|
|||
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
|
||||
|
||||
|
@ -792,9 +806,9 @@ module LogStash
|
|||
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)
|
||||
|
@ -839,8 +853,7 @@ module LogStash
|
|||
@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
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.logstash.settings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Root class for all setting definitions.
|
||||
* */
|
||||
public class BaseSetting<T> implements Setting<T> {
|
||||
|
||||
private String name; // not final because can be updated by deprecate
|
||||
T defaultValue;
|
||||
private T value = null;
|
||||
private final boolean strict;
|
||||
private final Predicate<T> validator;
|
||||
private boolean valueIsSet = false;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public BaseSetting<T> clone() {
|
||||
try {
|
||||
BaseSetting<T> clone = (BaseSetting<T>) super.clone();
|
||||
// copy mutable state here, so the clone can't change the internals of the original
|
||||
clone.value = value;
|
||||
clone.valueIsSet = valueIsSet;
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder<T> {
|
||||
private final String name;
|
||||
private boolean strict = true;
|
||||
private T defaultValue = null;
|
||||
private Predicate<T> validator = noValidator();
|
||||
|
||||
public Builder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Builder<T> defaultValue(T defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> strict(boolean strict) {
|
||||
this.strict = strict;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> validator(Predicate<T> validator) {
|
||||
this.validator = validator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BaseSetting<T> build() {
|
||||
return new BaseSetting<>(name, defaultValue, strict, validator);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Builder<T> create(String name) {
|
||||
return new Builder<>(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifically used by Coercible subclass to initialize doing validation in a second phase.
|
||||
* */
|
||||
protected BaseSetting(String name, boolean strict, Predicate<T> validator) {
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(validator);
|
||||
this.name = name;
|
||||
this.strict = strict;
|
||||
this.validator = validator;
|
||||
}
|
||||
protected BaseSetting(String name, T defaultValue, boolean strict, Predicate<T> validator) {
|
||||
Objects.requireNonNull(name);
|
||||
Objects.requireNonNull(validator);
|
||||
this.name = name;
|
||||
this.defaultValue = defaultValue;
|
||||
this.strict = strict;
|
||||
this.validator = validator;
|
||||
if (strict) {
|
||||
validate(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the setting with the original name to deprecate
|
||||
* */
|
||||
protected BaseSetting<T> deprecate(String deprecatedName) {
|
||||
// this force to get a copy of the original Setting, in case of a BooleanSetting it retains also all of its
|
||||
// coercing mechanisms
|
||||
BaseSetting<T> clone = this.clone();
|
||||
clone.updateName(deprecatedName);
|
||||
return clone;
|
||||
}
|
||||
|
||||
private void updateName(String deprecatedName) {
|
||||
this.name = deprecatedName;
|
||||
}
|
||||
|
||||
protected static <T> Predicate<T> noValidator() {
|
||||
return t -> true;
|
||||
}
|
||||
|
||||
public void validate(T input) throws IllegalArgumentException {
|
||||
if (!validator.test(input)) {
|
||||
throw new IllegalArgumentException("Failed to validate setting " + this.name + " with value: " + input);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public T value() {
|
||||
if (valueIsSet) {
|
||||
return value;
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return this.valueIsSet;
|
||||
}
|
||||
|
||||
public boolean isStrict() {
|
||||
return strict;
|
||||
}
|
||||
|
||||
public void setSafely(T newValue) {
|
||||
if (strict) {
|
||||
validate(newValue);
|
||||
}
|
||||
this.value = newValue;
|
||||
this.valueIsSet = true;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.value = null;
|
||||
this.valueIsSet = false;
|
||||
}
|
||||
|
||||
public void validateValue() {
|
||||
validate(this.value);
|
||||
}
|
||||
|
||||
public T getDefault() {
|
||||
return this.defaultValue;
|
||||
}
|
||||
|
||||
public void format(List<String> output) {
|
||||
T effectiveValue = this.value;
|
||||
String settingName = this.name;
|
||||
|
||||
if (effectiveValue != null && effectiveValue.equals(defaultValue)) {
|
||||
// print setting and its default value
|
||||
output.add(String.format("%s: %s", settingName, effectiveValue));
|
||||
} else if (defaultValue == null) {
|
||||
// print setting and warn it has been set
|
||||
output.add(String.format("*%s: %s", settingName, effectiveValue));
|
||||
} else if (effectiveValue == null) {
|
||||
// default setting not set by user
|
||||
output.add(String.format("%s: %s", settingName, defaultValue));
|
||||
} else {
|
||||
// print setting, warn it has been set, and show default value
|
||||
output.add(String.format("*%s: %s (default: %s)", settingName, effectiveValue, defaultValue));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Setting<T>> withDeprecatedAlias(String deprecatedAlias) {
|
||||
return SettingWithDeprecatedAlias.wrap(this, deprecatedAlias);
|
||||
}
|
||||
|
||||
public Setting<T> nullable() {
|
||||
return new NullableSetting<>(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.logstash.settings;
|
||||
|
||||
public class Boolean extends Coercible<java.lang.Boolean> {
|
||||
|
||||
public Boolean(String name, boolean defaultValue) {
|
||||
super(name, defaultValue, true, noValidator());
|
||||
}
|
||||
|
||||
public Boolean(String name, boolean defaultValue, boolean strict) {
|
||||
super(name, defaultValue, strict, noValidator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.lang.Boolean coerce(Object obj) {
|
||||
if (obj instanceof String) {
|
||||
switch((String) obj) {
|
||||
case "true": return true;
|
||||
case "false": return false;
|
||||
default: throw new IllegalArgumentException(coercionFailureMessage(obj));
|
||||
}
|
||||
}
|
||||
if (obj instanceof java.lang.Boolean) {
|
||||
return (java.lang.Boolean) obj;
|
||||
}
|
||||
throw new IllegalArgumentException(coercionFailureMessage(obj));
|
||||
}
|
||||
|
||||
private String coercionFailureMessage(Object obj) {
|
||||
return String.format("Cannot coerce `%s` to boolean (%s)", obj, getName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.logstash.settings;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public abstract class Coercible<T> extends BaseSetting<T> {
|
||||
public Coercible(String name, T defaultValue, boolean strict, Predicate<T> validator) {
|
||||
super(name, strict, validator);
|
||||
|
||||
if (strict) {
|
||||
T coercedDefault = coerce(defaultValue);
|
||||
validate(coercedDefault);
|
||||
this.defaultValue = coercedDefault;
|
||||
} else {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Object value) {
|
||||
T coercedValue = coerce(value);
|
||||
validate(coercedValue);
|
||||
super.setSafely(coercedValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSafely(T value) {
|
||||
this.set(value);
|
||||
}
|
||||
|
||||
public abstract T coerce(Object obj);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.logstash.settings;
|
||||
|
||||
import co.elastic.logstash.api.DeprecationLogger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.logstash.log.DefaultDeprecationLogger;
|
||||
|
||||
/**
|
||||
* A <code>DeprecatedAlias</code> provides a deprecated alias for a setting, and is meant
|
||||
* to be used exclusively through @see org.logstash.settings.SettingWithDeprecatedAlias#wrap()
|
||||
* */
|
||||
public final class DeprecatedAlias<T> extends SettingDelegator<T> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final DeprecationLogger DEPRECATION_LOGGER = new DefaultDeprecationLogger(LOGGER);
|
||||
|
||||
private SettingWithDeprecatedAlias<T> canonicalProxy;
|
||||
|
||||
DeprecatedAlias(SettingWithDeprecatedAlias<T> canonicalProxy, String aliasName) {
|
||||
super(canonicalProxy.getCanonicalSetting().deprecate(aliasName));
|
||||
this.canonicalProxy = canonicalProxy;
|
||||
}
|
||||
|
||||
// Because loggers are configure after the Settings declaration, this method is intended for lazy-logging
|
||||
// check https://github.com/elastic/logstash/pull/16339
|
||||
public void observePostProcess() {
|
||||
if (isSet()) {
|
||||
DEPRECATION_LOGGER.deprecated("The setting `{}` is a deprecated alias for `{}` and will be removed in a " +
|
||||
"future release of Logstash. Please use `{}` instead", getName(), canonicalProxy.getName(), canonicalProxy.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T value() {
|
||||
LOGGER.warn("The value of setting `{}` has been queried by its deprecated alias `{}`. " +
|
||||
"Code should be updated to query `{}` instead", canonicalProxy.getName(), getName(), canonicalProxy.getName());
|
||||
return super.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateValue() {
|
||||
// bypass deprecation warning
|
||||
if (isSet()) {
|
||||
getDelegate().validateValue();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.logstash.settings;
|
||||
|
||||
public class NullableSetting<T> extends SettingDelegator<T> {
|
||||
|
||||
NullableSetting(BaseSetting<T> delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(T input) throws IllegalArgumentException {
|
||||
if (input == null) {
|
||||
return;
|
||||
}
|
||||
getDelegate().validate(input);
|
||||
}
|
||||
|
||||
// prevent delegate from intercepting
|
||||
@Override
|
||||
public void validateValue() {
|
||||
validate(value());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.logstash.settings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Setting<T> extends Cloneable {
|
||||
|
||||
String getName();
|
||||
|
||||
T value();
|
||||
|
||||
boolean isSet();
|
||||
|
||||
boolean isStrict();
|
||||
|
||||
void setSafely(T newValue);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default void set(Object newValue) {
|
||||
//this could throw a class cast error
|
||||
setSafely((T) newValue);
|
||||
}
|
||||
|
||||
void reset();
|
||||
|
||||
void validateValue();
|
||||
|
||||
void validate(T input);
|
||||
|
||||
T getDefault();
|
||||
|
||||
void format(List<String> output);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.logstash.settings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
abstract class SettingDelegator<T> implements Setting<T> {
|
||||
private BaseSetting<T> delegate;
|
||||
|
||||
/**
|
||||
* Use this constructor to wrap another setting.
|
||||
* */
|
||||
SettingDelegator(BaseSetting<T> delegate) {
|
||||
Objects.requireNonNull(delegate);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
BaseSetting<T> getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T value() {
|
||||
return delegate.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSet() {
|
||||
return delegate.isSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStrict() {
|
||||
return delegate.isStrict();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSafely(T newValue) {
|
||||
delegate.setSafely(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
delegate.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateValue() {
|
||||
delegate.validateValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDefault() {
|
||||
return delegate.getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void format(List<String> output) {
|
||||
delegate.format(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(T input) {
|
||||
delegate.validate(input);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.logstash.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* A <code>SettingWithDeprecatedAlias</code> wraps any <code>Setting</code> to provide a deprecated
|
||||
* alias, and hooks @see org.logstash.settings.Setting#validate_value() to ensure that a deprecation
|
||||
* warning is fired when the setting is provided by its deprecated alias,
|
||||
* or to produce an error when both the canonical name and deprecated
|
||||
* alias are used together.
|
||||
* */
|
||||
// This class is public else the getDeprecatedAlias method can't be seen from setting_with_deprecated_alias_spec.rb
|
||||
public class SettingWithDeprecatedAlias<T> extends SettingDelegator<T> {
|
||||
|
||||
/**
|
||||
* Wraps the provided setting, returning a pair of connected settings
|
||||
* including the canonical setting and a deprecated alias.
|
||||
* @param canonicalSetting the setting to wrap
|
||||
* @param deprecatedAliasName the name for the deprecated alias
|
||||
*
|
||||
* @return List of [SettingWithDeprecatedAlias, DeprecatedAlias]
|
||||
* */
|
||||
static <T> List<Setting<T>> wrap(BaseSetting<T> canonicalSetting, String deprecatedAliasName) {
|
||||
final SettingWithDeprecatedAlias<T> settingProxy = new SettingWithDeprecatedAlias<>(canonicalSetting, deprecatedAliasName);
|
||||
return Arrays.asList(settingProxy, settingProxy.deprecatedAlias);
|
||||
}
|
||||
|
||||
private DeprecatedAlias<T> deprecatedAlias;
|
||||
|
||||
protected SettingWithDeprecatedAlias(BaseSetting<T> canonicalSetting, String deprecatedAliasName) {
|
||||
super(canonicalSetting);
|
||||
|
||||
this.deprecatedAlias = new DeprecatedAlias<T>(this, deprecatedAliasName);
|
||||
}
|
||||
|
||||
BaseSetting<T> getCanonicalSetting() {
|
||||
return getDelegate();
|
||||
}
|
||||
|
||||
public DeprecatedAlias<T> getDeprecatedAlias() {
|
||||
return deprecatedAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSafely(T value) {
|
||||
getCanonicalSetting().setSafely(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T value() {
|
||||
if (getCanonicalSetting().isSet()) {
|
||||
return super.value();
|
||||
}
|
||||
// bypass warning by querying the wrapped setting's value
|
||||
if (deprecatedAlias.isSet()) {
|
||||
return deprecatedAlias.getDelegate().value();
|
||||
}
|
||||
return getDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSet() {
|
||||
return getCanonicalSetting().isSet() || deprecatedAlias.isSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void format(List<String> output) {
|
||||
if (!(deprecatedAlias.isSet() && !getCanonicalSetting().isSet())) {
|
||||
super.format(output);
|
||||
return;
|
||||
}
|
||||
output.add(String.format("*%s: %s (via deprecated `%s`; default: %s)",
|
||||
getName(), value(), deprecatedAlias.getName(), getDefault()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateValue() {
|
||||
if (deprecatedAlias.isSet() && getCanonicalSetting().isSet()) {
|
||||
throw new IllegalStateException(String.format("Both `%s` and its deprecated alias `%s` have been set.\n" +
|
||||
"Please only set `%s`", getCanonicalSetting().getName(), deprecatedAlias.getName(), getCanonicalSetting().getName()));
|
||||
}
|
||||
super.validateValue();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.logstash.settings;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class BooleanTest {
|
||||
|
||||
|
||||
private Boolean sut;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
sut = new Boolean("api.enabled", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLiteralBooleanStringValueWhenCoercedToBooleanValueThenIsValidBooleanSetting() {
|
||||
sut.set("false");
|
||||
|
||||
Assert.assertFalse(sut.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenBooleanInstanceWhenCoercedThenReturnValidBooleanSetting() {
|
||||
sut.set(java.lang.Boolean.FALSE);
|
||||
|
||||
Assert.assertFalse(sut.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenInvalidStringLiteralForBooleanValueWhenCoercedThenThrowsAnError() {
|
||||
IllegalArgumentException exception = Assert.assertThrows(IllegalArgumentException.class, () -> sut.set("bananas"));
|
||||
assertThat(exception.getMessage(), equalTo("Cannot coerce `bananas` to boolean (api.enabled)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenInvalidTypeInstanceForBooleanValueWhenCoercedThenThrowsAnError() {
|
||||
IllegalArgumentException exception = Assert.assertThrows(IllegalArgumentException.class, () -> sut.set(1));
|
||||
assertThat(exception.getMessage(), equalTo("Cannot coerce `1` to boolean (api.enabled)"));
|
||||
}
|
||||
|
||||
}
|
|
@ -389,14 +389,14 @@ describe "Test Monitoring API" do
|
|||
end
|
||||
|
||||
#default
|
||||
logging_get_assert logstash_service, "INFO", "TRACE",
|
||||
logging_get_assert logstash_service, ["WARN", "INFO"], "TRACE",
|
||||
skip: 'logstash.licensechecker.licensereader' #custom (ERROR) level to start with
|
||||
|
||||
#root logger - does not apply to logger.slowlog
|
||||
logging_put_assert logstash_service.monitoring_api.logging_put({"logger." => "WARN"})
|
||||
logging_get_assert logstash_service, "WARN", "TRACE"
|
||||
logging_put_assert logstash_service.monitoring_api.logging_put({"logger." => "INFO"})
|
||||
logging_get_assert logstash_service, "INFO", "TRACE"
|
||||
logging_get_assert logstash_service, ["WARN", "INFO"], "TRACE"
|
||||
|
||||
#package logger
|
||||
logging_put_assert logstash_service.monitoring_api.logging_put({"logger.logstash.agent" => "DEBUG"})
|
||||
|
@ -422,7 +422,7 @@ describe "Test Monitoring API" do
|
|||
|
||||
# all log levels should be reset to original values
|
||||
logging_put_assert logstash_service.monitoring_api.logging_reset
|
||||
logging_get_assert logstash_service, "INFO", "TRACE"
|
||||
logging_get_assert logstash_service, ["WARN", "INFO"], "TRACE"
|
||||
end
|
||||
|
||||
|
||||
|
@ -433,7 +433,15 @@ describe "Test Monitoring API" do
|
|||
result["loggers"].each do |k, v|
|
||||
next if !k.empty? && k.eql?(skip)
|
||||
if k.start_with? "logstash", "org.logstash" #logstash is the ruby namespace, and org.logstash for java
|
||||
if logstash_level.is_a?(Array)
|
||||
if logstash_level.size == 1
|
||||
expect(v).to eq(logstash_level[0]), "logstash logger '#{k}' has logging level: #{v} expected: #{logstash_level[0]}"
|
||||
else
|
||||
expect(logstash_level).to include(v), "logstash logger '#{k}' has logging level: #{v} expected to be one of: #{logstash_level}"
|
||||
end
|
||||
else
|
||||
expect(v).to eq(logstash_level), "logstash logger '#{k}' has logging level: #{v} expected: #{logstash_level}"
|
||||
end
|
||||
elsif k.start_with? "slowlog"
|
||||
expect(v).to eq(slowlog_level), "slowlog logger '#{k}' has logging level: #{v} expected: #{slowlog_level}"
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue