aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com/yahoo/schema/RankProfileRegistry.java
blob: 0fddcbd4cc7eb66539b68be8b9ff2159157c7d10 (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
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.schema;

import com.yahoo.schema.document.SDDocumentType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Mapping from name to {@link RankProfile} as well as a reverse mapping of {@link RankProfile} to {@link Schema}.
 * Having both of these mappings consolidated here make it easier to remove dependencies on these mappings at
 * run time, since it is essentially only used when building rank profile config at deployment time.
 *
 * Global rank profiles are represented by the Search key null.
 *
 * @author Ulf Lilleengen
 */
public class RankProfileRegistry {

    private final Map<String, Map<String, RankProfile>> rankProfiles = new LinkedHashMap<>();
    private static final String globalRankProfilesKey = "[global]";

    /* These rank profiles can be overridden: 'default' rank profile, as that is documented to work. And 'unranked'. */
    static final Set<String> overridableRankProfileNames = new HashSet<>(Arrays.asList("default", "unranked"));

    public static RankProfileRegistry createRankProfileRegistryWithBuiltinRankProfiles(Schema schema) {
        RankProfileRegistry rankProfileRegistry = new RankProfileRegistry();
        rankProfileRegistry.add(new DefaultRankProfile(schema, rankProfileRegistry));
        rankProfileRegistry.add(new UnrankedRankProfile(schema, rankProfileRegistry));
        return rankProfileRegistry;
    }

    private String extractName(ImmutableSchema search) {
        return search != null ? search.getName() : globalRankProfilesKey;
    }

    /** Adds a rank profile to this registry */
    public void add(RankProfile rankProfile) {
        String schemaName = extractName(rankProfile.schema());
        if ( ! rankProfiles.containsKey(schemaName)) {
            rankProfiles.put(schemaName, new LinkedHashMap<>());
        }
        checkForDuplicate(rankProfile);
        rankProfiles.get(schemaName).put(rankProfile.name(), rankProfile);
    }

    private void checkForDuplicate(RankProfile rankProfile) {
        String rankProfileName = rankProfile.name();
        RankProfile existingRankProfileWithSameName = rankProfiles.get(extractName(rankProfile.schema())).get(rankProfileName);
        if (existingRankProfileWithSameName == null) return;

        if ( ! overridableRankProfileNames.contains(rankProfileName)) {
            throw new IllegalArgumentException("Duplicate rank profile '" + rankProfileName + "' in " +
                                               rankProfile.schema());
        }
    }

    /**
     * Returns a named rank profile, null if the search definition doesn't have one with the given name
     *
     * @param schema the {@link Schema} that owns the rank profile
     * @param name the name of the rank profile
     * @return the RankProfile to return.
     */
    public RankProfile get(String schema, String name) {
        Map<String, RankProfile> profiles = rankProfiles.get(schema);
        if (profiles == null) return null;
        return profiles.get(name);
    }

    public RankProfile get(ImmutableSchema schema, String name) {
        var profile = get(schema.getName(), name);
        if (profile != null) return profile;
        if (schema.inherited().isPresent()) return get(schema.inherited().get(), name);
        return null;
    }

    public RankProfile getGlobal(String name) {
        Map<String, RankProfile> profiles = rankProfiles.get(globalRankProfilesKey);
        if (profiles == null) return null;
        return profiles.get(name);
    }

    public RankProfile resolve(SDDocumentType docType, String name) {
        RankProfile rankProfile = get(docType.getName(), name);
        if (rankProfile != null) return rankProfile;
        for (var parent : docType.getInheritedTypes()) {
            RankProfile parentProfile = resolve(parent, name);
            if (parentProfile != null) return parentProfile;
        }
        return get(globalRankProfilesKey, name);
    }

    /**
     * Rank profiles that are collected across clusters.
     *
     * @return a set of global {@link RankProfile} instances
     */
    public Collection<RankProfile> all() {
        List<RankProfile> all = new ArrayList<>();
        for (var entry : rankProfiles.values()) {
            all.addAll(entry.values());
        }
        return all;
    }

    /**
     * Retrieve all rank profiles for a schema
     *
     * @param schema the schema to fetch rank profiles for, or null for the global ones
     * @return a collection of {@link RankProfile} instances
     */
    public Collection<RankProfile> rankProfilesOf(ImmutableSchema schema) {
        String key = schema == null ? globalRankProfilesKey : schema.getName();

        if ( ! rankProfiles.containsKey(key)) return List.of();

        var profiles = new LinkedHashMap<>(rankProfiles.get(key));
        // Add all profiles in inherited schemas, unless they are already present (overridden)
        while (schema != null && schema.inherited().isPresent()) {
            schema = schema.inherited().get();
            var inheritedProfiles = rankProfiles.get(schema.getName());
            if (inheritedProfiles != null) {
                for (Map.Entry<String, RankProfile> inheritedProfile : inheritedProfiles.entrySet()) {
                    profiles.putIfAbsent(inheritedProfile.getKey(), inheritedProfile.getValue());
                }
            }
        }
        return profiles.values();
    }

}