aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/query/profile/compiled/CompiledQueryProfile.java
blob: ae531c67dd1f88063493d00f83f45a00b830f20d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.query.profile.compiled;

import com.yahoo.component.AbstractComponent;
import com.yahoo.component.ComponentId;
import com.yahoo.processing.request.CompoundName;
import com.yahoo.processing.request.Properties;
import com.yahoo.search.query.profile.QueryProfileProperties;
import com.yahoo.search.query.profile.SubstituteString;
import com.yahoo.search.query.profile.types.QueryProfileType;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A query profile in a state where it is optimized for fast lookups.
 *
 * @author bratseth
 */
public class CompiledQueryProfile extends AbstractComponent implements Cloneable {

    private static final Pattern namePattern = Pattern.compile("[$a-zA-Z_/][-$a-zA-Z0-9_/()]*");

    private final CompiledQueryProfileRegistry registry;

    /** The type of this, or null if none */
    private final QueryProfileType type;

    /** The values of this */
    private final DimensionalMap<ValueWithSource> entries;

    /** Keys which have a type in this */
    private final DimensionalMap<QueryProfileType> types;

    /** Keys which are (typed or untyped) references to other query profiles in this. Used as a set. */
    private final DimensionalMap<Object> references;

    /** Values which are not overridable in this. Used as a set. */
    private final DimensionalMap<Object> unoverridables;

    /**
     * Creates a new query profile from an id.
     */
    public CompiledQueryProfile(ComponentId id,
                                QueryProfileType type,
                                DimensionalMap<ValueWithSource> entries,
                                DimensionalMap<QueryProfileType> types,
                                DimensionalMap<Object> references,
                                DimensionalMap<Object> unoverridables,
                                CompiledQueryProfileRegistry registry) {
        super(id);
        this.registry = registry;
        if (type != null)
            type.freeze();
        this.type = type;
        this.entries = entries;
        this.types = types;
        this.references = references;
        this.unoverridables = unoverridables;
        if ( ! id.isAnonymous())
            validateName(id.getName());
    }

    // ----------------- Public API -------------------------------------------------------------------------------

    /** Returns the registry this belongs to, or null if none (in which case runtime profile reference assignment won't work) */
    public CompiledQueryProfileRegistry getRegistry() { return registry; }

    /** Returns the type of this or null if it has no type */
    // TODO: Move into below
    public QueryProfileType getType() { return type; }

    /**
     * Returns whether or not the given field name can be overridden at runtime.
     * Attempts to override values which cannot be overridden will not fail but be ignored.
     * Default: true.
     *
     * @param name the name of the field to check
     * @param context the context in which to check, or null if none
     */
    public final boolean isOverridable(CompoundName name, Map<String, String> context) {
        return unoverridables.get(name, context) == null;
    }

    /** Returns the type of a given prefix reachable from this profile, or null if none */
    public final QueryProfileType getType(CompoundName name, Map<String, String> context) {
        return types.get(name, context);
    }

    /** Returns the types reachable from this, or an empty map (never null) if none */
    public DimensionalMap<QueryProfileType> getTypes() { return types; }

    /** Returns the references reachable from this, or an empty map (never null) if none */
    public DimensionalMap<Object> getReferences() { return references; }

    /**
     * Return all objects that start with the given prefix path using no context. Use "" to list all.
     * <p>
     * For example, if {a.d =&gt; "a.d-value" ,a.e =&gt; "a.e-value", b.d =&gt; "b.d-value", then calling listValues("a")
     * will return {"d" =&gt; "a.d-value","e" =&gt; "a.e-value"}
     */
    public final Map<String, Object> listValues(CompoundName prefix) {  return listValues(prefix, Collections.emptyMap()); }
    public final Map<String, Object> listValues(String prefix) { return listValues(new CompoundName(prefix)); }

    /**
     * Return all objects that start with the given prefix path. Use "" to list all.
     * <p>
     * For example, if {a.d =&gt; "a.d-value" ,a.e =&gt; "a.e-value", b.d =&gt; "b.d-value", then calling listValues("a")
     * will return {"d" =&gt; "a.d-value","e" =&gt; "a.e-value"}
     */
    public final Map<String, Object> listValues(String prefix, Map<String, String> context) {
        return listValues(new CompoundName(prefix), context);
    }

    /**
     * Return all objects that start with the given prefix path. Use "" to list all.
     * <p>
     * For example, if {a.d =&gt; "a.d-value" ,a.e =&gt; "a.e-value", b.d =&gt; "b.d-value", then calling listValues("a")
     * will return {"d" =&gt; "a.d-value","e" =&gt; "a.e-value"}
     */
    public final Map<String, Object> listValues(CompoundName prefix, Map<String, String> context) {
        return listValues(prefix, context, null);
    }

    /**
     * Adds all objects that start with the given path prefix to the given value map. Use "" to list all.
     * <p>
     * For example, if {a.d =&gt; "a.d-value" ,a.e =&gt; "a.e-value", b.d =&gt; "b.d-value", then calling listValues("a")
     * will return {"d" =&gt; "a.d-value","e" =&gt; "a.e-value"}
     */
    public Map<String, Object> listValues(CompoundName prefix, Map<String, String> context, Properties substitution) {
        Map<String, Object> values = new HashMap<>();
        for (Map.Entry<CompoundName, DimensionalValue<ValueWithSource>> entry : entries.entrySet()) {
            if ( ! entry.getKey().hasPrefix(prefix)) continue;

            ValueWithSource valueWithSource = entry.getValue().get(context);
            if (valueWithSource == null) continue;

            Object value = valueWithSource.value();
            if (value == null) continue;

            value = substitute(value, context, substitution);
            CompoundName suffixName = entry.getKey().rest(prefix.size());
            values.put(suffixName.toString(), value);
        }
        return values;
    }

    public Map<String, ValueWithSource> listValuesWithSources(CompoundName prefix,
                                                              Map<String, String> context,
                                                              Properties substitution) {
        Map<String, ValueWithSource> values = new HashMap<>();
        for (Map.Entry<CompoundName, DimensionalValue<ValueWithSource>> entry : entries.entrySet()) {
            if ( entry.getKey().size() <= prefix.size()) continue;
            if ( ! entry.getKey().hasPrefix(prefix)) continue;

            ValueWithSource valueWithSource = entry.getValue().get(context);
            if (valueWithSource == null) continue;
            if (valueWithSource.value() == null) continue;

            valueWithSource = valueWithSource.withValue(substitute(valueWithSource.value(), context, substitution));
            CompoundName suffixName = entry.getKey().rest(prefix.size());
            values.put(suffixName.toString(), valueWithSource);
        }
        return values;
    }

    public final Object get(String name) {
        return get(name, Map.of());
    }
    public final Object get(String name, Map<String, String> context) {
        return get(name, context, new QueryProfileProperties(this));
    }
    public final Object get(String name, Map<String, String> context, Properties substitution) {
        return get(new CompoundName(name), context, substitution);
    }
    public final Object get(CompoundName name, Map<String, String> context, Properties substitution) {
        ValueWithSource value = entries.get(name, context);
        if (value == null) return null;
        return substitute(value.value(), context, substitution);
    }

    /** Returns all the entries from the profile **/
    public final DimensionalMap<ValueWithSource> getEntries() {
        return this.entries;
    }

    private Object substitute(Object value, Map<String, String> context, Properties substitution) {
        if (value == null) return value;
        if (substitution == null) return value;
        if ( ! (value instanceof SubstituteString)) return value;
        return ((SubstituteString)value).substitute(context, substitution);
    }

    /** Throws IllegalArgumentException if the given string is not a valid query profile name */
    private static void validateName(String name) {
        Matcher nameMatcher = namePattern.matcher(name);
        if ( ! nameMatcher.matches())
            throw new IllegalArgumentException("Illegal name '" + name + "'");
    }

    @Override
    public CompiledQueryProfile clone() {
        return this; // immutable
    }

    @Override
    public String toString() {
        return "query profile '" + getId()  + "'" + (type!=null ? " of type '" + type.getId() + "'" : "");
    }

}