org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.java

Source

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.elasticsearch.cluster.metadata;

import com.carrotsearch.hppc.cursors.ObjectCursor;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.index.Index;
import org.elasticsearch.indices.IndexClosedException;
import org.elasticsearch.indices.IndexMissingException;

import java.util.*;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;

public class IndexNameExpressionResolver {

    private final ImmutableList<ExpressionResolver> expressionResolvers;

    @Inject
    public IndexNameExpressionResolver() {
        expressionResolvers = ImmutableList.<ExpressionResolver>of(new WildcardExpressionResolver());
    }

    /**
     * Same as {@link #concreteIndices(ClusterState, IndicesOptions, String...)}, but the index expressions and options
     * are encapsulated in the specified request.
     */
    public String[] concreteIndices(ClusterState state, IndicesRequest request)
            throws IndexMissingException, IllegalArgumentException {
        Context context = new Context(state, request.indicesOptions());
        return concreteIndices(context, request.indices());
    }

    /**
     * Translates the provided index expression into actual concrete indices.
     *
     * @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 IndexMissingException 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.
     */
    public String[] concreteIndices(ClusterState state, IndicesOptions options, String... indexExpressions)
            throws IndexMissingException, IllegalArgumentException {
        Context context = new Context(state, options);
        return concreteIndices(context, indexExpressions);
    }

    String[] concreteIndices(Context context, String... indexExpressions)
            throws IndexMissingException, IllegalArgumentException {
        if (indexExpressions == null || indexExpressions.length == 0) {
            indexExpressions = new String[] { MetaData.ALL };
        }
        MetaData metaData = context.getState().metaData();
        IndicesOptions options = context.getOptions();
        boolean failClosed = options.forbidClosedIndices() && options.ignoreUnavailable() == false;
        boolean failNoIndices = options.ignoreUnavailable() == false;
        // 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.
        if (indexExpressions.length == 1) {
            failNoIndices = options.allowNoIndices() == false;
        }

        List<String> expressions = Arrays.asList(indexExpressions);
        for (ExpressionResolver expressionResolver : expressionResolvers) {
            expressions = expressionResolver.resolve(context, expressions);
        }

        if (expressions.isEmpty()) {
            if (!options.allowNoIndices()) {
                throw new IndexMissingException(new Index(Arrays.toString(indexExpressions)));
            } else {
                return Strings.EMPTY_ARRAY;
            }
        }

        List<String> concreteIndices = new ArrayList<>(expressions.size());
        for (String expression : expressions) {
            List<IndexMetaData> indexMetaDatas;
            IndexMetaData indexMetaData = metaData.getIndices().get(expression);
            if (indexMetaData == null) {
                ImmutableOpenMap<String, AliasMetaData> indexAliasMap = metaData.aliases().get(expression);
                if (indexAliasMap == null) {
                    if (failNoIndices) {
                        throw new IndexMissingException(new Index(expression));
                    } else {
                        continue;
                    }
                }
                if (indexAliasMap.size() > 1 && !options.allowAliasesToMultipleIndices()) {
                    throw new IllegalArgumentException(
                            "Alias [" + expression + "] has more than one indices associated with it ["
                                    + Arrays.toString(indexAliasMap.keys().toArray(String.class))
                                    + "], can't execute a single index op");
                }
                indexMetaDatas = new ArrayList<>(indexAliasMap.size());
                for (ObjectObjectCursor<String, AliasMetaData> cursor : indexAliasMap) {
                    indexMetaDatas.add(metaData.getIndices().get(cursor.key));
                }
            } else {
                indexMetaDatas = Collections.singletonList(indexMetaData);
            }

            for (IndexMetaData found : indexMetaDatas) {
                if (found.getState() == IndexMetaData.State.CLOSE) {
                    if (failClosed) {
                        throw new IndexClosedException(new Index(found.getIndex()));
                    } else {
                        if (options.forbidClosedIndices() == false) {
                            concreteIndices.add(found.getIndex());
                        }
                    }
                } else if (found.getState() == IndexMetaData.State.OPEN) {
                    concreteIndices.add(found.getIndex());
                } else {
                    throw new IllegalStateException("index state [" + found.getState() + "] not supported");
                }
            }
        }

        if (options.allowNoIndices() == false && concreteIndices.isEmpty()) {
            throw new IndexMissingException(new Index(Arrays.toString(indexExpressions)));
        }
        return concreteIndices.toArray(new String[concreteIndices.size()]);
    }

    /**
     * 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 request   request containing the index or alias to be resolved to concrete index and
     *                  the indices options to be used for the index resolution
     * @throws IndexMissingException    if the resolved index or alias provided doesn't exist
     * @throws IllegalArgumentException if the index resolution lead to more than one index
     * @return the concrete index obtained as a result of the index resolution
     */
    public String concreteSingleIndex(ClusterState state, IndicesRequest request)
            throws IndexMissingException, IllegalArgumentException {
        String indexOrAlias = request.indices() != null && request.indices().length > 0 ? request.indices()[0]
                : null;
        String[] indices = concreteIndices(state, request.indicesOptions(), indexOrAlias);
        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];
    }

    /**
     * Iterates through the list of indices and selects the effective list of filtering aliases for the
     * given index.
     * <p/>
     * <p>Only aliases with filters are returned. If the indices list contains a non-filtering reference to
     * the index itself - null is returned. Returns <tt>null</tt> if no filtering is required.</p>
     */
    public String[] filteringAliases(ClusterState state, String index, String... expressions) {
        // expand the aliases wildcard
        List<String> resolvedExpressions = expressions != null ? Arrays.asList(expressions)
                : Collections.<String>emptyList();
        Context context = new Context(state, IndicesOptions.lenientExpandOpen());
        for (ExpressionResolver expressionResolver : expressionResolvers) {
            resolvedExpressions = expressionResolver.resolve(context, resolvedExpressions);
        }

        if (isAllIndices(resolvedExpressions)) {
            return null;
        }
        // optimize for the most common single index/alias scenario
        if (resolvedExpressions.size() == 1) {
            String alias = resolvedExpressions.get(0);
            IndexMetaData indexMetaData = state.metaData().getIndices().get(index);
            if (indexMetaData == null) {
                // Shouldn't happen
                throw new IndexMissingException(new Index(index));
            }
            AliasMetaData aliasMetaData = indexMetaData.aliases().get(alias);
            boolean filteringRequired = aliasMetaData != null && aliasMetaData.filteringRequired();
            if (!filteringRequired) {
                return null;
            }
            return new String[] { alias };
        }
        List<String> filteringAliases = null;
        for (String alias : resolvedExpressions) {
            if (alias.equals(index)) {
                return null;
            }

            IndexMetaData indexMetaData = state.metaData().getIndices().get(index);
            if (indexMetaData == null) {
                // Shouldn't happen
                throw new IndexMissingException(new Index(index));
            }

            AliasMetaData aliasMetaData = indexMetaData.aliases().get(alias);
            // Check that this is an alias for the current index
            // Otherwise - skip it
            if (aliasMetaData != null) {
                boolean filteringRequired = aliasMetaData.filteringRequired();
                if (filteringRequired) {
                    // If filtering required - add it to the list of filters
                    if (filteringAliases == null) {
                        filteringAliases = newArrayList();
                    }
                    filteringAliases.add(alias);
                } else {
                    // If not, we have a non filtering alias for this index - no filtering needed
                    return null;
                }
            }
        }
        if (filteringAliases == null) {
            return null;
        }
        return filteringAliases.toArray(new String[filteringAliases.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<String, Set<String>> resolveSearchRouting(ClusterState state, @Nullable String routing,
            String... expressions) {
        List<String> resolvedExpressions = expressions != null ? Arrays.asList(expressions)
                : Collections.<String>emptyList();
        Context context = new Context(state, IndicesOptions.lenientExpandOpen());
        for (ExpressionResolver expressionResolver : expressionResolvers) {
            resolvedExpressions = expressionResolver.resolve(context, resolvedExpressions);
        }

        if (isAllIndices(resolvedExpressions)) {
            return resolveSearchRoutingAllIndices(state.metaData(), routing);
        }

        if (resolvedExpressions.size() == 1) {
            return resolveSearchRoutingSingleValue(state.metaData(), routing, resolvedExpressions.get(0));
        }

        Map<String, Set<String>> routings = null;
        Set<String> paramRouting = null;
        // List of indices that don't require any routing
        Set<String> norouting = new HashSet<>();
        if (routing != null) {
            paramRouting = Strings.splitStringByCommaToSet(routing);
        }

        for (String expression : resolvedExpressions) {
            ImmutableOpenMap<String, AliasMetaData> indexToRoutingMap = state.metaData().getAliases()
                    .get(expression);
            if (indexToRoutingMap != null && !indexToRoutingMap.isEmpty()) {
                for (ObjectObjectCursor<String, AliasMetaData> indexRouting : indexToRoutingMap) {
                    if (!norouting.contains(indexRouting.key)) {
                        if (!indexRouting.value.searchRoutingValues().isEmpty()) {
                            // Routing alias
                            if (routings == null) {
                                routings = newHashMap();
                            }
                            Set<String> r = routings.get(indexRouting.key);
                            if (r == null) {
                                r = new HashSet<>();
                                routings.put(indexRouting.key, r);
                            }
                            r.addAll(indexRouting.value.searchRoutingValues());
                            if (paramRouting != null) {
                                r.retainAll(paramRouting);
                            }
                            if (r.isEmpty()) {
                                routings.remove(indexRouting.key);
                            }
                        } else {
                            // Non-routing alias
                            if (!norouting.contains(indexRouting.key)) {
                                norouting.add(indexRouting.key);
                                if (paramRouting != null) {
                                    Set<String> r = new HashSet<>(paramRouting);
                                    if (routings == null) {
                                        routings = newHashMap();
                                    }
                                    routings.put(indexRouting.key, r);
                                } else {
                                    if (routings != null) {
                                        routings.remove(indexRouting.key);
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                // Index
                if (!norouting.contains(expression)) {
                    norouting.add(expression);
                    if (paramRouting != null) {
                        Set<String> r = new HashSet<>(paramRouting);
                        if (routings == null) {
                            routings = newHashMap();
                        }
                        routings.put(expression, r);
                    } else {
                        if (routings != null) {
                            routings.remove(expression);
                        }
                    }
                }
            }

        }
        if (routings == null || routings.isEmpty()) {
            return null;
        }
        return routings;
    }

    private Map<String, Set<String>> resolveSearchRoutingSingleValue(MetaData metaData, @Nullable String routing,
            String aliasOrIndex) {
        Map<String, Set<String>> routings = null;
        Set<String> paramRouting = null;
        if (routing != null) {
            paramRouting = Strings.splitStringByCommaToSet(routing);
        }

        ImmutableOpenMap<String, AliasMetaData> indexToRoutingMap = metaData.getAliases().get(aliasOrIndex);
        if (indexToRoutingMap != null && !indexToRoutingMap.isEmpty()) {
            // It's an alias
            for (ObjectObjectCursor<String, AliasMetaData> indexRouting : indexToRoutingMap) {
                if (!indexRouting.value.searchRoutingValues().isEmpty()) {
                    // Routing alias
                    Set<String> r = new HashSet<>(indexRouting.value.searchRoutingValues());
                    if (paramRouting != null) {
                        r.retainAll(paramRouting);
                    }
                    if (!r.isEmpty()) {
                        if (routings == null) {
                            routings = newHashMap();
                        }
                        routings.put(indexRouting.key, r);
                    }
                } else {
                    // Non-routing alias
                    if (paramRouting != null) {
                        Set<String> r = new HashSet<>(paramRouting);
                        if (routings == null) {
                            routings = newHashMap();
                        }
                        routings.put(indexRouting.key, r);
                    }
                }
            }
        } else {
            // It's an index
            if (paramRouting != null) {
                routings = ImmutableMap.of(aliasOrIndex, paramRouting);
            }
        }
        return routings;
    }

    /**
     * Sets the same routing for all indices
     */
    private Map<String, Set<String>> resolveSearchRoutingAllIndices(MetaData metaData, String routing) {
        if (routing != null) {
            Set<String> r = Strings.splitStringByCommaToSet(routing);
            Map<String, Set<String>> routings = newHashMap();
            String[] concreteIndices = metaData.concreteAllIndices();
            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(List<String> 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(List<String> aliasesOrIndices) {
        return aliasesOrIndices != null && aliasesOrIndices.size() == 1
                && MetaData.ALL.equals(aliasesOrIndices.get(0));
    }

    /**
     * Identifies whether the first argument (an array containing index names) is a pattern that matches all indices
     *
     * @param indicesOrAliases the array containing index names
     * @param concreteIndices  array containing the concrete indices that the first argument refers to
     * @return true if the first argument is a pattern that maps to all available indices, false otherwise
     */
    boolean isPatternMatchingAllIndices(MetaData metaData, String[] indicesOrAliases, String[] concreteIndices) {
        // if we end up matching on all indices, check, if its a wildcard parameter, or a "-something" structure
        if (concreteIndices.length == metaData.concreteAllIndices().length && indicesOrAliases.length > 0) {

            //we might have something like /-test1,+test1 that would identify all indices
            //or something like /-test1 with test1 index missing and IndicesOptions.lenient()
            if (indicesOrAliases[0].charAt(0) == '-') {
                return true;
            }

            //otherwise we check if there's any simple regex
            for (String indexOrAlias : indicesOrAliases) {
                if (Regex.isSimpleMatchPattern(indexOrAlias)) {
                    return true;
                }
            }
        }
        return false;
    }

    final static class Context {

        private final ClusterState state;
        private final IndicesOptions options;

        Context(ClusterState state, IndicesOptions options) {
            this.state = state;
            this.options = options;
        }

        public ClusterState getState() {
            return state;
        }

        public IndicesOptions getOptions() {
            return options;
        }
    }

    private interface ExpressionResolver {

        /**
         * Resolves the list of expressions into other expressions if possible (possible concrete indices and aliases, but
         * that isn't required). The provided implementations can also be left untouched.
         *
         * @return a new list with expressions based on the provided expressions
         */
        List<String> resolve(Context context, List<String> expressions);

    }

    /**
     * Resolves alias/index name expressions with wildcards into the corresponding concrete indices/aliases
     */
    final static class WildcardExpressionResolver implements ExpressionResolver {

        @Override
        public List<String> resolve(Context context, List<String> expressions) {
            IndicesOptions options = context.getOptions();
            MetaData metaData = context.getState().metaData();
            if (options.expandWildcardsClosed() == false && options.expandWildcardsOpen() == false) {
                return expressions;
            }

            if (expressions.isEmpty() || (expressions.size() == 1 && MetaData.ALL.equals(expressions.get(0)))) {
                if (options.expandWildcardsOpen() && options.expandWildcardsClosed()) {
                    return Arrays.asList(metaData.concreteAllIndices());
                } else if (options.expandWildcardsOpen()) {
                    return Arrays.asList(metaData.concreteAllOpenIndices());
                } else if (options.expandWildcardsClosed()) {
                    return Arrays.asList(metaData.concreteAllClosedIndices());
                } else {
                    return Collections.emptyList();
                }
            }

            Set<String> result = null;
            for (int i = 0; i < expressions.size(); i++) {
                String aliasOrIndex = expressions.get(i);
                if (metaData.getAliasAndIndexMap().containsKey(aliasOrIndex)) {
                    if (result != null) {
                        result.add(aliasOrIndex);
                    }
                    continue;
                }
                boolean add = true;
                if (aliasOrIndex.charAt(0) == '+') {
                    // if its the first, add empty result set
                    if (i == 0) {
                        result = new HashSet<>();
                    }
                    add = true;
                    aliasOrIndex = aliasOrIndex.substring(1);
                } else if (aliasOrIndex.charAt(0) == '-') {
                    // if its the first, fill it with all the indices...
                    if (i == 0) {
                        String[] concreteIndices;
                        if (options.expandWildcardsOpen() && options.expandWildcardsClosed()) {
                            concreteIndices = metaData.concreteAllIndices();
                        } else if (options.expandWildcardsOpen()) {
                            concreteIndices = metaData.concreteAllOpenIndices();
                        } else if (options.expandWildcardsClosed()) {
                            concreteIndices = metaData.concreteAllClosedIndices();
                        } else {
                            assert false : "Shouldn't end up here";
                            concreteIndices = Strings.EMPTY_ARRAY;
                        }
                        result = new HashSet<>(Arrays.asList(concreteIndices));
                    }
                    add = false;
                    aliasOrIndex = aliasOrIndex.substring(1);
                }
                if (!Regex.isSimpleMatchPattern(aliasOrIndex)) {
                    if (!options.ignoreUnavailable() && !metaData.getAliasAndIndexMap().containsKey(aliasOrIndex)) {
                        throw new IndexMissingException(new Index(aliasOrIndex));
                    }
                    if (result != null) {
                        if (add) {
                            result.add(aliasOrIndex);
                        } else {
                            result.remove(aliasOrIndex);
                        }
                    }
                    continue;
                }
                if (result == null) {
                    // add all the previous ones...
                    result = new HashSet<>();
                    result.addAll(expressions.subList(0, i));
                }
                String[] indices;
                if (options.expandWildcardsOpen() && options.expandWildcardsClosed()) {
                    indices = metaData.concreteAllIndices();
                } else if (options.expandWildcardsOpen()) {
                    indices = metaData.concreteAllOpenIndices();
                } else if (options.expandWildcardsClosed()) {
                    indices = metaData.concreteAllClosedIndices();
                } else {
                    assert false : "this shouldn't get called if wildcards expand to none";
                    indices = Strings.EMPTY_ARRAY;
                }
                boolean found = false;
                // iterating over all concrete indices and see if there is a wildcard match
                for (String index : indices) {
                    if (Regex.simpleMatch(aliasOrIndex, index)) {
                        found = true;
                        if (add) {
                            result.add(index);
                        } else {
                            result.remove(index);
                        }
                    }
                }
                // iterating over all aliases and see if there is a wildcard match
                for (ObjectCursor<String> cursor : metaData.getAliases().keys()) {
                    String alias = cursor.value;
                    if (Regex.simpleMatch(aliasOrIndex, alias)) {
                        found = true;
                        if (add) {
                            result.add(alias);
                        } else {
                            result.remove(alias);
                        }
                    }
                }
                if (!found && !options.allowNoIndices()) {
                    throw new IndexMissingException(new Index(aliasOrIndex));
                }
            }
            if (result == null) {
                return expressions;
            }
            if (result.isEmpty() && !options.allowNoIndices()) {
                throw new IndexMissingException(new Index(StringUtils.join(expressions.iterator(), ',')));
            }
            return new ArrayList<>(result);
        }
    }

}