source: vlo/trunk/vlo-web-app/src/main/java/eu/clarin/cmdi/vlo/wicket/provider/FacetFieldValuesProvider.java @ 6005

Last change on this file since 6005 was 6005, checked in by Twan Goosen, 9 years ago

Values provider uses converted field value when sorting
Refs #553

File size: 11.1 KB
Line 
1/*
2 * Copyright (C) 2014 CLARIN
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17package eu.clarin.cmdi.vlo.wicket.provider;
18
19import com.google.common.base.Predicate;
20import com.google.common.collect.ImmutableList;
21import com.google.common.collect.Iterables;
22import com.google.common.collect.Lists;
23import com.google.common.collect.Ordering;
24import eu.clarin.cmdi.vlo.pojo.FieldValuesFilter;
25import eu.clarin.cmdi.vlo.pojo.FieldValuesOrder;
26import java.io.Serializable;
27import java.text.Collator;
28import java.util.Collection;
29import java.util.Iterator;
30import java.util.List;
31import java.util.Locale;
32import org.apache.solr.client.solrj.response.FacetField;
33import org.apache.solr.client.solrj.response.FacetField.Count;
34import org.apache.wicket.Session;
35import org.apache.wicket.WicketRuntimeException;
36import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
37import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
38import org.apache.wicket.model.IModel;
39import org.apache.wicket.model.Model;
40import org.apache.wicket.util.convert.IConverter;
41import org.slf4j.Logger;
42import org.slf4j.LoggerFactory;
43
44/**
45 * Provides facet values and counts (both through the {@link Count} object of
46 * SOLR) present on a {@link FacetField}, sortable by either count or name
47 *
48 * @see FieldValuesOrder
49 * @author twagoo
50 */
51public class FacetFieldValuesProvider extends SortableDataProvider<FacetField.Count, FieldValuesOrder> implements ListProvider<FacetField.Count> {
52   
53    private final static Logger logger = LoggerFactory.getLogger(FacetFieldValuesProvider.class);
54    private final IModel<FacetField> model;
55    private final int maxNumberOfItems;
56    private final Collection<String> lowPriorityValues;
57    private final FieldValueConverterProvider fieldValueConverterProvider;
58
59    /**
60     * cached result size
61     */
62    private Long size;
63    /**
64     * cached filtered values
65     */
66    private Iterable<Count> filtered;
67
68    /**
69     * Creates a provider without a maximum number of values. Bound to
70     * {@link Integer#MAX_VALUE}.
71     *
72     * @param model FacetField model to get values and counts for
73     * @param lowPriorityValues values that should with low priority (e.g.
74     * unknown, unspecified)
75     * @param fieldValueConverterProvider
76     */
77    public FacetFieldValuesProvider(IModel<FacetField> model, Collection<String> lowPriorityValues, FieldValueConverterProvider fieldValueConverterProvider) {
78        this(model, Integer.MAX_VALUE, lowPriorityValues, fieldValueConverterProvider);
79    }
80
81    /**
82     *
83     * @param model FacetField model to get values and counts for
84     * @param max maximum number of values to show
85     * @param lowPriorityValues (e.g. unknown, unspecified)
86     * @param fieldValueConverterProvider
87     */
88    public FacetFieldValuesProvider(IModel<FacetField> model, int max, Collection<String> lowPriorityValues, FieldValueConverterProvider fieldValueConverterProvider) {
89        this(model, max, lowPriorityValues, new SortParam<FieldValuesOrder>(FieldValuesOrder.COUNT, false), fieldValueConverterProvider);
90    }
91   
92    public FacetFieldValuesProvider(IModel<FacetField> model, int max, SortParam<FieldValuesOrder> sort, FieldValueConverterProvider fieldValueConverterProvider) {
93        this(model, max, null, sort, fieldValueConverterProvider);
94    }
95
96    /**
97     * Creates a provider with a set maximum number of values
98     *
99     * @param model FacetField model to get values and counts for
100     * @param max maximum number of values to show
101     * @param lowPriorityValues (e.g. unknown, unspecified)
102     * @param sort initial sort property and order
103     * @param fieldValueConverterProvider
104     */
105    public FacetFieldValuesProvider(IModel<FacetField> model, int max, Collection<String> lowPriorityValues, SortParam<FieldValuesOrder> sort, FieldValueConverterProvider fieldValueConverterProvider) {
106        this.model = model;
107        this.maxNumberOfItems = max;
108        this.lowPriorityValues = lowPriorityValues;
109        this.fieldValueConverterProvider = fieldValueConverterProvider;
110        setSort(sort);
111    }
112
113    /**
114     * override to achieve filtering
115     *
116     * @return model of string value that item values should contain
117     */
118    protected IModel<FieldValuesFilter> getFilterModel() {
119        return null;
120    }
121   
122    @Override
123    public Iterator<? extends FacetField.Count> iterator(long first, long count) {
124        // return iterator starting at specified offset
125        return getList().listIterator((int) first);
126    }
127   
128    @Override
129    public List<? extends FacetField.Count> getList() {
130        final Iterable<Count> filteredValues = getFilteredValues();
131        // sort what remains
132        final ImmutableList sorted = getOrdering().immutableSortedCopy(filteredValues);
133        if (sorted.size() > maxNumberOfItems) {
134            return Lists.newArrayList(sorted.subList(0, maxNumberOfItems));
135        } else {
136            // return iterator starting at specified offset
137            return sorted;
138        }
139    }
140   
141    @Override
142    public long size() {
143        if (size == null) {
144            size = getSize();
145        }
146        return size;
147    }
148   
149    @Override
150    public IModel<FacetField.Count> model(FacetField.Count object) {
151        return new Model(object);
152    }
153
154    /*
155     * CACHING AND FILTERING
156     */
157    private Iterable<FacetField.Count> filter(List<FacetField.Count> list) {
158        if (hasFilter()) {
159            return Iterables.filter(list, new Predicate<FacetField.Count>() {
160                @Override
161                public boolean apply(Count input) {
162                    return getFilterModel().getObject().matches(input);
163                }
164            });
165        } else {
166            return list;
167        }
168    }
169   
170    private Iterable<Count> getFilteredValues() {
171        if (filtered == null) {
172            // get all the values
173            final List<FacetField.Count> values = model.getObject().getValues();
174            filtered = filter(values);
175        }
176        return filtered;
177    }
178   
179    private long getSize() {
180        if (hasFilter()) {
181            return Math.min(maxNumberOfItems, Iterables.size(getFilteredValues()));
182        } else {
183            // Use value count from Solr, faster.
184            //
185            // Actual value count might be higher than what we want to show
186            // so get minimum.
187            return Math.min(maxNumberOfItems, model.getObject().getValueCount());
188        }
189    }
190   
191    private boolean hasFilter() {
192        return getFilterModel() != null && getFilterModel().getObject() != null && !getFilterModel().getObject().isEmpty();
193    }
194
195    /*
196     * ORDERING
197     */
198    private Ordering getOrdering() {
199        if (lowPriorityValues != null && getSort().getProperty() == FieldValuesOrder.COUNT) {
200            // in case of count, low priority fields should always be moved to the back
201            // rest should be sorted as requested
202            return new PriorityOrdering(lowPriorityValues).compound(getBaseOrdering());
203        } else {
204            // in other case (name), priorty should not be take into account
205            return getBaseOrdering();
206        }
207    }
208   
209    private Ordering getBaseOrdering() {
210        final Ordering ordering;
211        if (getSort().getProperty() == FieldValuesOrder.COUNT) {
212            ordering = new CountOrdering();
213        } else if (getSort().getProperty() == FieldValuesOrder.NAME) {
214            ordering = new NameOrdering(getLocale(), fieldValueConverterProvider.getConverter(model.getObject().getName()));
215        } else {
216            ordering = Ordering.natural();
217        }
218       
219        if (getSort().isAscending()) {
220            return ordering;
221        } else {
222            return ordering.reverse();
223        }
224    }
225   
226    protected Locale getLocale() {
227        try {
228            final Session session = Session.get();
229            if (session != null) {
230                return session.getLocale();
231            }
232        } catch (WicketRuntimeException ex) {
233            logger.info("No session available, falling back to JVM default locale");
234        }
235        return Locale.getDefault();
236    }
237   
238    private final static class CountOrdering extends Ordering<FacetField.Count> {
239       
240        @Override
241        public int compare(Count arg0, Count arg1) {
242            return Long.compare(arg0.getCount(), arg1.getCount());
243        }
244    };
245   
246    private final static class NameOrdering extends Ordering<FacetField.Count> implements Serializable {
247       
248        private final Collator collator;
249        private final IConverter<String> converter;
250        private final Locale locale;
251       
252        public NameOrdering(Locale locale, IConverter<String> converter) {
253            collator = Collator.getInstance(locale);
254            collator.setStrength(Collator.PRIMARY);
255            this.converter = converter;
256            this.locale = locale;
257        }
258       
259        @Override
260        public int compare(Count arg0, Count arg1) {
261            if (converter == null) {
262                return collator.compare(arg0.getName(), arg1.getName());
263            } else {
264                return collator.compare(
265                        converter.convertToString(arg0.getName(), locale),
266                        converter.convertToString(arg1.getName(), locale));
267            }
268        }
269    };
270
271    /**
272     * An ordering that pushes all values identified in a provided collection to
273     * the back of the list
274     */
275    private static class PriorityOrdering extends Ordering<FacetField.Count> {
276       
277        private final Collection<String> lowPriorityValues;
278       
279        public PriorityOrdering(Collection<String> lowPriorityValues) {
280            this.lowPriorityValues = lowPriorityValues;
281        }
282       
283        @Override
284        public int compare(Count arg0, Count arg1) {
285           
286            if (lowPriorityValues.contains(arg0.getName())) {
287                if (!lowPriorityValues.contains(arg1.getName())) {
288                    //arg0 is low priority, arg1 is not -> move arg0 to back
289                    return 1;
290                }
291            } else if (lowPriorityValues.contains(arg1.getName())) {
292                //arg0 is not low priority but arg1 is -> move arg1 to back
293                return -1;
294            }
295            // arg0 and arg1 are either both low priority or neither of them are,
296            // so fall back to secondary comparator (assuming a compound)
297            return 0;
298        }
299       
300    };
301   
302    @Override
303    public void detach() {
304        model.detach();
305        // invalidate cache variables
306        size = null;
307        filtered = null;
308    }
309}
Note: See TracBrowser for help on using the repository browser.