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

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

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