org.elasticsearch.cluster.metadata.IndexNameExpressionResolver Maven / Gradle / Ivy
Show all versions of elasticsearch Show documentation
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.cluster.metadata;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexAbstraction.Type;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.time.DateFormatter;
import org.elasticsearch.common.time.DateMathParser;
import org.elasticsearch.common.time.DateUtils;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.indices.IndexClosedException;
import org.elasticsearch.indices.InvalidIndexNameException;
import org.elasticsearch.indices.SystemIndices;
import org.elasticsearch.indices.SystemIndices.SystemIndexAccessLevel;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.function.LongSupplier;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class IndexNameExpressionResolver {
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(IndexNameExpressionResolver.class);
public static final String EXCLUDED_DATA_STREAMS_KEY = "es.excluded_ds";
public static final Version SYSTEM_INDEX_ENFORCEMENT_VERSION = Version.V_8_0_0;
private final ThreadContext threadContext;
private final SystemIndices systemIndices;
public IndexNameExpressionResolver(ThreadContext threadContext, SystemIndices systemIndices) {
this.threadContext = Objects.requireNonNull(threadContext, "Thread Context must not be null");
this.systemIndices = Objects.requireNonNull(systemIndices, "System Indices must not be null");
}
/**
* Same as {@link #concreteIndexNames(ClusterState, IndicesOptions, String...)}, but the index expressions and options
* are encapsulated in the specified request.
*/
public String[] concreteIndexNames(ClusterState state, IndicesRequest request) {
Context context = new Context(
state,
request.indicesOptions(),
false,
false,
request.includeDataStreams(),
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return concreteIndexNames(context, request.indices());
}
/**
* Same as {@link #concreteIndexNames(ClusterState, IndicesRequest)}, but access to system indices is always allowed.
*/
public String[] concreteIndexNamesWithSystemIndexAccess(ClusterState state, IndicesRequest request) {
Context context = new Context(
state,
request.indicesOptions(),
false,
false,
request.includeDataStreams(),
SystemIndexAccessLevel.BACKWARDS_COMPATIBLE_ONLY,
name -> true,
this.getNetNewSystemIndexPredicate()
);
return concreteIndexNames(context, request.indices());
}
/**
* Same as {@link #concreteIndices(ClusterState, IndicesOptions, String...)}, but the index expressions and options
* are encapsulated in the specified request and resolves data streams.
*/
public Index[] concreteIndices(ClusterState state, IndicesRequest request) {
Context context = new Context(
state,
request.indicesOptions(),
false,
false,
request.includeDataStreams(),
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return concreteIndices(context, request.indices());
}
/**
* Translates the provided index expression into actual concrete indices, properly deduplicated.
*
* @param state the cluster state containing all the data to resolve to expressions to concrete indices
* @param options defines how the aliases or indices need to be resolved to concrete indices
* @param indexExpressions expressions that can be resolved to alias or index names.
* @return the resolved concrete indices based on the cluster state, indices options and index expressions
* @throws IndexNotFoundException if one of the index expressions is pointing to a missing index or alias and the
* provided indices options in the context don't allow such a case, or if the final result of the indices resolution
* contains no indices and the indices options in the context don't allow such a case.
* @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided
* indices options in the context don't allow such a case; if a remote index is requested.
*/
public String[] concreteIndexNames(ClusterState state, IndicesOptions options, String... indexExpressions) {
Context context = new Context(
state,
options,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return concreteIndexNames(context, indexExpressions);
}
public String[] concreteIndexNames(ClusterState state, IndicesOptions options, boolean includeDataStreams, String... indexExpressions) {
Context context = new Context(
state,
options,
false,
false,
includeDataStreams,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return concreteIndexNames(context, indexExpressions);
}
public String[] concreteIndexNames(ClusterState state, IndicesOptions options, IndicesRequest request) {
Context context = new Context(
state,
options,
false,
false,
request.includeDataStreams(),
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return concreteIndexNames(context, request.indices());
}
public List dataStreamNames(ClusterState state, IndicesOptions options, String... indexExpressions) {
Context context = new Context(
state,
options,
false,
false,
true,
true,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
if (indexExpressions == null || indexExpressions.length == 0) {
indexExpressions = new String[] { "*" };
}
final List expressions = resolveExpressions(Arrays.asList(indexExpressions), context);
return ((expressions == null) ? List.of() : expressions).stream()
.map(x -> state.metadata().getIndicesLookup().get(x))
.filter(Objects::nonNull)
.filter(ia -> ia.getType() == IndexAbstraction.Type.DATA_STREAM)
.map(IndexAbstraction::getName)
.toList();
}
/**
* Returns {@link IndexAbstraction} instance for the provided write request. This instance isn't fully resolved,
* meaning that {@link IndexAbstraction#getWriteIndex()} should be invoked in order to get concrete write index.
*
* @param state The cluster state
* @param request The provided write request
* @return {@link IndexAbstraction} instance for the provided write request
*/
public IndexAbstraction resolveWriteIndexAbstraction(ClusterState state, DocWriteRequest request) {
boolean includeDataStreams = request.opType() == DocWriteRequest.OpType.CREATE && request.includeDataStreams();
Context context = new Context(
state,
request.indicesOptions(),
false,
false,
includeDataStreams,
true,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
final List expressions = resolveExpressions(List.of(request.index()), context);
if (expressions.size() == 1) {
IndexAbstraction ia = state.metadata().getIndicesLookup().get(expressions.get(0));
if (ia == null) {
throw new IndexNotFoundException(expressions.get(0));
}
if (ia.getType() == IndexAbstraction.Type.ALIAS) {
Index writeIndex = ia.getWriteIndex();
if (writeIndex == null) {
throw new IllegalArgumentException(
"no write index is defined for alias ["
+ ia.getName()
+ "]."
+ " The write index may be explicitly disabled using is_write_index=false or the alias points to multiple"
+ " indices without one being designated as a write index"
);
}
}
checkSystemIndexAccess(context, Set.of(ia.getWriteIndex()));
return ia;
} else {
throw new IllegalArgumentException(
"unable to return a single target as the provided expression and options got resolved to multiple targets"
);
}
}
private static List resolveExpressions(List expressions, Context context) {
return WildcardExpressionResolver.resolve(context, DateMathExpressionResolver.resolve(context, expressions));
}
/**
* Translates the provided index expression into actual concrete indices, properly deduplicated.
*
* @param state the cluster state containing all the data to resolve to expressions to concrete indices
* @param options defines how the aliases or indices need to be resolved to concrete indices
* @param indexExpressions expressions that can be resolved to alias or index names.
* @return the resolved concrete indices based on the cluster state, indices options and index expressions
* @throws IndexNotFoundException if one of the index expressions is pointing to a missing index or alias and the
* provided indices options in the context don't allow such a case, or if the final result of the indices resolution
* contains no indices and the indices options in the context don't allow such a case.
* @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided
* indices options in the context don't allow such a case; if a remote index is requested.
*/
public Index[] concreteIndices(ClusterState state, IndicesOptions options, String... indexExpressions) {
return concreteIndices(state, options, false, indexExpressions);
}
public Index[] concreteIndices(ClusterState state, IndicesOptions options, boolean includeDataStreams, String... indexExpressions) {
Context context = new Context(
state,
options,
false,
false,
includeDataStreams,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return concreteIndices(context, indexExpressions);
}
/**
* Translates the provided index expression into actual concrete indices, properly deduplicated.
*
* @param state the cluster state containing all the data to resolve to expressions to concrete indices
* @param startTime The start of the request where concrete indices is being invoked for
* @param request request containing expressions that can be resolved to alias, index, or data stream names.
* @return the resolved concrete indices based on the cluster state, indices options and index expressions
* provided indices options in the context don't allow such a case, or if the final result of the indices resolution
* contains no indices and the indices options in the context don't allow such a case.
* @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided
* indices options in the context don't allow such a case; if a remote index is requested.
*/
public Index[] concreteIndices(ClusterState state, IndicesRequest request, long startTime) {
Context context = new Context(
state,
request.indicesOptions(),
startTime,
false,
false,
request.includeDataStreams(),
false,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return concreteIndices(context, request.indices());
}
String[] concreteIndexNames(Context context, String... indexExpressions) {
Index[] indexes = concreteIndices(context, indexExpressions);
String[] names = new String[indexes.length];
for (int i = 0; i < indexes.length; i++) {
names[i] = indexes[i].getName();
}
return names;
}
Index[] concreteIndices(Context context, String... indexExpressions) {
IndicesOptions options = context.getOptions();
if (indexExpressions == null || indexExpressions.length == 0) {
indexExpressions = new String[] { Metadata.ALL };
} else {
if (options.ignoreUnavailable() == false) {
List crossClusterIndices = Arrays.stream(indexExpressions).filter(index -> index.contains(":")).toList();
if (crossClusterIndices.size() > 0) {
throw new IllegalArgumentException(
"Cross-cluster calls are not supported in this context but remote indices "
+ "were requested: "
+ crossClusterIndices
);
}
}
}
// If only one index is specified then whether we fail a request if an index is missing depends on the allow_no_indices
// option. At some point we should change this, because there shouldn't be a reason why whether a single index
// or multiple indices are specified yield different behaviour.
final boolean failNoIndices = indexExpressions.length == 1
? options.allowNoIndices() == false
: options.ignoreUnavailable() == false;
final List expressions = resolveExpressions(Arrays.asList(indexExpressions), context);
if (expressions.isEmpty()) {
if (options.allowNoIndices() == false) {
IndexNotFoundException infe;
if (indexExpressions.length == 1) {
if (indexExpressions[0].equals(Metadata.ALL)) {
infe = new IndexNotFoundException("no indices exist", (String) null);
} else {
infe = new IndexNotFoundException((String) null);
}
} else {
infe = new IndexNotFoundException((String) null);
}
infe.setResources("index_expression", indexExpressions);
throw infe;
} else {
return Index.EMPTY_ARRAY;
}
}
boolean excludedDataStreams = false;
final Set concreteIndices = new LinkedHashSet<>(expressions.size());
final SortedMap indicesLookup = context.state.metadata().getIndicesLookup();
for (String expression : expressions) {
IndexAbstraction indexAbstraction = indicesLookup.get(expression);
if (indexAbstraction == null) {
if (failNoIndices) {
IndexNotFoundException infe;
if (expression.equals(Metadata.ALL)) {
infe = new IndexNotFoundException("no indices exist", expression);
} else {
infe = new IndexNotFoundException(expression);
}
infe.setResources("index_expression", expression);
throw infe;
} else {
continue;
}
} else if (indexAbstraction.getType() == IndexAbstraction.Type.ALIAS && context.getOptions().ignoreAliases()) {
if (failNoIndices) {
throw aliasesNotSupportedException(expression);
} else {
continue;
}
} else if (indexAbstraction.isDataStreamRelated() && context.includeDataStreams() == false) {
excludedDataStreams = true;
continue;
}
if (indexAbstraction.getType() == IndexAbstraction.Type.ALIAS && context.isResolveToWriteIndex()) {
Index writeIndex = indexAbstraction.getWriteIndex();
if (writeIndex == null) {
throw new IllegalArgumentException(
"no write index is defined for alias ["
+ indexAbstraction.getName()
+ "]."
+ " The write index may be explicitly disabled using is_write_index=false or the alias points to multiple"
+ " indices without one being designated as a write index"
);
}
if (addIndex(writeIndex, null, context)) {
concreteIndices.add(writeIndex);
}
} else if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM && context.isResolveToWriteIndex()) {
Index writeIndex = indexAbstraction.getWriteIndex();
if (addIndex(writeIndex, null, context)) {
concreteIndices.add(writeIndex);
}
} else {
if (indexAbstraction.getIndices().size() > 1 && options.allowAliasesToMultipleIndices() == false) {
String[] indexNames = new String[indexAbstraction.getIndices().size()];
int i = 0;
for (Index indexName : indexAbstraction.getIndices()) {
indexNames[i++] = indexName.getName();
}
throw new IllegalArgumentException(
indexAbstraction.getType().getDisplayName()
+ " ["
+ expression
+ "] has more than one index associated with it "
+ Arrays.toString(indexNames)
+ ", can't execute a single index op"
);
}
for (Index index : indexAbstraction.getIndices()) {
if (shouldTrackConcreteIndex(context, options, index)) {
concreteIndices.add(index);
}
}
}
}
if (options.allowNoIndices() == false && concreteIndices.isEmpty()) {
IndexNotFoundException infe = new IndexNotFoundException((String) null);
infe.setResources("index_expression", indexExpressions);
if (excludedDataStreams) {
// Allows callers to handle IndexNotFoundException differently based on whether data streams were excluded.
infe.addMetadata(EXCLUDED_DATA_STREAMS_KEY, "true");
}
throw infe;
}
checkSystemIndexAccess(context, concreteIndices);
return concreteIndices.toArray(Index.EMPTY_ARRAY);
}
private void checkSystemIndexAccess(Context context, Set concreteIndices) {
final Metadata metadata = context.getState().metadata();
final Predicate systemIndexAccessPredicate = context.getSystemIndexAccessPredicate().negate();
final List systemIndicesThatShouldNotBeAccessed = concreteIndices.stream()
.map(metadata::index)
.filter(IndexMetadata::isSystem)
.filter(idxMetadata -> systemIndexAccessPredicate.test(idxMetadata.getIndex().getName()))
.toList();
if (systemIndicesThatShouldNotBeAccessed.isEmpty()) {
return;
}
final List resolvedSystemIndices = new ArrayList<>();
final List resolvedNetNewSystemIndices = new ArrayList<>();
final Set resolvedSystemDataStreams = new HashSet<>();
final SortedMap indicesLookup = metadata.getIndicesLookup();
for (IndexMetadata idxMetadata : systemIndicesThatShouldNotBeAccessed) {
IndexAbstraction abstraction = indicesLookup.get(idxMetadata.getIndex().getName());
if (abstraction.getParentDataStream() != null) {
resolvedSystemDataStreams.add(abstraction.getParentDataStream().getName());
} else if (systemIndices.isNetNewSystemIndex(idxMetadata.getIndex().getName())) {
resolvedNetNewSystemIndices.add(idxMetadata.getIndex().getName());
} else {
resolvedSystemIndices.add(idxMetadata.getIndex().getName());
}
}
if (resolvedSystemIndices.isEmpty() == false) {
Collections.sort(resolvedSystemIndices);
deprecationLogger.warn(
DeprecationCategory.API,
"open_system_index_access",
"this request accesses system indices: {}, but in a future major version, direct access to system "
+ "indices will be prevented by default",
resolvedSystemIndices
);
}
if (resolvedSystemDataStreams.isEmpty() == false) {
throw SystemIndices.dataStreamAccessException(threadContext, resolvedSystemDataStreams);
}
if (resolvedNetNewSystemIndices.isEmpty() == false) {
throw SystemIndices.netNewSystemIndexAccessException(threadContext, resolvedNetNewSystemIndices);
}
}
private static boolean shouldTrackConcreteIndex(Context context, IndicesOptions options, Index index) {
if (context.systemIndexAccessLevel == SystemIndexAccessLevel.BACKWARDS_COMPATIBLE_ONLY
&& context.netNewSystemIndexPredicate.test(index.getName())) {
// Exclude this one as it's a net-new system index, and we explicitly don't want those.
return false;
}
final IndexMetadata imd = context.state.metadata().index(index);
if (imd.getState() == IndexMetadata.State.CLOSE) {
if (options.forbidClosedIndices() && options.ignoreUnavailable() == false) {
throw new IndexClosedException(index);
} else {
return options.forbidClosedIndices() == false && addIndex(index, imd, context);
}
} else if (imd.getState() == IndexMetadata.State.OPEN) {
return addIndex(index, imd, context);
} else {
throw new IllegalStateException("index state [" + index + "] not supported");
}
}
private static boolean addIndex(Index index, IndexMetadata imd, Context context) {
// This used to check the `index.search.throttled` setting, but we eventually decided that it was
// trappy to hide throttled indices by default. In order to avoid breaking backward compatibility,
// we changed it to look at the `index.frozen` setting instead, since frozen indices were the only
// type of index to use the `search_throttled` threadpool at that time.
// NOTE: We can't reference the Setting object, which is only defined and registered in x-pack.
if (context.options.ignoreThrottled()) {
imd = imd != null ? imd : context.state.metadata().index(index);
return imd.getSettings().getAsBoolean("index.frozen", false) == false;
} else {
return true;
}
}
private static IllegalArgumentException aliasesNotSupportedException(String expression) {
return new IllegalArgumentException(
"The provided expression [" + expression + "] matches an " + "alias, specify the corresponding concrete indices instead."
);
}
/**
* Utility method that allows to resolve an index expression to its corresponding single concrete index.
* Callers should make sure they provide proper {@link org.elasticsearch.action.support.IndicesOptions}
* that require a single index as a result. The indices resolution must in fact return a single index when
* using this method, an {@link IllegalArgumentException} gets thrown otherwise.
*
* @param state the cluster state containing all the data to resolve to expression to a concrete index
* @param request The request that defines how the an alias or an index need to be resolved to a concrete index
* and the expression that can be resolved to an alias or an index name.
* @throws IllegalArgumentException if the index resolution returns more than one index; if a remote index is requested.
* @return the concrete index obtained as a result of the index resolution
*/
public Index concreteSingleIndex(ClusterState state, IndicesRequest request) {
String indexExpression = CollectionUtils.isEmpty(request.indices()) ? null : request.indices()[0];
Index[] indices = concreteIndices(state, request.indicesOptions(), indexExpression);
if (indices.length != 1) {
throw new IllegalArgumentException(
"unable to return a single index as the index and options" + " provided got resolved to multiple indices"
);
}
return indices[0];
}
/**
* Utility method that allows to resolve an index expression to its corresponding single write index.
*
* @param state the cluster state containing all the data to resolve to expression to a concrete index
* @param request The request that defines how the an alias or an index need to be resolved to a concrete index
* and the expression that can be resolved to an alias or an index name.
* @throws IllegalArgumentException if the index resolution does not lead to an index, or leads to more than one index
* @return the write index obtained as a result of the index resolution
*/
public Index concreteWriteIndex(ClusterState state, IndicesRequest request) {
if (request.indices() == null || (request.indices() != null && request.indices().length != 1)) {
throw new IllegalArgumentException("indices request must specify a single index expression");
}
return concreteWriteIndex(state, request.indicesOptions(), request.indices()[0], false, request.includeDataStreams());
}
/**
* Utility method that allows to resolve an index expression to its corresponding single write index.
*
* @param state the cluster state containing all the data to resolve to expression to a concrete index
* @param options defines how the aliases or indices need to be resolved to concrete indices
* @param index index that can be resolved to alias or index name.
* @param allowNoIndices whether to allow resolve to no index
* @param includeDataStreams Whether data streams should be included in the evaluation.
* @throws IllegalArgumentException if the index resolution does not lead to an index, or leads to more than one index, as well as
* if a remote index is requested.
* @return the write index obtained as a result of the index resolution or null if no index
*/
public Index concreteWriteIndex(
ClusterState state,
IndicesOptions options,
String index,
boolean allowNoIndices,
boolean includeDataStreams
) {
IndicesOptions combinedOptions = IndicesOptions.fromOptions(
options.ignoreUnavailable(),
allowNoIndices,
options.expandWildcardsOpen(),
options.expandWildcardsClosed(),
options.expandWildcardsHidden(),
options.allowAliasesToMultipleIndices(),
options.forbidClosedIndices(),
options.ignoreAliases(),
options.ignoreThrottled()
);
Context context = new Context(
state,
combinedOptions,
false,
true,
includeDataStreams,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
Index[] indices = concreteIndices(context, index);
if (allowNoIndices && indices.length == 0) {
return null;
}
if (indices.length != 1) {
throw new IllegalArgumentException(
"The index expression [" + index + "] and options provided did not point to a single write-index"
);
}
return indices[0];
}
/**
* @return whether the specified index, data stream or alias exists.
* If the data stream, index or alias contains date math then that is resolved too.
*/
public boolean hasIndexAbstraction(String indexAbstraction, ClusterState state) {
String resolvedAliasOrIndex = DateMathExpressionResolver.resolveExpression(indexAbstraction);
return state.metadata().hasIndexAbstraction(resolvedAliasOrIndex);
}
/**
* @return If the specified string is data math expression then this method returns the resolved expression.
*/
public static String resolveDateMathExpression(String dateExpression) {
return DateMathExpressionResolver.resolveExpression(dateExpression);
}
/**
* @param time instant to consider when parsing the expression
* @return If the specified string is data math expression then this method returns the resolved expression.
*/
public static String resolveDateMathExpression(String dateExpression, long time) {
return DateMathExpressionResolver.resolveExpression(dateExpression, () -> time);
}
/**
* Resolve an array of expressions to the set of indices and aliases that these expressions match.
*/
public Set resolveExpressions(ClusterState state, String... expressions) {
Context context = new Context(
state,
IndicesOptions.lenientExpandOpen(),
true,
false,
true,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
return Set.copyOf(resolveExpressions(Arrays.asList(expressions), context));
}
/**
* Iterates through the list of indices and selects the effective list of filtering aliases for the
* given index.
* Only aliases with filters are returned. If the indices list contains a non-filtering reference to
* the index itself - null is returned. Returns {@code null} if no filtering is required.
* NOTE: The provided expressions must have been resolved already via {@link #resolveExpressions}.
*/
public String[] filteringAliases(ClusterState state, String index, Set resolvedExpressions) {
return indexAliases(state, index, AliasMetadata::filteringRequired, DataStreamAlias::filteringRequired, false, resolvedExpressions);
}
/**
* Whether to generate the candidate set from index aliases, or from the set of resolved expressions.
* @param indexAliasesSize the number of aliases of the index
* @param resolvedExpressionsSize the number of resolved expressions
*/
// pkg-private for testing
boolean iterateIndexAliases(int indexAliasesSize, int resolvedExpressionsSize) {
return indexAliasesSize <= resolvedExpressionsSize;
}
/**
* Iterates through the list of indices and selects the effective list of required aliases for the given index.
* Only aliases where the given predicate tests successfully are returned. If the indices list contains a non-required reference to
* the index itself - null is returned. Returns {@code null} if no filtering is required.
*
NOTE: the provided expressions must have been resolved already via {@link #resolveExpressions}.
*/
public String[] indexAliases(
ClusterState state,
String index,
Predicate requiredAlias,
Predicate requiredDataStreamAlias,
boolean skipIdentity,
Set resolvedExpressions
) {
if (isAllIndices(resolvedExpressions)) {
return null;
}
final IndexMetadata indexMetadata = state.metadata().getIndices().get(index);
if (indexMetadata == null) {
// Shouldn't happen
throw new IndexNotFoundException(index);
}
if (skipIdentity == false && resolvedExpressions.contains(index)) {
return null;
}
IndexAbstraction ia = state.metadata().getIndicesLookup().get(index);
if (ia.getParentDataStream() != null) {
DataStream dataStream = ia.getParentDataStream().getDataStream();
Map dataStreamAliases = state.metadata().dataStreamAliases();
Stream stream;
if (iterateIndexAliases(dataStreamAliases.size(), resolvedExpressions.size())) {
stream = dataStreamAliases.values()
.stream()
.filter(dataStreamAlias -> resolvedExpressions.contains(dataStreamAlias.getName()));
} else {
stream = resolvedExpressions.stream().map(dataStreamAliases::get).filter(Objects::nonNull);
}
return stream.filter(dataStreamAlias -> dataStreamAlias.getDataStreams().contains(dataStream.getName()))
.filter(requiredDataStreamAlias)
.map(DataStreamAlias::getName)
.toArray(String[]::new);
} else {
final Map indexAliases = indexMetadata.getAliases();
final AliasMetadata[] aliasCandidates;
if (iterateIndexAliases(indexAliases.size(), resolvedExpressions.size())) {
// faster to iterate indexAliases
aliasCandidates = indexAliases.values()
.stream()
.filter(aliasMetadata -> resolvedExpressions.contains(aliasMetadata.alias()))
.toArray(AliasMetadata[]::new);
} else {
// faster to iterate resolvedExpressions
aliasCandidates = resolvedExpressions.stream()
.map(indexAliases::get)
.filter(Objects::nonNull)
.toArray(AliasMetadata[]::new);
}
List aliases = null;
for (AliasMetadata aliasMetadata : aliasCandidates) {
if (requiredAlias.test(aliasMetadata)) {
// If required - add it to the list of aliases
if (aliases == null) {
aliases = new ArrayList<>();
}
aliases.add(aliasMetadata.alias());
} else {
// If not, we have a non required alias for this index - no further checking needed
return null;
}
}
if (aliases == null) {
return null;
}
return aliases.toArray(new String[aliases.size()]);
}
}
/**
* Resolves the search routing if in the expression aliases are used. If expressions point to concrete indices
* or aliases with no routing defined the specified routing is used.
*
* @return routing values grouped by concrete index
*/
public Map> resolveSearchRouting(ClusterState state, @Nullable String routing, String... expressions) {
Context context = new Context(
state,
IndicesOptions.lenientExpandOpen(),
false,
false,
true,
getSystemIndexAccessLevel(),
getSystemIndexAccessPredicate(),
getNetNewSystemIndexPredicate()
);
final List resolvedExpressions = resolveExpressions(
expressions != null ? Arrays.asList(expressions) : Collections.emptyList(),
context
);
// TODO: it appears that this can never be true?
if (isAllIndices(resolvedExpressions)) {
return resolveSearchRoutingAllIndices(state.metadata(), routing);
}
Map> routings = null;
Set paramRouting = null;
// List of indices that don't require any routing
Set norouting = new HashSet<>();
if (routing != null) {
paramRouting = Sets.newHashSet(Strings.splitStringByCommaToArray(routing));
}
for (String expression : resolvedExpressions) {
IndexAbstraction indexAbstraction = state.metadata().getIndicesLookup().get(expression);
if (indexAbstraction != null && indexAbstraction.getType() == IndexAbstraction.Type.ALIAS) {
for (Index index : indexAbstraction.getIndices()) {
String concreteIndex = index.getName();
if (norouting.contains(concreteIndex) == false) {
AliasMetadata aliasMetadata = state.metadata().index(concreteIndex).getAliases().get(indexAbstraction.getName());
if (aliasMetadata != null && aliasMetadata.searchRoutingValues().isEmpty() == false) {
// Routing alias
if (routings == null) {
routings = new HashMap<>();
}
Set r = routings.computeIfAbsent(concreteIndex, k -> new HashSet<>());
r.addAll(aliasMetadata.searchRoutingValues());
if (paramRouting != null) {
r.retainAll(paramRouting);
}
if (r.isEmpty()) {
routings.remove(concreteIndex);
}
} else {
// Non-routing alias
if (norouting.contains(concreteIndex) == false) {
norouting.add(concreteIndex);
if (paramRouting != null) {
Set r = new HashSet<>(paramRouting);
if (routings == null) {
routings = new HashMap<>();
}
routings.put(concreteIndex, r);
} else {
if (routings != null) {
routings.remove(concreteIndex);
}
}
}
}
}
}
} else if (indexAbstraction != null && indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM) {
IndexAbstraction.DataStream dataStream = (IndexAbstraction.DataStream) indexAbstraction;
if (dataStream.getDataStream().isAllowCustomRouting() == false) {
continue;
}
if (dataStream.getIndices() != null) {
for (Index index : dataStream.getIndices()) {
String concreteIndex = index.getName();
if (norouting.contains(concreteIndex) == false) {
norouting.add(concreteIndex);
if (paramRouting != null) {
Set r = new HashSet<>(paramRouting);
if (routings == null) {
routings = new HashMap<>();
}
routings.put(concreteIndex, r);
} else {
if (routings != null) {
routings.remove(concreteIndex);
}
}
}
}
}
} else {
// Index
if (norouting.contains(expression) == false) {
norouting.add(expression);
if (paramRouting != null) {
Set r = new HashSet<>(paramRouting);
if (routings == null) {
routings = new HashMap<>();
}
routings.put(expression, r);
} else {
if (routings != null) {
routings.remove(expression);
}
}
}
}
}
if (routings == null || routings.isEmpty()) {
return null;
}
return routings;
}
/**
* Sets the same routing for all indices
*/
public static Map> resolveSearchRoutingAllIndices(Metadata metadata, String routing) {
if (routing != null) {
Set r = Sets.newHashSet(Strings.splitStringByCommaToArray(routing));
Map> routings = new HashMap<>();
String[] concreteIndices = metadata.getConcreteAllIndices();
for (String index : concreteIndices) {
routings.put(index, r);
}
return routings;
}
return null;
}
/**
* Identifies whether the array containing index names given as argument refers to all indices
* The empty or null array identifies all indices
*
* @param aliasesOrIndices the array containing index names
* @return true if the provided array maps to all indices, false otherwise
*/
public static boolean isAllIndices(Collection aliasesOrIndices) {
return aliasesOrIndices == null || aliasesOrIndices.isEmpty() || isExplicitAllPattern(aliasesOrIndices);
}
/**
* Identifies whether the array containing index names given as argument explicitly refers to all indices
* The empty or null array doesn't explicitly map to all indices
*
* @param aliasesOrIndices the array containing index names
* @return true if the provided array explicitly maps to all indices, false otherwise
*/
static boolean isExplicitAllPattern(Collection aliasesOrIndices) {
return aliasesOrIndices != null && aliasesOrIndices.size() == 1 && Metadata.ALL.equals(aliasesOrIndices.iterator().next());
}
public SystemIndexAccessLevel getSystemIndexAccessLevel() {
final SystemIndexAccessLevel accessLevel = SystemIndices.getSystemIndexAccessLevel(threadContext);
assert accessLevel != SystemIndexAccessLevel.BACKWARDS_COMPATIBLE_ONLY
: "BACKWARDS_COMPATIBLE_ONLY access level should never be used automatically, it should only be used in known special cases";
return accessLevel;
}
public Predicate getSystemIndexAccessPredicate() {
final SystemIndexAccessLevel systemIndexAccessLevel = getSystemIndexAccessLevel();
final Predicate systemIndexAccessLevelPredicate;
if (systemIndexAccessLevel == SystemIndexAccessLevel.NONE) {
systemIndexAccessLevelPredicate = s -> false;
} else if (systemIndexAccessLevel == SystemIndexAccessLevel.BACKWARDS_COMPATIBLE_ONLY) {
systemIndexAccessLevelPredicate = getNetNewSystemIndexPredicate();
} else if (systemIndexAccessLevel == SystemIndexAccessLevel.ALL) {
systemIndexAccessLevelPredicate = s -> true;
} else {
// everything other than allowed should be included in the deprecation message
systemIndexAccessLevelPredicate = systemIndices.getProductSystemIndexNamePredicate(threadContext);
}
return systemIndexAccessLevelPredicate;
}
public Automaton getSystemNameAutomaton() {
return systemIndices.getSystemNameAutomaton();
}
public Predicate getNetNewSystemIndexPredicate() {
return systemIndices::isNetNewSystemIndex;
}
public static class Context {
private final ClusterState state;
private final IndicesOptions options;
private final long startTime;
private final boolean preserveAliases;
private final boolean resolveToWriteIndex;
private final boolean includeDataStreams;
private final boolean preserveDataStreams;
private final SystemIndexAccessLevel systemIndexAccessLevel;
private final Predicate systemIndexAccessPredicate;
private final Predicate netNewSystemIndexPredicate;
Context(ClusterState state, IndicesOptions options, SystemIndexAccessLevel systemIndexAccessLevel) {
this(state, options, systemIndexAccessLevel, s -> true, s -> false);
}
Context(
ClusterState state,
IndicesOptions options,
SystemIndexAccessLevel systemIndexAccessLevel,
Predicate systemIndexAccessPredicate,
Predicate netNewSystemIndexPredicate
) {
this(
state,
options,
System.currentTimeMillis(),
systemIndexAccessLevel,
systemIndexAccessPredicate,
netNewSystemIndexPredicate
);
}
Context(
ClusterState state,
IndicesOptions options,
boolean preserveAliases,
boolean resolveToWriteIndex,
boolean includeDataStreams,
SystemIndexAccessLevel systemIndexAccessLevel,
Predicate systemIndexAccessPredicate,
Predicate netNewSystemIndexPredicate
) {
this(
state,
options,
System.currentTimeMillis(),
preserveAliases,
resolveToWriteIndex,
includeDataStreams,
false,
systemIndexAccessLevel,
systemIndexAccessPredicate,
netNewSystemIndexPredicate
);
}
Context(
ClusterState state,
IndicesOptions options,
boolean preserveAliases,
boolean resolveToWriteIndex,
boolean includeDataStreams,
boolean preserveDataStreams,
SystemIndexAccessLevel systemIndexAccessLevel,
Predicate systemIndexAccessPredicate,
Predicate netNewSystemIndexPredicate
) {
this(
state,
options,
System.currentTimeMillis(),
preserveAliases,
resolveToWriteIndex,
includeDataStreams,
preserveDataStreams,
systemIndexAccessLevel,
systemIndexAccessPredicate,
netNewSystemIndexPredicate
);
}
Context(
ClusterState state,
IndicesOptions options,
long startTime,
SystemIndexAccessLevel systemIndexAccessLevel,
Predicate systemIndexAccessPredicate,
Predicate netNewSystemIndexPredicate
) {
this(
state,
options,
startTime,
false,
false,
false,
false,
systemIndexAccessLevel,
systemIndexAccessPredicate,
netNewSystemIndexPredicate
);
}
protected Context(
ClusterState state,
IndicesOptions options,
long startTime,
boolean preserveAliases,
boolean resolveToWriteIndex,
boolean includeDataStreams,
boolean preserveDataStreams,
SystemIndexAccessLevel systemIndexAccessLevel,
Predicate systemIndexAccessPredicate,
Predicate netNewSystemIndexPredicate
) {
this.state = state;
this.options = options;
this.startTime = startTime;
this.preserveAliases = preserveAliases;
this.resolveToWriteIndex = resolveToWriteIndex;
this.includeDataStreams = includeDataStreams;
this.preserveDataStreams = preserveDataStreams;
this.systemIndexAccessLevel = systemIndexAccessLevel;
this.systemIndexAccessPredicate = systemIndexAccessPredicate;
this.netNewSystemIndexPredicate = netNewSystemIndexPredicate;
}
public ClusterState getState() {
return state;
}
public IndicesOptions getOptions() {
return options;
}
public long getStartTime() {
return startTime;
}
/**
* This is used to prevent resolving aliases to concrete indices but this also means
* that we might return aliases that point to a closed index. This is currently only used
* by {@link #filteringAliases(ClusterState, String, Set)} since it's the only one that needs aliases
*/
boolean isPreserveAliases() {
return preserveAliases;
}
/**
* This is used to require that aliases resolve to their write-index. It is currently not used in conjunction
* with preserveAliases
.
*/
boolean isResolveToWriteIndex() {
return resolveToWriteIndex;
}
public boolean includeDataStreams() {
return includeDataStreams;
}
public boolean isPreserveDataStreams() {
return preserveDataStreams;
}
/**
* Used to determine system index access is allowed in this context (e.g. for this request).
*/
public Predicate getSystemIndexAccessPredicate() {
return systemIndexAccessPredicate;
}
}
/**
* Resolves alias/index name expressions with wildcards into the corresponding concrete indices/aliases
*/
static final class WildcardExpressionResolver {
private WildcardExpressionResolver() {
// Utility class
}
public static List resolve(Context context, List expressions) {
IndicesOptions options = context.getOptions();
Metadata metadata = context.getState().metadata();
// only check open/closed since if we do not expand to open or closed it doesn't make sense to
// expand to hidden
if (options.expandWildcardsClosed() == false && options.expandWildcardsOpen() == false) {
return expressions;
}
if (isEmptyOrTrivialWildcard(expressions)) {
List resolvedExpressions = resolveEmptyOrTrivialWildcard(context);
if (context.includeDataStreams()) {
final IndexMetadata.State excludeState = excludeState(options);
final Map dataStreamsAbstractions = metadata.getIndicesLookup()
.entrySet()
.stream()
.filter(entry -> entry.getValue().getType() == IndexAbstraction.Type.DATA_STREAM)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// dedup backing indices if expand hidden indices option is true
Set resolvedIncludingDataStreams = new HashSet<>(resolvedExpressions);
resolvedIncludingDataStreams.addAll(
expand(
context,
excludeState,
dataStreamsAbstractions,
expressions.isEmpty() ? "_all" : expressions.get(0),
options.expandWildcardsHidden()
)
);
return new ArrayList<>(resolvedIncludingDataStreams);
}
return resolvedExpressions;
}
Set result = innerResolve(context, expressions, options, metadata);
if (result == null) {
return expressions;
}
if (result.isEmpty() && options.allowNoIndices() == false) {
IndexNotFoundException infe = new IndexNotFoundException((String) null);
infe.setResources("index_or_alias", expressions.toArray(new String[0]));
throw infe;
}
return new ArrayList<>(result);
}
private static Set innerResolve(Context context, List expressions, IndicesOptions options, Metadata metadata) {
Set result = null;
boolean wildcardSeen = false;
for (int i = 0; i < expressions.size(); i++) {
String expression = expressions.get(i);
if (Strings.isEmpty(expression)) {
throw indexNotFoundException(expression);
}
validateAliasOrIndex(expression);
if (aliasOrIndexExists(context, options, metadata, expression)) {
if (result != null) {
result.add(expression);
}
continue;
}
final boolean add;
if (expression.charAt(0) == '-' && wildcardSeen) {
add = false;
expression = expression.substring(1);
} else {
add = true;
}
if (result == null) {
// add all the previous ones...
result = new HashSet<>(expressions.subList(0, i));
}
if (Regex.isSimpleMatchPattern(expression) == false) {
// TODO why does wildcard resolver throw exceptions regarding non wildcarded expressions? This should not be done here.
if (options.ignoreUnavailable() == false) {
IndexAbstraction indexAbstraction = metadata.getIndicesLookup().get(expression);
if (indexAbstraction == null) {
throw indexNotFoundException(expression);
} else if (indexAbstraction.getType() == IndexAbstraction.Type.ALIAS && options.ignoreAliases()) {
throw aliasesNotSupportedException(expression);
} else if (indexAbstraction.isDataStreamRelated() && context.includeDataStreams() == false) {
throw indexNotFoundException(expression);
}
}
if (add) {
result.add(expression);
} else {
result.remove(expression);
}
continue;
}
final IndexMetadata.State excludeState = excludeState(options);
final Map matches = matches(context, metadata, expression);
Set expand = expand(context, excludeState, matches, expression, options.expandWildcardsHidden());
if (add) {
result.addAll(expand);
} else {
result.removeAll(expand);
}
if (options.allowNoIndices() == false && matches.isEmpty()) {
throw indexNotFoundException(expression);
}
if (Regex.isSimpleMatchPattern(expression)) {
wildcardSeen = true;
}
}
return result;
}
private static void validateAliasOrIndex(String expression) {
// Expressions can not start with an underscore. This is reserved for APIs. If the check gets here, the API
// does not exist and the path is interpreted as an expression. If the expression begins with an underscore,
// throw a specific error that is different from the [[IndexNotFoundException]], which is typically thrown
// if the expression can't be found.
if (expression.charAt(0) == '_') {
throw new InvalidIndexNameException(expression, "must not start with '_'.");
}
}
private static boolean aliasOrIndexExists(Context context, IndicesOptions options, Metadata metadata, String expression) {
IndexAbstraction indexAbstraction = metadata.getIndicesLookup().get(expression);
if (indexAbstraction == null) {
return false;
}
// treat aliases as unavailable indices when ignoreAliases is set to true (e.g. delete index and update aliases api)
if (indexAbstraction.getType() == IndexAbstraction.Type.ALIAS && options.ignoreAliases()) {
return false;
}
if (indexAbstraction.isDataStreamRelated() && context.includeDataStreams() == false) {
return false;
}
return true;
}
private static IndexNotFoundException indexNotFoundException(String expression) {
IndexNotFoundException infe = new IndexNotFoundException(expression);
infe.setResources("index_or_alias", expression);
return infe;
}
private static IndexMetadata.State excludeState(IndicesOptions options) {
final IndexMetadata.State excludeState;
if (options.expandWildcardsOpen() && options.expandWildcardsClosed()) {
excludeState = null;
} else if (options.expandWildcardsOpen() && options.expandWildcardsClosed() == false) {
excludeState = IndexMetadata.State.CLOSE;
} else if (options.expandWildcardsClosed() && options.expandWildcardsOpen() == false) {
excludeState = IndexMetadata.State.OPEN;
} else {
assert false : "this shouldn't get called if wildcards expand to none";
excludeState = null;
}
return excludeState;
}
public static Map matches(Context context, Metadata metadata, String expression) {
if (Regex.isMatchAllPattern(expression)) {
return filterIndicesLookup(context, metadata.getIndicesLookup(), null, context.getOptions());
} else if (expression.indexOf("*") == expression.length() - 1) {
return suffixWildcard(context, metadata, expression);
} else {
return otherWildcard(context, metadata, expression);
}
}
private static Map suffixWildcard(Context context, Metadata metadata, String expression) {
assert expression.length() >= 2 : "expression [" + expression + "] should have at least a length of 2";
String fromPrefix = expression.substring(0, expression.length() - 1);
char[] toPrefixCharArr = fromPrefix.toCharArray();
toPrefixCharArr[toPrefixCharArr.length - 1]++;
String toPrefix = new String(toPrefixCharArr);
SortedMap subMap = metadata.getIndicesLookup().subMap(fromPrefix, toPrefix);
return filterIndicesLookup(context, subMap, null, context.getOptions());
}
private static Map otherWildcard(Context context, Metadata metadata, String expression) {
final String pattern = expression;
return filterIndicesLookup(
context,
metadata.getIndicesLookup(),
e -> Regex.simpleMatch(pattern, e.getKey()),
context.getOptions()
);
}
private static Map filterIndicesLookup(
Context context,
SortedMap indicesLookup,
Predicate> filter,
IndicesOptions options
) {
boolean shouldConsumeStream = false;
Stream> stream = indicesLookup.entrySet().stream();
if (options.ignoreAliases()) {
shouldConsumeStream = true;
stream = stream.filter(e -> e.getValue().getType() != IndexAbstraction.Type.ALIAS);
}
if (filter != null) {
shouldConsumeStream = true;
stream = stream.filter(filter);
}
if (context.includeDataStreams() == false) {
shouldConsumeStream = true;
stream = stream.filter(e -> e.getValue().isDataStreamRelated() == false);
}
if (shouldConsumeStream) {
return stream.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
} else {
return indicesLookup;
}
}
private static Set expand(
Context context,
IndexMetadata.State excludeState,
Map matches,
String expression,
boolean includeHidden
) {
Set expand = new HashSet<>();
for (Map.Entry entry : matches.entrySet()) {
String aliasOrIndexName = entry.getKey();
IndexAbstraction indexAbstraction = entry.getValue();
if (indexAbstraction.isSystem()) {
if (context.netNewSystemIndexPredicate.test(indexAbstraction.getName())
&& context.systemIndexAccessPredicate.test(indexAbstraction.getName()) == false) {
continue;
}
if (indexAbstraction.getType() == Type.DATA_STREAM || indexAbstraction.getParentDataStream() != null) {
if (context.systemIndexAccessPredicate.test(indexAbstraction.getName()) == false) {
continue;
}
}
}
if (indexAbstraction.isHidden() == false || includeHidden || implicitHiddenMatch(aliasOrIndexName, expression)) {
if (context.isPreserveAliases() && indexAbstraction.getType() == IndexAbstraction.Type.ALIAS) {
expand.add(aliasOrIndexName);
} else {
for (Index index : indexAbstraction.getIndices()) {
IndexMetadata meta = context.state.metadata().index(index);
if (excludeState == null || meta.getState() != excludeState) {
expand.add(meta.getIndex().getName());
}
}
if (context.isPreserveDataStreams() && indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM) {
expand.add(indexAbstraction.getName());
}
}
}
}
return expand;
}
private static boolean implicitHiddenMatch(String itemName, String expression) {
return itemName.startsWith(".") && expression.startsWith(".") && Regex.isSimpleMatchPattern(expression);
}
private static boolean isEmptyOrTrivialWildcard(List expressions) {
return expressions.isEmpty()
|| (expressions.size() == 1 && (Metadata.ALL.equals(expressions.get(0)) || Regex.isMatchAllPattern(expressions.get(0))));
}
private static List resolveEmptyOrTrivialWildcard(Context context) {
final String[] allIndices = resolveEmptyOrTrivialWildcardToAllIndices(context.getOptions(), context.getState().metadata());
if (context.systemIndexAccessLevel == SystemIndexAccessLevel.ALL) {
return List.of(allIndices);
} else {
return resolveEmptyOrTrivialWildcardWithAllowedSystemIndices(context, allIndices);
}
}
private static List resolveEmptyOrTrivialWildcardWithAllowedSystemIndices(Context context, String[] allIndices) {
return Arrays.stream(allIndices).filter(name -> {
if (name.startsWith(".")) {
IndexAbstraction abstraction = context.state.metadata().getIndicesLookup().get(name);
assert abstraction != null : "null abstraction for " + name + " but was in array of all indices";
if (abstraction.isSystem()) {
if (context.netNewSystemIndexPredicate.test(name)) {
if (SystemIndexAccessLevel.BACKWARDS_COMPATIBLE_ONLY.equals(context.systemIndexAccessLevel)) {
return false;
} else {
return context.systemIndexAccessPredicate.test(name);
}
} else if (abstraction.getType() == Type.DATA_STREAM || abstraction.getParentDataStream() != null) {
return context.systemIndexAccessPredicate.test(name);
}
} else {
return true;
}
}
return true;
}).toList();
}
private static String[] resolveEmptyOrTrivialWildcardToAllIndices(IndicesOptions options, Metadata metadata) {
if (options.expandWildcardsOpen() && options.expandWildcardsClosed() && options.expandWildcardsHidden()) {
return metadata.getConcreteAllIndices();
} else if (options.expandWildcardsOpen() && options.expandWildcardsClosed()) {
return metadata.getConcreteVisibleIndices();
} else if (options.expandWildcardsOpen() && options.expandWildcardsHidden()) {
return metadata.getConcreteAllOpenIndices();
} else if (options.expandWildcardsOpen()) {
return metadata.getConcreteVisibleOpenIndices();
} else if (options.expandWildcardsClosed() && options.expandWildcardsHidden()) {
return metadata.getConcreteAllClosedIndices();
} else if (options.expandWildcardsClosed()) {
return metadata.getConcreteVisibleClosedIndices();
} else {
return Strings.EMPTY_ARRAY;
}
}
}
public static final class DateMathExpressionResolver {
private static final DateFormatter DEFAULT_DATE_FORMATTER = DateFormatter.forPattern("uuuu.MM.dd");
private static final String EXPRESSION_LEFT_BOUND = "<";
private static final String EXPRESSION_RIGHT_BOUND = ">";
private static final char LEFT_BOUND = '{';
private static final char RIGHT_BOUND = '}';
private static final char ESCAPE_CHAR = '\\';
private static final char TIME_ZONE_BOUND = '|';
private DateMathExpressionResolver() {
// utility class
}
public static List resolve(final Context context, List expressions) {
List result = new ArrayList<>(expressions.size());
for (String expression : expressions) {
result.add(resolveExpression(expression, context::getStartTime));
}
return result;
}
static String resolveExpression(String expression) {
return resolveExpression(expression, System::currentTimeMillis);
}
@SuppressWarnings("fallthrough")
static String resolveExpression(String expression, LongSupplier getTime) {
if (expression.startsWith(EXPRESSION_LEFT_BOUND) == false || expression.endsWith(EXPRESSION_RIGHT_BOUND) == false) {
return expression;
}
boolean escape = false;
boolean inDateFormat = false;
boolean inPlaceHolder = false;
final StringBuilder beforePlaceHolderSb = new StringBuilder();
StringBuilder inPlaceHolderSb = new StringBuilder();
final char[] text = expression.toCharArray();
final int from = 1;
final int length = text.length - 1;
for (int i = from; i < length; i++) {
boolean escapedChar = escape;
if (escape) {
escape = false;
}
char c = text[i];
if (c == ESCAPE_CHAR) {
if (escapedChar) {
beforePlaceHolderSb.append(c);
escape = false;
} else {
escape = true;
}
continue;
}
if (inPlaceHolder) {
switch (c) {
case LEFT_BOUND:
if (inDateFormat && escapedChar) {
inPlaceHolderSb.append(c);
} else if (inDateFormat == false) {
inDateFormat = true;
inPlaceHolderSb.append(c);
} else {
throw new ElasticsearchParseException(
"invalid dynamic name expression [{}]." + " invalid character in placeholder at position [{}]",
new String(text, from, length),
i
);
}
break;
case RIGHT_BOUND:
if (inDateFormat && escapedChar) {
inPlaceHolderSb.append(c);
} else if (inDateFormat) {
inDateFormat = false;
inPlaceHolderSb.append(c);
} else {
String inPlaceHolderString = inPlaceHolderSb.toString();
int dateTimeFormatLeftBoundIndex = inPlaceHolderString.indexOf(LEFT_BOUND);
String mathExpression;
String dateFormatterPattern;
DateFormatter dateFormatter;
final ZoneId timeZone;
if (dateTimeFormatLeftBoundIndex < 0) {
mathExpression = inPlaceHolderString;
dateFormatter = DEFAULT_DATE_FORMATTER;
timeZone = ZoneOffset.UTC;
} else {
if (inPlaceHolderString.lastIndexOf(RIGHT_BOUND) != inPlaceHolderString.length() - 1) {
throw new ElasticsearchParseException(
"invalid dynamic name expression [{}]. missing closing `}`" + " for date math format",
inPlaceHolderString
);
}
if (dateTimeFormatLeftBoundIndex == inPlaceHolderString.length() - 2) {
throw new ElasticsearchParseException(
"invalid dynamic name expression [{}]. missing date format",
inPlaceHolderString
);
}
mathExpression = inPlaceHolderString.substring(0, dateTimeFormatLeftBoundIndex);
String patternAndTZid = inPlaceHolderString.substring(
dateTimeFormatLeftBoundIndex + 1,
inPlaceHolderString.length() - 1
);
int formatPatternTimeZoneSeparatorIndex = patternAndTZid.indexOf(TIME_ZONE_BOUND);
if (formatPatternTimeZoneSeparatorIndex != -1) {
dateFormatterPattern = patternAndTZid.substring(0, formatPatternTimeZoneSeparatorIndex);
timeZone = DateUtils.of(patternAndTZid.substring(formatPatternTimeZoneSeparatorIndex + 1));
} else {
dateFormatterPattern = patternAndTZid;
timeZone = ZoneOffset.UTC;
}
dateFormatter = DateFormatter.forPattern(dateFormatterPattern);
}
DateFormatter formatter = dateFormatter.withZone(timeZone);
DateMathParser dateMathParser = formatter.toDateMathParser();
Instant instant = dateMathParser.parse(mathExpression, getTime, false, timeZone);
String time = formatter.format(instant);
beforePlaceHolderSb.append(time);
inPlaceHolderSb = new StringBuilder();
inPlaceHolder = false;
}
break;
default:
inPlaceHolderSb.append(c);
}
} else {
switch (c) {
case LEFT_BOUND:
if (escapedChar) {
beforePlaceHolderSb.append(c);
} else {
inPlaceHolder = true;
}
break;
case RIGHT_BOUND:
if (escapedChar == false) {
throw new ElasticsearchParseException(
"invalid dynamic name expression [{}]."
+ " invalid character at position [{}]. `{` and `}` are reserved characters and"
+ " should be escaped when used as part of the index name using `\\` (e.g. `\\{text\\}`)",
new String(text, from, length),
i
);
}
default:
beforePlaceHolderSb.append(c);
}
}
}
if (inPlaceHolder) {
throw new ElasticsearchParseException(
"invalid dynamic name expression [{}]. date math placeholder is open ended",
new String(text, from, length)
);
}
if (beforePlaceHolderSb.length() == 0) {
throw new ElasticsearchParseException("nothing captured");
}
return beforePlaceHolderSb.toString();
}
}
/**
* This is a context for the DateMathExpressionResolver which does not require {@code IndicesOptions} or {@code ClusterState}
* since it uses only the start time to resolve expressions.
*/
public static final class ResolverContext extends Context {
public ResolverContext() {
this(System.currentTimeMillis());
}
public ResolverContext(long startTime) {
super(null, null, startTime, false, false, false, false, SystemIndexAccessLevel.ALL, name -> false, name -> false);
}
@Override
public ClusterState getState() {
throw new UnsupportedOperationException("should never be called");
}
@Override
public IndicesOptions getOptions() {
throw new UnsupportedOperationException("should never be called");
}
}
}