aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/pagetemplates/engine/Organizer.java
blob: 0eb6ef8afeb0f27ebe032603f9d1619dca664491 (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
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.pagetemplates.engine;

import com.yahoo.search.Result;
import com.yahoo.search.pagetemplates.PageTemplate;
import com.yahoo.search.pagetemplates.model.*;
import com.yahoo.search.pagetemplates.result.SectionHitGroup;
import com.yahoo.search.query.Sorting;
import com.yahoo.search.result.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Reorganizes and prunes a result as prescribed by a resolved template.
 * This class is multithread safe.
 *
 * @author bratseth
 */
public class Organizer {

    /**
     * Organizes the given result
     *
     * @param templateChoice a choice between singleton lists of PageTemplates
     * @param resolution the resolution of (at least) the template choice and all choices contained in that template
     * @param result the result to organize
     */
    public void organize(Choice templateChoice, Resolution resolution, Result result) {
        PageTemplate template = (PageTemplate)templateChoice.get(resolution.getResolution(templateChoice)).get(0);
        SectionHitGroup sectionGroup = toGroup(template.getSection(), resolution, result);
        ErrorHit errors = result.hits().getErrorHit();

        // transfer state from existing hit
        sectionGroup.setQuery(result.hits().getQuery());
        if (errors instanceof DefaultErrorHit)
            sectionGroup.add((DefaultErrorHit)errors);
        result.hits().forEachField((name, value) -> sectionGroup.setField(name, value));
        result.setHits(sectionGroup);
    }

    /** Creates the hit group corresponding to a section, drawing data from the given result */
    private SectionHitGroup toGroup(Section section,Resolution resolution,Result result) {
        SectionHitGroup sectionGroup=new SectionHitGroup("section:" + section.getId());
        setField("id",section.getId(),sectionGroup);
        sectionGroup.setLeaf(section.elements(Section.class).size()==0);
        setField("layout",section.getLayout().getName(),sectionGroup);
        setField("region",section.getRegion(),sectionGroup);

        List<String> sourceList=new ArrayList<>();
        renderElements(resolution, result, sectionGroup, sourceList, section.elements());

        // Trim to max
        if (section.getMax()>=0)
            sectionGroup.trim(0,section.getMax());
        if (sectionGroup.size()>1)
            assignOrderer(section,resolution,sourceList,sectionGroup);

        return sectionGroup;
    }

    private void renderElements(Resolution resolution, Result result, SectionHitGroup sectionGroup, List<String> sourceList, List<PageElement> elements) {
        for (PageElement element : elements) {
            if (element instanceof Section) {
                sectionGroup.add(toGroup((Section)element,resolution,result));
            }
            else if (element instanceof Source) {
                addSource(resolution,(Source)element,sectionGroup,result,sourceList);
            }
            else if (element instanceof Renderer) {
                sectionGroup.renderers().add((Renderer)element);
            }
            else if (element instanceof Choice) {
                Choice choice=(Choice)element;
                if (choice.isEmpty()) continue; // Ignore
                int chosen=resolution.getResolution(choice);
                renderElements(resolution, result, sectionGroup, sourceList, choice.alternatives().get(chosen));
            }
            else if (element instanceof Placeholder) {
                Placeholder placeholder =(Placeholder)element;
                List<PageElement> mappedElements=
                        resolution.getResolution(placeholder.getValueContainer()).get(placeholder.getId());
                renderElements(resolution,result,sectionGroup,sourceList,mappedElements);
            }
        }
    }

    private void setField(String fieldName,Object value,Hit to) {
        if (value==null) return;
        to.setField(fieldName,value);
    }

    private void addSource(Resolution resolution,Source source,SectionHitGroup sectionGroup,Result result,List<String> sourceList) {
        renderElements(resolution,result,sectionGroup, sourceList, source.renderers());
        /*
        for (PageElement element : source.renderers()) {
            if (element instanceof Renderer)
            if (renderer.isEmpty()) continue;
            sectionGroup.renderers().add(renderer.get(resolution.getResolution(renderer)));
        }
        */

        if (source.getUrl()==null)
            addHitsFromSource(source,sectionGroup,result,sourceList);
        else
            sectionGroup.sources().add(source); // source to be rendered by the frontend
    }

    private void addHitsFromSource(Source source,SectionHitGroup sectionGroup,Result result,List<String> sourceList) {
        if (source==Source.any) { // Add any source not added yet
            for (Hit hit : result.hits()) {
                if ( ! (hit instanceof HitGroup)) continue;
                String groupId=hit.getId().stringValue();
                if ( ! groupId.startsWith("source:")) continue;
                String sourceName=groupId.substring(7);
                if (sourceList.contains(sourceName)) continue;
                sectionGroup.addAll(((HitGroup)hit).asList());
                sourceList.add(sourceName); // Add *'ed sources explicitly
            }
        }
        else {
            HitGroup sourceGroup=(HitGroup)result.hits().get("source:" + source.getName());
            if (sourceGroup!=null)
                sectionGroup.addAll(sourceGroup.asList());
            sourceList.add(source.getName()); // Add even if not found - may be added later
        }
    }

    private void assignOrderer(Section section,Resolution resolution,List<String> sourceList,HitGroup group) {
        if (section.getOrder()==null) { // then sort by relevance, source
            group.setOrderer(new HitSortOrderer(new RelevanceComparator(new SourceOrderComparator(sourceList))));
            return;
        }

        // replace a source field comparison by one which knows the source list order
        // and add default sorting at the end if necessary
        Sorting sorting=section.getOrder();
        int rankIndex=-1;
        int sourceIndex=-1;
        for (int i=0; i<sorting.fieldOrders().size(); i++) {
            Sorting.FieldOrder order=sorting.fieldOrders().get(i);
            if ("[relevance]".equals(order.getFieldName()) || "[rank]".equals(order.getFieldName()))
                rankIndex=i;
            else if (order.getFieldName().equals("[source]"))
                sourceIndex=i;
        }

        ChainableComparator comparator;
        Sorting beforeSource=null;
        Sorting afterSource=null;
        if (sourceIndex>=0) { // replace alphabetical sorting on source by sourceList order sorting
            if (sourceIndex>0) // sort fields before the source
                beforeSource=new Sorting(new ArrayList<>(sorting.fieldOrders().subList(0,sourceIndex)));
            if (sorting.fieldOrders().size()>sourceIndex+1) // sort fields after the source
                afterSource=new Sorting(new ArrayList<>(sorting.fieldOrders().subList(sourceIndex+1,sorting.fieldOrders().size()+1)));

            comparator=new SourceOrderComparator(sourceList, FieldComparator.create(afterSource));
            if (beforeSource!=null)
                comparator=new FieldComparator(beforeSource,comparator);

        }
        else if (rankIndex>=0) { // add sort by source at the end
            comparator=new FieldComparator(sorting,new SourceOrderComparator(sourceList));
        }
        else { // add sort by rank,source at the end
            comparator=new FieldComparator(sorting,new RelevanceComparator(new SourceOrderComparator(sourceList)));
        }
        group.setOrderer(new HitSortOrderer(comparator));
    }

}