/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.metadata.settings; import com.google.common.annotations.VisibleForTesting; import io.crate.analyze.expressions.*; import io.crate.data.Row; import io.crate.sql.tree.Expression; import io.crate.sql.tree.ObjectLiteral; import io.crate.types.BooleanType; import io.crate.types.IntegerType; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.TimeValue; import java.util.Locale; import java.util.concurrent.TimeUnit; public class SettingsAppliers { public static abstract class AbstractSettingsApplier implements SettingsApplier { protected final String name; final Settings defaultSettings; public AbstractSettingsApplier(String name, Settings defaultSettings) { this.name = name; this.defaultSettings = defaultSettings; } @Override public Settings getDefault() { return defaultSettings; } public void applyValue(Settings.Builder settingsBuilder, Object value) { try { settingsBuilder.put(name, validate(value)); } catch (InvalidSettingValueContentException e) { throw new IllegalArgumentException(e.getMessage()); } catch (IllegalArgumentException e) { throw invalidException(e); } } @VisibleForTesting public Object validate(Object value) { return value; } protected IllegalArgumentException invalidException(Exception cause) { return new IllegalArgumentException( String.format(Locale.ENGLISH, "Invalid value for argument '%s'", name), cause); } protected IllegalArgumentException invalidException() { return new IllegalArgumentException( String.format(Locale.ENGLISH, "Invalid value for argument '%s'", name)); } @Override public String toString() { return "SettingsApplier{" + "name='" + name + '\'' + '}'; } } public static abstract class NumberSettingsApplier extends AbstractSettingsApplier { protected final Setting setting; NumberSettingsApplier(Setting setting) { super(setting.name(), setting.defaultValue() == null ? Settings.EMPTY : Settings.builder().put(setting.name(), setting.defaultValue()).build()); this.setting = setting; } @Override public void apply(Settings.Builder settingsBuilder, Row parameters, Expression expression) { try { applyValue(settingsBuilder, ExpressionToNumberVisitor.convert(expression, parameters)); } catch (IllegalArgumentException e) { throw invalidException(e); } } } public static class BooleanSettingsApplier extends AbstractSettingsApplier { public BooleanSettingsApplier(BoolSetting setting) { super(setting.name(), Settings.builder().put(setting.name(), setting.defaultValue()).build()); } @Override public void apply(Settings.Builder settingsBuilder, Row parameters, Expression expression) { try { applyValue(settingsBuilder, ExpressionToObjectVisitor.convert(expression, parameters)); } catch (IllegalArgumentException e) { throw invalidException(e); } } @Override public Object validate(Object value) { return BooleanType.INSTANCE.value(value); } } public static class IntSettingsApplier extends NumberSettingsApplier { public IntSettingsApplier(IntSetting setting) { super(setting); } @Override @VisibleForTesting public Object validate(Object value) { Integer convertedValue = IntegerType.INSTANCE.value(value); IntSetting setting = (IntSetting) this.setting; if (convertedValue < setting.minValue() || convertedValue > setting.maxValue()) { throw invalidException(); } return convertedValue; } } public static class ByteSizeSettingsApplier extends AbstractSettingsApplier { private final ByteSizeSetting setting; public ByteSizeSettingsApplier(ByteSizeSetting setting) { super(setting.name(), setting.defaultValue() == null ? Settings.EMPTY : Settings.builder().put(setting.name(), setting.defaultValue()).build()); this.setting = setting; } private ByteSizeValue validate(ByteSizeValue num) { if (num.getBytes() < setting.minValue() || num.getBytes() > setting.maxValue()) { throw invalidException(); } return num; } @Override public void apply(Settings.Builder settingsBuilder, Row parameters, Expression expression) { ByteSizeValue byteSizeValue; try { byteSizeValue = ExpressionToByteSizeValueVisitor.convert(expression, parameters); } catch (IllegalArgumentException e) { throw invalidException(e); } if (byteSizeValue == null) { throw new IllegalArgumentException(String.format(Locale.ENGLISH, "'%s' does not support null values", name)); } applyValue(settingsBuilder, byteSizeValue); } void applyValue(Settings.Builder settingsBuilder, ByteSizeValue value) { value = validate(value); settingsBuilder.put(name, value.getBytes(), ByteSizeUnit.BYTES); } @Override public void applyValue(Settings.Builder settingsBuilder, Object value) { ByteSizeValue byteSizeValue; if (value instanceof Number) { byteSizeValue = new ByteSizeValue(((Number) value).longValue()); } else if (value instanceof String) { try { byteSizeValue = ByteSizeValue.parseBytesSizeValue((String) value, ExpressionToByteSizeValueVisitor.DEFAULT_VALUE.toString()); } catch (ElasticsearchParseException e) { throw invalidException(e); } } else { throw invalidException(); } applyValue(settingsBuilder, byteSizeValue); } } public static class StringSettingsApplier extends AbstractSettingsApplier { private final StringSetting setting; public StringSettingsApplier(StringSetting setting) { super(setting.name(), setting.defaultValue() == null ? Settings.EMPTY : Settings.builder().put(setting.name(), setting.defaultValue()).build()); this.setting = setting; } @Override @VisibleForTesting public Object validate(Object value) { String validation = this.setting.validate((String) value); if (validation != null) { throw new InvalidSettingValueContentException(validation); } return value; } @Override public void apply(Settings.Builder settingsBuilder, Row parameters, Expression expression) { if (expression instanceof ObjectLiteral) { throw new IllegalArgumentException( String.format(Locale.ENGLISH, "Object values are not allowed at '%s'", name)); } applyValue(settingsBuilder, ExpressionToStringVisitor.convert(expression, parameters)); } } public static class TimeSettingsApplier extends SettingsAppliers.AbstractSettingsApplier { private final TimeSetting setting; public TimeSettingsApplier(TimeSetting setting) { super(setting.name(), Settings.builder().put(setting.name(), setting.defaultValue()).build()); this.setting = setting; } private TimeValue validate(TimeValue value) { if (value.getMillis() < setting.minValue().getMillis() || value.getMillis() > setting.maxValue().getMillis()) { throw invalidException(); } return value; } @Override public void apply(Settings.Builder settingsBuilder, Row parameters, Expression expression) { TimeValue time; try { time = ExpressionToTimeValueVisitor.convert(expression, parameters, name); } catch (IllegalArgumentException e) { throw invalidException(e); } applyValue(settingsBuilder, time.millis()); } void applyValue(Settings.Builder settingsBuilder, TimeValue value) { value = validate(value); settingsBuilder.put(name, value.getMillis(), TimeUnit.MILLISECONDS); } @Override public void applyValue(Settings.Builder settingsBuilder, Object value) { TimeValue timeValue; if (value instanceof String) { try { timeValue = TimeValue.parseTimeValue((String) value, ExpressionToTimeValueVisitor.DEFAULT_VALUE, name); } catch (ElasticsearchParseException e) { throw invalidException(e); } } else if (value instanceof Number) { timeValue = new TimeValue(((Number) value).longValue()); } else { throw invalidException(); } applyValue(settingsBuilder, timeValue); } } static class InvalidSettingValueContentException extends IllegalArgumentException { InvalidSettingValueContentException(String message) { super(message); } } }