private ErrorMessage validate(Query query) {
        Sorting sorting = query.getRanking().getSorting();
        List<Sorting.FieldOrder> l = (sorting != null) ? sorting.fieldOrders() : null;

        if (l == null) {
            return null;
        }
        Map<String, AttributesConfig.Attribute> names = getAttributeNames();
        if (names == null) {
            return null;
        }

        String queryLocale = null;
        if (query.getModel().getLocale() != null) {
            queryLocale = query.getModel().getLocale().toString();
        }

        for (Sorting.FieldOrder f : l) {
            String name = f.getFieldName();
            if ("[rank]".equals(name) || "[docid]".equals(name)) {
                // built-in constants
            } else if ("[relevance]".equals(name) || "[relevancy]".equals(name)) { // aliases
                f.getSorter().setName("[rank]");
            } else if (names.containsKey(name)) {
                AttributesConfig.Attribute attrConfig = names.get(name);
                if (attrConfig == null)
                    return ErrorMessage.createInvalidQueryParameter("Cluster '" + getClusterName() +
                                                                    "' has no attribute config for field '" + name + "'");
                if (f.getSortOrder() == Sorting.Order.UNDEFINED) {
                    f.setAscending(attrConfig.sortascending());
                }
                if (f.getSorter().getClass().equals(Sorting.AttributeSorter.class)) {
                    // This indicates that it shall use default.
                    if ((attrConfig.datatype() == AttributesConfig.Attribute.Datatype.STRING)) {
                        if (attrConfig.sortfunction() == AttributesConfig.Attribute.Sortfunction.UCA) {
                            String locale = attrConfig.sortlocale();
                            if (locale == null || locale.isEmpty()) {
                                locale = queryLocale;
                            }
                            // can only use UcaSorter if we have knowledge about wanted locale
                            if (locale != null) {
                                f.setSorter(new Sorting.UcaSorter(name, locale, Sorting.UcaSorter.Strength.UNDEFINED));
                            } else {
                                // wanted UCA but no locale known, so use lowercase as fallback
                                f.setSorter(new Sorting.LowerCaseSorter(name));
                            }
                        } else if (attrConfig.sortfunction() == AttributesConfig.Attribute.Sortfunction.LOWERCASE) {
                            f.setSorter(new Sorting.LowerCaseSorter(name));
                        } else if (attrConfig.sortfunction() == AttributesConfig.Attribute.Sortfunction.RAW) {
                            f.setSorter(new Sorting.RawSorter(name));
                        } else {
                            // default if no config found for this string attribute
                            f.setSorter(new Sorting.LowerCaseSorter(name));
                        }
                    }
                    else if (attrConfig.datatype() == AttributesConfig.Attribute.Datatype.TENSOR) {
                        throw new IllegalArgumentException("Cannot sort on field '" + attrConfig.name() +
                                                           "' because it is a tensor");
                    }

                }
                if (f.getSorter() instanceof Sorting.UcaSorter sorter) {
                    String locale = sorter.getLocale();

                    if (locale == null || locale.isEmpty()) {
                        // first fallback
                        locale = attrConfig.sortlocale();
                    }
                    if (locale == null || locale.isEmpty()) {
                        // second fallback
                        locale = queryLocale;
                    }
                    // final fallback
                    if (locale == null || locale.isEmpty()) {
                        locale = "en_US";
                    }

                    Sorting.UcaSorter.Strength strength = sorter.getStrength();
                    if (sorter.getStrength() == Sorting.UcaSorter.Strength.UNDEFINED) {
                        strength = config2Strength(attrConfig.sortstrength());
                    }
                    if ((sorter.getStrength() == Sorting.UcaSorter.Strength.UNDEFINED) || (sorter.getLocale() == null) || sorter.getLocale().isEmpty()) {
                        sorter.setLocale(locale, strength);
                    }
                }
            } else {
                return ErrorMessage.createInvalidQueryParameter("Cluster '" + getClusterName() +
                                                                "' has no sortable attribute named '" + name + "'");
            }
        }
        return null;
    }
Check sorting specification makes sense to the search cluster before
passing it on to the backend.

@author Steinar Knutsen
container-search/src/main/java/com/yahoo/prelude/searcher/ValidateSortingSearcher.java