blob: cb86000fc9cfc084d323d87af1c8e9e239152ce6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.ignite.internal.configuration;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import org.apache.ignite.configuration.ConfigurationListenOnlyException;
import org.apache.ignite.configuration.ConfigurationProperty;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.notifications.ConfigurationListener;
import org.apache.ignite.internal.configuration.direct.DirectPropertyProxy;
import org.apache.ignite.internal.configuration.direct.KeyPathNode;
import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.NamedListNode;
import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
import org.apache.ignite.internal.configuration.util.KeyNotFoundException;
import org.jetbrains.annotations.Nullable;
/**
* Super class for dynamic configuration tree nodes. Has all common data and value retrieving algorithm in it.
*/
public abstract class ConfigurationNode<VIEWT> implements ConfigurationProperty<VIEWT> {
/** Listeners of property update. */
private final ConfigurationListenerHolder<ConfigurationListener<VIEWT>> updateListeners = new ConfigurationListenerHolder<>();
/** Full path to the current node. */
protected final List<String> keys;
/** Name of the current node. Same as last element of {@link #keys}. */
protected final String key;
/** Root key instance for the current trees root. */
protected final RootKey<?, ?> rootKey;
/** Configuration changer instance to get latest value of the root. */
protected final DynamicConfigurationChanger changer;
/** Only adding listeners mode, without the ability to get or update the property value. */
protected final boolean listenOnly;
/**
* Cached value of current trees root. Useful to determine whether you have the latest configuration value or not.
*/
private volatile TraversableTreeNode cachedRootNode;
/** Cached configuration value. Immutable. */
private VIEWT val;
/**
* Validity flag. Configuration is declared invalid if it's a part of named list configuration and corresponding entry is already
* removed.
*/
private boolean invalid;
/**
* Constructor.
*
* @param keys Full path to the current node.
* @param key Name of the current node. Same as last element of {@link #keys}.
* @param rootKey Root key.
* @param changer Configuration changer.
* @param listenOnly Only adding listeners mode, without the ability to get or update the property value.
*/
protected ConfigurationNode(
List<String> keys,
String key,
RootKey<?, ?> rootKey,
DynamicConfigurationChanger changer,
boolean listenOnly
) {
this.keys = keys;
this.key = key;
this.rootKey = rootKey;
this.changer = changer;
this.listenOnly = listenOnly;
assert Objects.equals(rootKey.key(), keys.get(0));
}
/** {@inheritDoc} */
@Override
public void listen(ConfigurationListener<VIEWT> listener) {
updateListeners.addListener(listener, changer.notificationCount());
}
/** {@inheritDoc} */
@Override
public void stopListen(ConfigurationListener<VIEWT> listener) {
updateListeners.removeListener(listener);
}
/**
* Returns an iterator of the listeners for the {@code notificationNumber} (were added for and before it).
*
* <p>NOTE: {@link Iterator#remove} - not supported.
*
* @param notificationNumber Configuration notification listener number.
*/
public Iterator<ConfigurationListener<VIEWT>> listeners(long notificationNumber) {
return updateListeners.listeners(notificationNumber);
}
/**
* Returns latest value of the configuration or throws exception.
*
* @return Latest configuration value.
* @throws NoSuchElementException If configuration is a part of already deleted named list configuration entry.
* @throws ConfigurationListenOnlyException If there was an attempt to get or update a property value in {@link #listenOnly listen-only}
* mode.
*/
protected final VIEWT refreshValue() throws NoSuchElementException {
TraversableTreeNode newRootNode = changer.getRootNode(rootKey);
TraversableTreeNode oldRootNode = cachedRootNode;
// 'invalid' and 'val' visibility is guaranteed by the 'cachedRootNode' volatile read
if (invalid) {
throw noSuchElementException();
} else if (listenOnly) {
throw listenOnlyException();
}
if (oldRootNode == newRootNode) {
return val;
}
try {
VIEWT newVal = ConfigurationUtil.<VIEWT>find(keys.subList(1, keys.size()), newRootNode, true).value();
synchronized (this) {
if (cachedRootNode == oldRootNode) {
beforeRefreshValue(newVal, val);
val = newVal;
cachedRootNode = newRootNode;
return newVal;
} else {
if (invalid) {
throw noSuchElementException();
}
return val;
}
}
} catch (KeyNotFoundException e) {
synchronized (this) {
invalid = true;
cachedRootNode = newRootNode;
}
throw noSuchElementException();
}
}
/**
* Returns exception instance with a proper error message.
*
* @return Exception instance with a proper error message.
*/
private NoSuchElementException noSuchElementException() {
return new NoSuchElementException(ConfigurationUtil.join(keys));
}
/**
* Callback from {@link #refreshValue()} that's called right before the update. Synchronized.
*
* @param newValue New configuration value.
* @param oldValue Old configuration value.
*/
protected void beforeRefreshValue(VIEWT newValue, @Nullable VIEWT oldValue) {
// No-op.
}
/**
* Returns Exception if there was an attempt to get or update a property value in {@link #listenOnly listen-only} mode.
*
* @return Exception if there was an attempt to get or update a property value in {@link #listenOnly listen-only} mode.
*/
protected final ConfigurationListenOnlyException listenOnlyException() {
return new ConfigurationListenOnlyException("`any` configuration node can only be used for listeners [keys=" + keys + ']');
}
/**
* Converts {@link #keys} into a list of {@link KeyPathNode}. Result is used in implementations of {@link DirectPropertyProxy}.
*/
protected final List<KeyPathNode> keyPath() {
if (listenOnly) {
throw listenOnlyException();
}
ConfigurationVisitor<List<KeyPathNode>> visitor = new ConfigurationVisitor<>() {
/** List with the result. */
private List<KeyPathNode> res = new ArrayList<>(keys.size());
/** Current index. */
private int idx = 1;
/** {@inheritDoc} */
@Nullable
@Override
public List<KeyPathNode> visitLeafNode(Field field, String key, Serializable val) {
res.add(new KeyPathNode(key));
return res;
}
/** {@inheritDoc} */
@Nullable
@Override
public List<KeyPathNode> visitInnerNode(Field field, String key, InnerNode node) {
res.add(new KeyPathNode(key));
if (keys.size() == idx) {
return res;
}
node.traverseChild(keys.get(idx++), this, true);
return res;
}
/** {@inheritDoc} */
@Nullable
@Override
public List<KeyPathNode> visitNamedListNode(Field field, String key, NamedListNode node) {
res.add(new KeyPathNode(key));
if (keys.size() == idx) {
return res;
}
InnerNode innerNode = node.getInnerNode(keys.get(idx++));
if (innerNode == null) {
throw noSuchElementException();
}
// This is important, node is added as a resolved named list entry here.
res.add(new KeyPathNode(innerNode.internalId().toString(), false));
if (keys.size() == idx) {
return res;
}
innerNode.traverseChild(keys.get(idx++), this, true);
return res;
}
};
return changer.getRootNode(rootKey).accept(null, keys.get(0), visitor);
}
/**
* Returns current value of the configuration.
*/
@Nullable
protected final VIEWT currentValue() {
return val;
}
}