All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.cluster.metadata.IndexNameExpressionResolver Maven / Gradle / Ivy

The newest version!
/*
 * 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.google.common.base.Predicate;
import org.elasticsearch.ElasticsearchParseException;
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.Tuple;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.indices.IndexClosedException;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import static com.google.common.collect.Maps.filterEntries;
import static com.google.common.collect.Maps.newHashMap;

public class IndexNameExpressionResolver extends AbstractComponent {

    private final List expressionResolvers;
    private final DateMathExpressionResolver dateMathExpressionResolver;

    @Inject
    public IndexNameExpressionResolver(Settings settings) {
        super(settings);
        expressionResolvers = Arrays.asList(
                dateMathExpressionResolver = new DateMathExpressionResolver(settings),
                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) {
        Context context = new Context(state, request.indicesOptions());
        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.
     */
    public String[] concreteIndices(ClusterState state, IndicesOptions options, String... indexExpressions) {
        Context context = new Context(state, options);
        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 options           defines how the aliases or indices need to be resolved to concrete indices
     * @param startTime         The start of the request where concrete indices is being invoked for
     * @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
     * 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, long startTime, String... indexExpressions) {
        Context context = new Context(state, options, startTime);
        return concreteIndices(context, indexExpressions);
    }

    String[] concreteIndices(Context context, String... indexExpressions) {
        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 expressions = Arrays.asList(indexExpressions);
        for (ExpressionResolver expressionResolver : expressionResolvers) {
            expressions = expressionResolver.resolve(context, expressions);
        }

        if (expressions.isEmpty()) {
            if (!options.allowNoIndices()) {
                IndexNotFoundException infe = new IndexNotFoundException((String)null);
                infe.setResources("index_expression", indexExpressions);
                throw infe;
            } else {
                return Strings.EMPTY_ARRAY;
            }
        }

        final Set concreteIndices = new HashSet<>(expressions.size());
        for (String expression : expressions) {
            AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(expression);
            if (aliasOrIndex == null) {
                if (failNoIndices) {
                    IndexNotFoundException infe = new IndexNotFoundException(expression);
                    infe.setResources("index_expression", expression);
                    throw infe;
                } else {
                    continue;
                }
            }

            Collection resolvedIndices = aliasOrIndex.getIndices();
            if (resolvedIndices.size() > 1 && !options.allowAliasesToMultipleIndices()) {
                String[] indexNames = new String[resolvedIndices.size()];
                int i = 0;
                for (IndexMetaData indexMetaData : resolvedIndices) {
                    indexNames[i++] = indexMetaData.getIndex();
                }
                throw new IllegalArgumentException("Alias [" + expression + "] has more than one indices associated with it [" + Arrays.toString(indexNames) + "], can't execute a single index op");
            }

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

        if (options.allowNoIndices() == false && concreteIndices.isEmpty()) {
            IndexNotFoundException infe = new IndexNotFoundException((String)null);
            infe.setResources("index_expression", indexExpressions);
            throw infe;
        }
        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 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 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) {
        String indexExpression = request.indices() != null && request.indices().length > 0 ? request.indices()[0] : null;
        String[] 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];
    }

    /**
     * @return whether the specified alias or index exists. If the alias or index contains datemath then that is resolved too.
     */
    public boolean hasIndexOrAlias(String aliasOrIndex, ClusterState state) {
        Context context = new Context(state, IndicesOptions.lenientExpandOpen());
        String resolvedAliasOrIndex = dateMathExpressionResolver.resolveExpression(aliasOrIndex, context);
        return state.metaData().getAliasAndIndexLookup().containsKey(resolvedAliasOrIndex);
    }

    /**
     * @return If the specified string is data math expression then this method returns the resolved expression.
     */
    public String resolveDateMathExpression(String dateExpression) {
        // The data math expression resolver doesn't rely on cluster state or indices options, because
        // it just resolves the date math to an actual date.
        return dateMathExpressionResolver.resolveExpression(dateExpression, new Context(null, null));
    }

    /**
     * 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 null if no filtering is required. */ public String[] filteringAliases(ClusterState state, String index, String... expressions) { // expand the aliases wildcard List resolvedExpressions = expressions != null ? Arrays.asList(expressions) : Collections.emptyList(); Context context = new Context(state, IndicesOptions.lenientExpandOpen(), true); 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 IndexNotFoundException(index); } AliasMetaData aliasMetaData = indexMetaData.getAliases().get(alias); boolean filteringRequired = aliasMetaData != null && aliasMetaData.filteringRequired(); if (!filteringRequired) { return null; } return new String[]{alias}; } List 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 IndexNotFoundException(index); } AliasMetaData aliasMetaData = indexMetaData.getAliases().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 = new ArrayList<>(); } 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> resolveSearchRouting(ClusterState state, @Nullable String routing, String... expressions) { List resolvedExpressions = expressions != null ? Arrays.asList(expressions) : Collections.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); } Map> routings = null; Set paramRouting = null; // List of indices that don't require any routing Set norouting = new HashSet<>(); if (routing != null) { paramRouting = Strings.splitStringByCommaToSet(routing); } for (String expression : resolvedExpressions) { AliasOrIndex aliasOrIndex = state.metaData().getAliasAndIndexLookup().get(expression); if (aliasOrIndex != null && aliasOrIndex.isAlias()) { AliasOrIndex.Alias alias = (AliasOrIndex.Alias) aliasOrIndex; for (Tuple item : alias.getConcreteIndexAndAliasMetaDatas()) { String concreteIndex = item.v1(); AliasMetaData aliasMetaData = item.v2(); if (!norouting.contains(concreteIndex)) { if (!aliasMetaData.searchRoutingValues().isEmpty()) { // Routing alias if (routings == null) { routings = newHashMap(); } Set r = routings.get(concreteIndex); if (r == null) { r = new HashSet<>(); routings.put(concreteIndex, r); } r.addAll(aliasMetaData.searchRoutingValues()); if (paramRouting != null) { r.retainAll(paramRouting); } if (r.isEmpty()) { routings.remove(concreteIndex); } } else { // Non-routing alias if (!norouting.contains(concreteIndex)) { norouting.add(concreteIndex); if (paramRouting != null) { Set r = new HashSet<>(paramRouting); if (routings == null) { routings = newHashMap(); } routings.put(concreteIndex, r); } else { if (routings != null) { routings.remove(concreteIndex); } } } } } } } else { // Index if (!norouting.contains(expression)) { norouting.add(expression); if (paramRouting != null) { Set 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; } /** * Sets the same routing for all indices */ private Map> resolveSearchRoutingAllIndices(MetaData metaData, String routing) { if (routing != null) { Set r = Strings.splitStringByCommaToSet(routing); Map> 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 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 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; private final long startTime; private final boolean preserveAliases; Context(ClusterState state, IndicesOptions options) { this(state, options, System.currentTimeMillis()); } Context(ClusterState state, IndicesOptions options, boolean preserveAliases) { this(state, options, System.currentTimeMillis(), preserveAliases); } public Context(ClusterState state, IndicesOptions options, long startTime) { this(state, options, startTime, false); } public Context(ClusterState state, IndicesOptions options, long startTime, boolean preserveAliases) { this.state = state; this.options = options; this.startTime = startTime; this.preserveAliases = preserveAliases; } 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, String...)} since it's the only one that needs aliases */ boolean isPreserveAliases() { return preserveAliases; } } 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 resolve(Context context, List expressions); } /** * Resolves alias/index name expressions with wildcards into the corresponding concrete indices/aliases */ final static class WildcardExpressionResolver implements ExpressionResolver { @Override public List resolve(Context context, List 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)) || Regex.isMatchAllPattern(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 result = null; for (int i = 0; i < expressions.size(); i++) { String expression = expressions.get(i); if (metaData.getAliasAndIndexLookup().containsKey(expression)) { if (result != null) { result.add(expression); } continue; } if (Strings.isEmpty(expression)) { throw new IndexNotFoundException(expression); } boolean add = true; if (expression.charAt(0) == '+') { // if its the first, add empty result set if (i == 0) { result = new HashSet<>(); } add = true; expression = expression.substring(1); } else if (expression.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; expression = expression.substring(1); } if (!Regex.isSimpleMatchPattern(expression)) { if (!options.ignoreUnavailable() && !metaData.getAliasAndIndexLookup().containsKey(expression)) { IndexNotFoundException infe = new IndexNotFoundException(expression); infe.setResources("index_or_alias", expression); throw infe; } if (result != null) { if (add) { result.add(expression); } else { result.remove(expression); } } continue; } if (result == null) { // add all the previous ones... result = new HashSet<>(); result.addAll(expressions.subList(0, i)); } 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; } final Map matches; if (Regex.isMatchAllPattern(expression)) { // Can only happen if the expressions was initially: '-*' matches = metaData.getAliasAndIndexLookup(); } else if (expression.indexOf("*") == expression.length() - 1) { // Suffix wildcard: 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); matches = metaData.getAliasAndIndexLookup().subMap(fromPrefix, toPrefix); } else { // Other wildcard expressions: final String pattern = expression; matches = filterEntries(metaData.getAliasAndIndexLookup(), new Predicate>() { @Override public boolean apply(@Nullable Map.Entry input) { return Regex.simpleMatch(pattern, input.getKey()); } }); } Set expand = new HashSet<>(); for (Map.Entry entry : matches.entrySet()) { AliasOrIndex aliasOrIndex = entry.getValue(); if (context.isPreserveAliases() && aliasOrIndex.isAlias()) { expand.add(entry.getKey()); } else { for (IndexMetaData meta : aliasOrIndex.getIndices()) { if (excludeState == null || meta.getState() != excludeState) { expand.add(meta.getIndex()); } } } } if (add) { result.addAll(expand); } else { result.removeAll(expand); } if (matches.isEmpty() && options.allowNoIndices() == false) { IndexNotFoundException infe = new IndexNotFoundException(expression); infe.setResources("index_or_alias", expression); throw infe; } } if (result == null) { return expressions; } if (result.isEmpty() && !options.allowNoIndices()) { IndexNotFoundException infe = new IndexNotFoundException((String)null); infe.setResources("index_or_alias", expressions.toArray(new String[0])); throw infe; } return new ArrayList<>(result); } } final static class DateMathExpressionResolver implements ExpressionResolver { 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 final DateTimeZone defaultTimeZone; private final String defaultDateFormatterPattern; private final DateTimeFormatter defaultDateFormatter; public DateMathExpressionResolver(Settings settings) { String defaultTimeZoneId = settings.get("date_math_expression_resolver.default_time_zone", "UTC"); this.defaultTimeZone = DateTimeZone.forID(defaultTimeZoneId); defaultDateFormatterPattern = settings.get("date_math_expression_resolver.default_date_format", "YYYY.MM.dd"); this.defaultDateFormatter = DateTimeFormat.forPattern(defaultDateFormatterPattern); } @Override public List resolve(final Context context, List expressions) { List result = new ArrayList<>(expressions.size()); for (String expression : expressions) { result.add(resolveExpression(expression, context)); } return result; } String resolveExpression(String expression, final Context context) { 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) { 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; DateTimeFormatter dateFormatter; final DateTimeZone timeZone; if (dateTimeFormatLeftBoundIndex < 0) { mathExpression = inPlaceHolderString; dateFormatterPattern = defaultDateFormatterPattern; dateFormatter = defaultDateFormatter; timeZone = defaultTimeZone; } 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 dateFormatterPatternAndTimeZoneId = inPlaceHolderString.substring(dateTimeFormatLeftBoundIndex + 1, inPlaceHolderString.length() - 1); int formatPatternTimeZoneSeparatorIndex = dateFormatterPatternAndTimeZoneId.indexOf(TIME_ZONE_BOUND); if (formatPatternTimeZoneSeparatorIndex != -1) { dateFormatterPattern = dateFormatterPatternAndTimeZoneId.substring(0, formatPatternTimeZoneSeparatorIndex); timeZone = DateTimeZone.forID(dateFormatterPatternAndTimeZoneId.substring(formatPatternTimeZoneSeparatorIndex + 1)); } else { dateFormatterPattern = dateFormatterPatternAndTimeZoneId; timeZone = defaultTimeZone; } dateFormatter = DateTimeFormat.forPattern(dateFormatterPattern); } DateTimeFormatter parser = dateFormatter.withZone(timeZone); FormatDateTimeFormatter formatter = new FormatDateTimeFormatter(dateFormatterPattern, parser, Locale.ROOT); DateMathParser dateMathParser = new DateMathParser(formatter); long millis = dateMathParser.parse(mathExpression, new Callable() { @Override public Long call() throws Exception { return context.getStartTime(); } }, false, timeZone); String time = formatter.printer().print(millis); 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) { 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(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy